Merge "Check the timestamp by areaId" into qt-qpr1-dev am: a60cc51cc7

Change-Id: I811efa0cf7ee5df05bd13a25e153f074ba4b7b23
diff --git a/.clang-format b/.clang-format
index fc4eb1b..82e72e7 100644
--- a/.clang-format
+++ b/.clang-format
@@ -1,13 +1,27 @@
+---
+Language: Java
+DisableFormat: true
+SortIncludes: false
+---
+Language: Cpp
 BasedOnStyle: Google
+AlignOperands: false
 AllowShortBlocksOnASingleLine: false
-AllowShortFunctionsOnASingleLine: false
-
-AccessModifierOffset: -2
-ColumnLimit: 100
+AllowShortFunctionsOnASingleLine: Inline
+AlwaysBreakBeforeMultilineStrings: false
 CommentPragmas: NOLINT:.*
-DerivePointerAlignment: false
+ColumnLimit: 100
+AccessModifierOffset: -4
 IndentWidth: 4
-PointerAlignment: Left
 TabWidth: 4
+ContinuationIndentWidth: 8
+ConstructorInitializerIndentWidth: 6
+SpacesBeforeTrailingComments: 2
+IncludeBlocks: Preserve
+DerivePointerAlignment: false
+PointerAlignment: Left
 UseTab: Never
-PenaltyExcessCharacter: 32
+BreakInheritanceList: AfterColon
+BreakConstructorInitializers: AfterColon
+PenaltyBreakBeforeFirstCallParameter: 100000
+---
diff --git a/CPPLINT.cfg b/CPPLINT.cfg
new file mode 100644
index 0000000..6708d34
--- /dev/null
+++ b/CPPLINT.cfg
@@ -0,0 +1,7 @@
+set noparent
+linelength=100
+# Do not enforce including header files in both .h and .cpp.
+filter=-build/include
+# Do not check access modifier indentation.
+# CPPLint enforces +1, but our rule is no indentation.
+filter=-whitespace/indent
diff --git a/EncryptionRunner/Android.bp b/EncryptionRunner/Android.bp
index 7910b73..c82d769 100644
--- a/EncryptionRunner/Android.bp
+++ b/EncryptionRunner/Android.bp
@@ -29,25 +29,3 @@
     installable: true,
 }
 
-android_test {
-    name: "EncryptionRunnerTest",
-    min_sdk_version: "23",
-    srcs: [
-        "test/**/*.java",
-    ],
-    product_variables: {
-        pdk: {
-            enabled: false,
-        },
-    },
-    libs: [
-        "android.test.base",
-        "android.test.runner",
-    ],
-    static_libs: [
-        "androidx.test.rules",
-        "EncryptionRunner",
-        "junit",
-        "truth-prebuilt",
-    ],
-}
diff --git a/EncryptionRunner/AndroidManifest.xml b/EncryptionRunner/AndroidManifest.xml
index 9b76d15..3ebdf42 100644
--- a/EncryptionRunner/AndroidManifest.xml
+++ b/EncryptionRunner/AndroidManifest.xml
@@ -18,5 +18,4 @@
         xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
         package="android.car.encryptionrunner" >
     <uses-sdk android:minSdkVersion="23" android:targetSdkVersion="23" />
-    <application />
 </manifest>
diff --git a/EncryptionRunner/src/android/car/encryptionrunner/DummyEncryptionRunner.java b/EncryptionRunner/src/android/car/encryptionrunner/DummyEncryptionRunner.java
index f38bf65..5b63dbc 100644
--- a/EncryptionRunner/src/android/car/encryptionrunner/DummyEncryptionRunner.java
+++ b/EncryptionRunner/src/android/car/encryptionrunner/DummyEncryptionRunner.java
@@ -31,6 +31,7 @@
 public class DummyEncryptionRunner implements EncryptionRunner {
 
     private static final String KEY = "key";
+    private static final byte[] DUMMY_MESSAGE = "Dummy Message".getBytes();
     @VisibleForTesting
     public static final String INIT = "init";
     @VisibleForTesting
@@ -48,6 +49,9 @@
         int SERVER = 2;
     }
 
+    private boolean mIsReconnect;
+    private boolean mInitReconnectVerification;
+    private Key mCurrentDummyKey;
     @Mode
     private int mMode;
     @HandshakeMessage.HandshakeState
@@ -90,12 +94,16 @@
         if (mState != HandshakeMessage.HandshakeState.IN_PROGRESS) {
             throw new HandshakeException("not waiting for response but got one");
         }
-        switch(mMode) {
+        switch (mMode) {
             case Mode.SERVER:
                 if (!CLIENT_RESPONSE.equals(new String(response))) {
                     throw new HandshakeException("unexpected response: " + new String(response));
                 }
                 mState = HandshakeMessage.HandshakeState.VERIFICATION_NEEDED;
+                if (mIsReconnect) {
+                    verifyPin();
+                    mState = HandshakeMessage.HandshakeState.RESUMING_SESSION;
+                }
                 return HandshakeMessage.newBuilder()
                         .setVerificationCode(VERIFICATION_CODE)
                         .setHandshakeState(mState)
@@ -105,17 +113,44 @@
                     throw new HandshakeException("unexpected response: " + new String(response));
                 }
                 mState = HandshakeMessage.HandshakeState.VERIFICATION_NEEDED;
+                if (mIsReconnect) {
+                    verifyPin();
+                    mState = HandshakeMessage.HandshakeState.RESUMING_SESSION;
+                }
                 return HandshakeMessage.newBuilder()
                         .setHandshakeState(mState)
                         .setNextMessage(CLIENT_RESPONSE.getBytes())
                         .setVerificationCode(VERIFICATION_CODE)
                         .build();
             default:
-                throw new IllegalStateException("unexpected state: "  + mState);
+                throw new IllegalStateException("unexpected role: " + mMode);
         }
     }
 
     @Override
+    public HandshakeMessage authenticateReconnection(byte[] message, byte[] previousKey)
+            throws HandshakeException {
+        mCurrentDummyKey = new DummyKey();
+        // Blindly verify the reconnection because this is a dummy encryption runner.
+        return HandshakeMessage.newBuilder()
+                .setHandshakeState(HandshakeMessage.HandshakeState.FINISHED)
+                .setKey(mCurrentDummyKey)
+                .setNextMessage(mInitReconnectVerification ? null : DUMMY_MESSAGE)
+                .build();
+    }
+
+    @Override
+    public HandshakeMessage initReconnectAuthentication(byte[] previousKey)
+            throws HandshakeException {
+        mInitReconnectVerification = true;
+        mState = HandshakeMessage.HandshakeState.RESUMING_SESSION;
+        return HandshakeMessage.newBuilder()
+                .setHandshakeState(mState)
+                .setNextMessage(DUMMY_MESSAGE)
+                .build();
+    }
+
+    @Override
     public Key keyOf(byte[] serialized) {
         return new DummyKey();
     }
@@ -126,10 +161,8 @@
             throw new IllegalStateException("asking to verify pin, state = " + mState);
         }
         mState = HandshakeMessage.HandshakeState.FINISHED;
-        return HandshakeMessage.newBuilder()
-                .setHandshakeState(mState)
-                .setKey(new DummyKey())
-                .build();
+        return HandshakeMessage.newBuilder().setKey(new DummyKey()).setHandshakeState(
+                mState).build();
     }
 
     @Override
@@ -137,8 +170,12 @@
         mState = HandshakeMessage.HandshakeState.INVALID;
     }
 
-    private class DummyKey implements Key {
+    @Override
+    public void setIsReconnect(boolean isReconnect) {
+        mIsReconnect = isReconnect;
+    }
 
+    private class DummyKey implements Key {
         @Override
         public byte[] asBytes() {
             return KEY.getBytes();
@@ -153,5 +190,10 @@
         public byte[] decryptData(byte[] encryptedData) {
             return encryptedData;
         }
+
+        @Override
+        public byte[] getUniqueSession() {
+            return KEY.getBytes();
+        }
     }
 }
diff --git a/EncryptionRunner/src/android/car/encryptionrunner/EncryptionRunner.java b/EncryptionRunner/src/android/car/encryptionrunner/EncryptionRunner.java
index ad444bc..f0a34b2 100644
--- a/EncryptionRunner/src/android/car/encryptionrunner/EncryptionRunner.java
+++ b/EncryptionRunner/src/android/car/encryptionrunner/EncryptionRunner.java
@@ -23,39 +23,63 @@
  * messages.
  *
  * To use this interface:
- * 1. As a client.
  *
- *  HandshakeMessage initialClientMessage = clientRunner.initHandshake();
- *  sendToServer(initialClientMessage.getNextMessage());
- *  byte message = getServerResponse();
+ * <p>1. As a client.
  *
- *  HandshakeMessage message = clientRunner.continueHandshake(message);
- *  message.getHandshakeState() should be VERIFICATION_NEEDED,
- *  otherwise if IN_PROGRESS just send the result of getNextMessage();
+ * {@code
+ * HandshakeMessage initialClientMessage = clientRunner.initHandshake();
+ * sendToServer(initialClientMessage.getNextMessage());
+ * byte message = getServerResponse();
+ * HandshakeMessage message = clientRunner.continueHandshake(message);
+ * }
  *
- *  Show user the verification code and ask to verify.
- *  message.getVerificationCode()
+ * <p>If it is a first-time connection,
  *
- *  if user agrees
- *  HandshakeMessage lastMessage = clientRunner.verifyPin();
- *  otherwise
- *  clientRunner.invalidPin();
+ * {@code message.getHandshakeState()} should be VERIFICATION_NEEDED, show user the verification
+ * code and ask to verify.
+ * After user confirmed, {@code HandshakeMessage lastMessage = clientRunner.verifyPin();} otherwise
+ * {@code clientRunner.invalidPin(); }
  *
- *  Use lastMessage.getKey() for encryption.
+ * Use {@code lastMessage.getKey()} to get the key for encryption.
  *
- * 2. As a server.
+ * <p>If it is a reconnection,
  *
- *  byte[] initialMessage = getClientMessageBytes();
- *  HandshakeMesssage message = serverRunner.respondToInitRequest(initialMessage);
+ * {@code message.getHandshakeState()} should be RESUMING_SESSION, PIN has been verified blindly,
+ * send the authentication message over to server, then authenticate the message from server.
  *
- *  sendToClient(message.getNextMessage());
+ * {@code
+ * clientMessage = clientRunner.initReconnectAuthentication(previousKey)
+ * sendToServer(clientMessage.getNextMessage());
+ * HandshakeMessage lastMessage = clientRunner.authenticateReconnection(previousKey, message)
+ * }
  *
- *  message.getHandshakeState() should be VERIFICATION_NEEDED,
- *  if so show user code and ask to verify
- *  message.getVerificationCode();
+ * {@code lastMessage.getHandshakeState()} should be FINISHED if reconnection handshake is done.
  *
- *  serverRunner.verifyPin or invalidPin and continue same as client above.
+ * <p>2. As a server.
  *
+ * {@code
+ * byte[] initialMessage = getClientMessageBytes();
+ * HandshakeMessage message = serverRunner.respondToInitRequest(initialMessage);
+ * sendToClient(message.getNextMessage());
+ * byte[] clientMessage = getClientResponse();
+ * HandshakeMessage message = serverRunner.continueHandshake(clientMessage);}
+ *
+ * <p>if it is a first-time connection,
+ *
+ * {@code message.getHandshakeState()} should be VERIFICATION_NEEDED, show user the verification
+ * code and ask to verify.
+ * After PIN is confirmed, {@code HandshakeMessage lastMessage = serverRunner.verifyPin}, otherwise
+ * {@code clientRunner.invalidPin(); }
+ * Use {@code lastMessage.getKey()} to get the key for encryption.
+ *
+ * <p>If it is a reconnection,
+ *
+ * {@code message.getHandshakeState()} should be RESUMING_SESSION,PIN has been verified blindly,
+ * waiting for client message.
+ * After client message been received,
+ * {@code serverMessage = serverRunner.authenticateReconnection(previousKey, message);
+ * sendToClient(serverMessage.getNextMessage());}
+ * {@code serverMessage.getHandshakeState()} should be FINISHED if reconnection handshake is done.
  *
  * Also see {@link EncryptionRunnerTest} for examples.
  */
@@ -68,6 +92,7 @@
      *
      * @return A handshake message with information about the handshake that is started.
      */
+    @NonNull
     HandshakeMessage initHandshake();
 
     /**
@@ -76,9 +101,9 @@
      *
      * @param initializationRequest the bytes that the other device sent over.
      * @return a handshake message with information about the handshake.
-     *
      * @throws HandshakeException if initialization request is invalid.
      */
+    @NonNull
     HandshakeMessage respondToInitRequest(@NonNull byte[] initializationRequest)
             throws HandshakeException;
 
@@ -87,9 +112,9 @@
      *
      * @param response the response from the other device.
      * @return a message that can be used to continue the handshake.
-     *
      * @throws HandshakeException if unexpected bytes in response.
      */
+    @NonNull
     HandshakeMessage continueHandshake(@NonNull byte[] response) throws HandshakeException;
 
     /**
@@ -98,6 +123,7 @@
      *
      * @throws HandshakeException if not in state to verify pin.
      */
+    @NonNull
     HandshakeMessage verifyPin() throws HandshakeException;
 
     /**
@@ -107,11 +133,49 @@
     void invalidPin();
 
     /**
+     * Verifies the reconnection message.
+     *
+     * <p>The message passed to this method should have been generated by
+     * {@link #initReconnectAuthentication(byte[] previousKey)}.
+     *
+     * <p>If the message is valid, then a {@link HandshakeMessage} will be returned that contains
+     * the encryption key and a handshake message which can be used to verify the other side of the
+     * connection.
+     *
+     * @param previousKey previously stored key.
+     * @param message     message from the client
+     * @return a handshake message with an encryption key if verification succeed.
+     * @throws HandshakeException if the message does not match.
+     */
+    @NonNull
+    HandshakeMessage authenticateReconnection(@NonNull byte[] message, @NonNull byte[] previousKey)
+            throws HandshakeException;
+
+    /**
+     * Initiates the reconnection verification by generating a message that should be sent to the
+     * device that is being reconnected to.
+     *
+     * @param previousKey previously stored key.
+     * @return a handshake message with client's message which will be sent to server.
+     * @throws HandshakeException when get encryption key's unique session fail.
+     */
+    @NonNull
+    HandshakeMessage initReconnectAuthentication(@NonNull byte[] previousKey)
+            throws HandshakeException;
+
+    /**
      * De-serializes a previously serialized key generated by an instance of this encryption runner.
      *
      * @param serialized the serialized bytes of the key.
      * @return the Key object used for encryption.
      */
+    @NonNull
     Key keyOf(@NonNull byte[] serialized);
 
+    /**
+     * Set the signal if it is a reconnection process.
+     *
+     * @param isReconnect {@code true} if it is a reconnect.
+     */
+    void setIsReconnect(boolean isReconnect);
 }
diff --git a/EncryptionRunner/src/android/car/encryptionrunner/HandshakeMessage.java b/EncryptionRunner/src/android/car/encryptionrunner/HandshakeMessage.java
index 347c9fa..fa6705d 100644
--- a/EncryptionRunner/src/android/car/encryptionrunner/HandshakeMessage.java
+++ b/EncryptionRunner/src/android/car/encryptionrunner/HandshakeMessage.java
@@ -33,12 +33,8 @@
      * States for handshake progress.
      */
     @Retention(RetentionPolicy.SOURCE)
-    @IntDef({
-            HandshakeState.UNKNOWN,
-            HandshakeState.IN_PROGRESS,
-            HandshakeState.VERIFICATION_NEEDED,
-            HandshakeState.FINISHED,
-            HandshakeState.INVALID})
+    @IntDef({HandshakeState.UNKNOWN, HandshakeState.IN_PROGRESS, HandshakeState.VERIFICATION_NEEDED,
+            HandshakeState.FINISHED, HandshakeState.INVALID, HandshakeState.RESUMING_SESSION,})
     public @interface HandshakeState {
         /**
          * The initial state, this value is not expected to be returned.
@@ -60,9 +56,14 @@
          * The handshake is complete and not successful.
          */
         int INVALID = 4;
+        /**
+         * The handshake is complete, but extra verification is needed.
+         */
+        int RESUMING_SESSION = 5;
     }
 
-    @HandshakeState private final int mHandshakeState;
+    @HandshakeState
+    private final int mHandshakeState;
     private final Key mKey;
     private final byte[] mNextMessage;
     private final String mVerificationCode;
@@ -121,7 +122,8 @@
     }
 
     static class Builder {
-        @HandshakeState int mHandshakeState;
+        @HandshakeState
+        int mHandshakeState;
         Key mKey;
         byte[] mNextMessage;
         String mVerificationCode;
diff --git a/EncryptionRunner/src/android/car/encryptionrunner/Key.java b/EncryptionRunner/src/android/car/encryptionrunner/Key.java
index ce35e98..2e32858 100644
--- a/EncryptionRunner/src/android/car/encryptionrunner/Key.java
+++ b/EncryptionRunner/src/android/car/encryptionrunner/Key.java
@@ -18,6 +18,7 @@
 
 import android.annotation.NonNull;
 
+import java.security.NoSuchAlgorithmException;
 import java.security.SignatureException;
 
 /**
@@ -27,7 +28,8 @@
     /**
      * Returns a serialized encryption key.
      */
-    @NonNull byte[] asBytes();
+    @NonNull
+    byte[] asBytes();
 
     /**
      * Encrypts data using this key.
@@ -35,6 +37,7 @@
      * @param data the data to be encrypted
      * @return the encrypted data.
      */
+    @NonNull
     byte[] encryptData(@NonNull byte[] data);
 
     /**
@@ -42,8 +45,16 @@
      *
      * @param encryptedData The encrypted data.
      * @return decrypted data.
-     *
      * @throws SignatureException if encrypted data is not properly signed.
      */
+    @NonNull
     byte[] decryptData(@NonNull byte[] encryptedData) throws SignatureException;
+
+    /**
+     * Returns a cryptographic digest of the key.
+     *
+     * @throws NoSuchAlgorithmException when a unique session can not be created.
+     */
+    @NonNull
+    byte[] getUniqueSession() throws NoSuchAlgorithmException;
 }
diff --git a/EncryptionRunner/src/android/car/encryptionrunner/Ukey2EncryptionRunner.java b/EncryptionRunner/src/android/car/encryptionrunner/Ukey2EncryptionRunner.java
index eed7852..904d5c2 100644
--- a/EncryptionRunner/src/android/car/encryptionrunner/Ukey2EncryptionRunner.java
+++ b/EncryptionRunner/src/android/car/encryptionrunner/Ukey2EncryptionRunner.java
@@ -16,16 +16,26 @@
 
 package android.car.encryptionrunner;
 
+import android.annotation.IntDef;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
 
 import com.google.security.cryptauth.lib.securegcm.D2DConnectionContext;
 import com.google.security.cryptauth.lib.securegcm.Ukey2Handshake;
+import com.google.security.cryptauth.lib.securemessage.CryptoOps;
 
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.security.InvalidKeyException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
 import java.security.SignatureException;
 
+import javax.crypto.spec.SecretKeySpec;
+
 /**
  * An {@link EncryptionRunner} that uses Ukey2 as the underlying implementation.
  */
@@ -33,15 +43,33 @@
 
     private static final Ukey2Handshake.HandshakeCipher CIPHER =
             Ukey2Handshake.HandshakeCipher.P256_SHA512;
-
+    private static final int RESUME_HMAC_LENGTH = 32;
+    private static final byte[] RESUME = "RESUME".getBytes();
+    private static final byte[] SERVER = "SERVER".getBytes();
+    private static final byte[] CLIENT = "CLIENT".getBytes();
     private static final int AUTH_STRING_LENGTH = 6;
 
+    @IntDef({Mode.UNKNOWN, Mode.CLIENT, Mode.SERVER})
+    private @interface Mode {
+        int UNKNOWN = 0;
+        int CLIENT = 1;
+        int SERVER = 2;
+    }
+
     private Ukey2Handshake mUkey2client;
     private boolean mRunnerIsInvalid;
+    private Key mCurrentKey;
+    private byte[] mCurrentUniqueSesion;
+    private byte[] mPrevUniqueSesion;
+    private boolean mIsReconnect;
+    private boolean mInitReconnectionVerification;
+    @Mode
+    private int mMode = Mode.UNKNOWN;
 
     @Override
     public HandshakeMessage initHandshake() {
         checkRunnerIsNew();
+        mMode = Mode.CLIENT;
         try {
             mUkey2client = Ukey2Handshake.forInitiator(CIPHER);
             return HandshakeMessage.newBuilder()
@@ -56,9 +84,15 @@
     }
 
     @Override
+    public void setIsReconnect(boolean isReconnect) {
+        mIsReconnect = isReconnect;
+    }
+
+    @Override
     public HandshakeMessage respondToInitRequest(byte[] initializationRequest)
             throws HandshakeException {
         checkRunnerIsNew();
+        mMode = Mode.SERVER;
         try {
             if (mUkey2client != null) {
                 throw new IllegalStateException("Cannot reuse encryption runners, "
@@ -101,11 +135,18 @@
             if (mUkey2client.getHandshakeState() == Ukey2Handshake.State.IN_PROGRESS) {
                 nextMessage = mUkey2client.getNextHandshakeMessage();
             }
-
             String verificationCode = null;
             if (mUkey2client.getHandshakeState() == Ukey2Handshake.State.VERIFICATION_NEEDED) {
+                // getVerificationString() needs to be called before verifyPin().
                 verificationCode = generateReadablePairingCode(
                         mUkey2client.getVerificationString(AUTH_STRING_LENGTH));
+                if (mIsReconnect) {
+                    HandshakeMessage handshakeMessage = verifyPin();
+                    return HandshakeMessage.newBuilder()
+                            .setHandshakeState(handshakeMessage.getHandshakeState())
+                            .setNextMessage(nextMessage)
+                            .build();
+                }
             }
             return HandshakeMessage.newBuilder()
                     .setHandshakeState(getHandshakeState())
@@ -158,20 +199,128 @@
         public byte[] decryptData(byte[] encryptedData) throws SignatureException {
             return mConnectionContext.decodeMessageFromPeer(encryptedData);
         }
+
+        @Override
+        public byte[] getUniqueSession() throws NoSuchAlgorithmException {
+            return mConnectionContext.getSessionUnique();
+        }
     }
 
     @Override
     public HandshakeMessage verifyPin() throws HandshakeException {
         checkInitialized();
         mUkey2client.verifyHandshake();
+        int state = getHandshakeState();
         try {
-            return HandshakeMessage.newBuilder()
-                    .setHandshakeState(getHandshakeState())
-                    .setKey(new UKey2Key(mUkey2client.toConnectionContext()))
-                    .build();
+            mCurrentKey = new UKey2Key(mUkey2client.toConnectionContext());
         } catch (com.google.security.cryptauth.lib.securegcm.HandshakeException e) {
             throw new HandshakeException(e);
         }
+        return HandshakeMessage.newBuilder()
+                .setHandshakeState(state)
+                .setKey(mCurrentKey)
+                .build();
+    }
+
+    /**
+     * <p>After getting message from the other device, authenticate the message with the previous
+     * stored key.
+     *
+     * If current device inits the reconnection authentication by calling {@code
+     * initReconnectAuthentication} and sends the message to the other device, the other device
+     * will call {@code authenticateReconnection()} with the received message and send its own
+     * message back to the init device. The init device will call {@code
+     * authenticateReconnection()} on the received message, but do not need to set the next
+     * message.
+     */
+    @Override
+    public HandshakeMessage authenticateReconnection(byte[] message, byte[] previousKey)
+            throws HandshakeException {
+        if (!mIsReconnect) {
+            throw new HandshakeException(
+                    "Reconnection authentication requires setIsReconnect(true)");
+        }
+        if (mCurrentKey == null) {
+            throw new HandshakeException("Current key is null, make sure verifyPin() is called.");
+        }
+        if (message.length != RESUME_HMAC_LENGTH) {
+            mRunnerIsInvalid = true;
+            throw new HandshakeException("Failing because (message.length =" + message.length
+                    + ") is not equal to " + RESUME_HMAC_LENGTH);
+        }
+        try {
+            mCurrentUniqueSesion = mCurrentKey.getUniqueSession();
+            mPrevUniqueSesion = keyOf(previousKey).getUniqueSession();
+        } catch (NoSuchAlgorithmException e) {
+            throw new HandshakeException(e);
+        }
+        switch (mMode) {
+            case Mode.SERVER:
+                if (!MessageDigest.isEqual(
+                        message, computeMAC(mPrevUniqueSesion, mCurrentUniqueSesion, CLIENT))) {
+                    mRunnerIsInvalid = true;
+                    throw new HandshakeException("Reconnection authentication failed.");
+                }
+                return HandshakeMessage.newBuilder()
+                        .setHandshakeState(HandshakeMessage.HandshakeState.FINISHED)
+                        .setKey(mCurrentKey)
+                        .setNextMessage(mInitReconnectionVerification ? null
+                                : computeMAC(mPrevUniqueSesion, mCurrentUniqueSesion, SERVER))
+                        .build();
+            case Mode.CLIENT:
+                if (!MessageDigest.isEqual(
+                        message, computeMAC(mPrevUniqueSesion, mCurrentUniqueSesion, SERVER))) {
+                    mRunnerIsInvalid = true;
+                    throw new HandshakeException("Reconnection authentication failed.");
+                }
+                return HandshakeMessage.newBuilder()
+                        .setHandshakeState(HandshakeMessage.HandshakeState.FINISHED)
+                        .setKey(mCurrentKey)
+                        .setNextMessage(mInitReconnectionVerification ? null
+                                : computeMAC(mPrevUniqueSesion, mCurrentUniqueSesion, CLIENT))
+                        .build();
+            default:
+                throw new IllegalStateException(
+                        "Encountered unexpected role during authenticateReconnection: " + mMode);
+        }
+    }
+
+    /**
+     * Both client and server can call this method to send authentication message to the other
+     * device.
+     */
+    @Override
+    public HandshakeMessage initReconnectAuthentication(byte[] previousKey)
+            throws HandshakeException {
+        if (!mIsReconnect) {
+            throw new HandshakeException(
+                    "Reconnection authentication requires setIsReconnect(true).");
+        }
+        if (mCurrentKey == null) {
+            throw new HandshakeException("Current key is null, make sure verifyPin() is called.");
+        }
+        mInitReconnectionVerification = true;
+        try {
+            mCurrentUniqueSesion = mCurrentKey.getUniqueSession();
+            mPrevUniqueSesion = keyOf(previousKey).getUniqueSession();
+        } catch (NoSuchAlgorithmException e) {
+            throw new HandshakeException(e);
+        }
+        switch (mMode) {
+            case Mode.SERVER:
+                return HandshakeMessage.newBuilder()
+                        .setHandshakeState(HandshakeMessage.HandshakeState.RESUMING_SESSION)
+                        .setNextMessage(computeMAC(mPrevUniqueSesion, mCurrentUniqueSesion, SERVER))
+                        .build();
+            case Mode.CLIENT:
+                return HandshakeMessage.newBuilder()
+                        .setHandshakeState(HandshakeMessage.HandshakeState.RESUMING_SESSION)
+                        .setNextMessage(computeMAC(mPrevUniqueSesion, mCurrentUniqueSesion, CLIENT))
+                        .build();
+            default:
+                throw new IllegalStateException(
+                        "Encountered unexpected role during authenticateReconnection: " + mMode);
+        }
     }
 
     @HandshakeMessage.HandshakeState
@@ -182,6 +331,9 @@
             case ERROR:
                 throw new IllegalStateException("unexpected error state");
             case FINISHED:
+                if (mIsReconnect) {
+                    return HandshakeMessage.HandshakeState.RESUMING_SESSION;
+                }
                 return HandshakeMessage.HandshakeState.FINISHED;
             case IN_PROGRESS:
                 return HandshakeMessage.HandshakeState.IN_PROGRESS;
@@ -218,4 +370,28 @@
             throw new IllegalStateException("runner has been invalidated");
         }
     }
+
+    @Nullable
+    private byte[] computeMAC(byte[] previous, byte[] next, byte[] info) {
+        try {
+            SecretKeySpec inputKeyMaterial = new SecretKeySpec(
+                    concatByteArrays(previous, next), "" /* key type is just plain raw bytes */);
+            return CryptoOps.hkdf(inputKeyMaterial, RESUME, info);
+        } catch (NoSuchAlgorithmException | InvalidKeyException e) {
+            // Does not happen in practice
+            Log.e(TAG, "Compute MAC failed");
+            return null;
+        }
+    }
+
+    private static byte[] concatByteArrays(@NonNull byte[] a, @NonNull byte[] b) {
+        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+        try {
+            outputStream.write(a);
+            outputStream.write(b);
+        } catch (IOException e) {
+            return new byte[0];
+        }
+        return outputStream.toByteArray();
+    }
 }
diff --git a/EncryptionRunner/test/android/car/encryptionrunner/EncryptionRunnerTest.java b/EncryptionRunner/test/android/car/encryptionrunner/EncryptionRunnerTest.java
deleted file mode 100644
index c7893ad..0000000
--- a/EncryptionRunner/test/android/car/encryptionrunner/EncryptionRunnerTest.java
+++ /dev/null
@@ -1,159 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.car.encryptionrunner;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.util.Log;
-
-import androidx.test.runner.AndroidJUnit4;
-
-import junit.framework.Assert;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@RunWith(AndroidJUnit4.class)
-public class EncryptionRunnerTest {
-
-    private static final byte[] sTestData = "test data".getBytes();
-    private interface RunnerFactory {
-        EncryptionRunner newRunner();
-    }
-
-    @Test
-    public void happyFlow_dummyRunner() throws Exception {
-        verifyRunners(EncryptionRunnerFactory::newDummyRunner);
-    }
-
-    @Test
-    public void happyFlow_ukey2Runner() throws Exception {
-        verifyRunners(EncryptionRunnerFactory::newRunner);
-    }
-
-    /**
-     * Runs through a happy flow of encryption runners and verifies that they behave as expected.
-     * Some * of the test is implementation specific because the interface doesn't specify how many
-     * round * trips may be needed but this test makes assumptions( i.e. white box testing).
-     */
-    private void verifyRunners(RunnerFactory runnerFactory) throws Exception {
-        EncryptionRunner clientRunner = runnerFactory.newRunner();
-        EncryptionRunner serverRunner = runnerFactory.newRunner();
-
-        verifyHandshake(clientRunner, serverRunner);
-
-        HandshakeMessage finalServerMessage = serverRunner.verifyPin();
-        assertThat(finalServerMessage.getHandshakeState())
-                .isEqualTo(HandshakeMessage.HandshakeState.FINISHED);
-        assertThat(finalServerMessage.getKey()).isNotNull();
-        assertThat(finalServerMessage.getNextMessage()).isNull();
-
-        HandshakeMessage finalClientMessage = clientRunner.verifyPin();
-        assertThat(finalClientMessage.getHandshakeState())
-                .isEqualTo(HandshakeMessage.HandshakeState.FINISHED);
-        assertThat(finalClientMessage.getKey()).isNotNull();
-        assertThat(finalClientMessage.getNextMessage()).isNull();
-
-        assertThat(finalServerMessage.getKey()
-                .decryptData(finalClientMessage.getKey().encryptData(sTestData)))
-                .isEqualTo(sTestData);
-        assertThat(finalClientMessage.getKey()
-                .decryptData(finalServerMessage.getKey().encryptData(sTestData)))
-                .isEqualTo(sTestData);
-    }
-
-    private void verifyHandshake(EncryptionRunner clientRunner, EncryptionRunner serverRunner)
-            throws Exception {
-        HandshakeMessage initialClientMessage = clientRunner.initHandshake();
-
-        assertThat(initialClientMessage.getHandshakeState())
-                .isEqualTo(HandshakeMessage.HandshakeState.IN_PROGRESS);
-        assertThat(initialClientMessage.getKey()).isNull();
-        assertThat(initialClientMessage.getNextMessage()).isNotNull();
-
-        // This and the following similar log statements are useful when running this test to
-        // find the payload sizes.
-        Log.i(EncryptionRunner.TAG,
-                "initial client size:" + initialClientMessage.getNextMessage().length);
-
-        HandshakeMessage initialServerMessage =
-                serverRunner.respondToInitRequest(initialClientMessage.getNextMessage());
-
-        assertThat(initialServerMessage.getHandshakeState())
-                .isEqualTo(HandshakeMessage.HandshakeState.IN_PROGRESS);
-        assertThat(initialServerMessage.getKey()).isNull();
-        assertThat(initialServerMessage.getNextMessage()).isNotNull();
-
-        Log.i(EncryptionRunner.TAG,
-                "initial server message size:" + initialServerMessage.getNextMessage().length);
-
-        HandshakeMessage clientMessage =
-                clientRunner.continueHandshake(initialServerMessage.getNextMessage());
-
-        assertThat(clientMessage.getHandshakeState())
-                .isEqualTo(HandshakeMessage.HandshakeState.VERIFICATION_NEEDED);
-        assertThat(clientMessage.getKey()).isNull();
-        assertThat(clientMessage.getVerificationCode()).isNotEmpty();
-        assertThat(clientMessage.getNextMessage()).isNotNull();
-
-        Log.i(EncryptionRunner.TAG,
-                "second client message size:" + clientMessage.getNextMessage().length);
-
-        HandshakeMessage serverMessage =
-                serverRunner.continueHandshake(clientMessage.getNextMessage());
-        assertThat(serverMessage.getHandshakeState())
-                .isEqualTo(HandshakeMessage.HandshakeState.VERIFICATION_NEEDED);
-        assertThat(serverMessage.getKey()).isNull();
-        assertThat(serverMessage.getNextMessage()).isNull();
-
-        Log.i(EncryptionRunner.TAG,
-                "last server message size:" + clientMessage.getNextMessage().length);
-    }
-
-    @Test
-    public void invalidPin_ukey2() throws Exception {
-        invalidPinTest(EncryptionRunnerFactory::newRunner);
-    }
-
-    @Test
-    public void invalidPin_dummy() throws Exception {
-        invalidPinTest(EncryptionRunnerFactory::newDummyRunner);
-    }
-
-    private void invalidPinTest(RunnerFactory runnerFactory) throws Exception {
-        EncryptionRunner clientRunner = runnerFactory.newRunner();
-        EncryptionRunner serverRunner = runnerFactory.newRunner();
-
-        verifyHandshake(clientRunner, serverRunner);
-        clientRunner.invalidPin();
-        serverRunner.invalidPin();
-
-        try {
-            clientRunner.verifyPin();
-            Assert.fail();
-        } catch (Exception ignored) {
-            // pass
-        }
-
-        try {
-            serverRunner.verifyPin();
-            Assert.fail();
-        } catch (Exception ignored) {
-            // pass
-        }
-    }
-}
diff --git a/EncryptionRunner/test/android/car/encryptionrunner/Ukey2EncryptionRunnerTest.java b/EncryptionRunner/test/android/car/encryptionrunner/Ukey2EncryptionRunnerTest.java
deleted file mode 100644
index 14ca77d..0000000
--- a/EncryptionRunner/test/android/car/encryptionrunner/Ukey2EncryptionRunnerTest.java
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.car.encryptionrunner;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@RunWith(AndroidJUnit4.class)
-public class Ukey2EncryptionRunnerTest {
-
-    private Ukey2EncryptionRunner mRunner;
-
-    @Before
-    public void setup() {
-        mRunner = new Ukey2EncryptionRunner();
-    }
-
-    @Test
-    public void generateReadablePairingCode_modsBytesAcrossRange() throws Exception {
-        // 194 is an example of a value that would fail if using signed instead of unsigned ints
-        // 194 -> 11000010
-        // 11000010 -> 194 (unsigned 8-bit int)
-        // 11000010 -> -62 (signed 8-bit int)
-        byte[] bytes = new byte[]{0, 7, (byte) 161, (byte) 194, (byte) 196, (byte) 255};
-        String pairingCode = mRunner.generateReadablePairingCode(bytes);
-
-        assertThat(pairingCode).isEqualTo("071465");
-    }
-}
diff --git a/OWNERS b/OWNERS
index b59c421..e728889 100644
--- a/OWNERS
+++ b/OWNERS
@@ -1,2 +1,6 @@
 # Each subdirectory should have its OWNERS.
 # Owned by Android Automotive Embedded (go/aae).
+felipeal@google.com
+gurunagarajan@google.com
+keunyoung@google.com
+sgurun@google.com
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg
index 38f9800..3da2dc9 100644
--- a/PREUPLOAD.cfg
+++ b/PREUPLOAD.cfg
@@ -5,3 +5,5 @@
 [Builtin Hooks]
 commit_msg_changeid_field = true
 commit_msg_test_field = true
+cpplint = true
+clang_format = true
diff --git a/car-bugreportd/car-bugreportd.rc b/car-bugreportd/car-bugreportd.rc
index 0935a60..e994859 100644
--- a/car-bugreportd/car-bugreportd.rc
+++ b/car-bugreportd/car-bugreportd.rc
@@ -10,8 +10,7 @@
 
 # car-dumpstatez generates a zipped bugreport but also uses a socket to print the file location once
 # it is finished.
-service car-dumpstatez /system/bin/dumpstate -S -d -z \
-        -o /data/user_de/0/com.android.shell/files/bugreports/bugreport
+service car-dumpstatez /system/bin/dumpstate -S -d -z
     socket dumpstate stream 0660 shell log
     class main
     disabled
diff --git a/car-lib/Android.bp b/car-lib/Android.bp
index b8d4639..df7c3b7 100644
--- a/car-lib/Android.bp
+++ b/car-lib/Android.bp
@@ -12,8 +12,19 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+filegroup {
+    name: "libcarpowermanager_aidl",
+    srcs: [
+        "src/android/car/ICar.aidl",
+        "src/android/car/hardware/power/ICarPower.aidl",
+        "src/android/car/hardware/power/ICarPowerStateListener.aidl",
+    ],
+    path: "src",
+}
+
 cc_library {
     name: "libcarpowermanager",
+    vendor_available: true,
 
     aidl: {
         export_aidl_headers: true,
@@ -40,9 +51,7 @@
     ],
 
     srcs: [
-        "src/android/car/ICar.aidl",
-        "src/android/car/hardware/power/ICarPower.aidl",
-        "src/android/car/hardware/power/ICarPowerStateListener.aidl",
+        ":libcarpowermanager_aidl",
         "native/CarPowerManager/CarPowerManager.cpp",
     ],
 }
@@ -56,7 +65,7 @@
     srcs: ["src/android/car/navigation/navigation_state.proto"]
 }
 
-// library to access settings from CarSettings
+// library to access settings from CarSettings 
 java_library {
     name: "android.car.settings",
     srcs: ["src/android/car/settings/CarSettings.java"]
@@ -66,18 +75,21 @@
     name: "android.car",
     srcs: [
         "src/**/*.java",
-        "src_feature_future/**/*.java",
         "src/**/I*.aidl",
     ],
     aidl: {
         include_dirs: [
             "system/bt/binder",
+            "packages/services/Car/watchdog/aidl",
         ],
     },
     exclude_srcs: [
         "src/android/car/storagemonitoring/IoStats.aidl",
         "src/android/car/storagemonitoring/IoStatsEntry.aidl",
     ],
+    static_libs: [
+        "carwatchdog_aidl_interface-java",
+    ],
     product_variables: {
         pdk: {
             enabled: false,
@@ -90,7 +102,6 @@
     name: "android.car-docs-default",
     srcs: [
         "src/**/*.java",
-        "src_feature_future/**/*.java",
     ],
     libs: [
         "android.car",
@@ -137,16 +148,20 @@
             api_file: ":android-car-last-released-api",
             removed_api_file: "api/removed.txt",
             args: " -hide 2 -hide 3 -hide 4 -hide 5 -hide 6 -hide 24 -hide 25 -hide 26 -hide 27 " +
-                  " -warning 7 -warning 8 -warning 9 -warning 10 -warning 11 -warning 12 " +
-                  " -warning 13 -warning 14 -warning 15 -warning 16 -warning 17 -warning 18 -hide 113 ",
+                " -warning 7 -warning 8 -warning 9 -warning 10 -warning 11 -warning 12 " +
+                " -warning 13 -warning 14 -warning 15 -warning 16 -warning 17 -warning 18 -hide 113 ",
         },
         current: {
             api_file: "api/current.txt",
             removed_api_file: "api/removed.txt",
             args: " -error 2 -error 3 -error 4 -error 5 -error 6 -error 7 -error 8 -error 9 -error 10 -error 11 " +
-                  " -error 12 -error 13 -error 14 -error 15 -error 16 -error 17 -error 18 -error 19 -error 20 " +
-                  " -error 21 -error 23 -error 24 -error 25 -hide 113 ",
+                " -error 12 -error 13 -error 14 -error 15 -error 16 -error 17 -error 18 -error 19 -error 20 " +
+                " -error 21 -error 23 -error 24 -error 25 -hide 113 ",
         },
+        api_lint: {
+            enabled: true,
+            baseline_file: "api/lint-baseline.txt",
+        }
     },
 }
 
@@ -164,16 +179,20 @@
             api_file: ":android-car-last-released-system-api",
             removed_api_file: "api/system-removed.txt",
             args: " -hide 2 -hide 3 -hide 4 -hide 5 -hide 6 -hide 24 -hide 25 -hide 26 -hide 27 " +
-                  " -warning 7 -warning 8 -warning 9 -warning 10 -warning 11 -warning 12 " +
-                  " -warning 13 -warning 14 -warning 15 -warning 16 -warning 17 -warning 18 -hide 113 ",
+                " -warning 7 -warning 8 -warning 9 -warning 10 -warning 11 -warning 12 " +
+                " -warning 13 -warning 14 -warning 15 -warning 16 -warning 17 -warning 18 -hide 113 ",
         },
         current: {
             api_file: "api/system-current.txt",
             removed_api_file: "api/system-removed.txt",
             args: " -error 2 -error 3 -error 4 -error 5 -error 6 -error 7 -error 8 -error 9 -error 10 -error 11 " +
-                  " -error 12 -error 13 -error 14 -error 15 -error 16 -error 17 -error 18 -error 19 -error 20 " +
-                  " -error 21 -error 23 -error 24 -error 25 -hide 113 ",
+                " -error 12 -error 13 -error 14 -error 15 -error 16 -error 17 -error 18 -error 19 -error 20 " +
+                " -error 21 -error 23 -error 24 -error 25 -hide 113 ",
         },
+        api_lint: {
+            enabled: true,
+            baseline_file: "api/system-lint-baseline.txt",
+        }
     },
 }
 
@@ -227,17 +246,28 @@
             enabled: false,
         },
     },
-    compile_dex: true,
+    installable: false,
     dist: {
         targets: ["dist_files"],
     }
 }
 
 java_library {
+    name: "android.car-stubs-dex",
+    static_libs: ["android.car-stubs"],
+    sdk_version: "current",
+    product_variables: {
+        pdk: {
+            enabled: false,
+        },
+    },
+    compile_dex: true,
+}
+
+java_library {
     name: "android.car-system-stubs",
     srcs: [
         ":android.car-system-stubs-docs",
-        "src_stub/**/*.java",
     ],
     sdk_version: "system_current",
     product_variables: {
@@ -245,13 +275,25 @@
             enabled: false,
         },
     },
-    compile_dex: true,
+    installable: false,
     dist: {
         targets: ["dist_files"],
     }
 }
 
 java_library {
+    name: "android.car-system-stubs-dex",
+    static_libs: ["android.car-system-stubs"],
+    sdk_version: "system_current",
+    product_variables: {
+        pdk: {
+            enabled: false,
+        },
+    },
+    compile_dex: true,
+}
+
+java_library {
     name: "android.car-test-stubs",
     srcs: [
         ":android.car-test-stubs-docs",
@@ -262,8 +304,42 @@
             enabled: false,
         },
     },
+    installable: false,
+}
+
+java_library {
+    name: "android.car-test-stubs-dex",
+    static_libs: ["android.car-test-stubs"],
+    sdk_version: "test_current",
+    product_variables: {
+        pdk: {
+            enabled: false,
+        },
+    },
     compile_dex: true,
     dist: {
         targets: ["dist_files"],
     }
 }
+
+// Export the api/system-current.txt file.
+filegroup {
+    name: "car-api-system-current.txt",
+    visibility: [
+        "//cts/tests/signature/api",
+    ],
+    srcs: [
+        "api/system-current.txt",
+    ],
+}
+
+// Export the api/system-removed.txt file.
+filegroup {
+    name: "car-api-system-removed.txt",
+    visibility: [
+        "//cts/tests/signature/api",
+    ],
+    srcs: [
+        "api/system-removed.txt",
+    ],
+}
diff --git a/car-lib/api/baseline.txt b/car-lib/api/baseline.txt
index 16e4b7b..e274b0a 100644
--- a/car-lib/api/baseline.txt
+++ b/car-lib/api/baseline.txt
@@ -1,41 +1,173 @@
 // Baseline format: 1.0
-HiddenTypeParameter: android.car.hardware.CarSensorManager#getPropertyList():
-    Method android.car.hardware.CarSensorManager.getPropertyList() references hidden type class android.car.hardware.CarPropertyConfig.
-HiddenTypeParameter: android.car.navigation.CarNavigationStatusManager#getInstrumentClusterInfo():
-    Method android.car.navigation.CarNavigationStatusManager.getInstrumentClusterInfo() references hidden type android.car.navigation.CarNavigationInstrumentCluster.
-
-
-HiddenTypedefConstant: android.car.CarInfoManager#getEvConnectorTypes():
-    Typedef references constant which isn't part of the API, skipping in documentation: android.car.EvConnectorType#UNKNOWN
-HiddenTypedefConstant: android.car.CarInfoManager#getFuelTypes():
-    Typedef references constant which isn't part of the API, skipping in documentation: android.car.FuelType#UNKNOWN
-HiddenTypedefConstant: android.car.hardware.CarSensorManager#getLatestSensorEvent(int) parameter #0:
-    Typedef references constant which isn't part of the API, skipping in documentation: android.car.hardware.CarSensorManager#SENSOR_TYPE_ENGINE_OIL_LEVEL
-HiddenTypedefConstant: android.car.hardware.CarSensorManager#isSensorSupported(int) parameter #0:
-    Typedef references constant which isn't part of the API, skipping in documentation: android.car.hardware.CarSensorManager#SENSOR_TYPE_ENGINE_OIL_LEVEL
-HiddenTypedefConstant: android.car.hardware.CarSensorManager#isSensorSupported(int[], int) parameter #1:
-    Typedef references constant which isn't part of the API, skipping in documentation: android.car.hardware.CarSensorManager#SENSOR_TYPE_ENGINE_OIL_LEVEL
-HiddenTypedefConstant: android.car.hardware.CarSensorManager#registerListener(android.car.hardware.CarSensorManager.OnSensorChangedListener, int, int) parameter #1:
-    Typedef references constant which isn't part of the API, skipping in documentation: android.car.hardware.CarSensorManager#SENSOR_TYPE_ENGINE_OIL_LEVEL
-HiddenTypedefConstant: android.car.hardware.CarSensorManager#unregisterListener(android.car.hardware.CarSensorManager.OnSensorChangedListener, int) parameter #1:
-    Typedef references constant which isn't part of the API, skipping in documentation: android.car.hardware.CarSensorManager#SENSOR_TYPE_ENGINE_OIL_LEVEL
-
-
-ReferencesHidden: android.car.hardware.CarSensorManager#getPropertyList():
-    Class android.car.hardware.CarPropertyConfig is hidden but was referenced (as return type parameter) from public method android.car.hardware.CarSensorManager.getPropertyList()
-ReferencesHidden: android.car.navigation.CarNavigationStatusManager#getInstrumentClusterInfo():
-    Class android.car.navigation.CarNavigationInstrumentCluster is hidden but was referenced (as return type) from public method android.car.navigation.CarNavigationStatusManager.getInstrumentClusterInfo()
-
-
-RequiresPermission: android.car.hardware.CarSensorManager#registerListener(android.car.hardware.CarSensorManager.OnSensorChangedListener, int, int):
-    Method 'registerListener' documentation mentions permissions already declared by @RequiresPermission
-
-
-SdkConstant: android.car.Car#CAR_INTENT_ACTION_MEDIA_TEMPLATE:
-    Field 'CAR_INTENT_ACTION_MEDIA_TEMPLATE' is missing @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
-
-
-Todo: android.car.CarInfoManager#getVehicleId():
-    Documentation mentions 'TODO'
-
-
+MissingPermission: android.car.VehiclePropertyIds#ABS_ACTIVE:
+    Permission Car.PERMISSION_CAR_DYNAMICS_STATE required by field VehiclePropertyIds.ABS_ACTIVE is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#AP_POWER_BOOTUP_REASON:
+    Permission Car.PERMISSION_CAR_POWER required by field VehiclePropertyIds.AP_POWER_BOOTUP_REASON is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#AP_POWER_STATE_REPORT:
+    Permission Car.PERMISSION_CAR_POWER required by field VehiclePropertyIds.AP_POWER_STATE_REPORT is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#AP_POWER_STATE_REQ:
+    Permission Car.PERMISSION_CAR_POWER required by field VehiclePropertyIds.AP_POWER_STATE_REQ is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#DISPLAY_BRIGHTNESS:
+    Permission Car.PERMISSION_CAR_POWER required by field VehiclePropertyIds.DISPLAY_BRIGHTNESS is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#DOOR_LOCK:
+    Permission Car.PERMISSION_CONTROL_CAR_DOORS required by field VehiclePropertyIds.DOOR_LOCK is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#DOOR_MOVE:
+    Permission Car.PERMISSION_CONTROL_CAR_DOORS required by field VehiclePropertyIds.DOOR_MOVE is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#DOOR_POS:
+    Permission Car.PERMISSION_CONTROL_CAR_DOORS required by field VehiclePropertyIds.DOOR_POS is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#ENGINE_COOLANT_TEMP:
+    Permission Car.PERMISSION_CAR_ENGINE_DETAILED required by field VehiclePropertyIds.ENGINE_COOLANT_TEMP is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#ENGINE_OIL_LEVEL:
+    Permission Car.PERMISSION_CAR_ENGINE_DETAILED required by field VehiclePropertyIds.ENGINE_OIL_LEVEL is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#ENGINE_OIL_TEMP:
+    Permission Car.PERMISSION_CAR_ENGINE_DETAILED required by field VehiclePropertyIds.ENGINE_OIL_TEMP is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#ENGINE_RPM:
+    Permission Car.PERMISSION_CAR_ENGINE_DETAILED required by field VehiclePropertyIds.ENGINE_RPM is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#FOG_LIGHTS_STATE:
+    Permission Car.PERMISSION_EXTERIOR_LIGHTS required by field VehiclePropertyIds.FOG_LIGHTS_STATE is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#FOG_LIGHTS_SWITCH:
+    Permission Car.PERMISSION_CONTROL_EXTERIOR_LIGHTS required by field VehiclePropertyIds.FOG_LIGHTS_SWITCH is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#HAZARD_LIGHTS_STATE:
+    Permission Car.PERMISSION_EXTERIOR_LIGHTS required by field VehiclePropertyIds.HAZARD_LIGHTS_STATE is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#HAZARD_LIGHTS_SWITCH:
+    Permission Car.PERMISSION_CONTROL_EXTERIOR_LIGHTS required by field VehiclePropertyIds.HAZARD_LIGHTS_SWITCH is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#HEADLIGHTS_STATE:
+    Permission Car.PERMISSION_EXTERIOR_LIGHTS required by field VehiclePropertyIds.HEADLIGHTS_STATE is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#HEADLIGHTS_SWITCH:
+    Permission Car.PERMISSION_CONTROL_EXTERIOR_LIGHTS required by field VehiclePropertyIds.HEADLIGHTS_SWITCH is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#HIGH_BEAM_LIGHTS_STATE:
+    Permission Car.PERMISSION_EXTERIOR_LIGHTS required by field VehiclePropertyIds.HIGH_BEAM_LIGHTS_STATE is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#HIGH_BEAM_LIGHTS_SWITCH:
+    Permission Car.PERMISSION_CONTROL_EXTERIOR_LIGHTS required by field VehiclePropertyIds.HIGH_BEAM_LIGHTS_SWITCH is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#HVAC_ACTUAL_FAN_SPEED_RPM:
+    Permission Car.PERMISSION_CONTROL_CAR_CLIMATE required by field VehiclePropertyIds.HVAC_ACTUAL_FAN_SPEED_RPM is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#HVAC_AC_ON:
+    Permission Car.PERMISSION_CONTROL_CAR_CLIMATE required by field VehiclePropertyIds.HVAC_AC_ON is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#HVAC_AUTO_ON:
+    Permission Car.PERMISSION_CONTROL_CAR_CLIMATE required by field VehiclePropertyIds.HVAC_AUTO_ON is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#HVAC_AUTO_RECIRC_ON:
+    Permission Car.PERMISSION_CONTROL_CAR_CLIMATE required by field VehiclePropertyIds.HVAC_AUTO_RECIRC_ON is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#HVAC_DEFROSTER:
+    Permission Car.PERMISSION_CONTROL_CAR_CLIMATE required by field VehiclePropertyIds.HVAC_DEFROSTER is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#HVAC_DUAL_ON:
+    Permission Car.PERMISSION_CONTROL_CAR_CLIMATE required by field VehiclePropertyIds.HVAC_DUAL_ON is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#HVAC_FAN_DIRECTION:
+    Permission Car.PERMISSION_CONTROL_CAR_CLIMATE required by field VehiclePropertyIds.HVAC_FAN_DIRECTION is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#HVAC_FAN_DIRECTION_AVAILABLE:
+    Permission Car.PERMISSION_CONTROL_CAR_CLIMATE required by field VehiclePropertyIds.HVAC_FAN_DIRECTION_AVAILABLE is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#HVAC_FAN_SPEED:
+    Permission Car.PERMISSION_CONTROL_CAR_CLIMATE required by field VehiclePropertyIds.HVAC_FAN_SPEED is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#HVAC_MAX_AC_ON:
+    Permission Car.PERMISSION_CONTROL_CAR_CLIMATE required by field VehiclePropertyIds.HVAC_MAX_AC_ON is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#HVAC_MAX_DEFROST_ON:
+    Permission Car.PERMISSION_CONTROL_CAR_CLIMATE required by field VehiclePropertyIds.HVAC_MAX_DEFROST_ON is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#HVAC_POWER_ON:
+    Permission Car.PERMISSION_CONTROL_CAR_CLIMATE required by field VehiclePropertyIds.HVAC_POWER_ON is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#HVAC_RECIRC_ON:
+    Permission Car.PERMISSION_CONTROL_CAR_CLIMATE required by field VehiclePropertyIds.HVAC_RECIRC_ON is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#HVAC_SEAT_TEMPERATURE:
+    Permission Car.PERMISSION_CONTROL_CAR_CLIMATE required by field VehiclePropertyIds.HVAC_SEAT_TEMPERATURE is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#HVAC_SEAT_VENTILATION:
+    Permission Car.PERMISSION_CONTROL_CAR_CLIMATE required by field VehiclePropertyIds.HVAC_SEAT_VENTILATION is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#HVAC_SIDE_MIRROR_HEAT:
+    Permission Car.PERMISSION_CONTROL_CAR_CLIMATE required by field VehiclePropertyIds.HVAC_SIDE_MIRROR_HEAT is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#HVAC_STEERING_WHEEL_HEAT:
+    Permission Car.PERMISSION_CONTROL_CAR_CLIMATE required by field VehiclePropertyIds.HVAC_STEERING_WHEEL_HEAT is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#HVAC_TEMPERATURE_CURRENT:
+    Permission Car.PERMISSION_CONTROL_CAR_CLIMATE required by field VehiclePropertyIds.HVAC_TEMPERATURE_CURRENT is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#HVAC_TEMPERATURE_DISPLAY_UNITS:
+    Permission Car.PERMISSION_CONTROL_CAR_CLIMATE required by field VehiclePropertyIds.HVAC_TEMPERATURE_DISPLAY_UNITS is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#HVAC_TEMPERATURE_SET:
+    Permission Car.PERMISSION_CONTROL_CAR_CLIMATE required by field VehiclePropertyIds.HVAC_TEMPERATURE_SET is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#MIRROR_FOLD:
+    Permission Car.PERMISSION_CONTROL_CAR_MIRRORS required by field VehiclePropertyIds.MIRROR_FOLD is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#MIRROR_LOCK:
+    Permission Car.PERMISSION_CONTROL_CAR_MIRRORS required by field VehiclePropertyIds.MIRROR_LOCK is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#MIRROR_Y_MOVE:
+    Permission Car.PERMISSION_CONTROL_CAR_MIRRORS required by field VehiclePropertyIds.MIRROR_Y_MOVE is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#MIRROR_Y_POS:
+    Permission Car.PERMISSION_CONTROL_CAR_MIRRORS required by field VehiclePropertyIds.MIRROR_Y_POS is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#MIRROR_Z_MOVE:
+    Permission Car.PERMISSION_CONTROL_CAR_MIRRORS required by field VehiclePropertyIds.MIRROR_Z_MOVE is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#MIRROR_Z_POS:
+    Permission Car.PERMISSION_CONTROL_CAR_MIRRORS required by field VehiclePropertyIds.MIRROR_Z_POS is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#OBD2_FREEZE_FRAME:
+    Permission Car.PERMISSION_CAR_DIAGNOSTIC_READ_ALL required by field VehiclePropertyIds.OBD2_FREEZE_FRAME is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#OBD2_FREEZE_FRAME_CLEAR:
+    Permission Car.PERMISSION_CAR_DIAGNOSTIC_CLEAR required by field VehiclePropertyIds.OBD2_FREEZE_FRAME_CLEAR is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#OBD2_FREEZE_FRAME_INFO:
+    Permission Car.PERMISSION_CAR_DIAGNOSTIC_READ_ALL required by field VehiclePropertyIds.OBD2_FREEZE_FRAME_INFO is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#OBD2_LIVE_FRAME:
+    Permission Car.PERMISSION_CAR_DIAGNOSTIC_READ_ALL required by field VehiclePropertyIds.OBD2_LIVE_FRAME is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#PERF_ODOMETER:
+    Permission Car.PERMISSION_MILEAGE required by field VehiclePropertyIds.PERF_ODOMETER is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#SEAT_BACKREST_ANGLE_1_MOVE:
+    Permission Car.PERMISSION_CONTROL_CAR_SEATS required by field VehiclePropertyIds.SEAT_BACKREST_ANGLE_1_MOVE is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#SEAT_BACKREST_ANGLE_1_POS:
+    Permission Car.PERMISSION_CONTROL_CAR_SEATS required by field VehiclePropertyIds.SEAT_BACKREST_ANGLE_1_POS is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#SEAT_BACKREST_ANGLE_2_MOVE:
+    Permission Car.PERMISSION_CONTROL_CAR_SEATS required by field VehiclePropertyIds.SEAT_BACKREST_ANGLE_2_MOVE is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#SEAT_BACKREST_ANGLE_2_POS:
+    Permission Car.PERMISSION_CONTROL_CAR_SEATS required by field VehiclePropertyIds.SEAT_BACKREST_ANGLE_2_POS is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#SEAT_BELT_BUCKLED:
+    Permission Car.PERMISSION_CONTROL_CAR_SEATS required by field VehiclePropertyIds.SEAT_BELT_BUCKLED is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#SEAT_BELT_HEIGHT_MOVE:
+    Permission Car.PERMISSION_CONTROL_CAR_SEATS required by field VehiclePropertyIds.SEAT_BELT_HEIGHT_MOVE is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#SEAT_BELT_HEIGHT_POS:
+    Permission Car.PERMISSION_CONTROL_CAR_SEATS required by field VehiclePropertyIds.SEAT_BELT_HEIGHT_POS is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#SEAT_DEPTH_MOVE:
+    Permission Car.PERMISSION_CONTROL_CAR_SEATS required by field VehiclePropertyIds.SEAT_DEPTH_MOVE is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#SEAT_DEPTH_POS:
+    Permission Car.PERMISSION_CONTROL_CAR_SEATS required by field VehiclePropertyIds.SEAT_DEPTH_POS is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#SEAT_FORE_AFT_MOVE:
+    Permission Car.PERMISSION_CONTROL_CAR_SEATS required by field VehiclePropertyIds.SEAT_FORE_AFT_MOVE is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#SEAT_FORE_AFT_POS:
+    Permission Car.PERMISSION_CONTROL_CAR_SEATS required by field VehiclePropertyIds.SEAT_FORE_AFT_POS is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#SEAT_HEADREST_ANGLE_MOVE:
+    Permission Car.PERMISSION_CONTROL_CAR_SEATS required by field VehiclePropertyIds.SEAT_HEADREST_ANGLE_MOVE is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#SEAT_HEADREST_ANGLE_POS:
+    Permission Car.PERMISSION_CONTROL_CAR_SEATS required by field VehiclePropertyIds.SEAT_HEADREST_ANGLE_POS is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#SEAT_HEADREST_FORE_AFT_MOVE:
+    Permission Car.PERMISSION_CONTROL_CAR_SEATS required by field VehiclePropertyIds.SEAT_HEADREST_FORE_AFT_MOVE is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#SEAT_HEADREST_FORE_AFT_POS:
+    Permission Car.PERMISSION_CONTROL_CAR_SEATS required by field VehiclePropertyIds.SEAT_HEADREST_FORE_AFT_POS is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#SEAT_HEADREST_HEIGHT_MOVE:
+    Permission Car.PERMISSION_CONTROL_CAR_SEATS required by field VehiclePropertyIds.SEAT_HEADREST_HEIGHT_MOVE is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#SEAT_HEADREST_HEIGHT_POS:
+    Permission Car.PERMISSION_CONTROL_CAR_SEATS required by field VehiclePropertyIds.SEAT_HEADREST_HEIGHT_POS is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#SEAT_HEIGHT_MOVE:
+    Permission Car.PERMISSION_CONTROL_CAR_SEATS required by field VehiclePropertyIds.SEAT_HEIGHT_MOVE is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#SEAT_HEIGHT_POS:
+    Permission Car.PERMISSION_CONTROL_CAR_SEATS required by field VehiclePropertyIds.SEAT_HEIGHT_POS is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#SEAT_LUMBAR_FORE_AFT_MOVE:
+    Permission Car.PERMISSION_CONTROL_CAR_SEATS required by field VehiclePropertyIds.SEAT_LUMBAR_FORE_AFT_MOVE is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#SEAT_LUMBAR_FORE_AFT_POS:
+    Permission Car.PERMISSION_CONTROL_CAR_SEATS required by field VehiclePropertyIds.SEAT_LUMBAR_FORE_AFT_POS is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#SEAT_LUMBAR_SIDE_SUPPORT_MOVE:
+    Permission Car.PERMISSION_CONTROL_CAR_SEATS required by field VehiclePropertyIds.SEAT_LUMBAR_SIDE_SUPPORT_MOVE is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#SEAT_LUMBAR_SIDE_SUPPORT_POS:
+    Permission Car.PERMISSION_CONTROL_CAR_SEATS required by field VehiclePropertyIds.SEAT_LUMBAR_SIDE_SUPPORT_POS is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#SEAT_MEMORY_SELECT:
+    Permission Car.PERMISSION_CONTROL_CAR_SEATS required by field VehiclePropertyIds.SEAT_MEMORY_SELECT is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#SEAT_MEMORY_SET:
+    Permission Car.PERMISSION_CONTROL_CAR_SEATS required by field VehiclePropertyIds.SEAT_MEMORY_SET is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#SEAT_OCCUPANCY:
+    Permission Car.PERMISSION_CONTROL_CAR_SEATS required by field VehiclePropertyIds.SEAT_OCCUPANCY is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#SEAT_TILT_MOVE:
+    Permission Car.PERMISSION_CONTROL_CAR_SEATS required by field VehiclePropertyIds.SEAT_TILT_MOVE is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#SEAT_TILT_POS:
+    Permission Car.PERMISSION_CONTROL_CAR_SEATS required by field VehiclePropertyIds.SEAT_TILT_POS is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#TIRE_PRESSURE:
+    Permission Car.PERMISSION_TIRES required by field VehiclePropertyIds.TIRE_PRESSURE is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#TRACTION_CONTROL_ACTIVE:
+    Permission Car.PERMISSION_CAR_DYNAMICS_STATE required by field VehiclePropertyIds.TRACTION_CONTROL_ACTIVE is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#TURN_SIGNAL_STATE:
+    Permission Car.PERMISSION_EXTERIOR_LIGHTS required by field VehiclePropertyIds.TURN_SIGNAL_STATE is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#VEHICLE_MAP_SERVICE:
+    Permission Car.PERMISSION_VMS_PUBLISHER required by field VehiclePropertyIds.VEHICLE_MAP_SERVICE is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#WINDOW_LOCK:
+    Permission Car.PERMISSION_CONTROL_CAR_WINDOWS required by field VehiclePropertyIds.WINDOW_LOCK is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#WINDOW_MOVE:
+    Permission Car.PERMISSION_CONTROL_CAR_WINDOWS required by field VehiclePropertyIds.WINDOW_MOVE is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#WINDOW_POS:
+    Permission Car.PERMISSION_CONTROL_CAR_WINDOWS required by field VehiclePropertyIds.WINDOW_POS is hidden or removed
diff --git a/car-lib/api/current.txt b/car-lib/api/current.txt
index 404350c..4aa0bbb 100644
--- a/car-lib/api/current.txt
+++ b/car-lib/api/current.txt
@@ -7,20 +7,28 @@
     method @Deprecated public static android.car.Car createCar(android.content.Context, android.content.ServiceConnection);
     method @Nullable public static android.car.Car createCar(android.content.Context);
     method @Nullable public static android.car.Car createCar(android.content.Context, @Nullable android.os.Handler);
+    method @NonNull public static android.car.Car createCar(@NonNull android.content.Context, @Nullable android.os.Handler, long, @NonNull android.car.Car.CarServiceLifecycleListener);
     method public void disconnect();
     method public int getCarConnectionType();
     method @Nullable public Object getCarManager(String);
     method public boolean isConnected();
     method public boolean isConnecting();
+    method public boolean isFeatureEnabled(@NonNull String);
     field public static final String APP_FOCUS_SERVICE = "app_focus";
     field public static final String AUDIO_SERVICE = "audio";
     field public static final String CAR_CONFIGURATION_SERVICE = "configuration";
-    field public static final String CAR_EXTRA_MEDIA_PACKAGE = "android.car.intent.extra.MEDIA_PACKAGE";
+    field public static final String CAR_EXTRA_BROWSE_SERVICE_FOR_SESSION = "android.media.session.BROWSE_SERVICE";
+    field public static final String CAR_EXTRA_MEDIA_COMPONENT = "android.car.intent.extra.MEDIA_COMPONENT";
     field public static final String CAR_INTENT_ACTION_MEDIA_TEMPLATE = "android.car.intent.action.MEDIA_TEMPLATE";
     field public static final String CAR_NAVIGATION_SERVICE = "car_navigation_service";
+    field public static final String CAR_OCCUPANT_ZONE_SERVICE = "car_occupant_zone_service";
     field public static final String CAR_UX_RESTRICTION_SERVICE = "uxrestriction";
+    field public static final long CAR_WAIT_TIMEOUT_DO_NOT_WAIT = 0L; // 0x0L
+    field public static final long CAR_WAIT_TIMEOUT_WAIT_FOREVER = -1L; // 0xffffffffffffffffL
     field public static final int CONNECTION_TYPE_EMBEDDED = 5; // 0x5
     field public static final String INFO_SERVICE = "info";
+    field public static final String META_DATA_DISTRACTION_OPTIMIZED = "distractionOptimized";
+    field public static final String META_DATA_REQUIRES_CAR_FEATURE = "requires-car-feature";
     field public static final String PACKAGE_SERVICE = "package";
     field public static final String PERMISSION_CAR_CONTROL_AUDIO_SETTINGS = "android.car.permission.CAR_CONTROL_AUDIO_SETTINGS";
     field public static final String PERMISSION_CAR_CONTROL_AUDIO_VOLUME = "android.car.permission.CAR_CONTROL_AUDIO_VOLUME";
@@ -41,6 +49,10 @@
     field @Deprecated public static final String SENSOR_SERVICE = "sensor";
   }
 
+  public static interface Car.CarServiceLifecycleListener {
+    method public void onLifecycleChanged(@NonNull android.car.Car, boolean);
+  }
+
   public final class CarAppFocusManager {
     method public void abandonAppFocus(android.car.CarAppFocusManager.OnAppFocusOwnershipCallback, int);
     method public void abandonAppFocus(android.car.CarAppFocusManager.OnAppFocusOwnershipCallback);
@@ -86,14 +98,51 @@
     ctor @Deprecated public CarNotConnectedException(Exception);
   }
 
+  public class CarOccupantZoneManager {
+    method @NonNull public java.util.List<android.view.Display> getAllDisplaysForOccupant(@NonNull android.car.CarOccupantZoneManager.OccupantZoneInfo);
+    method @NonNull public java.util.List<android.car.CarOccupantZoneManager.OccupantZoneInfo> getAllOccupantZones();
+    method @Nullable public android.view.Display getDisplayForOccupant(@NonNull android.car.CarOccupantZoneManager.OccupantZoneInfo, int);
+    method public int getDisplayType(@NonNull android.view.Display);
+    method public int getUserForOccupant(@NonNull android.car.CarOccupantZoneManager.OccupantZoneInfo);
+    method public void registerOccupantZoneConfigChangeListener(@NonNull android.car.CarOccupantZoneManager.OccupantZoneConfigChangeListener);
+    method public void unregisterOccupantZoneConfigChangeListener(@NonNull android.car.CarOccupantZoneManager.OccupantZoneConfigChangeListener);
+    field public static final int DISPLAY_TYPE_AUXILIARY = 5; // 0x5
+    field public static final int DISPLAY_TYPE_HUD = 3; // 0x3
+    field public static final int DISPLAY_TYPE_INPUT = 4; // 0x4
+    field public static final int DISPLAY_TYPE_INSTRUMENT_CLUSTER = 2; // 0x2
+    field public static final int DISPLAY_TYPE_MAIN = 1; // 0x1
+    field public static final int DISPLAY_TYPE_UNKNOWN = 0; // 0x0
+    field public static final int OCCUPANT_TYPE_DRIVER = 0; // 0x0
+    field public static final int OCCUPANT_TYPE_FRONT_PASSENGER = 1; // 0x1
+    field public static final int OCCUPANT_TYPE_REAR_PASSENGER = 2; // 0x2
+    field public static final int ZONE_CONFIG_CHANGE_FLAG_AUDIO = 4; // 0x4
+    field public static final int ZONE_CONFIG_CHANGE_FLAG_DISPLAY = 1; // 0x1
+    field public static final int ZONE_CONFIG_CHANGE_FLAG_USER = 2; // 0x2
+  }
+
+  public static interface CarOccupantZoneManager.OccupantZoneConfigChangeListener {
+    method public void onOccupantZoneConfigChanged(int);
+  }
+
+  public static final class CarOccupantZoneManager.OccupantZoneInfo implements android.os.Parcelable {
+    method public int describeContents();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.car.CarOccupantZoneManager.OccupantZoneInfo> CREATOR;
+    field public final int occupantType;
+    field public final int seat;
+    field public int zoneId;
+  }
+
   public final class EvConnectorType {
     field public static final int CHADEMO = 3; // 0x3
     field public static final int COMBO_1 = 4; // 0x4
     field public static final int COMBO_2 = 5; // 0x5
     field public static final int GBT = 9; // 0x9
+    field public static final int GBT_DC = 10; // 0xa
     field public static final int J1772 = 1; // 0x1
     field public static final int MENNEKES = 2; // 0x2
     field public static final int OTHER = 101; // 0x65
+    field public static final int SCAME = 11; // 0xb
     field public static final int TESLA_HPWC = 7; // 0x7
     field public static final int TESLA_ROADSTER = 6; // 0x6
     field public static final int TESLA_SUPERCHARGER = 8; // 0x8
@@ -148,136 +197,166 @@
     field public static final int VEHICLE_AREA_TYPE_WINDOW = 2; // 0x2
   }
 
+  public final class VehicleAreaWheel {
+    field public static final int WHEEL_LEFT_FRONT = 1; // 0x1
+    field public static final int WHEEL_LEFT_REAR = 4; // 0x4
+    field public static final int WHEEL_RIGHT_FRONT = 2; // 0x2
+    field public static final int WHEEL_RIGHT_REAR = 8; // 0x8
+    field public static final int WHEEL_UNKNOWN = 0; // 0x0
+  }
+
+  public final class VehicleGear {
+    method public static String toString(int);
+    field public static final int GEAR_DRIVE = 8; // 0x8
+    field public static final int GEAR_EIGHTH = 2048; // 0x800
+    field public static final int GEAR_FIFTH = 256; // 0x100
+    field public static final int GEAR_FIRST = 16; // 0x10
+    field public static final int GEAR_FOURTH = 128; // 0x80
+    field public static final int GEAR_NEUTRAL = 1; // 0x1
+    field public static final int GEAR_NINTH = 4096; // 0x1000
+    field public static final int GEAR_PARK = 4; // 0x4
+    field public static final int GEAR_REVERSE = 2; // 0x2
+    field public static final int GEAR_SECOND = 32; // 0x20
+    field public static final int GEAR_SEVENTH = 1024; // 0x400
+    field public static final int GEAR_SIXTH = 512; // 0x200
+    field public static final int GEAR_THIRD = 64; // 0x40
+    field public static final int GEAR_UNKNOWN = 0; // 0x0
+  }
+
   public final class VehiclePropertyIds {
     ctor public VehiclePropertyIds();
     method public static String toString(int);
-    field public static final int ABS_ACTIVE = 287310858; // 0x1120040a
-    field public static final int AP_POWER_BOOTUP_REASON = 289409538; // 0x11400a02
-    field public static final int AP_POWER_STATE_REPORT = 289475073; // 0x11410a01
-    field public static final int AP_POWER_STATE_REQ = 289475072; // 0x11410a00
-    field public static final int CABIN_LIGHTS_STATE = 289410817; // 0x11400f01
-    field public static final int CABIN_LIGHTS_SWITCH = 289410818; // 0x11400f02
-    field public static final int CURRENT_GEAR = 289408001; // 0x11400401
-    field public static final int DISPLAY_BRIGHTNESS = 289409539; // 0x11400a03
-    field public static final int DISTANCE_DISPLAY_UNITS = 289408512; // 0x11400600
-    field public static final int DOOR_LOCK = 371198722; // 0x16200b02
-    field public static final int DOOR_MOVE = 373295873; // 0x16400b01
-    field public static final int DOOR_POS = 373295872; // 0x16400b00
-    field public static final int ENGINE_COOLANT_TEMP = 291504897; // 0x11600301
-    field public static final int ENGINE_OIL_LEVEL = 289407747; // 0x11400303
-    field public static final int ENGINE_OIL_TEMP = 291504900; // 0x11600304
-    field public static final int ENGINE_RPM = 291504901; // 0x11600305
-    field public static final int ENV_OUTSIDE_TEMPERATURE = 291505923; // 0x11600703
-    field public static final int EV_BATTERY_DISPLAY_UNITS = 289408515; // 0x11400603
-    field public static final int EV_BATTERY_INSTANTANEOUS_CHARGE_RATE = 291504908; // 0x1160030c
-    field public static final int EV_BATTERY_LEVEL = 291504905; // 0x11600309
-    field public static final int EV_CHARGE_PORT_CONNECTED = 287310603; // 0x1120030b
-    field public static final int EV_CHARGE_PORT_OPEN = 287310602; // 0x1120030a
-    field public static final int FOG_LIGHTS_STATE = 289410562; // 0x11400e02
-    field public static final int FOG_LIGHTS_SWITCH = 289410578; // 0x11400e12
-    field public static final int FUEL_CONSUMPTION_UNITS_DISTANCE_OVER_VOLUME = 287311364; // 0x11200604
-    field public static final int FUEL_DOOR_OPEN = 287310600; // 0x11200308
-    field public static final int FUEL_LEVEL = 291504903; // 0x11600307
-    field public static final int FUEL_LEVEL_LOW = 287310853; // 0x11200405
-    field public static final int FUEL_VOLUME_DISPLAY_UNITS = 289408513; // 0x11400601
-    field public static final int GEAR_SELECTION = 289408000; // 0x11400400
-    field public static final int HAZARD_LIGHTS_STATE = 289410563; // 0x11400e03
-    field public static final int HAZARD_LIGHTS_SWITCH = 289410579; // 0x11400e13
-    field public static final int HEADLIGHTS_STATE = 289410560; // 0x11400e00
-    field public static final int HEADLIGHTS_SWITCH = 289410576; // 0x11400e10
-    field public static final int HIGH_BEAM_LIGHTS_STATE = 289410561; // 0x11400e01
-    field public static final int HIGH_BEAM_LIGHTS_SWITCH = 289410577; // 0x11400e11
-    field public static final int HVAC_ACTUAL_FAN_SPEED_RPM = 356517135; // 0x1540050f
-    field public static final int HVAC_AC_ON = 354419973; // 0x15200505
-    field public static final int HVAC_AUTO_ON = 354419978; // 0x1520050a
-    field public static final int HVAC_AUTO_RECIRC_ON = 354419986; // 0x15200512
-    field public static final int HVAC_DEFROSTER = 320865540; // 0x13200504
-    field public static final int HVAC_DUAL_ON = 354419977; // 0x15200509
-    field public static final int HVAC_FAN_DIRECTION = 356517121; // 0x15400501
-    field public static final int HVAC_FAN_DIRECTION_AVAILABLE = 356582673; // 0x15410511
-    field public static final int HVAC_FAN_SPEED = 356517120; // 0x15400500
-    field public static final int HVAC_MAX_AC_ON = 354419974; // 0x15200506
-    field public static final int HVAC_MAX_DEFROST_ON = 354419975; // 0x15200507
-    field public static final int HVAC_POWER_ON = 354419984; // 0x15200510
-    field public static final int HVAC_RECIRC_ON = 354419976; // 0x15200508
-    field public static final int HVAC_SEAT_TEMPERATURE = 356517131; // 0x1540050b
-    field public static final int HVAC_SEAT_VENTILATION = 356517139; // 0x15400513
-    field public static final int HVAC_SIDE_MIRROR_HEAT = 339739916; // 0x1440050c
-    field public static final int HVAC_STEERING_WHEEL_HEAT = 289408269; // 0x1140050d
-    field public static final int HVAC_TEMPERATURE_CURRENT = 358614274; // 0x15600502
-    field public static final int HVAC_TEMPERATURE_DISPLAY_UNITS = 289408270; // 0x1140050e
-    field public static final int HVAC_TEMPERATURE_SET = 358614275; // 0x15600503
+    field @RequiresPermission("android.car.permission.CAR_DYNAMICS_STATE") public static final int ABS_ACTIVE = 287310858; // 0x1120040a
+    field @RequiresPermission("android.car.permission.CAR_POWER") public static final int AP_POWER_BOOTUP_REASON = 289409538; // 0x11400a02
+    field @RequiresPermission("android.car.permission.CAR_POWER") public static final int AP_POWER_STATE_REPORT = 289475073; // 0x11410a01
+    field @RequiresPermission("android.car.permission.CAR_POWER") public static final int AP_POWER_STATE_REQ = 289475072; // 0x11410a00
+    field @RequiresPermission(android.car.Car.PERMISSION_READ_INTERIOR_LIGHTS) public static final int CABIN_LIGHTS_STATE = 289410817; // 0x11400f01
+    field @RequiresPermission(android.car.Car.PERMISSION_CONTROL_INTERIOR_LIGHTS) public static final int CABIN_LIGHTS_SWITCH = 289410818; // 0x11400f02
+    field @RequiresPermission(android.car.Car.PERMISSION_POWERTRAIN) public static final int CURRENT_GEAR = 289408001; // 0x11400401
+    field @RequiresPermission("android.car.permission.CAR_POWER") public static final int DISPLAY_BRIGHTNESS = 289409539; // 0x11400a03
+    field @RequiresPermission.Read(@androidx.annotation.RequiresPermission(android.car.Car.PERMISSION_READ_DISPLAY_UNITS)) @RequiresPermission.Write(@androidx.annotation.RequiresPermission(allOf={android.car.Car.PERMISSION_CONTROL_DISPLAY_UNITS, "android.car.permission.CAR_VENDOR_EXTENSION"})) public static final int DISTANCE_DISPLAY_UNITS = 289408512; // 0x11400600
+    field @RequiresPermission("android.car.permission.CONTROL_CAR_DOORS") public static final int DOOR_LOCK = 371198722; // 0x16200b02
+    field @RequiresPermission("android.car.permission.CONTROL_CAR_DOORS") public static final int DOOR_MOVE = 373295873; // 0x16400b01
+    field @RequiresPermission("android.car.permission.CONTROL_CAR_DOORS") public static final int DOOR_POS = 373295872; // 0x16400b00
+    field @RequiresPermission("android.car.permission.CAR_ENGINE_DETAILED") public static final int ENGINE_COOLANT_TEMP = 291504897; // 0x11600301
+    field @RequiresPermission("android.car.permission.CAR_ENGINE_DETAILED") public static final int ENGINE_OIL_LEVEL = 289407747; // 0x11400303
+    field @RequiresPermission("android.car.permission.CAR_ENGINE_DETAILED") public static final int ENGINE_OIL_TEMP = 291504900; // 0x11600304
+    field @RequiresPermission("android.car.permission.CAR_ENGINE_DETAILED") public static final int ENGINE_RPM = 291504901; // 0x11600305
+    field @RequiresPermission(android.car.Car.PERMISSION_EXTERIOR_ENVIRONMENT) public static final int ENV_OUTSIDE_TEMPERATURE = 291505923; // 0x11600703
+    field @RequiresPermission.Read(@androidx.annotation.RequiresPermission(android.car.Car.PERMISSION_READ_DISPLAY_UNITS)) @RequiresPermission.Write(@androidx.annotation.RequiresPermission(allOf={android.car.Car.PERMISSION_CONTROL_DISPLAY_UNITS, "android.car.permission.CAR_VENDOR_EXTENSION"})) public static final int EV_BATTERY_DISPLAY_UNITS = 289408515; // 0x11400603
+    field @RequiresPermission(android.car.Car.PERMISSION_ENERGY) public static final int EV_BATTERY_INSTANTANEOUS_CHARGE_RATE = 291504908; // 0x1160030c
+    field @RequiresPermission(android.car.Car.PERMISSION_ENERGY) public static final int EV_BATTERY_LEVEL = 291504905; // 0x11600309
+    field @RequiresPermission(android.car.Car.PERMISSION_ENERGY_PORTS) public static final int EV_CHARGE_PORT_CONNECTED = 287310603; // 0x1120030b
+    field @RequiresPermission.Read(@androidx.annotation.RequiresPermission(android.car.Car.PERMISSION_ENERGY_PORTS)) @RequiresPermission.Write(@androidx.annotation.RequiresPermission("android.car.permission.CONTROL_CAR_ENERGY_PORTS")) public static final int EV_CHARGE_PORT_OPEN = 287310602; // 0x1120030a
+    field @RequiresPermission("android.car.permission.CAR_EXTERIOR_LIGHTS") public static final int FOG_LIGHTS_STATE = 289410562; // 0x11400e02
+    field @RequiresPermission("android.car.permission.CONTROL_CAR_EXTERIOR_LIGHTS") public static final int FOG_LIGHTS_SWITCH = 289410578; // 0x11400e12
+    field @RequiresPermission.Read(@androidx.annotation.RequiresPermission(android.car.Car.PERMISSION_READ_DISPLAY_UNITS)) @RequiresPermission.Write(@androidx.annotation.RequiresPermission(allOf={android.car.Car.PERMISSION_CONTROL_DISPLAY_UNITS, "android.car.permission.CAR_VENDOR_EXTENSION"})) public static final int FUEL_CONSUMPTION_UNITS_DISTANCE_OVER_VOLUME = 287311364; // 0x11200604
+    field @RequiresPermission.Read(@androidx.annotation.RequiresPermission(android.car.Car.PERMISSION_ENERGY_PORTS)) @RequiresPermission.Write(@androidx.annotation.RequiresPermission("android.car.permission.CONTROL_CAR_ENERGY_PORTS")) public static final int FUEL_DOOR_OPEN = 287310600; // 0x11200308
+    field @RequiresPermission(android.car.Car.PERMISSION_ENERGY) public static final int FUEL_LEVEL = 291504903; // 0x11600307
+    field @RequiresPermission(android.car.Car.PERMISSION_ENERGY) public static final int FUEL_LEVEL_LOW = 287310853; // 0x11200405
+    field @RequiresPermission.Read(@androidx.annotation.RequiresPermission(android.car.Car.PERMISSION_READ_DISPLAY_UNITS)) @RequiresPermission.Write(@androidx.annotation.RequiresPermission(allOf={android.car.Car.PERMISSION_CONTROL_DISPLAY_UNITS, "android.car.permission.CAR_VENDOR_EXTENSION"})) public static final int FUEL_VOLUME_DISPLAY_UNITS = 289408513; // 0x11400601
+    field @RequiresPermission(android.car.Car.PERMISSION_POWERTRAIN) public static final int GEAR_SELECTION = 289408000; // 0x11400400
+    field @RequiresPermission("android.car.permission.CAR_EXTERIOR_LIGHTS") public static final int HAZARD_LIGHTS_STATE = 289410563; // 0x11400e03
+    field @RequiresPermission("android.car.permission.CONTROL_CAR_EXTERIOR_LIGHTS") public static final int HAZARD_LIGHTS_SWITCH = 289410579; // 0x11400e13
+    field @RequiresPermission("android.car.permission.CAR_EXTERIOR_LIGHTS") public static final int HEADLIGHTS_STATE = 289410560; // 0x11400e00
+    field @RequiresPermission("android.car.permission.CONTROL_CAR_EXTERIOR_LIGHTS") public static final int HEADLIGHTS_SWITCH = 289410576; // 0x11400e10
+    field @RequiresPermission("android.car.permission.CAR_EXTERIOR_LIGHTS") public static final int HIGH_BEAM_LIGHTS_STATE = 289410561; // 0x11400e01
+    field @RequiresPermission("android.car.permission.CONTROL_CAR_EXTERIOR_LIGHTS") public static final int HIGH_BEAM_LIGHTS_SWITCH = 289410577; // 0x11400e11
+    field @RequiresPermission("android.car.permission.CONTROL_CAR_CLIMATE") public static final int HVAC_ACTUAL_FAN_SPEED_RPM = 356517135; // 0x1540050f
+    field @RequiresPermission("android.car.permission.CONTROL_CAR_CLIMATE") public static final int HVAC_AC_ON = 354419973; // 0x15200505
+    field @RequiresPermission("android.car.permission.CONTROL_CAR_CLIMATE") public static final int HVAC_AUTO_ON = 354419978; // 0x1520050a
+    field @RequiresPermission("android.car.permission.CONTROL_CAR_CLIMATE") public static final int HVAC_AUTO_RECIRC_ON = 354419986; // 0x15200512
+    field @RequiresPermission("android.car.permission.CONTROL_CAR_CLIMATE") public static final int HVAC_DEFROSTER = 320865540; // 0x13200504
+    field @RequiresPermission("android.car.permission.CONTROL_CAR_CLIMATE") public static final int HVAC_DUAL_ON = 354419977; // 0x15200509
+    field @RequiresPermission("android.car.permission.CONTROL_CAR_CLIMATE") public static final int HVAC_FAN_DIRECTION = 356517121; // 0x15400501
+    field @RequiresPermission("android.car.permission.CONTROL_CAR_CLIMATE") public static final int HVAC_FAN_DIRECTION_AVAILABLE = 356582673; // 0x15410511
+    field @RequiresPermission("android.car.permission.CONTROL_CAR_CLIMATE") public static final int HVAC_FAN_SPEED = 356517120; // 0x15400500
+    field @RequiresPermission("android.car.permission.CONTROL_CAR_CLIMATE") public static final int HVAC_MAX_AC_ON = 354419974; // 0x15200506
+    field @RequiresPermission("android.car.permission.CONTROL_CAR_CLIMATE") public static final int HVAC_MAX_DEFROST_ON = 354419975; // 0x15200507
+    field @RequiresPermission("android.car.permission.CONTROL_CAR_CLIMATE") public static final int HVAC_POWER_ON = 354419984; // 0x15200510
+    field @RequiresPermission("android.car.permission.CONTROL_CAR_CLIMATE") public static final int HVAC_RECIRC_ON = 354419976; // 0x15200508
+    field @RequiresPermission("android.car.permission.CONTROL_CAR_CLIMATE") public static final int HVAC_SEAT_TEMPERATURE = 356517131; // 0x1540050b
+    field @RequiresPermission("android.car.permission.CONTROL_CAR_CLIMATE") public static final int HVAC_SEAT_VENTILATION = 356517139; // 0x15400513
+    field @RequiresPermission("android.car.permission.CONTROL_CAR_CLIMATE") public static final int HVAC_SIDE_MIRROR_HEAT = 339739916; // 0x1440050c
+    field @RequiresPermission("android.car.permission.CONTROL_CAR_CLIMATE") public static final int HVAC_STEERING_WHEEL_HEAT = 289408269; // 0x1140050d
+    field @RequiresPermission("android.car.permission.CONTROL_CAR_CLIMATE") public static final int HVAC_TEMPERATURE_CURRENT = 358614274; // 0x15600502
+    field @RequiresPermission("android.car.permission.CONTROL_CAR_CLIMATE") public static final int HVAC_TEMPERATURE_DISPLAY_UNITS = 289408270; // 0x1140050e
+    field @RequiresPermission("android.car.permission.CONTROL_CAR_CLIMATE") public static final int HVAC_TEMPERATURE_SET = 358614275; // 0x15600503
     field public static final int HW_KEY_INPUT = 289475088; // 0x11410a10
-    field public static final int IGNITION_STATE = 289408009; // 0x11400409
-    field public static final int INFO_DRIVER_SEAT = 356516106; // 0x1540010a
-    field public static final int INFO_EV_BATTERY_CAPACITY = 291504390; // 0x11600106
-    field public static final int INFO_EV_CONNECTOR_TYPE = 289472775; // 0x11410107
-    field public static final int INFO_EV_PORT_LOCATION = 289407241; // 0x11400109
-    field public static final int INFO_FUEL_CAPACITY = 291504388; // 0x11600104
-    field public static final int INFO_FUEL_DOOR_LOCATION = 289407240; // 0x11400108
-    field public static final int INFO_FUEL_TYPE = 289472773; // 0x11410105
-    field public static final int INFO_MAKE = 286261505; // 0x11100101
-    field public static final int INFO_MODEL = 286261506; // 0x11100102
-    field public static final int INFO_MODEL_YEAR = 289407235; // 0x11400103
-    field public static final int INFO_VIN = 286261504; // 0x11100100
+    field @RequiresPermission(android.car.Car.PERMISSION_POWERTRAIN) public static final int IGNITION_STATE = 289408009; // 0x11400409
+    field @RequiresPermission(android.car.Car.PERMISSION_CAR_INFO) public static final int INFO_DRIVER_SEAT = 356516106; // 0x1540010a
+    field @RequiresPermission(android.car.Car.PERMISSION_CAR_INFO) public static final int INFO_EV_BATTERY_CAPACITY = 291504390; // 0x11600106
+    field @RequiresPermission(android.car.Car.PERMISSION_CAR_INFO) public static final int INFO_EV_CONNECTOR_TYPE = 289472775; // 0x11410107
+    field @RequiresPermission(android.car.Car.PERMISSION_CAR_INFO) public static final int INFO_EV_PORT_LOCATION = 289407241; // 0x11400109
+    field @RequiresPermission(android.car.Car.PERMISSION_CAR_INFO) public static final int INFO_EXTERIOR_DIMENSIONS = 289472779; // 0x1141010b
+    field @RequiresPermission(android.car.Car.PERMISSION_CAR_INFO) public static final int INFO_FUEL_CAPACITY = 291504388; // 0x11600104
+    field @RequiresPermission(android.car.Car.PERMISSION_CAR_INFO) public static final int INFO_FUEL_DOOR_LOCATION = 289407240; // 0x11400108
+    field @RequiresPermission(android.car.Car.PERMISSION_CAR_INFO) public static final int INFO_FUEL_TYPE = 289472773; // 0x11410105
+    field @RequiresPermission(android.car.Car.PERMISSION_CAR_INFO) public static final int INFO_MAKE = 286261505; // 0x11100101
+    field @RequiresPermission(android.car.Car.PERMISSION_CAR_INFO) public static final int INFO_MODEL = 286261506; // 0x11100102
+    field @RequiresPermission(android.car.Car.PERMISSION_CAR_INFO) public static final int INFO_MODEL_YEAR = 289407235; // 0x11400103
+    field @RequiresPermission(android.car.Car.PERMISSION_CAR_INFO) public static final int INFO_MULTI_EV_PORT_LOCATIONS = 289472780; // 0x1141010c
+    field @RequiresPermission(android.car.Car.PERMISSION_IDENTIFICATION) public static final int INFO_VIN = 286261504; // 0x11100100
+    field public static final int INITIAL_USER_INFO = 299896583; // 0x11e00f07
     field public static final int INVALID = 0; // 0x0
-    field public static final int MIRROR_FOLD = 287312709; // 0x11200b45
-    field public static final int MIRROR_LOCK = 287312708; // 0x11200b44
-    field public static final int MIRROR_Y_MOVE = 339741507; // 0x14400b43
-    field public static final int MIRROR_Y_POS = 339741506; // 0x14400b42
-    field public static final int MIRROR_Z_MOVE = 339741505; // 0x14400b41
-    field public static final int MIRROR_Z_POS = 339741504; // 0x14400b40
-    field public static final int NIGHT_MODE = 287310855; // 0x11200407
-    field public static final int OBD2_FREEZE_FRAME = 299896065; // 0x11e00d01
-    field public static final int OBD2_FREEZE_FRAME_CLEAR = 299896067; // 0x11e00d03
-    field public static final int OBD2_FREEZE_FRAME_INFO = 299896066; // 0x11e00d02
-    field public static final int OBD2_LIVE_FRAME = 299896064; // 0x11e00d00
-    field public static final int PARKING_BRAKE_AUTO_APPLY = 287310851; // 0x11200403
-    field public static final int PARKING_BRAKE_ON = 287310850; // 0x11200402
-    field public static final int PERF_ODOMETER = 291504644; // 0x11600204
-    field public static final int PERF_STEERING_ANGLE = 291504649; // 0x11600209
-    field public static final int PERF_VEHICLE_SPEED = 291504647; // 0x11600207
-    field public static final int PERF_VEHICLE_SPEED_DISPLAY = 291504648; // 0x11600208
-    field public static final int RANGE_REMAINING = 291504904; // 0x11600308
-    field public static final int READING_LIGHTS_STATE = 356519683; // 0x15400f03
-    field public static final int READING_LIGHTS_SWITCH = 356519684; // 0x15400f04
-    field public static final int SEAT_BACKREST_ANGLE_1_MOVE = 356518792; // 0x15400b88
-    field public static final int SEAT_BACKREST_ANGLE_1_POS = 356518791; // 0x15400b87
-    field public static final int SEAT_BACKREST_ANGLE_2_MOVE = 356518794; // 0x15400b8a
-    field public static final int SEAT_BACKREST_ANGLE_2_POS = 356518793; // 0x15400b89
-    field public static final int SEAT_BELT_BUCKLED = 354421634; // 0x15200b82
-    field public static final int SEAT_BELT_HEIGHT_MOVE = 356518788; // 0x15400b84
-    field public static final int SEAT_BELT_HEIGHT_POS = 356518787; // 0x15400b83
-    field public static final int SEAT_DEPTH_MOVE = 356518798; // 0x15400b8e
-    field public static final int SEAT_DEPTH_POS = 356518797; // 0x15400b8d
-    field public static final int SEAT_FORE_AFT_MOVE = 356518790; // 0x15400b86
-    field public static final int SEAT_FORE_AFT_POS = 356518789; // 0x15400b85
-    field public static final int SEAT_HEADREST_ANGLE_MOVE = 356518808; // 0x15400b98
-    field public static final int SEAT_HEADREST_ANGLE_POS = 356518807; // 0x15400b97
-    field public static final int SEAT_HEADREST_FORE_AFT_MOVE = 356518810; // 0x15400b9a
-    field public static final int SEAT_HEADREST_FORE_AFT_POS = 356518809; // 0x15400b99
-    field public static final int SEAT_HEADREST_HEIGHT_MOVE = 356518806; // 0x15400b96
-    field public static final int SEAT_HEADREST_HEIGHT_POS = 289409941; // 0x11400b95
-    field public static final int SEAT_HEIGHT_MOVE = 356518796; // 0x15400b8c
-    field public static final int SEAT_HEIGHT_POS = 356518795; // 0x15400b8b
-    field public static final int SEAT_LUMBAR_FORE_AFT_MOVE = 356518802; // 0x15400b92
-    field public static final int SEAT_LUMBAR_FORE_AFT_POS = 356518801; // 0x15400b91
-    field public static final int SEAT_LUMBAR_SIDE_SUPPORT_MOVE = 356518804; // 0x15400b94
-    field public static final int SEAT_LUMBAR_SIDE_SUPPORT_POS = 356518803; // 0x15400b93
-    field public static final int SEAT_MEMORY_SELECT = 356518784; // 0x15400b80
-    field public static final int SEAT_MEMORY_SET = 356518785; // 0x15400b81
-    field public static final int SEAT_OCCUPANCY = 356518832; // 0x15400bb0
-    field public static final int SEAT_TILT_MOVE = 356518800; // 0x15400b90
-    field public static final int SEAT_TILT_POS = 356518799; // 0x15400b8f
-    field public static final int TIRE_PRESSURE = 392168201; // 0x17600309
-    field public static final int TIRE_PRESSURE_DISPLAY_UNITS = 289408514; // 0x11400602
-    field public static final int TRACTION_CONTROL_ACTIVE = 287310859; // 0x1120040b
-    field public static final int TURN_SIGNAL_STATE = 289408008; // 0x11400408
-    field public static final int VEHICLE_MAP_SERVICE = 299895808; // 0x11e00c00
-    field public static final int WHEEL_TICK = 290521862; // 0x11510306
-    field public static final int WINDOW_LOCK = 320867268; // 0x13200bc4
-    field public static final int WINDOW_MOVE = 322964417; // 0x13400bc1
-    field public static final int WINDOW_POS = 322964416; // 0x13400bc0
+    field @RequiresPermission("android.car.permission.CONTROL_CAR_MIRRORS") public static final int MIRROR_FOLD = 287312709; // 0x11200b45
+    field @RequiresPermission("android.car.permission.CONTROL_CAR_MIRRORS") public static final int MIRROR_LOCK = 287312708; // 0x11200b44
+    field @RequiresPermission("android.car.permission.CONTROL_CAR_MIRRORS") public static final int MIRROR_Y_MOVE = 339741507; // 0x14400b43
+    field @RequiresPermission("android.car.permission.CONTROL_CAR_MIRRORS") public static final int MIRROR_Y_POS = 339741506; // 0x14400b42
+    field @RequiresPermission("android.car.permission.CONTROL_CAR_MIRRORS") public static final int MIRROR_Z_MOVE = 339741505; // 0x14400b41
+    field @RequiresPermission("android.car.permission.CONTROL_CAR_MIRRORS") public static final int MIRROR_Z_POS = 339741504; // 0x14400b40
+    field @RequiresPermission(android.car.Car.PERMISSION_EXTERIOR_ENVIRONMENT) public static final int NIGHT_MODE = 287310855; // 0x11200407
+    field @RequiresPermission("android.car.permission.CAR_DIAGNOSTICS") public static final int OBD2_FREEZE_FRAME = 299896065; // 0x11e00d01
+    field @RequiresPermission("android.car.permission.CLEAR_CAR_DIAGNOSTICS") public static final int OBD2_FREEZE_FRAME_CLEAR = 299896067; // 0x11e00d03
+    field @RequiresPermission("android.car.permission.CAR_DIAGNOSTICS") public static final int OBD2_FREEZE_FRAME_INFO = 299896066; // 0x11e00d02
+    field @RequiresPermission("android.car.permission.CAR_DIAGNOSTICS") public static final int OBD2_LIVE_FRAME = 299896064; // 0x11e00d00
+    field @RequiresPermission(android.car.Car.PERMISSION_POWERTRAIN) public static final int PARKING_BRAKE_AUTO_APPLY = 287310851; // 0x11200403
+    field @RequiresPermission(android.car.Car.PERMISSION_POWERTRAIN) public static final int PARKING_BRAKE_ON = 287310850; // 0x11200402
+    field @RequiresPermission("android.car.permission.CAR_MILEAGE") public static final int PERF_ODOMETER = 291504644; // 0x11600204
+    field @RequiresPermission(android.car.Car.PERMISSION_READ_STEERING_STATE) public static final int PERF_REAR_STEERING_ANGLE = 291504656; // 0x11600210
+    field @RequiresPermission(android.car.Car.PERMISSION_READ_STEERING_STATE) public static final int PERF_STEERING_ANGLE = 291504649; // 0x11600209
+    field @RequiresPermission(android.car.Car.PERMISSION_SPEED) public static final int PERF_VEHICLE_SPEED = 291504647; // 0x11600207
+    field @RequiresPermission(android.car.Car.PERMISSION_SPEED) public static final int PERF_VEHICLE_SPEED_DISPLAY = 291504648; // 0x11600208
+    field @RequiresPermission.Read(@androidx.annotation.RequiresPermission(android.car.Car.PERMISSION_ENERGY)) @RequiresPermission.Write(@androidx.annotation.RequiresPermission("android.car.permission.ADJUST_RANGE_REMAINING")) public static final int RANGE_REMAINING = 291504904; // 0x11600308
+    field @RequiresPermission(android.car.Car.PERMISSION_READ_INTERIOR_LIGHTS) public static final int READING_LIGHTS_STATE = 356519683; // 0x15400f03
+    field @RequiresPermission(android.car.Car.PERMISSION_CONTROL_INTERIOR_LIGHTS) public static final int READING_LIGHTS_SWITCH = 356519684; // 0x15400f04
+    field @RequiresPermission("android.car.permission.CONTROL_CAR_SEATS") public static final int SEAT_BACKREST_ANGLE_1_MOVE = 356518792; // 0x15400b88
+    field @RequiresPermission("android.car.permission.CONTROL_CAR_SEATS") public static final int SEAT_BACKREST_ANGLE_1_POS = 356518791; // 0x15400b87
+    field @RequiresPermission("android.car.permission.CONTROL_CAR_SEATS") public static final int SEAT_BACKREST_ANGLE_2_MOVE = 356518794; // 0x15400b8a
+    field @RequiresPermission("android.car.permission.CONTROL_CAR_SEATS") public static final int SEAT_BACKREST_ANGLE_2_POS = 356518793; // 0x15400b89
+    field @RequiresPermission("android.car.permission.CONTROL_CAR_SEATS") public static final int SEAT_BELT_BUCKLED = 354421634; // 0x15200b82
+    field @RequiresPermission("android.car.permission.CONTROL_CAR_SEATS") public static final int SEAT_BELT_HEIGHT_MOVE = 356518788; // 0x15400b84
+    field @RequiresPermission("android.car.permission.CONTROL_CAR_SEATS") public static final int SEAT_BELT_HEIGHT_POS = 356518787; // 0x15400b83
+    field @RequiresPermission("android.car.permission.CONTROL_CAR_SEATS") public static final int SEAT_DEPTH_MOVE = 356518798; // 0x15400b8e
+    field @RequiresPermission("android.car.permission.CONTROL_CAR_SEATS") public static final int SEAT_DEPTH_POS = 356518797; // 0x15400b8d
+    field @RequiresPermission("android.car.permission.CONTROL_CAR_SEATS") public static final int SEAT_FORE_AFT_MOVE = 356518790; // 0x15400b86
+    field @RequiresPermission("android.car.permission.CONTROL_CAR_SEATS") public static final int SEAT_FORE_AFT_POS = 356518789; // 0x15400b85
+    field @RequiresPermission("android.car.permission.CONTROL_CAR_SEATS") public static final int SEAT_HEADREST_ANGLE_MOVE = 356518808; // 0x15400b98
+    field @RequiresPermission("android.car.permission.CONTROL_CAR_SEATS") public static final int SEAT_HEADREST_ANGLE_POS = 356518807; // 0x15400b97
+    field @RequiresPermission("android.car.permission.CONTROL_CAR_SEATS") public static final int SEAT_HEADREST_FORE_AFT_MOVE = 356518810; // 0x15400b9a
+    field @RequiresPermission("android.car.permission.CONTROL_CAR_SEATS") public static final int SEAT_HEADREST_FORE_AFT_POS = 356518809; // 0x15400b99
+    field @RequiresPermission("android.car.permission.CONTROL_CAR_SEATS") public static final int SEAT_HEADREST_HEIGHT_MOVE = 356518806; // 0x15400b96
+    field @RequiresPermission("android.car.permission.CONTROL_CAR_SEATS") public static final int SEAT_HEADREST_HEIGHT_POS = 289409941; // 0x11400b95
+    field @RequiresPermission("android.car.permission.CONTROL_CAR_SEATS") public static final int SEAT_HEIGHT_MOVE = 356518796; // 0x15400b8c
+    field @RequiresPermission("android.car.permission.CONTROL_CAR_SEATS") public static final int SEAT_HEIGHT_POS = 356518795; // 0x15400b8b
+    field @RequiresPermission("android.car.permission.CONTROL_CAR_SEATS") public static final int SEAT_LUMBAR_FORE_AFT_MOVE = 356518802; // 0x15400b92
+    field @RequiresPermission("android.car.permission.CONTROL_CAR_SEATS") public static final int SEAT_LUMBAR_FORE_AFT_POS = 356518801; // 0x15400b91
+    field @RequiresPermission("android.car.permission.CONTROL_CAR_SEATS") public static final int SEAT_LUMBAR_SIDE_SUPPORT_MOVE = 356518804; // 0x15400b94
+    field @RequiresPermission("android.car.permission.CONTROL_CAR_SEATS") public static final int SEAT_LUMBAR_SIDE_SUPPORT_POS = 356518803; // 0x15400b93
+    field @RequiresPermission("android.car.permission.CONTROL_CAR_SEATS") public static final int SEAT_MEMORY_SELECT = 356518784; // 0x15400b80
+    field @RequiresPermission("android.car.permission.CONTROL_CAR_SEATS") public static final int SEAT_MEMORY_SET = 356518785; // 0x15400b81
+    field @RequiresPermission("android.car.permission.CONTROL_CAR_SEATS") public static final int SEAT_OCCUPANCY = 356518832; // 0x15400bb0
+    field @RequiresPermission("android.car.permission.CONTROL_CAR_SEATS") public static final int SEAT_TILT_MOVE = 356518800; // 0x15400b90
+    field @RequiresPermission("android.car.permission.CONTROL_CAR_SEATS") public static final int SEAT_TILT_POS = 356518799; // 0x15400b8f
+    field @RequiresPermission("android.car.permission.CAR_TIRES") public static final int TIRE_PRESSURE = 392168201; // 0x17600309
+    field @RequiresPermission.Read(@androidx.annotation.RequiresPermission(android.car.Car.PERMISSION_READ_DISPLAY_UNITS)) @RequiresPermission.Write(@androidx.annotation.RequiresPermission(allOf={android.car.Car.PERMISSION_CONTROL_DISPLAY_UNITS, "android.car.permission.CAR_VENDOR_EXTENSION"})) public static final int TIRE_PRESSURE_DISPLAY_UNITS = 289408514; // 0x11400602
+    field @RequiresPermission("android.car.permission.CAR_DYNAMICS_STATE") public static final int TRACTION_CONTROL_ACTIVE = 287310859; // 0x1120040b
+    field @RequiresPermission("android.car.permission.CAR_EXTERIOR_LIGHTS") public static final int TURN_SIGNAL_STATE = 289408008; // 0x11400408
+    field @RequiresPermission(anyOf={"android.car.permission.VMS_PUBLISHER", "android.car.permission.VMS_SUBSCRIBER"}) public static final int VEHICLE_MAP_SERVICE = 299895808; // 0x11e00c00
+    field @RequiresPermission(android.car.Car.PERMISSION_SPEED) public static final int WHEEL_TICK = 290521862; // 0x11510306
+    field @RequiresPermission("android.car.permission.CONTROL_CAR_WINDOWS") public static final int WINDOW_LOCK = 320867268; // 0x13200bc4
+    field @RequiresPermission("android.car.permission.CONTROL_CAR_WINDOWS") public static final int WINDOW_MOVE = 322964417; // 0x13400bc1
+    field @RequiresPermission("android.car.permission.CONTROL_CAR_WINDOWS") public static final int WINDOW_POS = 322964416; // 0x13400bc0
   }
 
 }
@@ -286,6 +365,7 @@
 
   public final class CarPackageManager {
     method public boolean isActivityDistractionOptimized(String, String);
+    method public boolean isPendingIntentDistractionOptimized(@NonNull android.app.PendingIntent);
     method public boolean isServiceDistractionOptimized(String, String);
   }
 
@@ -356,6 +436,7 @@
     method @Nullable public T getMinValue(int);
     method @Nullable public T getMinValue();
     method public int getPropertyId();
+    method @NonNull public Class<T> getPropertyType();
     method public boolean isGlobalProperty();
     method public void writeToParcel(android.os.Parcel, int);
     field public static final android.os.Parcelable.Creator<android.car.hardware.CarPropertyConfig> CREATOR;
@@ -468,8 +549,13 @@
 
 package android.car.hardware.property {
 
+  public class CarInternalErrorException extends java.lang.RuntimeException {
+  }
+
   public class CarPropertyManager {
+    method public int getAreaId(int, int);
     method public boolean getBooleanProperty(int, int);
+    method @Nullable public android.car.hardware.CarPropertyConfig<?> getCarPropertyConfig(int);
     method public float getFloatProperty(int, int);
     method @NonNull public int[] getIntArrayProperty(int, int);
     method public int getIntProperty(int, int);
@@ -485,6 +571,11 @@
     method public <E> void setProperty(@NonNull Class<E>, int, int, @NonNull E);
     method public void unregisterCallback(@NonNull android.car.hardware.property.CarPropertyManager.CarPropertyEventCallback);
     method public void unregisterCallback(@NonNull android.car.hardware.property.CarPropertyManager.CarPropertyEventCallback, int);
+    field public static final int CAR_SET_PROPERTY_ERROR_CODE_ACCESS_DENIED = 4; // 0x4
+    field public static final int CAR_SET_PROPERTY_ERROR_CODE_INVALID_ARG = 2; // 0x2
+    field public static final int CAR_SET_PROPERTY_ERROR_CODE_PROPERTY_NOT_AVAILABLE = 3; // 0x3
+    field public static final int CAR_SET_PROPERTY_ERROR_CODE_TRY_AGAIN = 1; // 0x1
+    field public static final int CAR_SET_PROPERTY_ERROR_CODE_UNKNOWN = 5; // 0x5
     field public static final float SENSOR_RATE_FAST = 10.0f;
     field public static final float SENSOR_RATE_FASTEST = 100.0f;
     field public static final float SENSOR_RATE_NORMAL = 1.0f;
@@ -495,6 +586,16 @@
   public static interface CarPropertyManager.CarPropertyEventCallback {
     method public void onChangeEvent(android.car.hardware.CarPropertyValue);
     method public void onErrorEvent(int, int);
+    method public default void onErrorEvent(int, int, int);
+  }
+
+  public class PropertyAccessDeniedSecurityException extends java.lang.SecurityException {
+  }
+
+  public class PropertyNotAvailableAndRetryException extends java.lang.IllegalStateException {
+  }
+
+  public class PropertyNotAvailableException extends java.lang.IllegalStateException {
   }
 
 }
@@ -516,29 +617,18 @@
 
 package android.car.settings {
 
-  public class CarConfigurationManager {
-    method public android.car.settings.SpeedBumpConfiguration getSpeedBumpConfiguration();
+  @Deprecated public class CarConfigurationManager {
+    method @Deprecated public android.car.settings.SpeedBumpConfiguration getSpeedBumpConfiguration();
   }
 
-  public class CarSettings {
-    ctor public CarSettings();
-  }
-
-  public static final class CarSettings.Global {
-    ctor public CarSettings.Global();
-    field @Deprecated public static final String KEY_GARAGE_MODE_ENABLED = "android.car.GARAGE_MODE_ENABLED";
-    field @Deprecated public static final String KEY_GARAGE_MODE_MAINTENANCE_WINDOW = "android.car.GARAGE_MODE_MAINTENANCE_WINDOW";
-    field @Deprecated public static final String KEY_GARAGE_MODE_WAKE_UP_TIME = "android.car.GARAGE_MODE_WAKE_UP_TIME";
-  }
-
-  public final class SpeedBumpConfiguration implements android.os.Parcelable {
-    ctor public SpeedBumpConfiguration(double, double, long);
-    method public int describeContents();
-    method public double getAcquiredPermitsPerSecond();
-    method public double getMaxPermitPool();
-    method public long getPermitFillDelay();
-    method public void writeToParcel(android.os.Parcel, int);
-    field public static final android.os.Parcelable.Creator<android.car.settings.SpeedBumpConfiguration> CREATOR;
+  @Deprecated public final class SpeedBumpConfiguration implements android.os.Parcelable {
+    ctor @Deprecated public SpeedBumpConfiguration(double, double, long);
+    method @Deprecated public int describeContents();
+    method @Deprecated public double getAcquiredPermitsPerSecond();
+    method @Deprecated public double getMaxPermitPool();
+    method @Deprecated public long getPermitFillDelay();
+    method @Deprecated public void writeToParcel(android.os.Parcel, int);
+    field @Deprecated public static final android.os.Parcelable.Creator<android.car.settings.SpeedBumpConfiguration> CREATOR;
   }
 
 }
diff --git a/car-lib/api/lint-baseline.txt b/car-lib/api/lint-baseline.txt
new file mode 100644
index 0000000..c1216f2
--- /dev/null
+++ b/car-lib/api/lint-baseline.txt
@@ -0,0 +1,253 @@
+// Baseline format: 1.0
+CallbackInterface: android.car.CarAppFocusManager.OnAppFocusOwnershipCallback:
+    Callbacks must be abstract class instead of interface to enable extension in future API levels: OnAppFocusOwnershipCallback
+CallbackInterface: android.car.hardware.property.CarPropertyManager.CarPropertyEventCallback:
+    Callbacks must be abstract class instead of interface to enable extension in future API levels: CarPropertyEventCallback
+
+
+ConcreteCollection: android.car.hardware.property.CarPropertyManager#getPropertyList(android.util.ArraySet<java.lang.Integer>) parameter #0:
+    Parameter type is concrete collection (`android.util.ArraySet`); must be higher-level interface
+
+
+ExecutorRegistration: android.car.Car#createCar(android.content.Context, android.os.Handler, long, android.car.Car.CarServiceLifecycleListener):
+    Registration methods should have overload that accepts delivery Executor: `createCar`
+ExecutorRegistration: android.car.CarAppFocusManager#abandonAppFocus(android.car.CarAppFocusManager.OnAppFocusOwnershipCallback):
+    Registration methods should have overload that accepts delivery Executor: `abandonAppFocus`
+ExecutorRegistration: android.car.CarAppFocusManager#addFocusListener(android.car.CarAppFocusManager.OnAppFocusChangedListener, int):
+    Registration methods should have overload that accepts delivery Executor: `addFocusListener`
+ExecutorRegistration: android.car.CarAppFocusManager#isOwningFocus(android.car.CarAppFocusManager.OnAppFocusOwnershipCallback, int):
+    Registration methods should have overload that accepts delivery Executor: `isOwningFocus`
+ExecutorRegistration: android.car.CarAppFocusManager#requestAppFocus(int, android.car.CarAppFocusManager.OnAppFocusOwnershipCallback):
+    Registration methods should have overload that accepts delivery Executor: `requestAppFocus`
+ExecutorRegistration: android.car.CarOccupantZoneManager#registerOccupantZoneConfigChangeListener(android.car.CarOccupantZoneManager.OccupantZoneConfigChangeListener):
+    Registration methods should have overload that accepts delivery Executor: `registerOccupantZoneConfigChangeListener`
+ExecutorRegistration: android.car.drivingstate.CarUxRestrictionsManager#registerListener(android.car.drivingstate.CarUxRestrictionsManager.OnUxRestrictionsChangedListener):
+    Registration methods should have overload that accepts delivery Executor: `registerListener`
+ExecutorRegistration: android.car.hardware.CarSensorManager#registerListener(android.car.hardware.CarSensorManager.OnSensorChangedListener, int, int):
+    Registration methods should have overload that accepts delivery Executor: `registerListener`
+ExecutorRegistration: android.car.hardware.property.CarPropertyManager#registerCallback(android.car.hardware.property.CarPropertyManager.CarPropertyEventCallback, int, float):
+    Registration methods should have overload that accepts delivery Executor: `registerCallback`
+ExecutorRegistration: android.car.media.CarAudioManager#registerCarVolumeCallback(android.car.media.CarAudioManager.CarVolumeCallback):
+    Registration methods should have overload that accepts delivery Executor: `registerCarVolumeCallback`
+
+
+HiddenSuperclass: android.car.CarAppFocusManager:
+    Public class android.car.CarAppFocusManager stripped of unavailable superclass android.car.CarManagerBase
+HiddenSuperclass: android.car.CarInfoManager:
+    Public class android.car.CarInfoManager stripped of unavailable superclass android.car.CarManagerBase
+HiddenSuperclass: android.car.CarOccupantZoneManager:
+    Public class android.car.CarOccupantZoneManager stripped of unavailable superclass android.car.CarManagerBase
+HiddenSuperclass: android.car.content.pm.CarPackageManager:
+    Public class android.car.content.pm.CarPackageManager stripped of unavailable superclass android.car.CarManagerBase
+HiddenSuperclass: android.car.drivingstate.CarUxRestrictionsManager:
+    Public class android.car.drivingstate.CarUxRestrictionsManager stripped of unavailable superclass android.car.CarManagerBase
+HiddenSuperclass: android.car.hardware.CarSensorManager:
+    Public class android.car.hardware.CarSensorManager stripped of unavailable superclass android.car.CarManagerBase
+HiddenSuperclass: android.car.hardware.property.CarPropertyManager:
+    Public class android.car.hardware.property.CarPropertyManager stripped of unavailable superclass android.car.CarManagerBase
+HiddenSuperclass: android.car.media.CarAudioManager:
+    Public class android.car.media.CarAudioManager stripped of unavailable superclass android.car.CarManagerBase
+HiddenSuperclass: android.car.settings.CarConfigurationManager:
+    Public class android.car.settings.CarConfigurationManager stripped of unavailable superclass android.car.CarManagerBase
+
+
+IntentName: android.car.Car#CAR_EXTRA_BROWSE_SERVICE_FOR_SESSION:
+    Intent extra constant name must be EXTRA_FOO: CAR_EXTRA_BROWSE_SERVICE_FOR_SESSION
+IntentName: android.car.Car#CAR_EXTRA_MEDIA_COMPONENT:
+    Intent extra constant name must be EXTRA_FOO: CAR_EXTRA_MEDIA_COMPONENT
+IntentName: android.car.Car#CAR_INTENT_ACTION_MEDIA_TEMPLATE:
+    Intent action constant name must be ACTION_FOO: CAR_INTENT_ACTION_MEDIA_TEMPLATE
+
+
+ListenerLast: android.car.CarAppFocusManager#abandonAppFocus(android.car.CarAppFocusManager.OnAppFocusOwnershipCallback, int) parameter #1:
+    Listeners should always be at end of argument list (method `abandonAppFocus`)
+ListenerLast: android.car.CarAppFocusManager#isOwningFocus(android.car.CarAppFocusManager.OnAppFocusOwnershipCallback, int) parameter #1:
+    Listeners should always be at end of argument list (method `isOwningFocus`)
+
+
+MissingNullability: android.car.Car#createCar(android.content.Context) parameter #0:
+    Missing nullability on parameter `context` in method `createCar`
+MissingNullability: android.car.Car#createCar(android.content.Context, android.content.ServiceConnection) parameter #0:
+    Missing nullability on parameter `context` in method `createCar`
+MissingNullability: android.car.Car#createCar(android.content.Context, android.content.ServiceConnection) parameter #1:
+    Missing nullability on parameter `serviceConnectionListener` in method `createCar`
+MissingNullability: android.car.Car#createCar(android.content.Context, android.content.ServiceConnection, android.os.Handler) parameter #0:
+    Missing nullability on parameter `context` in method `createCar`
+MissingNullability: android.car.Car#createCar(android.content.Context, android.content.ServiceConnection, android.os.Handler) parameter #1:
+    Missing nullability on parameter `serviceConnectionListener` in method `createCar`
+MissingNullability: android.car.Car#createCar(android.content.Context, android.os.Handler) parameter #0:
+    Missing nullability on parameter `context` in method `createCar`
+MissingNullability: android.car.Car#getCarManager(String) parameter #0:
+    Missing nullability on parameter `serviceName` in method `getCarManager`
+MissingNullability: android.car.CarAppFocusManager#abandonAppFocus(android.car.CarAppFocusManager.OnAppFocusOwnershipCallback) parameter #0:
+    Missing nullability on parameter `ownershipCallback` in method `abandonAppFocus`
+MissingNullability: android.car.CarAppFocusManager#abandonAppFocus(android.car.CarAppFocusManager.OnAppFocusOwnershipCallback, int) parameter #0:
+    Missing nullability on parameter `ownershipCallback` in method `abandonAppFocus`
+MissingNullability: android.car.CarAppFocusManager#addFocusListener(android.car.CarAppFocusManager.OnAppFocusChangedListener, int) parameter #0:
+    Missing nullability on parameter `listener` in method `addFocusListener`
+MissingNullability: android.car.CarAppFocusManager#isOwningFocus(android.car.CarAppFocusManager.OnAppFocusOwnershipCallback, int) parameter #0:
+    Missing nullability on parameter `callback` in method `isOwningFocus`
+MissingNullability: android.car.CarAppFocusManager#removeFocusListener(android.car.CarAppFocusManager.OnAppFocusChangedListener) parameter #0:
+    Missing nullability on parameter `listener` in method `removeFocusListener`
+MissingNullability: android.car.CarAppFocusManager#removeFocusListener(android.car.CarAppFocusManager.OnAppFocusChangedListener, int) parameter #0:
+    Missing nullability on parameter `listener` in method `removeFocusListener`
+MissingNullability: android.car.CarAppFocusManager#requestAppFocus(int, android.car.CarAppFocusManager.OnAppFocusOwnershipCallback) parameter #1:
+    Missing nullability on parameter `ownershipCallback` in method `requestAppFocus`
+MissingNullability: android.car.CarInfoManager#getEvConnectorTypes():
+    Missing nullability on method `getEvConnectorTypes` return
+MissingNullability: android.car.CarInfoManager#getFuelTypes():
+    Missing nullability on method `getFuelTypes` return
+MissingNullability: android.car.CarNotConnectedException#CarNotConnectedException(Exception) parameter #0:
+    Missing nullability on parameter `cause` in method `CarNotConnectedException`
+MissingNullability: android.car.CarNotConnectedException#CarNotConnectedException(String) parameter #0:
+    Missing nullability on parameter `name` in method `CarNotConnectedException`
+MissingNullability: android.car.CarNotConnectedException#CarNotConnectedException(String, Throwable) parameter #0:
+    Missing nullability on parameter `name` in method `CarNotConnectedException`
+MissingNullability: android.car.CarNotConnectedException#CarNotConnectedException(String, Throwable) parameter #1:
+    Missing nullability on parameter `cause` in method `CarNotConnectedException`
+MissingNullability: android.car.CarOccupantZoneManager.OccupantZoneInfo#CREATOR:
+    Missing nullability on field `CREATOR` in class `class android.car.CarOccupantZoneManager.OccupantZoneInfo`
+MissingNullability: android.car.CarOccupantZoneManager.OccupantZoneInfo#writeToParcel(android.os.Parcel, int) parameter #0:
+    Missing nullability on parameter `dest` in method `writeToParcel`
+MissingNullability: android.car.VehicleGear#toString(int):
+    Missing nullability on method `toString` return
+MissingNullability: android.car.VehiclePropertyIds#toString(int):
+    Missing nullability on method `toString` return
+MissingNullability: android.car.content.pm.CarPackageManager#isActivityDistractionOptimized(String, String) parameter #0:
+    Missing nullability on parameter `packageName` in method `isActivityDistractionOptimized`
+MissingNullability: android.car.content.pm.CarPackageManager#isActivityDistractionOptimized(String, String) parameter #1:
+    Missing nullability on parameter `className` in method `isActivityDistractionOptimized`
+MissingNullability: android.car.content.pm.CarPackageManager#isServiceDistractionOptimized(String, String) parameter #0:
+    Missing nullability on parameter `packageName` in method `isServiceDistractionOptimized`
+MissingNullability: android.car.content.pm.CarPackageManager#isServiceDistractionOptimized(String, String) parameter #1:
+    Missing nullability on parameter `className` in method `isServiceDistractionOptimized`
+MissingNullability: android.car.drivingstate.CarUxRestrictions#CREATOR:
+    Missing nullability on field `CREATOR` in class `class android.car.drivingstate.CarUxRestrictions`
+MissingNullability: android.car.drivingstate.CarUxRestrictions#CarUxRestrictions(android.car.drivingstate.CarUxRestrictions) parameter #0:
+    Missing nullability on parameter `uxRestrictions` in method `CarUxRestrictions`
+MissingNullability: android.car.drivingstate.CarUxRestrictions#isSameRestrictions(android.car.drivingstate.CarUxRestrictions) parameter #0:
+    Missing nullability on parameter `other` in method `isSameRestrictions`
+MissingNullability: android.car.drivingstate.CarUxRestrictions#writeToParcel(android.os.Parcel, int) parameter #0:
+    Missing nullability on parameter `dest` in method `writeToParcel`
+MissingNullability: android.car.drivingstate.CarUxRestrictions.Builder#build():
+    Missing nullability on method `build` return
+MissingNullability: android.car.drivingstate.CarUxRestrictions.Builder#setMaxContentDepth(int):
+    Missing nullability on method `setMaxContentDepth` return
+MissingNullability: android.car.drivingstate.CarUxRestrictions.Builder#setMaxCumulativeContentItems(int):
+    Missing nullability on method `setMaxCumulativeContentItems` return
+MissingNullability: android.car.drivingstate.CarUxRestrictions.Builder#setMaxStringLength(int):
+    Missing nullability on method `setMaxStringLength` return
+MissingNullability: android.car.drivingstate.CarUxRestrictionsManager.OnUxRestrictionsChangedListener#onUxRestrictionsChanged(android.car.drivingstate.CarUxRestrictions) parameter #0:
+    Missing nullability on parameter `restrictionInfo` in method `onUxRestrictionsChanged`
+MissingNullability: android.car.hardware.CarPropertyConfig#CREATOR:
+    Missing nullability on field `CREATOR` in class `class android.car.hardware.CarPropertyConfig`
+MissingNullability: android.car.hardware.CarPropertyConfig#writeToParcel(android.os.Parcel, int) parameter #0:
+    Missing nullability on parameter `dest` in method `writeToParcel`
+MissingNullability: android.car.hardware.CarPropertyValue#CREATOR:
+    Missing nullability on field `CREATOR` in class `class android.car.hardware.CarPropertyValue`
+MissingNullability: android.car.hardware.CarPropertyValue#writeToParcel(android.os.Parcel, int) parameter #0:
+    Missing nullability on parameter `dest` in method `writeToParcel`
+MissingNullability: android.car.hardware.CarSensorEvent#CREATOR:
+    Missing nullability on field `CREATOR` in class `class android.car.hardware.CarSensorEvent`
+MissingNullability: android.car.hardware.CarSensorEvent#floatValues:
+    Missing nullability on field `floatValues` in class `class android.car.hardware.CarSensorEvent`
+MissingNullability: android.car.hardware.CarSensorEvent#intValues:
+    Missing nullability on field `intValues` in class `class android.car.hardware.CarSensorEvent`
+MissingNullability: android.car.hardware.CarSensorEvent#longValues:
+    Missing nullability on field `longValues` in class `class android.car.hardware.CarSensorEvent`
+MissingNullability: android.car.hardware.CarSensorEvent#writeToParcel(android.os.Parcel, int) parameter #0:
+    Missing nullability on parameter `dest` in method `writeToParcel`
+MissingNullability: android.car.hardware.CarSensorManager.OnSensorChangedListener#onSensorChanged(android.car.hardware.CarSensorEvent) parameter #0:
+    Missing nullability on parameter `event` in method `onSensorChanged`
+MissingNullability: android.car.hardware.property.CarPropertyManager.CarPropertyEventCallback#onChangeEvent(android.car.hardware.CarPropertyValue) parameter #0:
+    Missing nullability on parameter `value` in method `onChangeEvent`
+MissingNullability: android.car.settings.CarConfigurationManager#getSpeedBumpConfiguration():
+    Missing nullability on method `getSpeedBumpConfiguration` return
+MissingNullability: android.car.settings.SpeedBumpConfiguration#CREATOR:
+    Missing nullability on field `CREATOR` in class `class android.car.settings.SpeedBumpConfiguration`
+MissingNullability: android.car.settings.SpeedBumpConfiguration#writeToParcel(android.os.Parcel, int) parameter #0:
+    Missing nullability on parameter `desk` in method `writeToParcel`
+
+
+MutableBareField: android.car.CarOccupantZoneManager.OccupantZoneInfo#zoneId:
+    Bare field zoneId must be marked final, or moved behind accessors if mutable
+MutableBareField: android.car.hardware.CarSensorEvent#sensorType:
+    Bare field sensorType must be marked final, or moved behind accessors if mutable
+MutableBareField: android.car.hardware.CarSensorEvent#timestamp:
+    Bare field timestamp must be marked final, or moved behind accessors if mutable
+MutableBareField: android.car.hardware.CarSensorEvent.EnvironmentData#temperature:
+    Bare field temperature must be marked final, or moved behind accessors if mutable
+MutableBareField: android.car.hardware.CarSensorEvent.EnvironmentData#timestamp:
+    Bare field timestamp must be marked final, or moved behind accessors if mutable
+
+
+NotCloseable: android.car.Car:
+    Classes that release resources (disconnect()) should implement AutoClosable and CloseGuard: class android.car.Car
+
+
+PublicTypedef: android.car.drivingstate.CarUxRestrictions.CarUxRestrictionsInfo:
+    Don't expose @IntDef: CarUxRestrictionsInfo must be hidden.
+PublicTypedef: android.car.hardware.CarPropertyValue.PropertyStatus:
+    Don't expose @IntDef: PropertyStatus must be hidden.
+
+
+RegistrationName: android.car.CarOccupantZoneManager#registerOccupantZoneConfigChangeListener(android.car.CarOccupantZoneManager.OccupantZoneConfigChangeListener):
+    Listener methods should be named add/remove; was registerOccupantZoneConfigChangeListener
+RegistrationName: android.car.CarOccupantZoneManager#unregisterOccupantZoneConfigChangeListener(android.car.CarOccupantZoneManager.OccupantZoneConfigChangeListener):
+    Listener methods should be named add/remove; was unregisterOccupantZoneConfigChangeListener
+RegistrationName: android.car.drivingstate.CarUxRestrictionsManager#registerListener(android.car.drivingstate.CarUxRestrictionsManager.OnUxRestrictionsChangedListener):
+    Listener methods should be named add/remove; was registerListener
+RegistrationName: android.car.drivingstate.CarUxRestrictionsManager#unregisterListener():
+    Listener methods should be named add/remove; was unregisterListener
+RegistrationName: android.car.hardware.CarSensorManager#registerListener(android.car.hardware.CarSensorManager.OnSensorChangedListener, int, int):
+    Listener methods should be named add/remove; was registerListener
+RegistrationName: android.car.hardware.CarSensorManager#unregisterListener(android.car.hardware.CarSensorManager.OnSensorChangedListener):
+    Listener methods should be named add/remove; was unregisterListener
+RegistrationName: android.car.hardware.CarSensorManager#unregisterListener(android.car.hardware.CarSensorManager.OnSensorChangedListener, int):
+    Listener methods should be named add/remove; was unregisterListener
+
+
+SamShouldBeLast: android.car.CarAppFocusManager#addFocusListener(android.car.CarAppFocusManager.OnAppFocusChangedListener, int):
+    SAM-compatible parameters (such as parameter 1, "listener", in android.car.CarAppFocusManager.addFocusListener) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
+SamShouldBeLast: android.car.CarAppFocusManager#removeFocusListener(android.car.CarAppFocusManager.OnAppFocusChangedListener, int):
+    SAM-compatible parameters (such as parameter 1, "listener", in android.car.CarAppFocusManager.removeFocusListener) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
+SamShouldBeLast: android.car.hardware.CarSensorManager#registerListener(android.car.hardware.CarSensorManager.OnSensorChangedListener, int, int):
+    SAM-compatible parameters (such as parameter 1, "listener", in android.car.hardware.CarSensorManager.registerListener) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
+SamShouldBeLast: android.car.hardware.CarSensorManager#unregisterListener(android.car.hardware.CarSensorManager.OnSensorChangedListener, int):
+    SAM-compatible parameters (such as parameter 1, "listener", in android.car.hardware.CarSensorManager.unregisterListener) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
+
+
+ServiceName: android.car.Car#CAR_CONFIGURATION_SERVICE:
+    Inconsistent service value; expected `CAR_CONFIGURATION`, was `configuration`
+ServiceName: android.car.Car#CAR_NAVIGATION_SERVICE:
+    Inconsistent service value; expected `CAR_NAVIGATION`, was `car_navigation_service`
+ServiceName: android.car.Car#CAR_OCCUPANT_ZONE_SERVICE:
+    Inconsistent service value; expected `CAR_OCCUPANT_ZONE`, was `car_occupant_zone_service`
+ServiceName: android.car.Car#CAR_UX_RESTRICTION_SERVICE:
+    Inconsistent service value; expected `CAR_UX_RESTRICTION`, was `uxrestriction`
+
+
+StaticUtils: android.car.VehiclePropertyIds:
+    Fully-static utility classes must not have constructor
+StaticUtils: android.car.settings.CarSettings.Global:
+    Fully-static utility classes must not have constructor
+
+
+VisiblySynchronized: PsiThisExpression:this:
+    Internal locks must not be exposed (synchronizing on this or class is still externally observable): method android.car.CarAppFocusManager.addFocusListener(android.car.CarAppFocusManager.OnAppFocusChangedListener,int)
+VisiblySynchronized: android.car.CarAppFocusManager#abandonAppFocus(android.car.CarAppFocusManager.OnAppFocusOwnershipCallback):
+    Internal locks must not be exposed (synchronizing on this or class is still externally observable): method android.car.CarAppFocusManager.abandonAppFocus(android.car.CarAppFocusManager.OnAppFocusOwnershipCallback)
+VisiblySynchronized: android.car.CarAppFocusManager#abandonAppFocus(android.car.CarAppFocusManager.OnAppFocusOwnershipCallback, int):
+    Internal locks must not be exposed (synchronizing on this or class is still externally observable): method android.car.CarAppFocusManager.abandonAppFocus(android.car.CarAppFocusManager.OnAppFocusOwnershipCallback,int)
+VisiblySynchronized: android.car.CarAppFocusManager#addFocusListener(android.car.CarAppFocusManager.OnAppFocusChangedListener, int):
+    Internal locks must not be exposed (synchronizing on this or class is still externally observable): method android.car.CarAppFocusManager.addFocusListener(android.car.CarAppFocusManager.OnAppFocusChangedListener,int)
+VisiblySynchronized: android.car.CarAppFocusManager#isOwningFocus(android.car.CarAppFocusManager.OnAppFocusOwnershipCallback, int):
+    Internal locks must not be exposed (synchronizing on this or class is still externally observable): method android.car.CarAppFocusManager.isOwningFocus(android.car.CarAppFocusManager.OnAppFocusOwnershipCallback,int)
+VisiblySynchronized: android.car.CarAppFocusManager#removeFocusListener(android.car.CarAppFocusManager.OnAppFocusChangedListener):
+    Internal locks must not be exposed (synchronizing on this or class is still externally observable): method android.car.CarAppFocusManager.removeFocusListener(android.car.CarAppFocusManager.OnAppFocusChangedListener)
+VisiblySynchronized: android.car.CarAppFocusManager#removeFocusListener(android.car.CarAppFocusManager.OnAppFocusChangedListener, int):
+    Internal locks must not be exposed (synchronizing on this or class is still externally observable): method android.car.CarAppFocusManager.removeFocusListener(android.car.CarAppFocusManager.OnAppFocusChangedListener,int)
+VisiblySynchronized: android.car.CarAppFocusManager#requestAppFocus(int, android.car.CarAppFocusManager.OnAppFocusOwnershipCallback):
+    Internal locks must not be exposed (synchronizing on this or class is still externally observable): method android.car.CarAppFocusManager.requestAppFocus(int,android.car.CarAppFocusManager.OnAppFocusOwnershipCallback)
+VisiblySynchronized: android.car.drivingstate.CarUxRestrictionsManager#unregisterListener():
+    Internal locks must not be exposed (synchronizing on this or class is still externally observable): method android.car.drivingstate.CarUxRestrictionsManager.unregisterListener()
diff --git a/car-lib/api/removed.txt b/car-lib/api/removed.txt
index d802177..b14f0a6 100644
--- a/car-lib/api/removed.txt
+++ b/car-lib/api/removed.txt
@@ -1 +1,9 @@
 // Signature format: 2.0
+package android.car {
+
+  public final class Car {
+    field @Deprecated public static final String CAR_EXTRA_MEDIA_PACKAGE = "android.car.intent.extra.MEDIA_PACKAGE";
+  }
+
+}
+
diff --git a/car-lib/api/system-current.txt b/car-lib/api/system-current.txt
index a32ffba..f34ba4e 100644
--- a/car-lib/api/system-current.txt
+++ b/car-lib/api/system-current.txt
@@ -12,12 +12,25 @@
   }
 
   public final class Car {
+    method @RequiresPermission(android.car.Car.PERMISSION_CONTROL_CAR_FEATURES) public int disableFeature(@NonNull String);
+    method @RequiresPermission(android.car.Car.PERMISSION_CONTROL_CAR_FEATURES) public int enableFeature(@NonNull String);
+    method @NonNull @RequiresPermission(android.car.Car.PERMISSION_CONTROL_CAR_FEATURES) public java.util.List<java.lang.String> getAllEnabledFeatures();
+    method @NonNull @RequiresPermission(android.car.Car.PERMISSION_CONTROL_CAR_FEATURES) public java.util.List<java.lang.String> getAllPendingDisabledFeatures();
+    method @NonNull @RequiresPermission(android.car.Car.PERMISSION_CONTROL_CAR_FEATURES) public java.util.List<java.lang.String> getAllPendingEnabledFeatures();
     field @Deprecated public static final String CABIN_SERVICE = "cabin";
     field public static final String CAR_DRIVING_STATE_SERVICE = "drivingstate";
     field public static final String CAR_EXTRA_CLUSTER_ACTIVITY_STATE = "android.car.cluster.ClusterActivityState";
     field public static final String CAR_TRUST_AGENT_ENROLLMENT_SERVICE = "trust_enroll";
+    field public static final String CAR_USER_SERVICE = "car_user_service";
+    field public static final String CAR_WATCHDOG_SERVICE = "car_watchdog";
     field public static final String DIAGNOSTIC_SERVICE = "diagnostic";
+    field public static final int FEATURE_REQUEST_ALREADY_IN_THE_STATE = 1; // 0x1
+    field public static final int FEATURE_REQUEST_MANDATORY = 2; // 0x2
+    field public static final int FEATURE_REQUEST_NOT_EXISTING = 3; // 0x3
+    field public static final int FEATURE_REQUEST_SUCCESS = 0; // 0x0
     field @Deprecated public static final String HVAC_SERVICE = "hvac";
+    field public static final String OCCUPANT_AWARENESS_SERVICE = "occupant_awareness";
+    field public static final String PERMISSION_ADJUST_RANGE_REMAINING = "android.car.permission.ADJUST_RANGE_REMAINING";
     field public static final String PERMISSION_CAR_DIAGNOSTIC_CLEAR = "android.car.permission.CLEAR_CAR_DIAGNOSTICS";
     field public static final String PERMISSION_CAR_DIAGNOSTIC_READ_ALL = "android.car.permission.CAR_DIAGNOSTICS";
     field public static final String PERMISSION_CAR_DRIVING_STATE = "android.car.permission.CAR_DRIVING_STATE";
@@ -32,16 +45,22 @@
     field public static final String PERMISSION_CONTROL_APP_BLOCKING = "android.car.permission.CONTROL_APP_BLOCKING";
     field public static final String PERMISSION_CONTROL_CAR_CLIMATE = "android.car.permission.CONTROL_CAR_CLIMATE";
     field public static final String PERMISSION_CONTROL_CAR_DOORS = "android.car.permission.CONTROL_CAR_DOORS";
+    field public static final String PERMISSION_CONTROL_CAR_FEATURES = "android.car.permission.CONTROL_CAR_FEATURES";
     field public static final String PERMISSION_CONTROL_CAR_MIRRORS = "android.car.permission.CONTROL_CAR_MIRRORS";
+    field public static final String PERMISSION_CONTROL_CAR_OCCUPANT_AWARENESS_SYSTEM = "android.car.permission.CONTROL_CAR_OCCUPANT_AWARENESS_SYSTEM";
     field public static final String PERMISSION_CONTROL_CAR_SEATS = "android.car.permission.CONTROL_CAR_SEATS";
     field public static final String PERMISSION_CONTROL_CAR_WINDOWS = "android.car.permission.CONTROL_CAR_WINDOWS";
+    field public static final String PERMISSION_CONTROL_ENERGY_PORTS = "android.car.permission.CONTROL_CAR_ENERGY_PORTS";
     field public static final String PERMISSION_CONTROL_EXTERIOR_LIGHTS = "android.car.permission.CONTROL_CAR_EXTERIOR_LIGHTS";
     field public static final String PERMISSION_EXTERIOR_LIGHTS = "android.car.permission.CAR_EXTERIOR_LIGHTS";
     field public static final String PERMISSION_MILEAGE = "android.car.permission.CAR_MILEAGE";
     field @Deprecated public static final String PERMISSION_MOCK_VEHICLE_HAL = "android.car.permission.CAR_MOCK_VEHICLE_HAL";
+    field public static final String PERMISSION_READ_CAR_OCCUPANT_AWARENESS_STATE = "android.car.permission.READ_CAR_OCCUPANT_AWARENESS_STATE";
+    field public static final String PERMISSION_READ_CAR_VENDOR_PERMISSION_INFO = "android.car.permission.READ_CAR_VENDOR_PERMISSION_INFO";
     field public static final String PERMISSION_RECEIVE_CAR_AUDIO_DUCKING_EVENTS = "android.car.permission.RECEIVE_CAR_AUDIO_DUCKING_EVENTS";
     field public static final String PERMISSION_STORAGE_MONITORING = "android.car.permission.STORAGE_MONITORING";
     field public static final String PERMISSION_TIRES = "android.car.permission.CAR_TIRES";
+    field public static final String PERMISSION_USE_CAR_WATCHDOG = "android.car.permission.USE_CAR_WATCHDOG";
     field public static final String PERMISSION_VENDOR_EXTENSION = "android.car.permission.CAR_VENDOR_EXTENSION";
     field public static final String PERMISSION_VMS_PUBLISHER = "android.car.permission.VMS_PUBLISHER";
     field public static final String PERMISSION_VMS_SUBSCRIBER = "android.car.permission.VMS_SUBSCRIBER";
@@ -49,15 +68,21 @@
     field public static final String PROJECTION_SERVICE = "projection";
     field public static final String STORAGE_MONITORING_SERVICE = "storage_monitoring";
     field public static final String TEST_SERVICE = "car-service-test";
+    field public static final String VEHICLE_MAP_SERVICE = "vehicle_map_service";
     field @Deprecated public static final String VENDOR_EXTENSION_SERVICE = "vendor_extension";
-    field public static final String VMS_SUBSCRIBER_SERVICE = "vehicle_map_subscriber_service";
+    field @Deprecated public static final String VMS_SUBSCRIBER_SERVICE = "vehicle_map_subscriber_service";
+  }
+
+  public class CarOccupantZoneManager {
+    method @RequiresPermission(android.car.Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS) public int getAudioZoneIdForOccupant(@NonNull android.car.CarOccupantZoneManager.OccupantZoneInfo);
+    method @Nullable @RequiresPermission(android.car.Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS) public android.car.CarOccupantZoneManager.OccupantZoneInfo getOccupantForAudioZoneId(int);
   }
 
   public final class CarProjectionManager {
     method @RequiresPermission(android.car.Car.PERMISSION_CAR_PROJECTION) public void addKeyEventHandler(@NonNull java.util.Set<java.lang.Integer>, @NonNull android.car.CarProjectionManager.ProjectionKeyEventHandler);
     method @RequiresPermission(android.car.Car.PERMISSION_CAR_PROJECTION) public void addKeyEventHandler(@NonNull java.util.Set<java.lang.Integer>, @Nullable java.util.concurrent.Executor, @NonNull android.car.CarProjectionManager.ProjectionKeyEventHandler);
-    method @RequiresPermission(android.car.Car.PERMISSION_CAR_PROJECTION) @NonNull public java.util.List<java.lang.Integer> getAvailableWifiChannels(int);
-    method @RequiresPermission(android.car.Car.PERMISSION_CAR_PROJECTION) @NonNull public android.os.Bundle getProjectionOptions();
+    method @NonNull @RequiresPermission(android.car.Car.PERMISSION_CAR_PROJECTION) public java.util.List<java.lang.Integer> getAvailableWifiChannels(int);
+    method @NonNull @RequiresPermission(android.car.Car.PERMISSION_CAR_PROJECTION) public android.os.Bundle getProjectionOptions();
     method @RequiresPermission(android.car.Car.PERMISSION_CAR_PROJECTION) public void registerProjectionListener(@NonNull android.car.CarProjectionManager.CarProjectionListener, int);
     method @RequiresPermission(android.car.Car.PERMISSION_CAR_PROJECTION) public void registerProjectionRunner(@NonNull android.content.Intent);
     method @RequiresPermission(android.car.Car.PERMISSION_CAR_PROJECTION_STATUS) public void registerProjectionStatusListener(@NonNull android.car.CarProjectionManager.ProjectionStatusListener);
@@ -89,7 +114,8 @@
   public abstract static class CarProjectionManager.ProjectionAccessPointCallback {
     ctor public CarProjectionManager.ProjectionAccessPointCallback();
     method public void onFailed(int);
-    method public void onStarted(android.net.wifi.WifiConfiguration);
+    method @Deprecated public void onStarted(@Nullable android.net.wifi.WifiConfiguration);
+    method public void onStarted(@NonNull android.net.wifi.SoftApConfiguration);
     method public void onStopped();
     field public static final int ERROR_GENERIC = 2; // 0x2
     field public static final int ERROR_INCOMPATIBLE_MODE = 3; // 0x3
@@ -122,14 +148,6 @@
     field public static final int MIRROR_DRIVER_RIGHT = 2; // 0x2
   }
 
-  public final class VehicleAreaWheel {
-    field public static final int WHEEL_LEFT_FRONT = 1; // 0x1
-    field public static final int WHEEL_LEFT_REAR = 4; // 0x4
-    field public static final int WHEEL_RIGHT_FRONT = 2; // 0x2
-    field public static final int WHEEL_RIGHT_REAR = 8; // 0x8
-    field public static final int WHEEL_UNKNOWN = 0; // 0x0
-  }
-
   public final class VehicleAreaWindow {
     field public static final int WINDOW_FRONT_WINDSHIELD = 1; // 0x1
     field public static final int WINDOW_REAR_WINDSHIELD = 2; // 0x2
@@ -214,19 +232,22 @@
 
   public abstract class InstrumentClusterRenderingService extends android.app.Service {
     ctor public InstrumentClusterRenderingService();
-    method @Nullable public android.graphics.Bitmap getBitmap(android.net.Uri);
+    method @Deprecated @Nullable public android.graphics.Bitmap getBitmap(android.net.Uri);
+    method @Nullable public android.graphics.Bitmap getBitmap(@NonNull android.net.Uri, int, int);
+    method @Nullable public android.graphics.Bitmap getBitmap(@NonNull android.net.Uri, int, int, float);
     method @MainThread @Nullable public abstract android.car.cluster.renderer.NavigationRenderer getNavigationRenderer();
     method @CallSuper public android.os.IBinder onBind(android.content.Intent);
     method @MainThread public void onKeyEvent(@NonNull android.view.KeyEvent);
     method @MainThread public void onNavigationComponentLaunched();
     method @MainThread public void onNavigationComponentReleased();
+    method public boolean startFixedActivityModeForDisplayAndUser(@NonNull android.content.Intent, @NonNull android.app.ActivityOptions, int);
     method protected boolean startNavigationActivity(@NonNull android.content.ComponentName);
+    method public void stopFixedActivityMode(int);
   }
 
   @UiThread public abstract class NavigationRenderer {
     ctor public NavigationRenderer();
     method public abstract android.car.navigation.CarNavigationInstrumentCluster getNavigationProperties();
-    method @Deprecated public void onEvent(int, android.os.Bundle);
     method public void onNavigationStateChanged(@Nullable android.os.Bundle);
   }
 
@@ -282,10 +303,10 @@
   public final class CarDiagnosticEvent implements android.os.Parcelable {
     ctor public CarDiagnosticEvent(android.os.Parcel);
     method public int describeContents();
-    method @Nullable @android.car.diagnostic.CarDiagnosticEvent.FuelSystemStatus.Status public Integer getFuelSystemStatus();
-    method @Nullable @android.car.diagnostic.CarDiagnosticEvent.FuelType.Type public Integer getFuelType();
+    method @android.car.diagnostic.CarDiagnosticEvent.FuelSystemStatus.Status @Nullable public Integer getFuelSystemStatus();
+    method @android.car.diagnostic.CarDiagnosticEvent.FuelType.Type @Nullable public Integer getFuelType();
     method @Nullable public android.car.diagnostic.CarDiagnosticEvent.CommonIgnitionMonitors getIgnitionMonitors();
-    method @Nullable @android.car.diagnostic.CarDiagnosticEvent.SecondaryAirStatus.Status public Integer getSecondaryAirStatus();
+    method @android.car.diagnostic.CarDiagnosticEvent.SecondaryAirStatus.Status @Nullable public Integer getSecondaryAirStatus();
     method public float getSystemFloatSensor(int, float);
     method @Nullable public Float getSystemFloatSensor(int);
     method public int getSystemIntegerSensor(int, int);
@@ -343,7 +364,7 @@
     field public static final int OPEN_SYSTEM_FAILURE = 8; // 0x8
   }
 
-  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) @IntDef({android.car.diagnostic.CarDiagnosticEvent.FuelSystemStatus.OPEN_INSUFFICIENT_ENGINE_TEMPERATURE, android.car.diagnostic.CarDiagnosticEvent.FuelSystemStatus.CLOSED_LOOP, android.car.diagnostic.CarDiagnosticEvent.FuelSystemStatus.OPEN_ENGINE_LOAD_OR_DECELERATION, android.car.diagnostic.CarDiagnosticEvent.FuelSystemStatus.OPEN_SYSTEM_FAILURE, android.car.diagnostic.CarDiagnosticEvent.FuelSystemStatus.CLOSED_LOOP_BUT_FEEDBACK_FAULT}) public static @interface CarDiagnosticEvent.FuelSystemStatus.Status {
+  @IntDef({android.car.diagnostic.CarDiagnosticEvent.FuelSystemStatus.OPEN_INSUFFICIENT_ENGINE_TEMPERATURE, android.car.diagnostic.CarDiagnosticEvent.FuelSystemStatus.CLOSED_LOOP, android.car.diagnostic.CarDiagnosticEvent.FuelSystemStatus.OPEN_ENGINE_LOAD_OR_DECELERATION, android.car.diagnostic.CarDiagnosticEvent.FuelSystemStatus.OPEN_SYSTEM_FAILURE, android.car.diagnostic.CarDiagnosticEvent.FuelSystemStatus.CLOSED_LOOP_BUT_FEEDBACK_FAULT}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface CarDiagnosticEvent.FuelSystemStatus.Status {
   }
 
   public static final class CarDiagnosticEvent.FuelType {
@@ -373,7 +394,7 @@
     field public static final int PROPANE = 7; // 0x7
   }
 
-  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) @IntDef({android.car.diagnostic.CarDiagnosticEvent.FuelType.NOT_AVAILABLE, android.car.diagnostic.CarDiagnosticEvent.FuelType.GASOLINE, android.car.diagnostic.CarDiagnosticEvent.FuelType.METHANOL, android.car.diagnostic.CarDiagnosticEvent.FuelType.ETHANOL, android.car.diagnostic.CarDiagnosticEvent.FuelType.DIESEL, android.car.diagnostic.CarDiagnosticEvent.FuelType.LPG, android.car.diagnostic.CarDiagnosticEvent.FuelType.CNG, android.car.diagnostic.CarDiagnosticEvent.FuelType.PROPANE, android.car.diagnostic.CarDiagnosticEvent.FuelType.ELECTRIC, android.car.diagnostic.CarDiagnosticEvent.FuelType.BIFUEL_RUNNING_GASOLINE, android.car.diagnostic.CarDiagnosticEvent.FuelType.BIFUEL_RUNNING_METHANOL, android.car.diagnostic.CarDiagnosticEvent.FuelType.BIFUEL_RUNNING_ETHANOL, android.car.diagnostic.CarDiagnosticEvent.FuelType.BIFUEL_RUNNING_LPG, android.car.diagnostic.CarDiagnosticEvent.FuelType.BIFUEL_RUNNING_CNG, android.car.diagnostic.CarDiagnosticEvent.FuelType.BIFUEL_RUNNING_PROPANE, android.car.diagnostic.CarDiagnosticEvent.FuelType.BIFUEL_RUNNING_ELECTRIC, android.car.diagnostic.CarDiagnosticEvent.FuelType.BIFUEL_RUNNING_ELECTRIC_AND_COMBUSTION, android.car.diagnostic.CarDiagnosticEvent.FuelType.HYBRID_GASOLINE, android.car.diagnostic.CarDiagnosticEvent.FuelType.HYBRID_ETHANOL, android.car.diagnostic.CarDiagnosticEvent.FuelType.HYBRID_DIESEL, android.car.diagnostic.CarDiagnosticEvent.FuelType.HYBRID_ELECTRIC, android.car.diagnostic.CarDiagnosticEvent.FuelType.HYBRID_RUNNING_ELECTRIC_AND_COMBUSTION, android.car.diagnostic.CarDiagnosticEvent.FuelType.HYBRID_REGENERATIVE, android.car.diagnostic.CarDiagnosticEvent.FuelType.BIFUEL_RUNNING_DIESEL}) public static @interface CarDiagnosticEvent.FuelType.Type {
+  @IntDef({android.car.diagnostic.CarDiagnosticEvent.FuelType.NOT_AVAILABLE, android.car.diagnostic.CarDiagnosticEvent.FuelType.GASOLINE, android.car.diagnostic.CarDiagnosticEvent.FuelType.METHANOL, android.car.diagnostic.CarDiagnosticEvent.FuelType.ETHANOL, android.car.diagnostic.CarDiagnosticEvent.FuelType.DIESEL, android.car.diagnostic.CarDiagnosticEvent.FuelType.LPG, android.car.diagnostic.CarDiagnosticEvent.FuelType.CNG, android.car.diagnostic.CarDiagnosticEvent.FuelType.PROPANE, android.car.diagnostic.CarDiagnosticEvent.FuelType.ELECTRIC, android.car.diagnostic.CarDiagnosticEvent.FuelType.BIFUEL_RUNNING_GASOLINE, android.car.diagnostic.CarDiagnosticEvent.FuelType.BIFUEL_RUNNING_METHANOL, android.car.diagnostic.CarDiagnosticEvent.FuelType.BIFUEL_RUNNING_ETHANOL, android.car.diagnostic.CarDiagnosticEvent.FuelType.BIFUEL_RUNNING_LPG, android.car.diagnostic.CarDiagnosticEvent.FuelType.BIFUEL_RUNNING_CNG, android.car.diagnostic.CarDiagnosticEvent.FuelType.BIFUEL_RUNNING_PROPANE, android.car.diagnostic.CarDiagnosticEvent.FuelType.BIFUEL_RUNNING_ELECTRIC, android.car.diagnostic.CarDiagnosticEvent.FuelType.BIFUEL_RUNNING_ELECTRIC_AND_COMBUSTION, android.car.diagnostic.CarDiagnosticEvent.FuelType.HYBRID_GASOLINE, android.car.diagnostic.CarDiagnosticEvent.FuelType.HYBRID_ETHANOL, android.car.diagnostic.CarDiagnosticEvent.FuelType.HYBRID_DIESEL, android.car.diagnostic.CarDiagnosticEvent.FuelType.HYBRID_ELECTRIC, android.car.diagnostic.CarDiagnosticEvent.FuelType.HYBRID_RUNNING_ELECTRIC_AND_COMBUSTION, android.car.diagnostic.CarDiagnosticEvent.FuelType.HYBRID_REGENERATIVE, android.car.diagnostic.CarDiagnosticEvent.FuelType.BIFUEL_RUNNING_DIESEL}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface CarDiagnosticEvent.FuelType.Type {
   }
 
   public static final class CarDiagnosticEvent.IgnitionMonitor {
@@ -388,7 +409,7 @@
     field public static final int UPSTREAM = 1; // 0x1
   }
 
-  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) @IntDef({android.car.diagnostic.CarDiagnosticEvent.SecondaryAirStatus.UPSTREAM, android.car.diagnostic.CarDiagnosticEvent.SecondaryAirStatus.DOWNSTREAM_OF_CATALYCIC_CONVERTER, android.car.diagnostic.CarDiagnosticEvent.SecondaryAirStatus.FROM_OUTSIDE_OR_OFF, android.car.diagnostic.CarDiagnosticEvent.SecondaryAirStatus.PUMP_ON_FOR_DIAGNOSTICS}) public static @interface CarDiagnosticEvent.SecondaryAirStatus.Status {
+  @IntDef({android.car.diagnostic.CarDiagnosticEvent.SecondaryAirStatus.UPSTREAM, android.car.diagnostic.CarDiagnosticEvent.SecondaryAirStatus.DOWNSTREAM_OF_CATALYCIC_CONVERTER, android.car.diagnostic.CarDiagnosticEvent.SecondaryAirStatus.FROM_OUTSIDE_OR_OFF, android.car.diagnostic.CarDiagnosticEvent.SecondaryAirStatus.PUMP_ON_FOR_DIAGNOSTICS}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface CarDiagnosticEvent.SecondaryAirStatus.Status {
   }
 
   public static final class CarDiagnosticEvent.SparkIgnitionMonitors extends android.car.diagnostic.CarDiagnosticEvent.CommonIgnitionMonitors {
@@ -419,7 +440,7 @@
     field public static final int FRAME_TYPE_LIVE = 0; // 0x0
   }
 
-  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) @IntDef({android.car.diagnostic.CarDiagnosticManager.FRAME_TYPE_LIVE, android.car.diagnostic.CarDiagnosticManager.FRAME_TYPE_FREEZE}) public static @interface CarDiagnosticManager.FrameType {
+  @IntDef({android.car.diagnostic.CarDiagnosticManager.FRAME_TYPE_LIVE, android.car.diagnostic.CarDiagnosticManager.FRAME_TYPE_FREEZE}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface CarDiagnosticManager.FrameType {
   }
 
   public static interface CarDiagnosticManager.OnDiagnosticEventListener {
@@ -570,8 +591,16 @@
 
 package android.car.hardware {
 
+  public final class CarHvacFanDirection {
+    field public static final int DEFROST = 4; // 0x4
+    field public static final int DEFROST_AND_FLOOR = 6; // 0x6
+    field public static final int FACE = 1; // 0x1
+    field public static final int FACE_AND_FLOOR = 3; // 0x3
+    field public static final int FLOOR = 2; // 0x2
+    field public static final int UNKNOWN = 0; // 0x0
+  }
+
   public final class CarPropertyConfig<T> implements android.os.Parcelable {
-    method @NonNull public Class<T> getPropertyType();
     method public static <T> android.car.hardware.CarPropertyConfig.Builder<T> newBuilder(Class<T>, int, int, int);
   }
 
@@ -722,6 +751,49 @@
 
 }
 
+package android.car.hardware.property {
+
+  public final class VehicleVendorPermission {
+    field public static final String PERMISSION_GET_CAR_VENDOR_CATEGORY_1 = "android.car.permission.GET_CAR_VENDOR_CATEGORY_1";
+    field public static final String PERMISSION_GET_CAR_VENDOR_CATEGORY_10 = "android.car.permission.GET_CAR_VENDOR_CATEGORY_10";
+    field public static final String PERMISSION_GET_CAR_VENDOR_CATEGORY_2 = "android.car.permission.GET_CAR_VENDOR_CATEGORY_2";
+    field public static final String PERMISSION_GET_CAR_VENDOR_CATEGORY_3 = "android.car.permission.GET_CAR_VENDOR_CATEGORY_3";
+    field public static final String PERMISSION_GET_CAR_VENDOR_CATEGORY_4 = "android.car.permission.GET_CAR_VENDOR_CATEGORY_4";
+    field public static final String PERMISSION_GET_CAR_VENDOR_CATEGORY_5 = "android.car.permission.GET_CAR_VENDOR_CATEGORY_5";
+    field public static final String PERMISSION_GET_CAR_VENDOR_CATEGORY_6 = "android.car.permission.GET_CAR_VENDOR_CATEGORY_6";
+    field public static final String PERMISSION_GET_CAR_VENDOR_CATEGORY_7 = "android.car.permission.GET_CAR_VENDOR_CATEGORY_7";
+    field public static final String PERMISSION_GET_CAR_VENDOR_CATEGORY_8 = "android.car.permission.GET_CAR_VENDOR_CATEGORY_8";
+    field public static final String PERMISSION_GET_CAR_VENDOR_CATEGORY_9 = "android.car.permission.GET_CAR_VENDOR_CATEGORY_9";
+    field public static final String PERMISSION_GET_CAR_VENDOR_CATEGORY_DOOR = "android.car.permission.GET_CAR_VENDOR_CATEGORY_DOOR";
+    field public static final String PERMISSION_GET_CAR_VENDOR_CATEGORY_ENGINE = "android.car.permission.GET_CAR_VENDOR_CATEGORY_ENGINE";
+    field public static final String PERMISSION_GET_CAR_VENDOR_CATEGORY_HVAC = "android.car.permission.GET_CAR_VENDOR_CATEGORY_HVAC";
+    field public static final String PERMISSION_GET_CAR_VENDOR_CATEGORY_INFO = "android.car.permission.GET_CAR_VENDOR_CATEGORY_INFO";
+    field public static final String PERMISSION_GET_CAR_VENDOR_CATEGORY_LIGHT = "android.car.permission.GET_CAR_VENDOR_CATEGORY_LIGHT";
+    field public static final String PERMISSION_GET_CAR_VENDOR_CATEGORY_MIRROR = "android.car.permission.GET_CAR_VENDOR_CATEGORY_MIRROR";
+    field public static final String PERMISSION_GET_CAR_VENDOR_CATEGORY_SEAT = "android.car.permission.GET_CAR_VENDOR_CATEGORY_SEAT";
+    field public static final String PERMISSION_GET_CAR_VENDOR_CATEGORY_WINDOW = "android.car.permission.GET_CAR_VENDOR_CATEGORY_WINDOW";
+    field public static final String PERMISSION_SET_CAR_VENDOR_CATEGORY_1 = "android.car.permission.SET_CAR_VENDOR_CATEGORY_1";
+    field public static final String PERMISSION_SET_CAR_VENDOR_CATEGORY_10 = "android.car.permission.SET_CAR_VENDOR_CATEGORY_10";
+    field public static final String PERMISSION_SET_CAR_VENDOR_CATEGORY_2 = "android.car.permission.SET_CAR_VENDOR_CATEGORY_2";
+    field public static final String PERMISSION_SET_CAR_VENDOR_CATEGORY_3 = "android.car.permission.SET_CAR_VENDOR_CATEGORY_3";
+    field public static final String PERMISSION_SET_CAR_VENDOR_CATEGORY_4 = "android.car.permission.SET_CAR_VENDOR_CATEGORY_4";
+    field public static final String PERMISSION_SET_CAR_VENDOR_CATEGORY_5 = "android.car.permission.SET_CAR_VENDOR_CATEGORY_5";
+    field public static final String PERMISSION_SET_CAR_VENDOR_CATEGORY_6 = "android.car.permission.SET_CAR_VENDOR_CATEGORY_6";
+    field public static final String PERMISSION_SET_CAR_VENDOR_CATEGORY_7 = "android.car.permission.SET_CAR_VENDOR_CATEGORY_7";
+    field public static final String PERMISSION_SET_CAR_VENDOR_CATEGORY_8 = "android.car.permission.SET_CAR_VENDOR_CATEGORY_8";
+    field public static final String PERMISSION_SET_CAR_VENDOR_CATEGORY_9 = "android.car.permission.SET_CAR_VENDOR_CATEGORY_9";
+    field public static final String PERMISSION_SET_CAR_VENDOR_CATEGORY_DOOR = "android.car.permission.SET_CAR_VENDOR_CATEGORY_DOOR";
+    field public static final String PERMISSION_SET_CAR_VENDOR_CATEGORY_ENGINE = "android.car.permission.SET_CAR_VENDOR_CATEGORY_ENGINE";
+    field public static final String PERMISSION_SET_CAR_VENDOR_CATEGORY_HVAC = "android.car.permission.SET_CAR_VENDOR_CATEGORY_HVAC";
+    field public static final String PERMISSION_SET_CAR_VENDOR_CATEGORY_INFO = "android.car.permission.SET_CAR_VENDOR_CATEGORY_INFO";
+    field public static final String PERMISSION_SET_CAR_VENDOR_CATEGORY_LIGHT = "android.car.permission.SET_CAR_VENDOR_CATEGORY_LIGHT";
+    field public static final String PERMISSION_SET_CAR_VENDOR_CATEGORY_MIRROR = "android.car.permission.SET_CAR_VENDOR_CATEGORY_MIRROR";
+    field public static final String PERMISSION_SET_CAR_VENDOR_CATEGORY_SEAT = "android.car.permission.SET_CAR_VENDOR_CATEGORY_SEAT";
+    field public static final String PERMISSION_SET_CAR_VENDOR_CATEGORY_WINDOW = "android.car.permission.SET_CAR_VENDOR_CATEGORY_WINDOW";
+  }
+
+}
+
 package android.car.input {
 
   public abstract class CarInputHandlingService extends android.app.Service {
@@ -747,6 +819,7 @@
 
   public final class CarAudioManager {
     method @RequiresPermission(android.car.Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS) public android.car.media.CarAudioPatchHandle createAudioPatch(String, int, int);
+    method @NonNull @RequiresPermission(android.car.Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS) public java.util.List<java.lang.Integer> getAudioZoneIds();
     method @NonNull @RequiresPermission(android.car.Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS) public String[] getExternalSources();
     method @RequiresPermission(android.car.Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME) public int getGroupMaxVolume(int);
     method @RequiresPermission(android.car.Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME) public int getGroupMaxVolume(int, int);
@@ -754,6 +827,8 @@
     method @RequiresPermission(android.car.Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME) public int getGroupMinVolume(int, int);
     method @RequiresPermission(android.car.Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME) public int getGroupVolume(int);
     method @RequiresPermission(android.car.Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME) public int getGroupVolume(int, int);
+    method @NonNull @RequiresPermission(android.car.Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS) public java.util.List<android.media.AudioDeviceInfo> getInputDevicesForZoneId(int);
+    method @Nullable @RequiresPermission(android.car.Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS) public android.media.AudioDeviceInfo getOutputDeviceForUsage(int, int);
     method @NonNull @RequiresPermission(android.car.Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME) public int[] getUsagesForVolumeGroupId(int);
     method @NonNull @RequiresPermission(android.car.Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME) public int[] getUsagesForVolumeGroupId(int, int);
     method @RequiresPermission(android.car.Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME) public int getVolumeGroupCount();
@@ -766,16 +841,30 @@
     method @RequiresPermission(android.car.Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME) public void setGroupVolume(int, int, int);
     method @RequiresPermission(android.car.Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME) public void setGroupVolume(int, int, int, int);
     field public static final String AUDIOFOCUS_EXTRA_RECEIVE_DUCKING_EVENTS = "android.car.media.AUDIOFOCUS_EXTRA_RECEIVE_DUCKING_EVENTS";
+    field public static final int INVALID_AUDIO_ZONE = -1; // 0xffffffff
     field public static final int PRIMARY_AUDIO_ZONE = 0; // 0x0
   }
 
   public final class CarAudioPatchHandle implements android.os.Parcelable {
-    ctor public CarAudioPatchHandle(android.media.AudioPatch);
     method public int describeContents();
     method public void writeToParcel(android.os.Parcel, int);
     field public static final android.os.Parcelable.Creator<android.car.media.CarAudioPatchHandle> CREATOR;
   }
 
+  public final class CarMediaManager {
+    method @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public void addMediaSourceListener(@NonNull android.car.media.CarMediaManager.MediaSourceChangedListener, int);
+    method @NonNull @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public java.util.List<android.content.ComponentName> getLastMediaSources(int);
+    method @NonNull @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public android.content.ComponentName getMediaSource(int);
+    method @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public void removeMediaSourceListener(@NonNull android.car.media.CarMediaManager.MediaSourceChangedListener, int);
+    method @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public void setMediaSource(@NonNull android.content.ComponentName, int);
+    field public static final int MEDIA_SOURCE_MODE_BROWSE = 1; // 0x1
+    field public static final int MEDIA_SOURCE_MODE_PLAYBACK = 0; // 0x0
+  }
+
+  public static interface CarMediaManager.MediaSourceChangedListener {
+    method public void onMediaSourceChanged(@NonNull android.content.ComponentName);
+  }
+
 }
 
 package android.car.navigation {
@@ -805,6 +894,113 @@
 
 }
 
+package android.car.occupantawareness {
+
+  public final class DriverMonitoringDetection implements android.os.Parcelable {
+    ctor public DriverMonitoringDetection();
+    ctor public DriverMonitoringDetection(int, boolean, long);
+    method public int describeContents();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.car.occupantawareness.DriverMonitoringDetection> CREATOR;
+    field public final int confidenceLevel;
+    field public final long gazeDurationMillis;
+    field public final boolean isLookingOnRoad;
+  }
+
+  public final class GazeDetection implements android.os.Parcelable {
+    ctor public GazeDetection(int, @Nullable android.car.occupantawareness.Point3D, @Nullable android.car.occupantawareness.Point3D, @Nullable android.car.occupantawareness.Point3D, @Nullable android.car.occupantawareness.Point3D, int, long);
+    method public int describeContents();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.car.occupantawareness.GazeDetection> CREATOR;
+    field public static final int VEHICLE_REGION_CENTER_INSTRUMENT_CLUSTER = 1; // 0x1
+    field public static final int VEHICLE_REGION_FORWARD_ROADWAY = 5; // 0x5
+    field public static final int VEHICLE_REGION_HEAD_UNIT_DISPLAY = 8; // 0x8
+    field public static final int VEHICLE_REGION_LEFT_ROADWAY = 6; // 0x6
+    field public static final int VEHICLE_REGION_LEFT_SIDE_MIRROR = 3; // 0x3
+    field public static final int VEHICLE_REGION_REAR_VIEW_MIRROR = 2; // 0x2
+    field public static final int VEHICLE_REGION_RIGHT_ROADWAY = 7; // 0x7
+    field public static final int VEHICLE_REGION_RIGHT_SIDE_MIRROR = 4; // 0x4
+    field public static final int VEHICLE_REGION_UNKNOWN = 0; // 0x0
+    field public final int confidenceLevel;
+    field public final long durationOnTargetMillis;
+    field @Nullable public final android.car.occupantawareness.Point3D gazeAngleUnitVector;
+    field public final int gazeTarget;
+    field @Nullable public final android.car.occupantawareness.Point3D headAngleUnitVector;
+    field @Nullable public final android.car.occupantawareness.Point3D leftEyePosition;
+    field @Nullable public final android.car.occupantawareness.Point3D rightEyePosition;
+  }
+
+  public final class OccupantAwarenessDetection implements android.os.Parcelable {
+    ctor public OccupantAwarenessDetection(int, long, boolean, @Nullable android.car.occupantawareness.GazeDetection, @Nullable android.car.occupantawareness.DriverMonitoringDetection);
+    method public int describeContents();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field public static final int CONFIDENCE_LEVEL_HIGH = 2; // 0x2
+    field public static final int CONFIDENCE_LEVEL_LOW = 1; // 0x1
+    field public static final int CONFIDENCE_LEVEL_MAX = 3; // 0x3
+    field public static final int CONFIDENCE_LEVEL_NONE = 0; // 0x0
+    field @NonNull public static final android.os.Parcelable.Creator<android.car.occupantawareness.OccupantAwarenessDetection> CREATOR;
+    field public static final int VEHICLE_OCCUPANT_ALL_FRONT_OCCUPANTS = 6; // 0x6
+    field public static final int VEHICLE_OCCUPANT_ALL_OCCUPANTS = 510; // 0x1fe
+    field public static final int VEHICLE_OCCUPANT_ALL_ROW_2_OCCUPANTS = 56; // 0x38
+    field public static final int VEHICLE_OCCUPANT_ALL_ROW_3_OCCUPANTS = 448; // 0x1c0
+    field public static final int VEHICLE_OCCUPANT_DRIVER = 4; // 0x4
+    field public static final int VEHICLE_OCCUPANT_FRONT_PASSENGER = 2; // 0x2
+    field public static final int VEHICLE_OCCUPANT_NONE = 0; // 0x0
+    field public static final int VEHICLE_OCCUPANT_ROW_2_PASSENGER_CENTER = 16; // 0x10
+    field public static final int VEHICLE_OCCUPANT_ROW_2_PASSENGER_LEFT = 8; // 0x8
+    field public static final int VEHICLE_OCCUPANT_ROW_2_PASSENGER_RIGHT = 32; // 0x20
+    field public static final int VEHICLE_OCCUPANT_ROW_3_PASSENGER_CENTER = 128; // 0x80
+    field public static final int VEHICLE_OCCUPANT_ROW_3_PASSENGER_LEFT = 64; // 0x40
+    field public static final int VEHICLE_OCCUPANT_ROW_3_PASSENGER_RIGHT = 256; // 0x100
+    field @Nullable public final android.car.occupantawareness.DriverMonitoringDetection driverMonitoringDetection;
+    field @Nullable public final android.car.occupantawareness.GazeDetection gazeDetection;
+    field public final boolean isPresent;
+    field public final int role;
+    field public final long timestampMillis;
+  }
+
+  public class OccupantAwarenessManager {
+    method @RequiresPermission(android.car.Car.PERMISSION_READ_CAR_OCCUPANT_AWARENESS_STATE) public int getCapabilityForRole(int);
+    method @RequiresPermission(android.car.Car.PERMISSION_READ_CAR_OCCUPANT_AWARENESS_STATE) public void registerChangeCallback(@NonNull android.car.occupantawareness.OccupantAwarenessManager.ChangeCallback);
+    method @RequiresPermission(android.car.Car.PERMISSION_READ_CAR_OCCUPANT_AWARENESS_STATE) public void unregisterChangeCallback();
+  }
+
+  public abstract static class OccupantAwarenessManager.ChangeCallback {
+    ctor public OccupantAwarenessManager.ChangeCallback();
+    method public abstract void onDetectionEvent(@NonNull android.car.occupantawareness.OccupantAwarenessDetection);
+    method public abstract void onSystemStateChanged(@NonNull android.car.occupantawareness.SystemStatusEvent);
+  }
+
+  public final class Point3D implements android.os.Parcelable {
+    ctor public Point3D(double, double, double);
+    method public int describeContents();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.car.occupantawareness.Point3D> CREATOR;
+    field public final double x;
+    field public final double y;
+    field public final double z;
+  }
+
+  public final class SystemStatusEvent implements android.os.Parcelable {
+    ctor public SystemStatusEvent(int, int);
+    ctor public SystemStatusEvent();
+    method public int describeContents();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.car.occupantawareness.SystemStatusEvent> CREATOR;
+    field public static final int DETECTION_TYPE_DRIVER_MONITORING = 4; // 0x4
+    field public static final int DETECTION_TYPE_GAZE = 2; // 0x2
+    field public static final int DETECTION_TYPE_NONE = 0; // 0x0
+    field public static final int DETECTION_TYPE_PRESENCE = 1; // 0x1
+    field public static final int SYSTEM_STATUS_NOT_READY = 2; // 0x2
+    field public static final int SYSTEM_STATUS_NOT_SUPPORTED = 1; // 0x1
+    field public static final int SYSTEM_STATUS_READY = 0; // 0x0
+    field public static final int SYSTEM_STATUS_SYSTEM_FAILURE = 3; // 0x3
+    field public final int detectionType;
+    field public final int systemStatus;
+  }
+
+}
+
 package android.car.projection {
 
   public class ProjectionOptions {
@@ -983,50 +1179,75 @@
 
 package android.car.trust {
 
-  public final class CarTrustAgentEnrollmentManager {
-    method @RequiresPermission(android.car.Car.PERMISSION_CAR_ENROLL_TRUST) public void enrollmentHandshakeAccepted(android.bluetooth.BluetoothDevice);
-    method @RequiresPermission(android.car.Car.PERMISSION_CAR_ENROLL_TRUST) @NonNull public java.util.List<android.car.trust.TrustedDeviceInfo> getEnrolledDeviceInfoForUser(int);
-    method @RequiresPermission(android.car.Car.PERMISSION_CAR_ENROLL_TRUST) public boolean isEscrowTokenActive(long, int);
-    method @RequiresPermission(android.car.Car.PERMISSION_CAR_ENROLL_TRUST) public void removeAllTrustedDevices(int);
-    method @RequiresPermission(android.car.Car.PERMISSION_CAR_ENROLL_TRUST) public void removeEscrowToken(long, int);
-    method @RequiresPermission(android.car.Car.PERMISSION_CAR_ENROLL_TRUST) public void setBleCallback(@Nullable android.car.trust.CarTrustAgentEnrollmentManager.CarTrustAgentBleCallback);
-    method @RequiresPermission(android.car.Car.PERMISSION_CAR_ENROLL_TRUST) public void setEnrollmentCallback(@Nullable android.car.trust.CarTrustAgentEnrollmentManager.CarTrustAgentEnrollmentCallback);
-    method @RequiresPermission(android.car.Car.PERMISSION_CAR_ENROLL_TRUST) public void setTrustedDeviceEnrollmentEnabled(boolean);
-    method @RequiresPermission(android.car.Car.PERMISSION_CAR_ENROLL_TRUST) public void setTrustedDeviceUnlockEnabled(boolean);
-    method @RequiresPermission(android.car.Car.PERMISSION_CAR_ENROLL_TRUST) public void startEnrollmentAdvertising();
-    method @RequiresPermission(android.car.Car.PERMISSION_CAR_ENROLL_TRUST) public void stopEnrollmentAdvertising();
-    method @RequiresPermission(android.car.Car.PERMISSION_CAR_ENROLL_TRUST) public void terminateEnrollmentHandshake();
-    field public static final int ENROLLMENT_HANDSHAKE_FAILURE = 1; // 0x1
-    field public static final int ENROLLMENT_NOT_ALLOWED = 2; // 0x2
+  @Deprecated public final class CarTrustAgentEnrollmentManager {
+    method @Deprecated @RequiresPermission(android.car.Car.PERMISSION_CAR_ENROLL_TRUST) public void enrollmentHandshakeAccepted(android.bluetooth.BluetoothDevice);
+    method @Deprecated @NonNull @RequiresPermission(android.car.Car.PERMISSION_CAR_ENROLL_TRUST) public java.util.List<android.car.trust.TrustedDeviceInfo> getEnrolledDeviceInfoForUser(int);
+    method @Deprecated @RequiresPermission(android.car.Car.PERMISSION_CAR_ENROLL_TRUST) public boolean isEscrowTokenActive(long, int);
+    method @Deprecated @RequiresPermission(android.car.Car.PERMISSION_CAR_ENROLL_TRUST) public void removeAllTrustedDevices(int);
+    method @Deprecated @RequiresPermission(android.car.Car.PERMISSION_CAR_ENROLL_TRUST) public void removeEscrowToken(long, int);
+    method @Deprecated @RequiresPermission(android.car.Car.PERMISSION_CAR_ENROLL_TRUST) public void setBleCallback(@Nullable android.car.trust.CarTrustAgentEnrollmentManager.CarTrustAgentBleCallback);
+    method @Deprecated @RequiresPermission(android.car.Car.PERMISSION_CAR_ENROLL_TRUST) public void setEnrollmentCallback(@Nullable android.car.trust.CarTrustAgentEnrollmentManager.CarTrustAgentEnrollmentCallback);
+    method @Deprecated @RequiresPermission(android.car.Car.PERMISSION_CAR_ENROLL_TRUST) public void setTrustedDeviceEnrollmentEnabled(boolean);
+    method @Deprecated @RequiresPermission(android.car.Car.PERMISSION_CAR_ENROLL_TRUST) public void setTrustedDeviceUnlockEnabled(boolean);
+    method @Deprecated @RequiresPermission(android.car.Car.PERMISSION_CAR_ENROLL_TRUST) public void startEnrollmentAdvertising();
+    method @Deprecated @RequiresPermission(android.car.Car.PERMISSION_CAR_ENROLL_TRUST) public void stopEnrollmentAdvertising();
+    method @Deprecated @RequiresPermission(android.car.Car.PERMISSION_CAR_ENROLL_TRUST) public void terminateEnrollmentHandshake();
+    field @Deprecated public static final int ENROLLMENT_HANDSHAKE_FAILURE = 1; // 0x1
+    field @Deprecated public static final int ENROLLMENT_NOT_ALLOWED = 2; // 0x2
   }
 
-  public static interface CarTrustAgentEnrollmentManager.CarTrustAgentBleCallback {
-    method public void onBleEnrollmentDeviceConnected(android.bluetooth.BluetoothDevice);
-    method public void onBleEnrollmentDeviceDisconnected(android.bluetooth.BluetoothDevice);
-    method public void onEnrollmentAdvertisingFailed();
-    method public void onEnrollmentAdvertisingStarted();
+  @Deprecated public static interface CarTrustAgentEnrollmentManager.CarTrustAgentBleCallback {
+    method @Deprecated public void onBleEnrollmentDeviceConnected(android.bluetooth.BluetoothDevice);
+    method @Deprecated public void onBleEnrollmentDeviceDisconnected(android.bluetooth.BluetoothDevice);
+    method @Deprecated public void onEnrollmentAdvertisingFailed();
+    method @Deprecated public void onEnrollmentAdvertisingStarted();
   }
 
-  public static interface CarTrustAgentEnrollmentManager.CarTrustAgentEnrollmentCallback {
-    method public void onAuthStringAvailable(android.bluetooth.BluetoothDevice, String);
-    method public void onEnrollmentHandshakeFailure(@Nullable android.bluetooth.BluetoothDevice, int);
-    method public void onEscrowTokenActiveStateChanged(long, boolean);
-    method public void onEscrowTokenAdded(long);
-    method public void onEscrowTokenRemoved(long);
+  @Deprecated public static interface CarTrustAgentEnrollmentManager.CarTrustAgentEnrollmentCallback {
+    method @Deprecated public void onAuthStringAvailable(android.bluetooth.BluetoothDevice, String);
+    method @Deprecated public void onEnrollmentHandshakeFailure(@Nullable android.bluetooth.BluetoothDevice, int);
+    method @Deprecated public void onEscrowTokenActiveStateChanged(long, boolean);
+    method @Deprecated public void onEscrowTokenAdded(long);
+    method @Deprecated public void onEscrowTokenRemoved(long);
   }
 
-  public final class TrustedDeviceInfo implements android.os.Parcelable {
-    ctor public TrustedDeviceInfo(long, @NonNull String, @NonNull String);
-    ctor public TrustedDeviceInfo(android.os.Parcel);
-    method public int describeContents();
-    method public static android.car.trust.TrustedDeviceInfo deserialize(String);
-    method @NonNull public String getAddress();
-    method public long getHandle();
-    method @NonNull public String getName();
-    method public String serialize();
-    method public void writeToParcel(android.os.Parcel, int);
-    field public static final android.os.Parcelable.Creator CREATOR;
-    field public static final String DEFAULT_NAME = "Default";
+  @Deprecated public final class TrustedDeviceInfo implements android.os.Parcelable {
+    ctor @Deprecated public TrustedDeviceInfo(long, @NonNull String, @NonNull String);
+    ctor @Deprecated public TrustedDeviceInfo(android.os.Parcel);
+    method @Deprecated public int describeContents();
+    method @Deprecated public static android.car.trust.TrustedDeviceInfo deserialize(String);
+    method @Deprecated @NonNull public String getAddress();
+    method @Deprecated public long getHandle();
+    method @Deprecated @NonNull public String getName();
+    method @Deprecated public String serialize();
+    method @Deprecated public void writeToParcel(android.os.Parcel, int);
+    field @Deprecated public static final android.os.Parcelable.Creator CREATOR;
+    field @Deprecated public static final String DEFAULT_NAME = "Default";
+  }
+
+}
+
+package android.car.user {
+
+  public final class CarUserManager {
+    method @RequiresPermission(anyOf={android.Manifest.permission.INTERACT_ACROSS_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS_FULL}) public void addListener(@NonNull java.util.concurrent.Executor, @NonNull android.car.user.CarUserManager.UserLifecycleListener);
+    method @RequiresPermission(anyOf={android.Manifest.permission.INTERACT_ACROSS_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS_FULL}) public void removeListener(@NonNull android.car.user.CarUserManager.UserLifecycleListener);
+    field public static final int USER_LIFECYCLE_EVENT_TYPE_STARTING = 1; // 0x1
+    field public static final int USER_LIFECYCLE_EVENT_TYPE_STOPPED = 6; // 0x6
+    field public static final int USER_LIFECYCLE_EVENT_TYPE_STOPPING = 5; // 0x5
+    field public static final int USER_LIFECYCLE_EVENT_TYPE_SWITCHING = 2; // 0x2
+    field public static final int USER_LIFECYCLE_EVENT_TYPE_UNLOCKED = 4; // 0x4
+    field public static final int USER_LIFECYCLE_EVENT_TYPE_UNLOCKING = 3; // 0x3
+  }
+
+  public final class CarUserManager.UserLifecycleEvent {
+    method public int getEventType();
+    method @Nullable public android.os.UserHandle getPreviousUserHandle();
+    method @NonNull public android.os.UserHandle getUserHandle();
+  }
+
+  public static interface CarUserManager.UserLifecycleListener {
+    method public void onEvent(@NonNull android.car.user.CarUserManager.UserLifecycleEvent);
   }
 
 }
@@ -1036,97 +1257,126 @@
   public final class VmsAssociatedLayer implements android.os.Parcelable {
     ctor public VmsAssociatedLayer(@NonNull android.car.vms.VmsLayer, @NonNull java.util.Set<java.lang.Integer>);
     method public int describeContents();
-    method @NonNull public java.util.Set<java.lang.Integer> getPublisherIds();
+    method @NonNull public java.util.Set<java.lang.Integer> getProviderIds();
+    method @Deprecated @NonNull public java.util.Set<java.lang.Integer> getPublisherIds();
     method @NonNull public android.car.vms.VmsLayer getVmsLayer();
-    method public void writeToParcel(android.os.Parcel, int);
-    field public static final android.os.Parcelable.Creator<android.car.vms.VmsAssociatedLayer> CREATOR;
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.car.vms.VmsAssociatedLayer> CREATOR;
   }
 
   public final class VmsAvailableLayers implements android.os.Parcelable {
-    ctor public VmsAvailableLayers(@NonNull java.util.Set<android.car.vms.VmsAssociatedLayer>, int);
+    ctor @Deprecated public VmsAvailableLayers(@NonNull java.util.Set<android.car.vms.VmsAssociatedLayer>, int);
+    ctor public VmsAvailableLayers(int, @NonNull java.util.Set<android.car.vms.VmsAssociatedLayer>);
     method public int describeContents();
     method @NonNull public java.util.Set<android.car.vms.VmsAssociatedLayer> getAssociatedLayers();
-    method public int getSequence();
-    method public void writeToParcel(android.os.Parcel, int);
-    field public static final android.os.Parcelable.Creator<android.car.vms.VmsAvailableLayers> CREATOR;
+    method @Deprecated public int getSequence();
+    method public int getSequenceNumber();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.car.vms.VmsAvailableLayers> CREATOR;
+  }
+
+  public final class VmsClient {
+    method @NonNull @RequiresPermission(anyOf={android.car.Car.PERMISSION_VMS_PUBLISHER, android.car.Car.PERMISSION_VMS_SUBSCRIBER}) public android.car.vms.VmsAvailableLayers getAvailableLayers();
+    method @Nullable @RequiresPermission(anyOf={android.car.Car.PERMISSION_VMS_PUBLISHER, android.car.Car.PERMISSION_VMS_SUBSCRIBER}) public byte[] getProviderDescription(int);
+    method @NonNull @RequiresPermission(anyOf={android.car.Car.PERMISSION_VMS_PUBLISHER, android.car.Car.PERMISSION_VMS_SUBSCRIBER}) public android.car.vms.VmsSubscriptionState getSubscriptionState();
+    method @RequiresPermission(android.car.Car.PERMISSION_VMS_SUBSCRIBER) public boolean isMonitoringEnabled();
+    method @RequiresPermission(android.car.Car.PERMISSION_VMS_PUBLISHER) public void publishPacket(int, @NonNull android.car.vms.VmsLayer, @NonNull byte[]);
+    method @RequiresPermission(android.car.Car.PERMISSION_VMS_PUBLISHER) public int registerProvider(@NonNull byte[]);
+    method @RequiresPermission(android.car.Car.PERMISSION_VMS_SUBSCRIBER) public void setMonitoringEnabled(boolean);
+    method @RequiresPermission(android.car.Car.PERMISSION_VMS_PUBLISHER) public void setProviderOfferings(int, @NonNull java.util.Set<android.car.vms.VmsLayerDependency>);
+    method @RequiresPermission(android.car.Car.PERMISSION_VMS_SUBSCRIBER) public void setSubscriptions(@NonNull java.util.Set<android.car.vms.VmsAssociatedLayer>);
+    method @RequiresPermission(android.car.Car.PERMISSION_VMS_PUBLISHER) public void unregisterProvider(int);
+  }
+
+  public final class VmsClientManager {
+    method @RequiresPermission(anyOf={android.car.Car.PERMISSION_VMS_PUBLISHER, android.car.Car.PERMISSION_VMS_SUBSCRIBER}) public void registerVmsClientCallback(@NonNull java.util.concurrent.Executor, @NonNull android.car.vms.VmsClientManager.VmsClientCallback);
+    method @RequiresPermission(anyOf={android.car.Car.PERMISSION_VMS_PUBLISHER, android.car.Car.PERMISSION_VMS_SUBSCRIBER}) public void unregisterVmsClientCallback(@NonNull android.car.vms.VmsClientManager.VmsClientCallback);
+  }
+
+  public static interface VmsClientManager.VmsClientCallback {
+    method public void onClientConnected(@NonNull android.car.vms.VmsClient);
+    method public void onLayerAvailabilityChanged(@NonNull android.car.vms.VmsAvailableLayers);
+    method public void onPacketReceived(int, @NonNull android.car.vms.VmsLayer, @NonNull byte[]);
+    method public void onSubscriptionStateChanged(@NonNull android.car.vms.VmsSubscriptionState);
   }
 
   public final class VmsLayer implements android.os.Parcelable {
     ctor public VmsLayer(int, int, int);
     method public int describeContents();
-    method public int getSubtype();
+    method public int getChannel();
+    method @Deprecated public int getSubtype();
     method public int getType();
     method public int getVersion();
-    method public void writeToParcel(android.os.Parcel, int);
-    field public static final android.os.Parcelable.Creator<android.car.vms.VmsLayer> CREATOR;
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.car.vms.VmsLayer> CREATOR;
   }
 
   public final class VmsLayerDependency implements android.os.Parcelable {
-    ctor public VmsLayerDependency(@NonNull android.car.vms.VmsLayer, @NonNull java.util.Set<android.car.vms.VmsLayer>);
     ctor public VmsLayerDependency(@NonNull android.car.vms.VmsLayer);
+    ctor public VmsLayerDependency(@NonNull android.car.vms.VmsLayer, @NonNull java.util.Set<android.car.vms.VmsLayer>);
     method public int describeContents();
     method @NonNull public java.util.Set<android.car.vms.VmsLayer> getDependencies();
     method @NonNull public android.car.vms.VmsLayer getLayer();
-    method public void writeToParcel(android.os.Parcel, int);
-    field public static final android.os.Parcelable.Creator<android.car.vms.VmsLayerDependency> CREATOR;
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.car.vms.VmsLayerDependency> CREATOR;
   }
 
-  public final class VmsLayersOffering implements android.os.Parcelable {
-    ctor public VmsLayersOffering(@NonNull java.util.Set<android.car.vms.VmsLayerDependency>, int);
-    method public int describeContents();
-    method public java.util.Set<android.car.vms.VmsLayerDependency> getDependencies();
-    method public int getPublisherId();
-    method public void writeToParcel(android.os.Parcel, int);
-    field public static final android.os.Parcelable.Creator<android.car.vms.VmsLayersOffering> CREATOR;
+  @Deprecated public final class VmsLayersOffering implements android.os.Parcelable {
+    ctor @Deprecated public VmsLayersOffering(@NonNull java.util.Set<android.car.vms.VmsLayerDependency>, int);
+    method @Deprecated public int describeContents();
+    method @Deprecated @NonNull public java.util.Set<android.car.vms.VmsLayerDependency> getDependencies();
+    method @Deprecated public int getPublisherId();
+    method @Deprecated public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @Deprecated @NonNull public static final android.os.Parcelable.Creator<android.car.vms.VmsLayersOffering> CREATOR;
   }
 
-  public final class VmsOperationRecorder {
-    method public void addHalSubscription(int, android.car.vms.VmsLayer);
-    method public void addPromiscuousSubscription(int);
-    method public void addSubscription(int, android.car.vms.VmsLayer);
-    method public static android.car.vms.VmsOperationRecorder get();
-    method public void getPublisherId(int);
-    method public void removeHalSubscription(int, android.car.vms.VmsLayer);
-    method public void removePromiscuousSubscription(int);
-    method public void removeSubscription(int, android.car.vms.VmsLayer);
-    method public void setHalPublisherLayersOffering(android.car.vms.VmsLayersOffering);
-    method public void setLayersOffering(android.car.vms.VmsLayersOffering);
-    method public void setPublisherLayersOffering(android.car.vms.VmsLayersOffering);
-    method public void startMonitoring();
-    method public void stopMonitoring();
-    method public void subscribe(android.car.vms.VmsLayer);
-    method public void subscribe(android.car.vms.VmsLayer, int);
-    method public void unsubscribe(android.car.vms.VmsLayer);
-    method public void unsubscribe(android.car.vms.VmsLayer, int);
+  @Deprecated public final class VmsOperationRecorder {
+    method @Deprecated public void addHalSubscription(int, android.car.vms.VmsLayer);
+    method @Deprecated public void addPromiscuousSubscription(int);
+    method @Deprecated public void addSubscription(int, android.car.vms.VmsLayer);
+    method @Deprecated public static android.car.vms.VmsOperationRecorder get();
+    method @Deprecated public void getPublisherId(int);
+    method @Deprecated public void removeHalSubscription(int, android.car.vms.VmsLayer);
+    method @Deprecated public void removePromiscuousSubscription(int);
+    method @Deprecated public void removeSubscription(int, android.car.vms.VmsLayer);
+    method @Deprecated public void setHalPublisherLayersOffering(android.car.vms.VmsLayersOffering);
+    method @Deprecated public void setLayersOffering(android.car.vms.VmsLayersOffering);
+    method @Deprecated public void setPublisherLayersOffering(android.car.vms.VmsLayersOffering);
+    method @Deprecated public void startMonitoring();
+    method @Deprecated public void stopMonitoring();
+    method @Deprecated public void subscribe(android.car.vms.VmsLayer);
+    method @Deprecated public void subscribe(android.car.vms.VmsLayer, int);
+    method @Deprecated public void unsubscribe(android.car.vms.VmsLayer);
+    method @Deprecated public void unsubscribe(android.car.vms.VmsLayer, int);
   }
 
-  public abstract class VmsPublisherClientService extends android.app.Service {
-    ctor public VmsPublisherClientService();
-    method public final int getPublisherId(byte[]);
-    method public final android.car.vms.VmsSubscriptionState getSubscriptions();
-    method public android.os.IBinder onBind(android.content.Intent);
-    method protected abstract void onVmsPublisherServiceReady();
-    method public abstract void onVmsSubscriptionChange(@NonNull android.car.vms.VmsSubscriptionState);
-    method public final void publish(@NonNull android.car.vms.VmsLayer, int, byte[]);
-    method public final void setLayersOffering(@NonNull android.car.vms.VmsLayersOffering);
+  @Deprecated public abstract class VmsPublisherClientService extends android.app.Service {
+    ctor @Deprecated public VmsPublisherClientService();
+    method @Deprecated public final int getPublisherId(byte[]);
+    method @Deprecated public final android.car.vms.VmsSubscriptionState getSubscriptions();
+    method @Deprecated public android.os.IBinder onBind(android.content.Intent);
+    method @Deprecated protected abstract void onVmsPublisherServiceReady();
+    method @Deprecated public abstract void onVmsSubscriptionChange(@NonNull android.car.vms.VmsSubscriptionState);
+    method @Deprecated public final void publish(@NonNull android.car.vms.VmsLayer, int, byte[]);
+    method @Deprecated public final void setLayersOffering(@NonNull android.car.vms.VmsLayersOffering);
   }
 
-  public final class VmsSubscriberManager {
-    method public void clearVmsSubscriberClientCallback();
-    method @NonNull public android.car.vms.VmsAvailableLayers getAvailableLayers();
-    method @NonNull public byte[] getPublisherInfo(int);
-    method public void setVmsSubscriberClientCallback(@NonNull java.util.concurrent.Executor, @NonNull android.car.vms.VmsSubscriberManager.VmsSubscriberClientCallback);
-    method public void startMonitoring();
-    method public void stopMonitoring();
-    method public void subscribe(@NonNull android.car.vms.VmsLayer);
-    method public void subscribe(@NonNull android.car.vms.VmsLayer, int);
-    method public void unsubscribe(@NonNull android.car.vms.VmsLayer);
-    method public void unsubscribe(@NonNull android.car.vms.VmsLayer, int);
+  @Deprecated public final class VmsSubscriberManager {
+    method @Deprecated public void clearVmsSubscriberClientCallback();
+    method @Deprecated @NonNull public android.car.vms.VmsAvailableLayers getAvailableLayers();
+    method @Deprecated @NonNull public byte[] getPublisherInfo(int);
+    method @Deprecated public void setVmsSubscriberClientCallback(@NonNull java.util.concurrent.Executor, @NonNull android.car.vms.VmsSubscriberManager.VmsSubscriberClientCallback);
+    method @Deprecated public void startMonitoring();
+    method @Deprecated public void stopMonitoring();
+    method @Deprecated public void subscribe(@NonNull android.car.vms.VmsLayer);
+    method @Deprecated public void subscribe(@NonNull android.car.vms.VmsLayer, int);
+    method @Deprecated public void unsubscribe(@NonNull android.car.vms.VmsLayer);
+    method @Deprecated public void unsubscribe(@NonNull android.car.vms.VmsLayer, int);
   }
 
-  public static interface VmsSubscriberManager.VmsSubscriberClientCallback {
-    method public void onLayersAvailabilityChanged(@NonNull android.car.vms.VmsAvailableLayers);
-    method public void onVmsMessageReceived(@NonNull android.car.vms.VmsLayer, byte[]);
+  @Deprecated public static interface VmsSubscriberManager.VmsSubscriberClientCallback {
+    method @Deprecated public void onLayersAvailabilityChanged(@NonNull android.car.vms.VmsAvailableLayers);
+    method @Deprecated public void onVmsMessageReceived(@NonNull android.car.vms.VmsLayer, byte[]);
   }
 
   public final class VmsSubscriptionState implements android.os.Parcelable {
@@ -1135,8 +1385,27 @@
     method @NonNull public java.util.Set<android.car.vms.VmsAssociatedLayer> getAssociatedLayers();
     method @NonNull public java.util.Set<android.car.vms.VmsLayer> getLayers();
     method public int getSequenceNumber();
-    method public void writeToParcel(android.os.Parcel, int);
-    field public static final android.os.Parcelable.Creator<android.car.vms.VmsSubscriptionState> CREATOR;
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.car.vms.VmsSubscriptionState> CREATOR;
+  }
+
+}
+
+package android.car.watchdog {
+
+  public final class CarWatchdogManager {
+    method @RequiresPermission(android.car.Car.PERMISSION_USE_CAR_WATCHDOG) public void registerClient(@NonNull java.util.concurrent.Executor, @NonNull android.car.watchdog.CarWatchdogManager.CarWatchdogClientCallback, int);
+    method @RequiresPermission(android.car.Car.PERMISSION_USE_CAR_WATCHDOG) public void tellClientAlive(@NonNull android.car.watchdog.CarWatchdogManager.CarWatchdogClientCallback, int);
+    method @RequiresPermission(android.car.Car.PERMISSION_USE_CAR_WATCHDOG) public void unregisterClient(@NonNull android.car.watchdog.CarWatchdogManager.CarWatchdogClientCallback);
+    field public static final int TIMEOUT_CRITICAL = 0; // 0x0
+    field public static final int TIMEOUT_MODERATE = 1; // 0x1
+    field public static final int TIMEOUT_NORMAL = 2; // 0x2
+  }
+
+  public abstract class CarWatchdogManager.CarWatchdogClientCallback {
+    ctor public CarWatchdogManager.CarWatchdogClientCallback();
+    method public boolean onCheckHealthStatus(int, int);
+    method public void onPrepareProcessTermination();
   }
 
 }
diff --git a/car-lib/api/system-lint-baseline.txt b/car-lib/api/system-lint-baseline.txt
new file mode 100644
index 0000000..0f151f2
--- /dev/null
+++ b/car-lib/api/system-lint-baseline.txt
@@ -0,0 +1,773 @@
+// Baseline format: 1.0
+AllUpper: android.car.diagnostic.CarDiagnosticEvent.SparkIgnitionMonitors#EGR:
+    Constant EGR must be marked static final
+
+
+ArrayReturn: android.car.content.pm.AppBlockingPackageInfo#AppBlockingPackageInfo(String, int, int, int, android.content.pm.Signature[], String[]) parameter #4:
+    Method parameter should be Collection<Signature> (or subclass) instead of raw array; was `android.content.pm.Signature[]`
+ArrayReturn: android.car.content.pm.AppBlockingPackageInfo#signatures:
+    Field should be Collection<Signature> (or subclass) instead of raw array; was `android.content.pm.Signature[]`
+ArrayReturn: android.car.content.pm.CarAppBlockingPolicy#CarAppBlockingPolicy(android.car.content.pm.AppBlockingPackageInfo[], android.car.content.pm.AppBlockingPackageInfo[]) parameter #0:
+    Method parameter should be Collection<AppBlockingPackageInfo> (or subclass) instead of raw array; was `android.car.content.pm.AppBlockingPackageInfo[]`
+ArrayReturn: android.car.content.pm.CarAppBlockingPolicy#CarAppBlockingPolicy(android.car.content.pm.AppBlockingPackageInfo[], android.car.content.pm.AppBlockingPackageInfo[]) parameter #1:
+    Method parameter should be Collection<AppBlockingPackageInfo> (or subclass) instead of raw array; was `android.car.content.pm.AppBlockingPackageInfo[]`
+ArrayReturn: android.car.content.pm.CarAppBlockingPolicy#blacklists:
+    Field should be Collection<AppBlockingPackageInfo> (or subclass) instead of raw array; was `android.car.content.pm.AppBlockingPackageInfo[]`
+ArrayReturn: android.car.content.pm.CarAppBlockingPolicy#whitelists:
+    Field should be Collection<AppBlockingPackageInfo> (or subclass) instead of raw array; was `android.car.content.pm.AppBlockingPackageInfo[]`
+ArrayReturn: android.car.input.CarInputHandlingService#CarInputHandlingService(android.car.input.CarInputHandlingService.InputFilter[]) parameter #0:
+    Method parameter should be Collection<InputFilter> (or subclass) instead of raw array; was `android.car.input.CarInputHandlingService.InputFilter[]`
+
+
+AutoBoxing: android.car.diagnostic.CarDiagnosticEvent#getFuelSystemStatus():
+    Must avoid boxed primitives (`java.lang.Integer`)
+AutoBoxing: android.car.diagnostic.CarDiagnosticEvent#getFuelType():
+    Must avoid boxed primitives (`java.lang.Integer`)
+AutoBoxing: android.car.diagnostic.CarDiagnosticEvent#getSecondaryAirStatus():
+    Must avoid boxed primitives (`java.lang.Integer`)
+AutoBoxing: android.car.diagnostic.CarDiagnosticEvent#getSystemFloatSensor(int):
+    Must avoid boxed primitives (`java.lang.Float`)
+AutoBoxing: android.car.diagnostic.CarDiagnosticEvent#getSystemIntegerSensor(int):
+    Must avoid boxed primitives (`java.lang.Integer`)
+AutoBoxing: android.car.diagnostic.CarDiagnosticEvent#getVendorFloatSensor(int):
+    Must avoid boxed primitives (`java.lang.Float`)
+AutoBoxing: android.car.diagnostic.CarDiagnosticEvent#getVendorIntegerSensor(int):
+    Must avoid boxed primitives (`java.lang.Integer`)
+
+
+CallbackInterface: android.car.hardware.CarVendorExtensionManager.CarVendorExtensionCallback:
+    Callbacks must be abstract class instead of interface to enable extension in future API levels: CarVendorExtensionCallback
+CallbackInterface: android.car.hardware.cabin.CarCabinManager.CarCabinEventCallback:
+    Callbacks must be abstract class instead of interface to enable extension in future API levels: CarCabinEventCallback
+CallbackInterface: android.car.hardware.hvac.CarHvacManager.CarHvacEventCallback:
+    Callbacks must be abstract class instead of interface to enable extension in future API levels: CarHvacEventCallback
+CallbackInterface: android.car.trust.CarTrustAgentEnrollmentManager.CarTrustAgentBleCallback:
+    Callbacks must be abstract class instead of interface to enable extension in future API levels: CarTrustAgentBleCallback
+CallbackInterface: android.car.trust.CarTrustAgentEnrollmentManager.CarTrustAgentEnrollmentCallback:
+    Callbacks must be abstract class instead of interface to enable extension in future API levels: CarTrustAgentEnrollmentCallback
+CallbackInterface: android.car.vms.VmsClientManager.VmsClientCallback:
+    Callbacks must be abstract class instead of interface to enable extension in future API levels: VmsClientCallback
+CallbackInterface: android.car.vms.VmsSubscriberManager.VmsSubscriberClientCallback:
+    Callbacks must be abstract class instead of interface to enable extension in future API levels: VmsSubscriberClientCallback
+
+
+ConcreteCollection: android.car.hardware.CarPropertyConfig.Builder#setConfigArray(java.util.ArrayList<java.lang.Integer>) parameter #0:
+    Parameter type is concrete collection (`java.util.ArrayList`); must be higher-level interface
+
+
+EqualsAndHashCode: android.car.storagemonitoring.LifetimeWriteInfo#equals(Object):
+    Must override both equals and hashCode; missing one in android.car.storagemonitoring.LifetimeWriteInfo
+EqualsAndHashCode: android.car.vms.VmsAvailableLayers#equals(Object):
+    Must override both equals and hashCode; missing one in android.car.vms.VmsAvailableLayers
+
+
+ExecutorRegistration: android.car.CarProjectionManager#registerProjectionListener(android.car.CarProjectionManager.CarProjectionListener, int):
+    Registration methods should have overload that accepts delivery Executor: `registerProjectionListener`
+ExecutorRegistration: android.car.CarProjectionManager#registerProjectionStatusListener(android.car.CarProjectionManager.ProjectionStatusListener):
+    Registration methods should have overload that accepts delivery Executor: `registerProjectionStatusListener`
+ExecutorRegistration: android.car.CarProjectionManager#startProjectionAccessPoint(android.car.CarProjectionManager.ProjectionAccessPointCallback):
+    Registration methods should have overload that accepts delivery Executor: `startProjectionAccessPoint`
+ExecutorRegistration: android.car.diagnostic.CarDiagnosticManager#registerListener(android.car.diagnostic.CarDiagnosticManager.OnDiagnosticEventListener, int, int):
+    Registration methods should have overload that accepts delivery Executor: `registerListener`
+ExecutorRegistration: android.car.drivingstate.CarDrivingStateManager#registerListener(android.car.drivingstate.CarDrivingStateManager.CarDrivingStateEventListener):
+    Registration methods should have overload that accepts delivery Executor: `registerListener`
+ExecutorRegistration: android.car.hardware.CarVendorExtensionManager#registerCallback(android.car.hardware.CarVendorExtensionManager.CarVendorExtensionCallback):
+    Registration methods should have overload that accepts delivery Executor: `registerCallback`
+ExecutorRegistration: android.car.hardware.cabin.CarCabinManager#registerCallback(android.car.hardware.cabin.CarCabinManager.CarCabinEventCallback):
+    Registration methods should have overload that accepts delivery Executor: `registerCallback`
+ExecutorRegistration: android.car.hardware.hvac.CarHvacManager#registerCallback(android.car.hardware.hvac.CarHvacManager.CarHvacEventCallback):
+    Registration methods should have overload that accepts delivery Executor: `registerCallback`
+ExecutorRegistration: android.car.storagemonitoring.CarStorageMonitoringManager#registerListener(android.car.storagemonitoring.CarStorageMonitoringManager.IoStatsListener):
+    Registration methods should have overload that accepts delivery Executor: `registerListener`
+ExecutorRegistration: android.car.trust.CarTrustAgentEnrollmentManager#setBleCallback(android.car.trust.CarTrustAgentEnrollmentManager.CarTrustAgentBleCallback):
+    Registration methods should have overload that accepts delivery Executor: `setBleCallback`
+ExecutorRegistration: android.car.trust.CarTrustAgentEnrollmentManager#setEnrollmentCallback(android.car.trust.CarTrustAgentEnrollmentManager.CarTrustAgentEnrollmentCallback):
+    Registration methods should have overload that accepts delivery Executor: `setEnrollmentCallback`
+
+
+HiddenSuperclass: android.car.CarAppFocusManager:
+    Public class android.car.CarAppFocusManager stripped of unavailable superclass android.car.CarManagerBase
+HiddenSuperclass: android.car.CarInfoManager:
+    Public class android.car.CarInfoManager stripped of unavailable superclass android.car.CarManagerBase
+HiddenSuperclass: android.car.CarOccupantZoneManager:
+    Public class android.car.CarOccupantZoneManager stripped of unavailable superclass android.car.CarManagerBase
+HiddenSuperclass: android.car.CarProjectionManager:
+    Public class android.car.CarProjectionManager stripped of unavailable superclass android.car.CarManagerBase
+HiddenSuperclass: android.car.cluster.CarInstrumentClusterManager:
+    Public class android.car.cluster.CarInstrumentClusterManager stripped of unavailable superclass android.car.CarManagerBase
+HiddenSuperclass: android.car.content.pm.CarPackageManager:
+    Public class android.car.content.pm.CarPackageManager stripped of unavailable superclass android.car.CarManagerBase
+HiddenSuperclass: android.car.diagnostic.CarDiagnosticManager:
+    Public class android.car.diagnostic.CarDiagnosticManager stripped of unavailable superclass android.car.CarManagerBase
+HiddenSuperclass: android.car.drivingstate.CarDrivingStateManager:
+    Public class android.car.drivingstate.CarDrivingStateManager stripped of unavailable superclass android.car.CarManagerBase
+HiddenSuperclass: android.car.drivingstate.CarUxRestrictionsManager:
+    Public class android.car.drivingstate.CarUxRestrictionsManager stripped of unavailable superclass android.car.CarManagerBase
+HiddenSuperclass: android.car.hardware.CarSensorManager:
+    Public class android.car.hardware.CarSensorManager stripped of unavailable superclass android.car.CarManagerBase
+HiddenSuperclass: android.car.hardware.CarVendorExtensionManager:
+    Public class android.car.hardware.CarVendorExtensionManager stripped of unavailable superclass android.car.CarManagerBase
+HiddenSuperclass: android.car.hardware.cabin.CarCabinManager:
+    Public class android.car.hardware.cabin.CarCabinManager stripped of unavailable superclass android.car.CarManagerBase
+HiddenSuperclass: android.car.hardware.hvac.CarHvacManager:
+    Public class android.car.hardware.hvac.CarHvacManager stripped of unavailable superclass android.car.CarManagerBase
+HiddenSuperclass: android.car.hardware.power.CarPowerManager:
+    Public class android.car.hardware.power.CarPowerManager stripped of unavailable superclass android.car.CarManagerBase
+HiddenSuperclass: android.car.hardware.property.CarPropertyManager:
+    Public class android.car.hardware.property.CarPropertyManager stripped of unavailable superclass android.car.CarManagerBase
+HiddenSuperclass: android.car.media.CarAudioManager:
+    Public class android.car.media.CarAudioManager stripped of unavailable superclass android.car.CarManagerBase
+HiddenSuperclass: android.car.navigation.CarNavigationStatusManager:
+    Public class android.car.navigation.CarNavigationStatusManager stripped of unavailable superclass android.car.CarManagerBase
+HiddenSuperclass: android.car.settings.CarConfigurationManager:
+    Public class android.car.settings.CarConfigurationManager stripped of unavailable superclass android.car.CarManagerBase
+HiddenSuperclass: android.car.storagemonitoring.CarStorageMonitoringManager:
+    Public class android.car.storagemonitoring.CarStorageMonitoringManager stripped of unavailable superclass android.car.CarManagerBase
+HiddenSuperclass: android.car.trust.CarTrustAgentEnrollmentManager:
+    Public class android.car.trust.CarTrustAgentEnrollmentManager stripped of unavailable superclass android.car.CarManagerBase
+HiddenSuperclass: android.car.user.CarUserManager:
+    Public class android.car.user.CarUserManager stripped of unavailable superclass android.car.CarManagerBase
+HiddenSuperclass: android.car.vms.VmsClientManager:
+    Public class android.car.vms.VmsClientManager stripped of unavailable superclass android.car.CarManagerBase
+HiddenSuperclass: android.car.vms.VmsSubscriberManager:
+    Public class android.car.vms.VmsSubscriberManager stripped of unavailable superclass android.car.CarManagerBase
+
+
+IntentName: android.car.Car#CAR_EXTRA_CLUSTER_ACTIVITY_STATE:
+    Intent extra constant name must be EXTRA_FOO: CAR_EXTRA_CLUSTER_ACTIVITY_STATE
+IntentName: android.car.media.CarAudioManager#AUDIOFOCUS_EXTRA_RECEIVE_DUCKING_EVENTS:
+    Intent extra constant name must be EXTRA_FOO: AUDIOFOCUS_EXTRA_RECEIVE_DUCKING_EVENTS
+
+
+InternalField: android.car.input.CarInputHandlingService.InputFilter#mKeyCode:
+    Internal field mKeyCode must not be exposed
+InternalField: android.car.input.CarInputHandlingService.InputFilter#mTargetDisplay:
+    Internal field mTargetDisplay must not be exposed
+
+
+MinMaxConstant: android.car.diagnostic.IntegerSensorIndex#MAX_AIR_FLOW_RATE_FROM_MASS_AIR_FLOW_SENSOR:
+    If min/max could change in future, make them dynamic methods: android.car.diagnostic.IntegerSensorIndex#MAX_AIR_FLOW_RATE_FROM_MASS_AIR_FLOW_SENSOR
+MinMaxConstant: android.car.diagnostic.IntegerSensorIndex#MAX_FUEL_AIR_EQUIVALENCE_RATIO:
+    If min/max could change in future, make them dynamic methods: android.car.diagnostic.IntegerSensorIndex#MAX_FUEL_AIR_EQUIVALENCE_RATIO
+MinMaxConstant: android.car.diagnostic.IntegerSensorIndex#MAX_INTAKE_MANIFOLD_ABSOLUTE_PRESSURE:
+    If min/max could change in future, make them dynamic methods: android.car.diagnostic.IntegerSensorIndex#MAX_INTAKE_MANIFOLD_ABSOLUTE_PRESSURE
+MinMaxConstant: android.car.diagnostic.IntegerSensorIndex#MAX_OXYGEN_SENSOR_CURRENT:
+    If min/max could change in future, make them dynamic methods: android.car.diagnostic.IntegerSensorIndex#MAX_OXYGEN_SENSOR_CURRENT
+MinMaxConstant: android.car.diagnostic.IntegerSensorIndex#MAX_OXYGEN_SENSOR_VOLTAGE:
+    If min/max could change in future, make them dynamic methods: android.car.diagnostic.IntegerSensorIndex#MAX_OXYGEN_SENSOR_VOLTAGE
+
+
+MissingNullability: android.car.AoapService#dump(java.io.FileDescriptor, java.io.PrintWriter, String[]) parameter #0:
+    Missing nullability on parameter `fd` in method `dump`
+MissingNullability: android.car.AoapService#dump(java.io.FileDescriptor, java.io.PrintWriter, String[]) parameter #1:
+    Missing nullability on parameter `writer` in method `dump`
+MissingNullability: android.car.AoapService#dump(java.io.FileDescriptor, java.io.PrintWriter, String[]) parameter #2:
+    Missing nullability on parameter `args` in method `dump`
+MissingNullability: android.car.AoapService#onBind(android.content.Intent):
+    Missing nullability on method `onBind` return
+MissingNullability: android.car.AoapService#onBind(android.content.Intent) parameter #0:
+    Missing nullability on parameter `intent` in method `onBind`
+MissingNullability: android.car.AoapService#onUnbind(android.content.Intent) parameter #0:
+    Missing nullability on parameter `intent` in method `onUnbind`
+MissingNullability: android.car.CarProjectionManager.ProjectionAccessPointCallback#onStarted(android.net.wifi.WifiConfiguration) parameter #0:
+    Missing nullability on parameter `wifiConfiguration` in method `onStarted`
+MissingNullability: android.car.cluster.CarInstrumentClusterManager#registerCallback(String, android.car.cluster.CarInstrumentClusterManager.Callback) parameter #0:
+    Missing nullability on parameter `category` in method `registerCallback`
+MissingNullability: android.car.cluster.CarInstrumentClusterManager#registerCallback(String, android.car.cluster.CarInstrumentClusterManager.Callback) parameter #1:
+    Missing nullability on parameter `callback` in method `registerCallback`
+MissingNullability: android.car.cluster.CarInstrumentClusterManager#startActivity(android.content.Intent) parameter #0:
+    Missing nullability on parameter `intent` in method `startActivity`
+MissingNullability: android.car.cluster.CarInstrumentClusterManager#unregisterCallback(android.car.cluster.CarInstrumentClusterManager.Callback) parameter #0:
+    Missing nullability on parameter `callback` in method `unregisterCallback`
+MissingNullability: android.car.cluster.CarInstrumentClusterManager.Callback#onClusterActivityStateChanged(String, android.os.Bundle) parameter #0:
+    Missing nullability on parameter `category` in method `onClusterActivityStateChanged`
+MissingNullability: android.car.cluster.CarInstrumentClusterManager.Callback#onClusterActivityStateChanged(String, android.os.Bundle) parameter #1:
+    Missing nullability on parameter `clusterActivityState` in method `onClusterActivityStateChanged`
+MissingNullability: android.car.cluster.renderer.InstrumentClusterRenderer#createNavigationRenderer():
+    Missing nullability on method `createNavigationRenderer` return
+MissingNullability: android.car.cluster.renderer.InstrumentClusterRenderer#onCreate(android.content.Context) parameter #0:
+    Missing nullability on parameter `context` in method `onCreate`
+MissingNullability: android.car.cluster.renderer.InstrumentClusterRenderingService#dump(java.io.FileDescriptor, java.io.PrintWriter, String[]) parameter #0:
+    Missing nullability on parameter `fd` in method `dump`
+MissingNullability: android.car.cluster.renderer.InstrumentClusterRenderingService#dump(java.io.FileDescriptor, java.io.PrintWriter, String[]) parameter #1:
+    Missing nullability on parameter `writer` in method `dump`
+MissingNullability: android.car.cluster.renderer.InstrumentClusterRenderingService#dump(java.io.FileDescriptor, java.io.PrintWriter, String[]) parameter #2:
+    Missing nullability on parameter `args` in method `dump`
+MissingNullability: android.car.cluster.renderer.InstrumentClusterRenderingService#getBitmap(android.net.Uri) parameter #0:
+    Missing nullability on parameter `uri` in method `getBitmap`
+MissingNullability: android.car.cluster.renderer.InstrumentClusterRenderingService#getBitmap(android.net.Uri, int, int) parameter #0:
+    Missing nullability on parameter `uri` in method `getBitmap`
+MissingNullability: android.car.cluster.renderer.InstrumentClusterRenderingService#onBind(android.content.Intent):
+    Missing nullability on method `onBind` return
+MissingNullability: android.car.cluster.renderer.InstrumentClusterRenderingService#onBind(android.content.Intent) parameter #0:
+    Missing nullability on parameter `intent` in method `onBind`
+MissingNullability: android.car.cluster.renderer.NavigationRenderer#getNavigationProperties():
+    Missing nullability on method `getNavigationProperties` return
+MissingNullability: android.car.content.pm.AppBlockingPackageInfo#AppBlockingPackageInfo(String, int, int, int, android.content.pm.Signature[], String[]) parameter #0:
+    Missing nullability on parameter `packageName` in method `AppBlockingPackageInfo`
+MissingNullability: android.car.content.pm.AppBlockingPackageInfo#AppBlockingPackageInfo(android.os.Parcel) parameter #0:
+    Missing nullability on parameter `in` in method `AppBlockingPackageInfo`
+MissingNullability: android.car.content.pm.AppBlockingPackageInfo#CREATOR:
+    Missing nullability on field `CREATOR` in class `class android.car.content.pm.AppBlockingPackageInfo`
+MissingNullability: android.car.content.pm.AppBlockingPackageInfo#activities:
+    Missing nullability on field `activities` in class `class android.car.content.pm.AppBlockingPackageInfo`
+MissingNullability: android.car.content.pm.AppBlockingPackageInfo#packageName:
+    Missing nullability on field `packageName` in class `class android.car.content.pm.AppBlockingPackageInfo`
+MissingNullability: android.car.content.pm.AppBlockingPackageInfo#signatures:
+    Missing nullability on field `signatures` in class `class android.car.content.pm.AppBlockingPackageInfo`
+MissingNullability: android.car.content.pm.AppBlockingPackageInfo#writeToParcel(android.os.Parcel, int) parameter #0:
+    Missing nullability on parameter `dest` in method `writeToParcel`
+MissingNullability: android.car.content.pm.CarAppBlockingPolicy#CREATOR:
+    Missing nullability on field `CREATOR` in class `class android.car.content.pm.CarAppBlockingPolicy`
+MissingNullability: android.car.content.pm.CarAppBlockingPolicy#CarAppBlockingPolicy(android.car.content.pm.AppBlockingPackageInfo[], android.car.content.pm.AppBlockingPackageInfo[]) parameter #0:
+    Missing nullability on parameter `whitelists` in method `CarAppBlockingPolicy`
+MissingNullability: android.car.content.pm.CarAppBlockingPolicy#CarAppBlockingPolicy(android.car.content.pm.AppBlockingPackageInfo[], android.car.content.pm.AppBlockingPackageInfo[]) parameter #1:
+    Missing nullability on parameter `blacklists` in method `CarAppBlockingPolicy`
+MissingNullability: android.car.content.pm.CarAppBlockingPolicy#CarAppBlockingPolicy(android.os.Parcel) parameter #0:
+    Missing nullability on parameter `in` in method `CarAppBlockingPolicy`
+MissingNullability: android.car.content.pm.CarAppBlockingPolicy#blacklists:
+    Missing nullability on field `blacklists` in class `class android.car.content.pm.CarAppBlockingPolicy`
+MissingNullability: android.car.content.pm.CarAppBlockingPolicy#whitelists:
+    Missing nullability on field `whitelists` in class `class android.car.content.pm.CarAppBlockingPolicy`
+MissingNullability: android.car.content.pm.CarAppBlockingPolicy#writeToParcel(android.os.Parcel, int) parameter #0:
+    Missing nullability on parameter `dest` in method `writeToParcel`
+MissingNullability: android.car.content.pm.CarAppBlockingPolicyService#getAppBlockingPolicy():
+    Missing nullability on method `getAppBlockingPolicy` return
+MissingNullability: android.car.content.pm.CarAppBlockingPolicyService#onBind(android.content.Intent):
+    Missing nullability on method `onBind` return
+MissingNullability: android.car.content.pm.CarAppBlockingPolicyService#onBind(android.content.Intent) parameter #0:
+    Missing nullability on parameter `intent` in method `onBind`
+MissingNullability: android.car.content.pm.CarAppBlockingPolicyService#onStartCommand(android.content.Intent, int, int) parameter #0:
+    Missing nullability on parameter `intent` in method `onStartCommand`
+MissingNullability: android.car.content.pm.CarAppBlockingPolicyService#onUnbind(android.content.Intent) parameter #0:
+    Missing nullability on parameter `intent` in method `onUnbind`
+MissingNullability: android.car.content.pm.CarPackageManager#isActivityBackedBySafeActivity(android.content.ComponentName) parameter #0:
+    Missing nullability on parameter `activityName` in method `isActivityBackedBySafeActivity`
+MissingNullability: android.car.content.pm.CarPackageManager#setAppBlockingPolicy(String, android.car.content.pm.CarAppBlockingPolicy, int) parameter #0:
+    Missing nullability on parameter `packageName` in method `setAppBlockingPolicy`
+MissingNullability: android.car.content.pm.CarPackageManager#setAppBlockingPolicy(String, android.car.content.pm.CarAppBlockingPolicy, int) parameter #1:
+    Missing nullability on parameter `policy` in method `setAppBlockingPolicy`
+MissingNullability: android.car.diagnostic.CarDiagnosticEvent#CREATOR:
+    Missing nullability on field `CREATOR` in class `class android.car.diagnostic.CarDiagnosticEvent`
+MissingNullability: android.car.diagnostic.CarDiagnosticEvent#CarDiagnosticEvent(android.os.Parcel) parameter #0:
+    Missing nullability on parameter `in` in method `CarDiagnosticEvent`
+MissingNullability: android.car.diagnostic.CarDiagnosticEvent#dtc:
+    Missing nullability on field `dtc` in class `class android.car.diagnostic.CarDiagnosticEvent`
+MissingNullability: android.car.diagnostic.CarDiagnosticEvent#writeToJson(android.util.JsonWriter) parameter #0:
+    Missing nullability on parameter `jsonWriter` in method `writeToJson`
+MissingNullability: android.car.diagnostic.CarDiagnosticEvent#writeToParcel(android.os.Parcel, int) parameter #0:
+    Missing nullability on parameter `dest` in method `writeToParcel`
+MissingNullability: android.car.diagnostic.CarDiagnosticEvent.Builder#build():
+    Missing nullability on method `build` return
+MissingNullability: android.car.diagnostic.CarDiagnosticEvent.Builder#newFreezeFrameBuilder():
+    Missing nullability on method `newFreezeFrameBuilder` return
+MissingNullability: android.car.diagnostic.CarDiagnosticEvent.Builder#newLiveFrameBuilder():
+    Missing nullability on method `newLiveFrameBuilder` return
+MissingNullability: android.car.diagnostic.CarDiagnosticEvent.Builder#setDtc(String):
+    Missing nullability on method `setDtc` return
+MissingNullability: android.car.diagnostic.CarDiagnosticEvent.Builder#setDtc(String) parameter #0:
+    Missing nullability on parameter `dtc` in method `setDtc`
+MissingNullability: android.car.diagnostic.CarDiagnosticEvent.Builder#setFloatValue(int, float):
+    Missing nullability on method `setFloatValue` return
+MissingNullability: android.car.diagnostic.CarDiagnosticEvent.Builder#setIntValue(int, int):
+    Missing nullability on method `setIntValue` return
+MissingNullability: android.car.diagnostic.CarDiagnosticEvent.Builder#setTimeStamp(long):
+    Missing nullability on method `setTimeStamp` return
+MissingNullability: android.car.diagnostic.CarDiagnosticEvent.Builder#withDtc(String) parameter #0:
+    Missing nullability on parameter `dtc` in method `withDtc`
+MissingNullability: android.car.diagnostic.CarDiagnosticEvent.CommonIgnitionMonitors#components:
+    Missing nullability on field `components` in class `class android.car.diagnostic.CarDiagnosticEvent.CommonIgnitionMonitors`
+MissingNullability: android.car.diagnostic.CarDiagnosticEvent.CommonIgnitionMonitors#fuelSystem:
+    Missing nullability on field `fuelSystem` in class `class android.car.diagnostic.CarDiagnosticEvent.CommonIgnitionMonitors`
+MissingNullability: android.car.diagnostic.CarDiagnosticEvent.CommonIgnitionMonitors#misfire:
+    Missing nullability on field `misfire` in class `class android.car.diagnostic.CarDiagnosticEvent.CommonIgnitionMonitors`
+MissingNullability: android.car.diagnostic.CarDiagnosticEvent.CompressionIgnitionMonitors#EGROrVVT:
+    Missing nullability on field `EGROrVVT` in class `class android.car.diagnostic.CarDiagnosticEvent.CompressionIgnitionMonitors`
+MissingNullability: android.car.diagnostic.CarDiagnosticEvent.CompressionIgnitionMonitors#NMHCCatalyst:
+    Missing nullability on field `NMHCCatalyst` in class `class android.car.diagnostic.CarDiagnosticEvent.CompressionIgnitionMonitors`
+MissingNullability: android.car.diagnostic.CarDiagnosticEvent.CompressionIgnitionMonitors#NOxSCR:
+    Missing nullability on field `NOxSCR` in class `class android.car.diagnostic.CarDiagnosticEvent.CompressionIgnitionMonitors`
+MissingNullability: android.car.diagnostic.CarDiagnosticEvent.CompressionIgnitionMonitors#PMFilter:
+    Missing nullability on field `PMFilter` in class `class android.car.diagnostic.CarDiagnosticEvent.CompressionIgnitionMonitors`
+MissingNullability: android.car.diagnostic.CarDiagnosticEvent.CompressionIgnitionMonitors#boostPressure:
+    Missing nullability on field `boostPressure` in class `class android.car.diagnostic.CarDiagnosticEvent.CompressionIgnitionMonitors`
+MissingNullability: android.car.diagnostic.CarDiagnosticEvent.CompressionIgnitionMonitors#exhaustGasSensor:
+    Missing nullability on field `exhaustGasSensor` in class `class android.car.diagnostic.CarDiagnosticEvent.CompressionIgnitionMonitors`
+MissingNullability: android.car.diagnostic.CarDiagnosticEvent.SparkIgnitionMonitors#ACRefrigerant:
+    Missing nullability on field `ACRefrigerant` in class `class android.car.diagnostic.CarDiagnosticEvent.SparkIgnitionMonitors`
+MissingNullability: android.car.diagnostic.CarDiagnosticEvent.SparkIgnitionMonitors#EGR:
+    Missing nullability on field `EGR` in class `class android.car.diagnostic.CarDiagnosticEvent.SparkIgnitionMonitors`
+MissingNullability: android.car.diagnostic.CarDiagnosticEvent.SparkIgnitionMonitors#catalyst:
+    Missing nullability on field `catalyst` in class `class android.car.diagnostic.CarDiagnosticEvent.SparkIgnitionMonitors`
+MissingNullability: android.car.diagnostic.CarDiagnosticEvent.SparkIgnitionMonitors#evaporativeSystem:
+    Missing nullability on field `evaporativeSystem` in class `class android.car.diagnostic.CarDiagnosticEvent.SparkIgnitionMonitors`
+MissingNullability: android.car.diagnostic.CarDiagnosticEvent.SparkIgnitionMonitors#heatedCatalyst:
+    Missing nullability on field `heatedCatalyst` in class `class android.car.diagnostic.CarDiagnosticEvent.SparkIgnitionMonitors`
+MissingNullability: android.car.diagnostic.CarDiagnosticEvent.SparkIgnitionMonitors#oxygenSensor:
+    Missing nullability on field `oxygenSensor` in class `class android.car.diagnostic.CarDiagnosticEvent.SparkIgnitionMonitors`
+MissingNullability: android.car.diagnostic.CarDiagnosticEvent.SparkIgnitionMonitors#oxygenSensorHeater:
+    Missing nullability on field `oxygenSensorHeater` in class `class android.car.diagnostic.CarDiagnosticEvent.SparkIgnitionMonitors`
+MissingNullability: android.car.diagnostic.CarDiagnosticEvent.SparkIgnitionMonitors#secondaryAirSystem:
+    Missing nullability on field `secondaryAirSystem` in class `class android.car.diagnostic.CarDiagnosticEvent.SparkIgnitionMonitors`
+MissingNullability: android.car.diagnostic.CarDiagnosticManager#clearFreezeFrames(long...) parameter #0:
+    Missing nullability on parameter `timestamps` in method `clearFreezeFrames`
+MissingNullability: android.car.diagnostic.CarDiagnosticManager#getFreezeFrameTimestamps():
+    Missing nullability on method `getFreezeFrameTimestamps` return
+MissingNullability: android.car.diagnostic.CarDiagnosticManager#registerListener(android.car.diagnostic.CarDiagnosticManager.OnDiagnosticEventListener, int, int) parameter #0:
+    Missing nullability on parameter `listener` in method `registerListener`
+MissingNullability: android.car.diagnostic.CarDiagnosticManager#unregisterListener(android.car.diagnostic.CarDiagnosticManager.OnDiagnosticEventListener) parameter #0:
+    Missing nullability on parameter `listener` in method `unregisterListener`
+MissingNullability: android.car.diagnostic.CarDiagnosticManager.OnDiagnosticEventListener#onDiagnosticEvent(android.car.diagnostic.CarDiagnosticEvent) parameter #0:
+    Missing nullability on parameter `carDiagnosticEvent` in method `onDiagnosticEvent`
+MissingNullability: android.car.drivingstate.CarDrivingStateEvent#CREATOR:
+    Missing nullability on field `CREATOR` in class `class android.car.drivingstate.CarDrivingStateEvent`
+MissingNullability: android.car.drivingstate.CarDrivingStateEvent#writeToParcel(android.os.Parcel, int) parameter #0:
+    Missing nullability on parameter `dest` in method `writeToParcel`
+MissingNullability: android.car.drivingstate.CarDrivingStateManager.CarDrivingStateEventListener#onDrivingStateChanged(android.car.drivingstate.CarDrivingStateEvent) parameter #0:
+    Missing nullability on parameter `event` in method `onDrivingStateChanged`
+MissingNullability: android.car.hardware.CarPropertyConfig#newBuilder(Class<T>, int, int, int):
+    Missing nullability on method `newBuilder` return
+MissingNullability: android.car.hardware.CarPropertyConfig#newBuilder(Class<T>, int, int, int) parameter #0:
+    Missing nullability on parameter `type` in method `newBuilder`
+MissingNullability: android.car.hardware.CarPropertyConfig.Builder#addArea(int):
+    Missing nullability on method `addArea` return
+MissingNullability: android.car.hardware.CarPropertyConfig.Builder#addAreaConfig(int, T, T):
+    Missing nullability on method `addAreaConfig` return
+MissingNullability: android.car.hardware.CarPropertyConfig.Builder#addAreas(int[]):
+    Missing nullability on method `addAreas` return
+MissingNullability: android.car.hardware.CarPropertyConfig.Builder#addAreas(int[]) parameter #0:
+    Missing nullability on parameter `areaIds` in method `addAreas`
+MissingNullability: android.car.hardware.CarPropertyConfig.Builder#build():
+    Missing nullability on method `build` return
+MissingNullability: android.car.hardware.CarPropertyConfig.Builder#setAccess(int):
+    Missing nullability on method `setAccess` return
+MissingNullability: android.car.hardware.CarPropertyConfig.Builder#setChangeMode(int):
+    Missing nullability on method `setChangeMode` return
+MissingNullability: android.car.hardware.CarPropertyConfig.Builder#setConfigArray(java.util.ArrayList<java.lang.Integer>):
+    Missing nullability on method `setConfigArray` return
+MissingNullability: android.car.hardware.CarPropertyConfig.Builder#setConfigArray(java.util.ArrayList<java.lang.Integer>) parameter #0:
+    Missing nullability on parameter `configArray` in method `setConfigArray`
+MissingNullability: android.car.hardware.CarPropertyConfig.Builder#setConfigString(String):
+    Missing nullability on method `setConfigString` return
+MissingNullability: android.car.hardware.CarPropertyConfig.Builder#setConfigString(String) parameter #0:
+    Missing nullability on parameter `configString` in method `setConfigString`
+MissingNullability: android.car.hardware.CarPropertyConfig.Builder#setMaxSampleRate(float):
+    Missing nullability on method `setMaxSampleRate` return
+MissingNullability: android.car.hardware.CarPropertyConfig.Builder#setMinSampleRate(float):
+    Missing nullability on method `setMinSampleRate` return
+MissingNullability: android.car.hardware.CarVendorExtensionManager#getGlobalProperty(Class<E>, int) parameter #0:
+    Missing nullability on parameter `propertyClass` in method `getGlobalProperty`
+MissingNullability: android.car.hardware.CarVendorExtensionManager#getProperties():
+    Missing nullability on method `getProperties` return
+MissingNullability: android.car.hardware.CarVendorExtensionManager#getProperty(Class<E>, int, int) parameter #0:
+    Missing nullability on parameter `propertyClass` in method `getProperty`
+MissingNullability: android.car.hardware.CarVendorExtensionManager#registerCallback(android.car.hardware.CarVendorExtensionManager.CarVendorExtensionCallback) parameter #0:
+    Missing nullability on parameter `callback` in method `registerCallback`
+MissingNullability: android.car.hardware.CarVendorExtensionManager#setGlobalProperty(Class<E>, int, E) parameter #0:
+    Missing nullability on parameter `propertyClass` in method `setGlobalProperty`
+MissingNullability: android.car.hardware.CarVendorExtensionManager#setProperty(Class<E>, int, int, E) parameter #0:
+    Missing nullability on parameter `propertyClass` in method `setProperty`
+MissingNullability: android.car.hardware.CarVendorExtensionManager#unregisterCallback(android.car.hardware.CarVendorExtensionManager.CarVendorExtensionCallback) parameter #0:
+    Missing nullability on parameter `callback` in method `unregisterCallback`
+MissingNullability: android.car.hardware.CarVendorExtensionManager.CarVendorExtensionCallback#onChangeEvent(android.car.hardware.CarPropertyValue) parameter #0:
+    Missing nullability on parameter `value` in method `onChangeEvent`
+MissingNullability: android.car.hardware.cabin.CarCabinManager#getPropertyList():
+    Missing nullability on method `getPropertyList` return
+MissingNullability: android.car.hardware.cabin.CarCabinManager#registerCallback(android.car.hardware.cabin.CarCabinManager.CarCabinEventCallback) parameter #0:
+    Missing nullability on parameter `callback` in method `registerCallback`
+MissingNullability: android.car.hardware.cabin.CarCabinManager#unregisterCallback(android.car.hardware.cabin.CarCabinManager.CarCabinEventCallback) parameter #0:
+    Missing nullability on parameter `callback` in method `unregisterCallback`
+MissingNullability: android.car.hardware.cabin.CarCabinManager.CarCabinEventCallback#onChangeEvent(android.car.hardware.CarPropertyValue) parameter #0:
+    Missing nullability on parameter `value` in method `onChangeEvent`
+MissingNullability: android.car.hardware.hvac.CarHvacManager#getPropertyList():
+    Missing nullability on method `getPropertyList` return
+MissingNullability: android.car.hardware.hvac.CarHvacManager#registerCallback(android.car.hardware.hvac.CarHvacManager.CarHvacEventCallback) parameter #0:
+    Missing nullability on parameter `callback` in method `registerCallback`
+MissingNullability: android.car.hardware.hvac.CarHvacManager#unregisterCallback(android.car.hardware.hvac.CarHvacManager.CarHvacEventCallback) parameter #0:
+    Missing nullability on parameter `callback` in method `unregisterCallback`
+MissingNullability: android.car.hardware.hvac.CarHvacManager.CarHvacEventCallback#onChangeEvent(android.car.hardware.CarPropertyValue) parameter #0:
+    Missing nullability on parameter `value` in method `onChangeEvent`
+MissingNullability: android.car.input.CarInputHandlingService#CarInputHandlingService(android.car.input.CarInputHandlingService.InputFilter[]) parameter #0:
+    Missing nullability on parameter `handledKeys` in method `CarInputHandlingService`
+MissingNullability: android.car.input.CarInputHandlingService#dump(java.io.FileDescriptor, java.io.PrintWriter, String[]) parameter #0:
+    Missing nullability on parameter `fd` in method `dump`
+MissingNullability: android.car.input.CarInputHandlingService#dump(java.io.FileDescriptor, java.io.PrintWriter, String[]) parameter #1:
+    Missing nullability on parameter `writer` in method `dump`
+MissingNullability: android.car.input.CarInputHandlingService#dump(java.io.FileDescriptor, java.io.PrintWriter, String[]) parameter #2:
+    Missing nullability on parameter `args` in method `dump`
+MissingNullability: android.car.input.CarInputHandlingService#onBind(android.content.Intent):
+    Missing nullability on method `onBind` return
+MissingNullability: android.car.input.CarInputHandlingService#onBind(android.content.Intent) parameter #0:
+    Missing nullability on parameter `intent` in method `onBind`
+MissingNullability: android.car.input.CarInputHandlingService#onKeyEvent(android.view.KeyEvent, int) parameter #0:
+    Missing nullability on parameter `keyEvent` in method `onKeyEvent`
+MissingNullability: android.car.input.CarInputHandlingService.InputFilter#CREATOR:
+    Missing nullability on field `CREATOR` in class `class android.car.input.CarInputHandlingService.InputFilter`
+MissingNullability: android.car.input.CarInputHandlingService.InputFilter#writeToParcel(android.os.Parcel, int) parameter #0:
+    Missing nullability on parameter `dest` in method `writeToParcel`
+MissingNullability: android.car.media.CarAudioManager#createAudioPatch(String, int, int):
+    Missing nullability on method `createAudioPatch` return
+MissingNullability: android.car.media.CarAudioManager#createAudioPatch(String, int, int) parameter #0:
+    Missing nullability on parameter `sourceAddress` in method `createAudioPatch`
+MissingNullability: android.car.media.CarAudioManager#releaseAudioPatch(android.car.media.CarAudioPatchHandle) parameter #0:
+    Missing nullability on parameter `patch` in method `releaseAudioPatch`
+MissingNullability: android.car.media.CarAudioPatchHandle#CREATOR:
+    Missing nullability on field `CREATOR` in class `class android.car.media.CarAudioPatchHandle`
+MissingNullability: android.car.media.CarAudioPatchHandle#writeToParcel(android.os.Parcel, int) parameter #0:
+    Missing nullability on parameter `out` in method `writeToParcel`
+MissingNullability: android.car.navigation.CarNavigationInstrumentCluster#CREATOR:
+    Missing nullability on field `CREATOR` in class `class android.car.navigation.CarNavigationInstrumentCluster`
+MissingNullability: android.car.navigation.CarNavigationInstrumentCluster#CarNavigationInstrumentCluster(android.car.navigation.CarNavigationInstrumentCluster) parameter #0:
+    Missing nullability on parameter `that` in method `CarNavigationInstrumentCluster`
+MissingNullability: android.car.navigation.CarNavigationInstrumentCluster#createCluster(int):
+    Missing nullability on method `createCluster` return
+MissingNullability: android.car.navigation.CarNavigationInstrumentCluster#createCustomImageCluster(int, int, int, int):
+    Missing nullability on method `createCustomImageCluster` return
+MissingNullability: android.car.navigation.CarNavigationInstrumentCluster#writeToParcel(android.os.Parcel, int) parameter #0:
+    Missing nullability on parameter `dest` in method `writeToParcel`
+MissingNullability: android.car.navigation.CarNavigationStatusManager#getInstrumentClusterInfo():
+    Missing nullability on method `getInstrumentClusterInfo` return
+MissingNullability: android.car.navigation.CarNavigationStatusManager#sendEvent(int, android.os.Bundle) parameter #1:
+    Missing nullability on parameter `bundle` in method `sendEvent`
+MissingNullability: android.car.navigation.CarNavigationStatusManager#sendNavigationStateChange(android.os.Bundle) parameter #0:
+    Missing nullability on parameter `bundle` in method `sendNavigationStateChange`
+MissingNullability: android.car.projection.ProjectionOptions#ProjectionOptions(android.os.Bundle) parameter #0:
+    Missing nullability on parameter `bundle` in method `ProjectionOptions`
+MissingNullability: android.car.projection.ProjectionStatus#CREATOR:
+    Missing nullability on field `CREATOR` in class `class android.car.projection.ProjectionStatus`
+MissingNullability: android.car.projection.ProjectionStatus#builder(String, int) parameter #0:
+    Missing nullability on parameter `packageName` in method `builder`
+MissingNullability: android.car.projection.ProjectionStatus#writeToParcel(android.os.Parcel, int) parameter #0:
+    Missing nullability on parameter `dest` in method `writeToParcel`
+MissingNullability: android.car.projection.ProjectionStatus.Builder#addMobileDevice(android.car.projection.ProjectionStatus.MobileDevice) parameter #0:
+    Missing nullability on parameter `mobileDevice` in method `addMobileDevice`
+MissingNullability: android.car.projection.ProjectionStatus.Builder#build():
+    Missing nullability on method `build` return
+MissingNullability: android.car.projection.ProjectionStatus.Builder#setExtras(android.os.Bundle) parameter #0:
+    Missing nullability on parameter `extras` in method `setExtras`
+MissingNullability: android.car.projection.ProjectionStatus.MobileDevice#CREATOR:
+    Missing nullability on field `CREATOR` in class `class android.car.projection.ProjectionStatus.MobileDevice`
+MissingNullability: android.car.projection.ProjectionStatus.MobileDevice#builder(int, String) parameter #1:
+    Missing nullability on parameter `name` in method `builder`
+MissingNullability: android.car.projection.ProjectionStatus.MobileDevice#writeToParcel(android.os.Parcel, int) parameter #0:
+    Missing nullability on parameter `dest` in method `writeToParcel`
+MissingNullability: android.car.projection.ProjectionStatus.MobileDevice.Builder#setExtras(android.os.Bundle) parameter #0:
+    Missing nullability on parameter `extras` in method `setExtras`
+MissingNullability: android.car.storagemonitoring.CarStorageMonitoringManager#getAggregateIoStats():
+    Missing nullability on method `getAggregateIoStats` return
+MissingNullability: android.car.storagemonitoring.CarStorageMonitoringManager#getBootIoStats():
+    Missing nullability on method `getBootIoStats` return
+MissingNullability: android.car.storagemonitoring.CarStorageMonitoringManager#getIoStatsDeltas():
+    Missing nullability on method `getIoStatsDeltas` return
+MissingNullability: android.car.storagemonitoring.CarStorageMonitoringManager#getWearEstimate():
+    Missing nullability on method `getWearEstimate` return
+MissingNullability: android.car.storagemonitoring.CarStorageMonitoringManager#getWearEstimateHistory():
+    Missing nullability on method `getWearEstimateHistory` return
+MissingNullability: android.car.storagemonitoring.CarStorageMonitoringManager#registerListener(android.car.storagemonitoring.CarStorageMonitoringManager.IoStatsListener) parameter #0:
+    Missing nullability on parameter `listener` in method `registerListener`
+MissingNullability: android.car.storagemonitoring.CarStorageMonitoringManager#unregisterListener(android.car.storagemonitoring.CarStorageMonitoringManager.IoStatsListener) parameter #0:
+    Missing nullability on parameter `listener` in method `unregisterListener`
+MissingNullability: android.car.storagemonitoring.CarStorageMonitoringManager.IoStatsListener#onSnapshot(android.car.storagemonitoring.IoStats) parameter #0:
+    Missing nullability on parameter `snapshot` in method `onSnapshot`
+MissingNullability: android.car.storagemonitoring.IoStats#CREATOR:
+    Missing nullability on field `CREATOR` in class `class android.car.storagemonitoring.IoStats`
+MissingNullability: android.car.storagemonitoring.IoStats#IoStats(android.os.Parcel) parameter #0:
+    Missing nullability on parameter `in` in method `IoStats`
+MissingNullability: android.car.storagemonitoring.IoStats#IoStats(java.util.List<android.car.storagemonitoring.IoStatsEntry>, long) parameter #0:
+    Missing nullability on parameter `stats` in method `IoStats`
+MissingNullability: android.car.storagemonitoring.IoStats#getBackgroundTotals():
+    Missing nullability on method `getBackgroundTotals` return
+MissingNullability: android.car.storagemonitoring.IoStats#getForegroundTotals():
+    Missing nullability on method `getForegroundTotals` return
+MissingNullability: android.car.storagemonitoring.IoStats#getStats():
+    Missing nullability on method `getStats` return
+MissingNullability: android.car.storagemonitoring.IoStats#getTotals():
+    Missing nullability on method `getTotals` return
+MissingNullability: android.car.storagemonitoring.IoStats#getUserIdStats(int):
+    Missing nullability on method `getUserIdStats` return
+MissingNullability: android.car.storagemonitoring.IoStats#writeToParcel(android.os.Parcel, int) parameter #0:
+    Missing nullability on parameter `dest` in method `writeToParcel`
+MissingNullability: android.car.storagemonitoring.IoStatsEntry#CREATOR:
+    Missing nullability on field `CREATOR` in class `class android.car.storagemonitoring.IoStatsEntry`
+MissingNullability: android.car.storagemonitoring.IoStatsEntry#IoStatsEntry(android.car.storagemonitoring.UidIoRecord, long) parameter #0:
+    Missing nullability on parameter `record` in method `IoStatsEntry`
+MissingNullability: android.car.storagemonitoring.IoStatsEntry#IoStatsEntry(android.os.Parcel) parameter #0:
+    Missing nullability on parameter `in` in method `IoStatsEntry`
+MissingNullability: android.car.storagemonitoring.IoStatsEntry#IoStatsEntry(int, long, android.car.storagemonitoring.IoStatsEntry.Metrics, android.car.storagemonitoring.IoStatsEntry.Metrics) parameter #2:
+    Missing nullability on parameter `foreground` in method `IoStatsEntry`
+MissingNullability: android.car.storagemonitoring.IoStatsEntry#IoStatsEntry(int, long, android.car.storagemonitoring.IoStatsEntry.Metrics, android.car.storagemonitoring.IoStatsEntry.Metrics) parameter #3:
+    Missing nullability on parameter `background` in method `IoStatsEntry`
+MissingNullability: android.car.storagemonitoring.IoStatsEntry#background:
+    Missing nullability on field `background` in class `class android.car.storagemonitoring.IoStatsEntry`
+MissingNullability: android.car.storagemonitoring.IoStatsEntry#foreground:
+    Missing nullability on field `foreground` in class `class android.car.storagemonitoring.IoStatsEntry`
+MissingNullability: android.car.storagemonitoring.IoStatsEntry#writeToParcel(android.os.Parcel, int) parameter #0:
+    Missing nullability on parameter `dest` in method `writeToParcel`
+MissingNullability: android.car.storagemonitoring.IoStatsEntry.Metrics#CREATOR:
+    Missing nullability on field `CREATOR` in class `class android.car.storagemonitoring.IoStatsEntry.Metrics`
+MissingNullability: android.car.storagemonitoring.IoStatsEntry.Metrics#Metrics(android.os.Parcel) parameter #0:
+    Missing nullability on parameter `in` in method `Metrics`
+MissingNullability: android.car.storagemonitoring.IoStatsEntry.Metrics#writeToParcel(android.os.Parcel, int) parameter #0:
+    Missing nullability on parameter `dest` in method `writeToParcel`
+MissingNullability: android.car.storagemonitoring.LifetimeWriteInfo#CREATOR:
+    Missing nullability on field `CREATOR` in class `class android.car.storagemonitoring.LifetimeWriteInfo`
+MissingNullability: android.car.storagemonitoring.LifetimeWriteInfo#LifetimeWriteInfo(String, String, long) parameter #0:
+    Missing nullability on parameter `partition` in method `LifetimeWriteInfo`
+MissingNullability: android.car.storagemonitoring.LifetimeWriteInfo#LifetimeWriteInfo(String, String, long) parameter #1:
+    Missing nullability on parameter `fstype` in method `LifetimeWriteInfo`
+MissingNullability: android.car.storagemonitoring.LifetimeWriteInfo#LifetimeWriteInfo(android.os.Parcel) parameter #0:
+    Missing nullability on parameter `in` in method `LifetimeWriteInfo`
+MissingNullability: android.car.storagemonitoring.LifetimeWriteInfo#fstype:
+    Missing nullability on field `fstype` in class `class android.car.storagemonitoring.LifetimeWriteInfo`
+MissingNullability: android.car.storagemonitoring.LifetimeWriteInfo#partition:
+    Missing nullability on field `partition` in class `class android.car.storagemonitoring.LifetimeWriteInfo`
+MissingNullability: android.car.storagemonitoring.LifetimeWriteInfo#writeToParcel(android.os.Parcel, int) parameter #0:
+    Missing nullability on parameter `dest` in method `writeToParcel`
+MissingNullability: android.car.storagemonitoring.WearEstimate#CREATOR:
+    Missing nullability on field `CREATOR` in class `class android.car.storagemonitoring.WearEstimate`
+MissingNullability: android.car.storagemonitoring.WearEstimate#WearEstimate(android.os.Parcel) parameter #0:
+    Missing nullability on parameter `in` in method `WearEstimate`
+MissingNullability: android.car.storagemonitoring.WearEstimate#writeToParcel(android.os.Parcel, int) parameter #0:
+    Missing nullability on parameter `dest` in method `writeToParcel`
+MissingNullability: android.car.storagemonitoring.WearEstimateChange#CREATOR:
+    Missing nullability on field `CREATOR` in class `class android.car.storagemonitoring.WearEstimateChange`
+MissingNullability: android.car.storagemonitoring.WearEstimateChange#WearEstimateChange(android.car.storagemonitoring.WearEstimate, android.car.storagemonitoring.WearEstimate, long, java.time.Instant, boolean) parameter #0:
+    Missing nullability on parameter `oldEstimate` in method `WearEstimateChange`
+MissingNullability: android.car.storagemonitoring.WearEstimateChange#WearEstimateChange(android.car.storagemonitoring.WearEstimate, android.car.storagemonitoring.WearEstimate, long, java.time.Instant, boolean) parameter #1:
+    Missing nullability on parameter `newEstimate` in method `WearEstimateChange`
+MissingNullability: android.car.storagemonitoring.WearEstimateChange#WearEstimateChange(android.car.storagemonitoring.WearEstimate, android.car.storagemonitoring.WearEstimate, long, java.time.Instant, boolean) parameter #3:
+    Missing nullability on parameter `dateAtChange` in method `WearEstimateChange`
+MissingNullability: android.car.storagemonitoring.WearEstimateChange#WearEstimateChange(android.os.Parcel) parameter #0:
+    Missing nullability on parameter `in` in method `WearEstimateChange`
+MissingNullability: android.car.storagemonitoring.WearEstimateChange#writeToParcel(android.os.Parcel, int) parameter #0:
+    Missing nullability on parameter `dest` in method `writeToParcel`
+MissingNullability: android.car.trust.CarTrustAgentEnrollmentManager#enrollmentHandshakeAccepted(android.bluetooth.BluetoothDevice) parameter #0:
+    Missing nullability on parameter `device` in method `enrollmentHandshakeAccepted`
+MissingNullability: android.car.trust.CarTrustAgentEnrollmentManager.CarTrustAgentBleCallback#onBleEnrollmentDeviceConnected(android.bluetooth.BluetoothDevice) parameter #0:
+    Missing nullability on parameter `device` in method `onBleEnrollmentDeviceConnected`
+MissingNullability: android.car.trust.CarTrustAgentEnrollmentManager.CarTrustAgentBleCallback#onBleEnrollmentDeviceDisconnected(android.bluetooth.BluetoothDevice) parameter #0:
+    Missing nullability on parameter `device` in method `onBleEnrollmentDeviceDisconnected`
+MissingNullability: android.car.trust.CarTrustAgentEnrollmentManager.CarTrustAgentEnrollmentCallback#onAuthStringAvailable(android.bluetooth.BluetoothDevice, String) parameter #0:
+    Missing nullability on parameter `device` in method `onAuthStringAvailable`
+MissingNullability: android.car.trust.CarTrustAgentEnrollmentManager.CarTrustAgentEnrollmentCallback#onAuthStringAvailable(android.bluetooth.BluetoothDevice, String) parameter #1:
+    Missing nullability on parameter `authString` in method `onAuthStringAvailable`
+MissingNullability: android.car.trust.TrustedDeviceInfo#CREATOR:
+    Missing nullability on field `CREATOR` in class `class android.car.trust.TrustedDeviceInfo`
+MissingNullability: android.car.trust.TrustedDeviceInfo#TrustedDeviceInfo(android.os.Parcel) parameter #0:
+    Missing nullability on parameter `in` in method `TrustedDeviceInfo`
+MissingNullability: android.car.trust.TrustedDeviceInfo#deserialize(String):
+    Missing nullability on method `deserialize` return
+MissingNullability: android.car.trust.TrustedDeviceInfo#deserialize(String) parameter #0:
+    Missing nullability on parameter `deviceInfo` in method `deserialize`
+MissingNullability: android.car.trust.TrustedDeviceInfo#serialize():
+    Missing nullability on method `serialize` return
+MissingNullability: android.car.trust.TrustedDeviceInfo#writeToParcel(android.os.Parcel, int) parameter #0:
+    Missing nullability on parameter `dest` in method `writeToParcel`
+MissingNullability: android.car.vms.VmsAssociatedLayer#CREATOR:
+    Missing nullability on field `CREATOR` in class `class android.car.vms.VmsAssociatedLayer`
+MissingNullability: android.car.vms.VmsAssociatedLayer#writeToParcel(android.os.Parcel, int) parameter #0:
+    Missing nullability on parameter `out` in method `writeToParcel`
+MissingNullability: android.car.vms.VmsAvailableLayers#CREATOR:
+    Missing nullability on field `CREATOR` in class `class android.car.vms.VmsAvailableLayers`
+MissingNullability: android.car.vms.VmsAvailableLayers#writeToParcel(android.os.Parcel, int) parameter #0:
+    Missing nullability on parameter `out` in method `writeToParcel`
+MissingNullability: android.car.vms.VmsLayer#CREATOR:
+    Missing nullability on field `CREATOR` in class `class android.car.vms.VmsLayer`
+MissingNullability: android.car.vms.VmsLayer#writeToParcel(android.os.Parcel, int) parameter #0:
+    Missing nullability on parameter `out` in method `writeToParcel`
+MissingNullability: android.car.vms.VmsLayerDependency#CREATOR:
+    Missing nullability on field `CREATOR` in class `class android.car.vms.VmsLayerDependency`
+MissingNullability: android.car.vms.VmsLayerDependency#writeToParcel(android.os.Parcel, int) parameter #0:
+    Missing nullability on parameter `out` in method `writeToParcel`
+MissingNullability: android.car.vms.VmsLayersOffering#CREATOR:
+    Missing nullability on field `CREATOR` in class `class android.car.vms.VmsLayersOffering`
+MissingNullability: android.car.vms.VmsLayersOffering#getDependencies():
+    Missing nullability on method `getDependencies` return
+MissingNullability: android.car.vms.VmsLayersOffering#writeToParcel(android.os.Parcel, int) parameter #0:
+    Missing nullability on parameter `out` in method `writeToParcel`
+MissingNullability: android.car.vms.VmsOperationRecorder#addHalSubscription(int, android.car.vms.VmsLayer) parameter #1:
+    Missing nullability on parameter `layer` in method `addHalSubscription`
+MissingNullability: android.car.vms.VmsOperationRecorder#addSubscription(int, android.car.vms.VmsLayer) parameter #1:
+    Missing nullability on parameter `layer` in method `addSubscription`
+MissingNullability: android.car.vms.VmsOperationRecorder#get():
+    Missing nullability on method `get` return
+MissingNullability: android.car.vms.VmsOperationRecorder#removeHalSubscription(int, android.car.vms.VmsLayer) parameter #1:
+    Missing nullability on parameter `layer` in method `removeHalSubscription`
+MissingNullability: android.car.vms.VmsOperationRecorder#removeSubscription(int, android.car.vms.VmsLayer) parameter #1:
+    Missing nullability on parameter `layer` in method `removeSubscription`
+MissingNullability: android.car.vms.VmsOperationRecorder#setHalPublisherLayersOffering(android.car.vms.VmsLayersOffering) parameter #0:
+    Missing nullability on parameter `layersOffering` in method `setHalPublisherLayersOffering`
+MissingNullability: android.car.vms.VmsOperationRecorder#setLayersOffering(android.car.vms.VmsLayersOffering) parameter #0:
+    Missing nullability on parameter `layersOffering` in method `setLayersOffering`
+MissingNullability: android.car.vms.VmsOperationRecorder#setPublisherLayersOffering(android.car.vms.VmsLayersOffering) parameter #0:
+    Missing nullability on parameter `layersOffering` in method `setPublisherLayersOffering`
+MissingNullability: android.car.vms.VmsOperationRecorder#subscribe(android.car.vms.VmsLayer) parameter #0:
+    Missing nullability on parameter `layer` in method `subscribe`
+MissingNullability: android.car.vms.VmsOperationRecorder#subscribe(android.car.vms.VmsLayer, int) parameter #0:
+    Missing nullability on parameter `layer` in method `subscribe`
+MissingNullability: android.car.vms.VmsOperationRecorder#unsubscribe(android.car.vms.VmsLayer) parameter #0:
+    Missing nullability on parameter `layer` in method `unsubscribe`
+MissingNullability: android.car.vms.VmsOperationRecorder#unsubscribe(android.car.vms.VmsLayer, int) parameter #0:
+    Missing nullability on parameter `layer` in method `unsubscribe`
+MissingNullability: android.car.vms.VmsPublisherClientService#getPublisherId(byte[]) parameter #0:
+    Missing nullability on parameter `publisherInfo` in method `getPublisherId`
+MissingNullability: android.car.vms.VmsPublisherClientService#getSubscriptions():
+    Missing nullability on method `getSubscriptions` return
+MissingNullability: android.car.vms.VmsPublisherClientService#onBind(android.content.Intent):
+    Missing nullability on method `onBind` return
+MissingNullability: android.car.vms.VmsPublisherClientService#onBind(android.content.Intent) parameter #0:
+    Missing nullability on parameter `intent` in method `onBind`
+MissingNullability: android.car.vms.VmsPublisherClientService#onUnbind(android.content.Intent) parameter #0:
+    Missing nullability on parameter `intent` in method `onUnbind`
+MissingNullability: android.car.vms.VmsPublisherClientService#publish(android.car.vms.VmsLayer, int, byte[]) parameter #2:
+    Missing nullability on parameter `payload` in method `publish`
+MissingNullability: android.car.vms.VmsSubscriberManager.VmsSubscriberClientCallback#onVmsMessageReceived(android.car.vms.VmsLayer, byte[]) parameter #1:
+    Missing nullability on parameter `payload` in method `onVmsMessageReceived`
+MissingNullability: android.car.vms.VmsSubscriptionState#CREATOR:
+    Missing nullability on field `CREATOR` in class `class android.car.vms.VmsSubscriptionState`
+MissingNullability: android.car.vms.VmsSubscriptionState#writeToParcel(android.os.Parcel, int) parameter #0:
+    Missing nullability on parameter `out` in method `writeToParcel`
+
+
+OnNameExpected: android.car.AoapService#canSwitchToAoap(android.hardware.usb.UsbDevice):
+    If implemented by developer, should follow the on<Something> style; otherwise consider marking final
+OnNameExpected: android.car.AoapService#dump(java.io.FileDescriptor, java.io.PrintWriter, String[]):
+    If implemented by developer, should follow the on<Something> style; otherwise consider marking final
+OnNameExpected: android.car.AoapService#isDeviceSupported(android.hardware.usb.UsbDevice):
+    Methods implemented by developers should follow the on<Something> style, was `isDeviceSupported`
+OnNameExpected: android.car.cluster.renderer.InstrumentClusterRenderingService#dump(java.io.FileDescriptor, java.io.PrintWriter, String[]):
+    If implemented by developer, should follow the on<Something> style; otherwise consider marking final
+OnNameExpected: android.car.cluster.renderer.InstrumentClusterRenderingService#getBitmap(android.net.Uri, int, int):
+    If implemented by developer, should follow the on<Something> style; otherwise consider marking final
+OnNameExpected: android.car.cluster.renderer.InstrumentClusterRenderingService#getNavigationRenderer():
+    Methods implemented by developers should follow the on<Something> style, was `getNavigationRenderer`
+OnNameExpected: android.car.cluster.renderer.InstrumentClusterRenderingService#startNavigationActivity(android.content.ComponentName):
+    If implemented by developer, should follow the on<Something> style; otherwise consider marking final
+OnNameExpected: android.car.content.pm.CarAppBlockingPolicyService#getAppBlockingPolicy():
+    Methods implemented by developers should follow the on<Something> style, was `getAppBlockingPolicy`
+OnNameExpected: android.car.input.CarInputHandlingService#dump(java.io.FileDescriptor, java.io.PrintWriter, String[]):
+    If implemented by developer, should follow the on<Something> style; otherwise consider marking final
+
+
+ParcelConstructor: android.car.content.pm.AppBlockingPackageInfo#AppBlockingPackageInfo(android.os.Parcel):
+    Parcelable inflation is exposed through CREATOR, not raw constructors, in android.car.content.pm.AppBlockingPackageInfo
+ParcelConstructor: android.car.content.pm.CarAppBlockingPolicy#CarAppBlockingPolicy(android.os.Parcel):
+    Parcelable inflation is exposed through CREATOR, not raw constructors, in android.car.content.pm.CarAppBlockingPolicy
+ParcelConstructor: android.car.diagnostic.CarDiagnosticEvent#CarDiagnosticEvent(android.os.Parcel):
+    Parcelable inflation is exposed through CREATOR, not raw constructors, in android.car.diagnostic.CarDiagnosticEvent
+ParcelConstructor: android.car.storagemonitoring.IoStats#IoStats(android.os.Parcel):
+    Parcelable inflation is exposed through CREATOR, not raw constructors, in android.car.storagemonitoring.IoStats
+ParcelConstructor: android.car.storagemonitoring.IoStatsEntry#IoStatsEntry(android.os.Parcel):
+    Parcelable inflation is exposed through CREATOR, not raw constructors, in android.car.storagemonitoring.IoStatsEntry
+ParcelConstructor: android.car.storagemonitoring.IoStatsEntry.Metrics#Metrics(android.os.Parcel):
+    Parcelable inflation is exposed through CREATOR, not raw constructors, in android.car.storagemonitoring.IoStatsEntry.Metrics
+ParcelConstructor: android.car.storagemonitoring.LifetimeWriteInfo#LifetimeWriteInfo(android.os.Parcel):
+    Parcelable inflation is exposed through CREATOR, not raw constructors, in android.car.storagemonitoring.LifetimeWriteInfo
+ParcelConstructor: android.car.storagemonitoring.WearEstimate#WearEstimate(android.os.Parcel):
+    Parcelable inflation is exposed through CREATOR, not raw constructors, in android.car.storagemonitoring.WearEstimate
+ParcelConstructor: android.car.storagemonitoring.WearEstimateChange#WearEstimateChange(android.os.Parcel):
+    Parcelable inflation is exposed through CREATOR, not raw constructors, in android.car.storagemonitoring.WearEstimateChange
+ParcelConstructor: android.car.trust.TrustedDeviceInfo#TrustedDeviceInfo(android.os.Parcel):
+    Parcelable inflation is exposed through CREATOR, not raw constructors, in android.car.trust.TrustedDeviceInfo
+
+
+ProtectedMember: android.car.AoapService#dump(java.io.FileDescriptor, java.io.PrintWriter, String[]):
+    Protected methods not allowed; must be public: method android.car.AoapService.dump(java.io.FileDescriptor,java.io.PrintWriter,String[])}
+ProtectedMember: android.car.cluster.renderer.InstrumentClusterRenderer#createNavigationRenderer():
+    Protected methods not allowed; must be public: method android.car.cluster.renderer.InstrumentClusterRenderer.createNavigationRenderer()}
+ProtectedMember: android.car.cluster.renderer.InstrumentClusterRenderingService#dump(java.io.FileDescriptor, java.io.PrintWriter, String[]):
+    Protected methods not allowed; must be public: method android.car.cluster.renderer.InstrumentClusterRenderingService.dump(java.io.FileDescriptor,java.io.PrintWriter,String[])}
+ProtectedMember: android.car.cluster.renderer.InstrumentClusterRenderingService#startNavigationActivity(android.content.ComponentName):
+    Protected methods not allowed; must be public: method android.car.cluster.renderer.InstrumentClusterRenderingService.startNavigationActivity(android.content.ComponentName)}
+ProtectedMember: android.car.content.pm.CarAppBlockingPolicyService#getAppBlockingPolicy():
+    Protected methods not allowed; must be public: method android.car.content.pm.CarAppBlockingPolicyService.getAppBlockingPolicy()}
+ProtectedMember: android.car.input.CarInputHandlingService#dump(java.io.FileDescriptor, java.io.PrintWriter, String[]):
+    Protected methods not allowed; must be public: method android.car.input.CarInputHandlingService.dump(java.io.FileDescriptor,java.io.PrintWriter,String[])}
+ProtectedMember: android.car.input.CarInputHandlingService#onKeyEvent(android.view.KeyEvent, int):
+    Protected methods not allowed; must be public: method android.car.input.CarInputHandlingService.onKeyEvent(android.view.KeyEvent,int)}
+ProtectedMember: android.car.vms.VmsPublisherClientService#onVmsPublisherServiceReady():
+    Protected methods not allowed; must be public: method android.car.vms.VmsPublisherClientService.onVmsPublisherServiceReady()}
+
+
+PublicTypedef: android.car.diagnostic.CarDiagnosticEvent.FuelSystemStatus.Status:
+    Don't expose @IntDef: Status must be hidden.
+PublicTypedef: android.car.diagnostic.CarDiagnosticEvent.FuelType.Type:
+    Don't expose @IntDef: Type must be hidden.
+PublicTypedef: android.car.diagnostic.CarDiagnosticEvent.SecondaryAirStatus.Status:
+    Don't expose @IntDef: Status must be hidden.
+PublicTypedef: android.car.diagnostic.CarDiagnosticManager.FrameType:
+    Don't expose @IntDef: FrameType must be hidden.
+
+
+RegistrationName: android.car.CarProjectionManager#registerProjectionListener(android.car.CarProjectionManager.CarProjectionListener, int):
+    Listener methods should be named add/remove; was registerProjectionListener
+RegistrationName: android.car.CarProjectionManager#registerProjectionStatusListener(android.car.CarProjectionManager.ProjectionStatusListener):
+    Listener methods should be named add/remove; was registerProjectionStatusListener
+RegistrationName: android.car.CarProjectionManager#unregisterProjectionListener():
+    Listener methods should be named add/remove; was unregisterProjectionListener
+RegistrationName: android.car.CarProjectionManager#unregisterProjectionStatusListener(android.car.CarProjectionManager.ProjectionStatusListener):
+    Listener methods should be named add/remove; was unregisterProjectionStatusListener
+RegistrationName: android.car.diagnostic.CarDiagnosticManager#registerListener(android.car.diagnostic.CarDiagnosticManager.OnDiagnosticEventListener, int, int):
+    Listener methods should be named add/remove; was registerListener
+RegistrationName: android.car.diagnostic.CarDiagnosticManager#unregisterListener(android.car.diagnostic.CarDiagnosticManager.OnDiagnosticEventListener):
+    Listener methods should be named add/remove; was unregisterListener
+RegistrationName: android.car.drivingstate.CarDrivingStateManager#registerListener(android.car.drivingstate.CarDrivingStateManager.CarDrivingStateEventListener):
+    Listener methods should be named add/remove; was registerListener
+RegistrationName: android.car.drivingstate.CarDrivingStateManager#unregisterListener():
+    Listener methods should be named add/remove; was unregisterListener
+RegistrationName: android.car.storagemonitoring.CarStorageMonitoringManager#registerListener(android.car.storagemonitoring.CarStorageMonitoringManager.IoStatsListener):
+    Listener methods should be named add/remove; was registerListener
+RegistrationName: android.car.storagemonitoring.CarStorageMonitoringManager#unregisterListener(android.car.storagemonitoring.CarStorageMonitoringManager.IoStatsListener):
+    Listener methods should be named add/remove; was unregisterListener
+
+
+SamShouldBeLast: android.car.CarProjectionManager#registerProjectionListener(android.car.CarProjectionManager.CarProjectionListener, int):
+    SAM-compatible parameters (such as parameter 1, "listener", in android.car.CarProjectionManager.registerProjectionListener) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
+SamShouldBeLast: android.car.diagnostic.CarDiagnosticManager#registerListener(android.car.diagnostic.CarDiagnosticManager.OnDiagnosticEventListener, int, int):
+    SAM-compatible parameters (such as parameter 1, "listener", in android.car.diagnostic.CarDiagnosticManager.registerListener) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
+
+
+ServiceName: android.car.Car#CAR_DRIVING_STATE_SERVICE:
+    Inconsistent service value; expected `CAR_DRIVING_STATE`, was `drivingstate`
+ServiceName: android.car.Car#CAR_TRUST_AGENT_ENROLLMENT_SERVICE:
+    Inconsistent service value; expected `CAR_TRUST_AGENT_ENROLLMENT`, was `trust_enroll`
+ServiceName: android.car.Car#CAR_USER_SERVICE:
+    Inconsistent service value; expected `CAR_USER`, was `car_user_service`
+ServiceName: android.car.Car#PERMISSION_CAR_TEST_SERVICE:
+    Inconsistent service value; expected `PERMISSION_CAR_TEST`, was `android.car.permission.CAR_TEST_SERVICE`
+ServiceName: android.car.Car#TEST_SERVICE:
+    Inconsistent service value; expected `TEST`, was `car-service-test`
+ServiceName: android.car.Car#VMS_SUBSCRIBER_SERVICE:
+    Inconsistent service value; expected `VMS_SUBSCRIBER`, was `vehicle_map_subscriber_service`
+
+
+StartWithLower: android.car.diagnostic.CarDiagnosticEvent.CompressionIgnitionMonitors#EGROrVVT:
+    Non-static field EGROrVVT must be named using fooBar style
+StartWithLower: android.car.diagnostic.CarDiagnosticEvent.CompressionIgnitionMonitors#NMHCCatalyst:
+    Non-static field NMHCCatalyst must be named using fooBar style
+StartWithLower: android.car.diagnostic.CarDiagnosticEvent.CompressionIgnitionMonitors#NOxSCR:
+    Non-static field NOxSCR must be named using fooBar style
+StartWithLower: android.car.diagnostic.CarDiagnosticEvent.CompressionIgnitionMonitors#PMFilter:
+    Non-static field PMFilter must be named using fooBar style
+StartWithLower: android.car.diagnostic.CarDiagnosticEvent.SparkIgnitionMonitors#ACRefrigerant:
+    Non-static field ACRefrigerant must be named using fooBar style
+StartWithLower: android.car.diagnostic.CarDiagnosticEvent.SparkIgnitionMonitors#EGR:
+    Non-static field EGR must be named using fooBar style
+
+
+UserHandleName: android.car.projection.ProjectionOptions:
+    Classes holding a set of parameters should be called `FooParams`, was `ProjectionOptions`
+
+
+VisiblySynchronized: android.car.drivingstate.CarDrivingStateManager#registerListener(android.car.drivingstate.CarDrivingStateManager.CarDrivingStateEventListener):
+    Internal locks must not be exposed: method android.car.drivingstate.CarDrivingStateManager.registerListener(android.car.drivingstate.CarDrivingStateManager.CarDrivingStateEventListener)
+VisiblySynchronized: android.car.drivingstate.CarDrivingStateManager#unregisterListener():
+    Internal locks must not be exposed: method android.car.drivingstate.CarDrivingStateManager.unregisterListener()
+VisiblySynchronized: android.car.hardware.cabin.CarCabinManager#registerCallback(android.car.hardware.cabin.CarCabinManager.CarCabinEventCallback):
+    Internal locks must not be exposed: method android.car.hardware.cabin.CarCabinManager.registerCallback(android.car.hardware.cabin.CarCabinManager.CarCabinEventCallback)
+VisiblySynchronized: android.car.hardware.cabin.CarCabinManager#unregisterCallback(android.car.hardware.cabin.CarCabinManager.CarCabinEventCallback):
+    Internal locks must not be exposed: method android.car.hardware.cabin.CarCabinManager.unregisterCallback(android.car.hardware.cabin.CarCabinManager.CarCabinEventCallback)
+VisiblySynchronized: android.car.hardware.hvac.CarHvacManager#registerCallback(android.car.hardware.hvac.CarHvacManager.CarHvacEventCallback):
+    Internal locks must not be exposed: method android.car.hardware.hvac.CarHvacManager.registerCallback(android.car.hardware.hvac.CarHvacManager.CarHvacEventCallback)
+VisiblySynchronized: android.car.hardware.hvac.CarHvacManager#unregisterCallback(android.car.hardware.hvac.CarHvacManager.CarHvacEventCallback):
+    Internal locks must not be exposed: method android.car.hardware.hvac.CarHvacManager.unregisterCallback(android.car.hardware.hvac.CarHvacManager.CarHvacEventCallback)
diff --git a/car-lib/api/system-removed.txt b/car-lib/api/system-removed.txt
index d802177..b8614e7 100644
--- a/car-lib/api/system-removed.txt
+++ b/car-lib/api/system-removed.txt
@@ -1 +1,9 @@
 // Signature format: 2.0
+package android.car.cluster.renderer {
+
+  @UiThread public abstract class NavigationRenderer {
+    method public void onEvent(int, android.os.Bundle);
+  }
+
+}
+
diff --git a/car-lib/api/test-baseline.txt b/car-lib/api/test-baseline.txt
index 6cb7b91..e274b0a 100644
--- a/car-lib/api/test-baseline.txt
+++ b/car-lib/api/test-baseline.txt
@@ -1,42 +1,173 @@
 // Baseline format: 1.0
-HiddenTypeParameter: android.car.hardware.CarSensorManager#getPropertyList():
-    Method android.car.hardware.CarSensorManager.getPropertyList() references hidden type class android.car.hardware.CarPropertyConfig.
-HiddenTypeParameter: android.car.navigation.CarNavigationStatusManager#getInstrumentClusterInfo():
-    Method android.car.navigation.CarNavigationStatusManager.getInstrumentClusterInfo() references hidden type android.car.navigation.CarNavigationInstrumentCluster.
-
-
-HiddenTypedefConstant: android.car.CarInfoManager#getEvConnectorTypes():
-    Typedef references constant which isn't part of the API, skipping in documentation: android.car.EvConnectorType#UNKNOWN
-HiddenTypedefConstant: android.car.CarInfoManager#getFuelTypes():
-    Typedef references constant which isn't part of the API, skipping in documentation: android.car.FuelType#UNKNOWN
-HiddenTypedefConstant: android.car.hardware.CarSensorManager#getLatestSensorEvent(int) parameter #0:
-    Typedef references constant which isn't part of the API, skipping in documentation: android.car.hardware.CarSensorManager#SENSOR_TYPE_ENGINE_OIL_LEVEL
-HiddenTypedefConstant: android.car.hardware.CarSensorManager#isSensorSupported(int) parameter #0:
-    Typedef references constant which isn't part of the API, skipping in documentation: android.car.hardware.CarSensorManager#SENSOR_TYPE_ENGINE_OIL_LEVEL
-HiddenTypedefConstant: android.car.hardware.CarSensorManager#isSensorSupported(int[], int) parameter #1:
-    Typedef references constant which isn't part of the API, skipping in documentation: android.car.hardware.CarSensorManager#SENSOR_TYPE_ENGINE_OIL_LEVEL
-HiddenTypedefConstant: android.car.hardware.CarSensorManager#registerListener(android.car.hardware.CarSensorManager.OnSensorChangedListener, int, int) parameter #1:
-    Typedef references constant which isn't part of the API, skipping in documentation: android.car.hardware.CarSensorManager#SENSOR_TYPE_ENGINE_OIL_LEVEL
-HiddenTypedefConstant: android.car.hardware.CarSensorManager#unregisterListener(android.car.hardware.CarSensorManager.OnSensorChangedListener, int) parameter #1:
-    Typedef references constant which isn't part of the API, skipping in documentation: android.car.hardware.CarSensorManager#SENSOR_TYPE_ENGINE_OIL_LEVEL
-
-
-
-ReferencesHidden: android.car.hardware.CarSensorManager#getPropertyList():
-    Class android.car.hardware.CarPropertyConfig is hidden but was referenced (as return type parameter) from public method android.car.hardware.CarSensorManager.getPropertyList()
-ReferencesHidden: android.car.navigation.CarNavigationStatusManager#getInstrumentClusterInfo():
-    Class android.car.navigation.CarNavigationInstrumentCluster is hidden but was referenced (as return type) from public method android.car.navigation.CarNavigationStatusManager.getInstrumentClusterInfo()
-
-
-RequiresPermission: android.car.hardware.CarSensorManager#registerListener(android.car.hardware.CarSensorManager.OnSensorChangedListener, int, int):
-    Method 'registerListener' documentation mentions permissions already declared by @RequiresPermission
-
-
-SdkConstant: android.car.Car#CAR_INTENT_ACTION_MEDIA_TEMPLATE:
-    Field 'CAR_INTENT_ACTION_MEDIA_TEMPLATE' is missing @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
-
-
-Todo: android.car.CarInfoManager#getVehicleId():
-    Documentation mentions 'TODO'
-
-
+MissingPermission: android.car.VehiclePropertyIds#ABS_ACTIVE:
+    Permission Car.PERMISSION_CAR_DYNAMICS_STATE required by field VehiclePropertyIds.ABS_ACTIVE is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#AP_POWER_BOOTUP_REASON:
+    Permission Car.PERMISSION_CAR_POWER required by field VehiclePropertyIds.AP_POWER_BOOTUP_REASON is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#AP_POWER_STATE_REPORT:
+    Permission Car.PERMISSION_CAR_POWER required by field VehiclePropertyIds.AP_POWER_STATE_REPORT is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#AP_POWER_STATE_REQ:
+    Permission Car.PERMISSION_CAR_POWER required by field VehiclePropertyIds.AP_POWER_STATE_REQ is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#DISPLAY_BRIGHTNESS:
+    Permission Car.PERMISSION_CAR_POWER required by field VehiclePropertyIds.DISPLAY_BRIGHTNESS is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#DOOR_LOCK:
+    Permission Car.PERMISSION_CONTROL_CAR_DOORS required by field VehiclePropertyIds.DOOR_LOCK is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#DOOR_MOVE:
+    Permission Car.PERMISSION_CONTROL_CAR_DOORS required by field VehiclePropertyIds.DOOR_MOVE is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#DOOR_POS:
+    Permission Car.PERMISSION_CONTROL_CAR_DOORS required by field VehiclePropertyIds.DOOR_POS is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#ENGINE_COOLANT_TEMP:
+    Permission Car.PERMISSION_CAR_ENGINE_DETAILED required by field VehiclePropertyIds.ENGINE_COOLANT_TEMP is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#ENGINE_OIL_LEVEL:
+    Permission Car.PERMISSION_CAR_ENGINE_DETAILED required by field VehiclePropertyIds.ENGINE_OIL_LEVEL is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#ENGINE_OIL_TEMP:
+    Permission Car.PERMISSION_CAR_ENGINE_DETAILED required by field VehiclePropertyIds.ENGINE_OIL_TEMP is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#ENGINE_RPM:
+    Permission Car.PERMISSION_CAR_ENGINE_DETAILED required by field VehiclePropertyIds.ENGINE_RPM is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#FOG_LIGHTS_STATE:
+    Permission Car.PERMISSION_EXTERIOR_LIGHTS required by field VehiclePropertyIds.FOG_LIGHTS_STATE is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#FOG_LIGHTS_SWITCH:
+    Permission Car.PERMISSION_CONTROL_EXTERIOR_LIGHTS required by field VehiclePropertyIds.FOG_LIGHTS_SWITCH is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#HAZARD_LIGHTS_STATE:
+    Permission Car.PERMISSION_EXTERIOR_LIGHTS required by field VehiclePropertyIds.HAZARD_LIGHTS_STATE is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#HAZARD_LIGHTS_SWITCH:
+    Permission Car.PERMISSION_CONTROL_EXTERIOR_LIGHTS required by field VehiclePropertyIds.HAZARD_LIGHTS_SWITCH is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#HEADLIGHTS_STATE:
+    Permission Car.PERMISSION_EXTERIOR_LIGHTS required by field VehiclePropertyIds.HEADLIGHTS_STATE is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#HEADLIGHTS_SWITCH:
+    Permission Car.PERMISSION_CONTROL_EXTERIOR_LIGHTS required by field VehiclePropertyIds.HEADLIGHTS_SWITCH is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#HIGH_BEAM_LIGHTS_STATE:
+    Permission Car.PERMISSION_EXTERIOR_LIGHTS required by field VehiclePropertyIds.HIGH_BEAM_LIGHTS_STATE is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#HIGH_BEAM_LIGHTS_SWITCH:
+    Permission Car.PERMISSION_CONTROL_EXTERIOR_LIGHTS required by field VehiclePropertyIds.HIGH_BEAM_LIGHTS_SWITCH is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#HVAC_ACTUAL_FAN_SPEED_RPM:
+    Permission Car.PERMISSION_CONTROL_CAR_CLIMATE required by field VehiclePropertyIds.HVAC_ACTUAL_FAN_SPEED_RPM is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#HVAC_AC_ON:
+    Permission Car.PERMISSION_CONTROL_CAR_CLIMATE required by field VehiclePropertyIds.HVAC_AC_ON is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#HVAC_AUTO_ON:
+    Permission Car.PERMISSION_CONTROL_CAR_CLIMATE required by field VehiclePropertyIds.HVAC_AUTO_ON is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#HVAC_AUTO_RECIRC_ON:
+    Permission Car.PERMISSION_CONTROL_CAR_CLIMATE required by field VehiclePropertyIds.HVAC_AUTO_RECIRC_ON is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#HVAC_DEFROSTER:
+    Permission Car.PERMISSION_CONTROL_CAR_CLIMATE required by field VehiclePropertyIds.HVAC_DEFROSTER is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#HVAC_DUAL_ON:
+    Permission Car.PERMISSION_CONTROL_CAR_CLIMATE required by field VehiclePropertyIds.HVAC_DUAL_ON is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#HVAC_FAN_DIRECTION:
+    Permission Car.PERMISSION_CONTROL_CAR_CLIMATE required by field VehiclePropertyIds.HVAC_FAN_DIRECTION is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#HVAC_FAN_DIRECTION_AVAILABLE:
+    Permission Car.PERMISSION_CONTROL_CAR_CLIMATE required by field VehiclePropertyIds.HVAC_FAN_DIRECTION_AVAILABLE is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#HVAC_FAN_SPEED:
+    Permission Car.PERMISSION_CONTROL_CAR_CLIMATE required by field VehiclePropertyIds.HVAC_FAN_SPEED is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#HVAC_MAX_AC_ON:
+    Permission Car.PERMISSION_CONTROL_CAR_CLIMATE required by field VehiclePropertyIds.HVAC_MAX_AC_ON is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#HVAC_MAX_DEFROST_ON:
+    Permission Car.PERMISSION_CONTROL_CAR_CLIMATE required by field VehiclePropertyIds.HVAC_MAX_DEFROST_ON is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#HVAC_POWER_ON:
+    Permission Car.PERMISSION_CONTROL_CAR_CLIMATE required by field VehiclePropertyIds.HVAC_POWER_ON is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#HVAC_RECIRC_ON:
+    Permission Car.PERMISSION_CONTROL_CAR_CLIMATE required by field VehiclePropertyIds.HVAC_RECIRC_ON is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#HVAC_SEAT_TEMPERATURE:
+    Permission Car.PERMISSION_CONTROL_CAR_CLIMATE required by field VehiclePropertyIds.HVAC_SEAT_TEMPERATURE is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#HVAC_SEAT_VENTILATION:
+    Permission Car.PERMISSION_CONTROL_CAR_CLIMATE required by field VehiclePropertyIds.HVAC_SEAT_VENTILATION is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#HVAC_SIDE_MIRROR_HEAT:
+    Permission Car.PERMISSION_CONTROL_CAR_CLIMATE required by field VehiclePropertyIds.HVAC_SIDE_MIRROR_HEAT is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#HVAC_STEERING_WHEEL_HEAT:
+    Permission Car.PERMISSION_CONTROL_CAR_CLIMATE required by field VehiclePropertyIds.HVAC_STEERING_WHEEL_HEAT is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#HVAC_TEMPERATURE_CURRENT:
+    Permission Car.PERMISSION_CONTROL_CAR_CLIMATE required by field VehiclePropertyIds.HVAC_TEMPERATURE_CURRENT is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#HVAC_TEMPERATURE_DISPLAY_UNITS:
+    Permission Car.PERMISSION_CONTROL_CAR_CLIMATE required by field VehiclePropertyIds.HVAC_TEMPERATURE_DISPLAY_UNITS is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#HVAC_TEMPERATURE_SET:
+    Permission Car.PERMISSION_CONTROL_CAR_CLIMATE required by field VehiclePropertyIds.HVAC_TEMPERATURE_SET is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#MIRROR_FOLD:
+    Permission Car.PERMISSION_CONTROL_CAR_MIRRORS required by field VehiclePropertyIds.MIRROR_FOLD is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#MIRROR_LOCK:
+    Permission Car.PERMISSION_CONTROL_CAR_MIRRORS required by field VehiclePropertyIds.MIRROR_LOCK is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#MIRROR_Y_MOVE:
+    Permission Car.PERMISSION_CONTROL_CAR_MIRRORS required by field VehiclePropertyIds.MIRROR_Y_MOVE is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#MIRROR_Y_POS:
+    Permission Car.PERMISSION_CONTROL_CAR_MIRRORS required by field VehiclePropertyIds.MIRROR_Y_POS is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#MIRROR_Z_MOVE:
+    Permission Car.PERMISSION_CONTROL_CAR_MIRRORS required by field VehiclePropertyIds.MIRROR_Z_MOVE is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#MIRROR_Z_POS:
+    Permission Car.PERMISSION_CONTROL_CAR_MIRRORS required by field VehiclePropertyIds.MIRROR_Z_POS is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#OBD2_FREEZE_FRAME:
+    Permission Car.PERMISSION_CAR_DIAGNOSTIC_READ_ALL required by field VehiclePropertyIds.OBD2_FREEZE_FRAME is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#OBD2_FREEZE_FRAME_CLEAR:
+    Permission Car.PERMISSION_CAR_DIAGNOSTIC_CLEAR required by field VehiclePropertyIds.OBD2_FREEZE_FRAME_CLEAR is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#OBD2_FREEZE_FRAME_INFO:
+    Permission Car.PERMISSION_CAR_DIAGNOSTIC_READ_ALL required by field VehiclePropertyIds.OBD2_FREEZE_FRAME_INFO is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#OBD2_LIVE_FRAME:
+    Permission Car.PERMISSION_CAR_DIAGNOSTIC_READ_ALL required by field VehiclePropertyIds.OBD2_LIVE_FRAME is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#PERF_ODOMETER:
+    Permission Car.PERMISSION_MILEAGE required by field VehiclePropertyIds.PERF_ODOMETER is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#SEAT_BACKREST_ANGLE_1_MOVE:
+    Permission Car.PERMISSION_CONTROL_CAR_SEATS required by field VehiclePropertyIds.SEAT_BACKREST_ANGLE_1_MOVE is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#SEAT_BACKREST_ANGLE_1_POS:
+    Permission Car.PERMISSION_CONTROL_CAR_SEATS required by field VehiclePropertyIds.SEAT_BACKREST_ANGLE_1_POS is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#SEAT_BACKREST_ANGLE_2_MOVE:
+    Permission Car.PERMISSION_CONTROL_CAR_SEATS required by field VehiclePropertyIds.SEAT_BACKREST_ANGLE_2_MOVE is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#SEAT_BACKREST_ANGLE_2_POS:
+    Permission Car.PERMISSION_CONTROL_CAR_SEATS required by field VehiclePropertyIds.SEAT_BACKREST_ANGLE_2_POS is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#SEAT_BELT_BUCKLED:
+    Permission Car.PERMISSION_CONTROL_CAR_SEATS required by field VehiclePropertyIds.SEAT_BELT_BUCKLED is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#SEAT_BELT_HEIGHT_MOVE:
+    Permission Car.PERMISSION_CONTROL_CAR_SEATS required by field VehiclePropertyIds.SEAT_BELT_HEIGHT_MOVE is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#SEAT_BELT_HEIGHT_POS:
+    Permission Car.PERMISSION_CONTROL_CAR_SEATS required by field VehiclePropertyIds.SEAT_BELT_HEIGHT_POS is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#SEAT_DEPTH_MOVE:
+    Permission Car.PERMISSION_CONTROL_CAR_SEATS required by field VehiclePropertyIds.SEAT_DEPTH_MOVE is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#SEAT_DEPTH_POS:
+    Permission Car.PERMISSION_CONTROL_CAR_SEATS required by field VehiclePropertyIds.SEAT_DEPTH_POS is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#SEAT_FORE_AFT_MOVE:
+    Permission Car.PERMISSION_CONTROL_CAR_SEATS required by field VehiclePropertyIds.SEAT_FORE_AFT_MOVE is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#SEAT_FORE_AFT_POS:
+    Permission Car.PERMISSION_CONTROL_CAR_SEATS required by field VehiclePropertyIds.SEAT_FORE_AFT_POS is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#SEAT_HEADREST_ANGLE_MOVE:
+    Permission Car.PERMISSION_CONTROL_CAR_SEATS required by field VehiclePropertyIds.SEAT_HEADREST_ANGLE_MOVE is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#SEAT_HEADREST_ANGLE_POS:
+    Permission Car.PERMISSION_CONTROL_CAR_SEATS required by field VehiclePropertyIds.SEAT_HEADREST_ANGLE_POS is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#SEAT_HEADREST_FORE_AFT_MOVE:
+    Permission Car.PERMISSION_CONTROL_CAR_SEATS required by field VehiclePropertyIds.SEAT_HEADREST_FORE_AFT_MOVE is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#SEAT_HEADREST_FORE_AFT_POS:
+    Permission Car.PERMISSION_CONTROL_CAR_SEATS required by field VehiclePropertyIds.SEAT_HEADREST_FORE_AFT_POS is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#SEAT_HEADREST_HEIGHT_MOVE:
+    Permission Car.PERMISSION_CONTROL_CAR_SEATS required by field VehiclePropertyIds.SEAT_HEADREST_HEIGHT_MOVE is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#SEAT_HEADREST_HEIGHT_POS:
+    Permission Car.PERMISSION_CONTROL_CAR_SEATS required by field VehiclePropertyIds.SEAT_HEADREST_HEIGHT_POS is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#SEAT_HEIGHT_MOVE:
+    Permission Car.PERMISSION_CONTROL_CAR_SEATS required by field VehiclePropertyIds.SEAT_HEIGHT_MOVE is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#SEAT_HEIGHT_POS:
+    Permission Car.PERMISSION_CONTROL_CAR_SEATS required by field VehiclePropertyIds.SEAT_HEIGHT_POS is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#SEAT_LUMBAR_FORE_AFT_MOVE:
+    Permission Car.PERMISSION_CONTROL_CAR_SEATS required by field VehiclePropertyIds.SEAT_LUMBAR_FORE_AFT_MOVE is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#SEAT_LUMBAR_FORE_AFT_POS:
+    Permission Car.PERMISSION_CONTROL_CAR_SEATS required by field VehiclePropertyIds.SEAT_LUMBAR_FORE_AFT_POS is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#SEAT_LUMBAR_SIDE_SUPPORT_MOVE:
+    Permission Car.PERMISSION_CONTROL_CAR_SEATS required by field VehiclePropertyIds.SEAT_LUMBAR_SIDE_SUPPORT_MOVE is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#SEAT_LUMBAR_SIDE_SUPPORT_POS:
+    Permission Car.PERMISSION_CONTROL_CAR_SEATS required by field VehiclePropertyIds.SEAT_LUMBAR_SIDE_SUPPORT_POS is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#SEAT_MEMORY_SELECT:
+    Permission Car.PERMISSION_CONTROL_CAR_SEATS required by field VehiclePropertyIds.SEAT_MEMORY_SELECT is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#SEAT_MEMORY_SET:
+    Permission Car.PERMISSION_CONTROL_CAR_SEATS required by field VehiclePropertyIds.SEAT_MEMORY_SET is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#SEAT_OCCUPANCY:
+    Permission Car.PERMISSION_CONTROL_CAR_SEATS required by field VehiclePropertyIds.SEAT_OCCUPANCY is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#SEAT_TILT_MOVE:
+    Permission Car.PERMISSION_CONTROL_CAR_SEATS required by field VehiclePropertyIds.SEAT_TILT_MOVE is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#SEAT_TILT_POS:
+    Permission Car.PERMISSION_CONTROL_CAR_SEATS required by field VehiclePropertyIds.SEAT_TILT_POS is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#TIRE_PRESSURE:
+    Permission Car.PERMISSION_TIRES required by field VehiclePropertyIds.TIRE_PRESSURE is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#TRACTION_CONTROL_ACTIVE:
+    Permission Car.PERMISSION_CAR_DYNAMICS_STATE required by field VehiclePropertyIds.TRACTION_CONTROL_ACTIVE is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#TURN_SIGNAL_STATE:
+    Permission Car.PERMISSION_EXTERIOR_LIGHTS required by field VehiclePropertyIds.TURN_SIGNAL_STATE is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#VEHICLE_MAP_SERVICE:
+    Permission Car.PERMISSION_VMS_PUBLISHER required by field VehiclePropertyIds.VEHICLE_MAP_SERVICE is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#WINDOW_LOCK:
+    Permission Car.PERMISSION_CONTROL_CAR_WINDOWS required by field VehiclePropertyIds.WINDOW_LOCK is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#WINDOW_MOVE:
+    Permission Car.PERMISSION_CONTROL_CAR_WINDOWS required by field VehiclePropertyIds.WINDOW_MOVE is hidden or removed
+MissingPermission: android.car.VehiclePropertyIds#WINDOW_POS:
+    Permission Car.PERMISSION_CONTROL_CAR_WINDOWS required by field VehiclePropertyIds.WINDOW_POS is hidden or removed
diff --git a/car-lib/api/test-current.txt b/car-lib/api/test-current.txt
index 515bd93..09d9fd7 100644
--- a/car-lib/api/test-current.txt
+++ b/car-lib/api/test-current.txt
@@ -1,4 +1,30 @@
 // Signature format: 2.0
+package android.car {
+
+  public final class Car {
+    field public static final String CAR_USER_SERVICE = "car_user_service";
+  }
+
+  public final class CarAppFocusManager {
+    method public int[] getActiveAppTypes();
+  }
+
+  public class VehiclePropertyType {
+    field public static final int BOOLEAN = 2097152; // 0x200000
+    field public static final int BYTES = 7340032; // 0x700000
+    field public static final int FLOAT = 6291456; // 0x600000
+    field public static final int FLOAT_VEC = 6356992; // 0x610000
+    field public static final int INT32 = 4194304; // 0x400000
+    field public static final int INT32_VEC = 4259840; // 0x410000
+    field public static final int INT64 = 5242880; // 0x500000
+    field public static final int INT64_VEC = 5308416; // 0x510000
+    field public static final int MASK = 16711680; // 0xff0000
+    field public static final int MIXED = 14680064; // 0xe00000
+    field public static final int STRING = 1048576; // 0x100000
+  }
+
+}
+
 package android.car.content.pm {
 
   public final class CarPackageManager {
@@ -23,3 +49,31 @@
 
 }
 
+package android.car.user {
+
+  public final class CarUserManager {
+    method @RequiresPermission(anyOf={android.Manifest.permission.INTERACT_ACROSS_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS_FULL}) public void addListener(@NonNull java.util.concurrent.Executor, @NonNull android.car.user.CarUserManager.UserLifecycleListener);
+    method public int createUser(@Nullable String);
+    method public static String lifecycleEventTypeToString(int);
+    method @RequiresPermission(anyOf={android.Manifest.permission.INTERACT_ACROSS_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS_FULL}) public void removeListener(@NonNull android.car.user.CarUserManager.UserLifecycleListener);
+    method public void removeUser(int);
+    field public static final int USER_LIFECYCLE_EVENT_TYPE_STARTING = 1; // 0x1
+    field public static final int USER_LIFECYCLE_EVENT_TYPE_STOPPED = 6; // 0x6
+    field public static final int USER_LIFECYCLE_EVENT_TYPE_STOPPING = 5; // 0x5
+    field public static final int USER_LIFECYCLE_EVENT_TYPE_SWITCHING = 2; // 0x2
+    field public static final int USER_LIFECYCLE_EVENT_TYPE_UNLOCKED = 4; // 0x4
+    field public static final int USER_LIFECYCLE_EVENT_TYPE_UNLOCKING = 3; // 0x3
+  }
+
+  public final class CarUserManager.UserLifecycleEvent {
+    method public int getEventType();
+    method @Nullable public android.os.UserHandle getPreviousUserHandle();
+    method @NonNull public android.os.UserHandle getUserHandle();
+  }
+
+  public static interface CarUserManager.UserLifecycleListener {
+    method public void onEvent(@NonNull android.car.user.CarUserManager.UserLifecycleEvent);
+  }
+
+}
+
diff --git a/car-lib/src/android/car/AoapService.java b/car-lib/src/android/car/AoapService.java
index 0637e75..de21621 100644
--- a/car-lib/src/android/car/AoapService.java
+++ b/car-lib/src/android/car/AoapService.java
@@ -16,8 +16,6 @@
 
 package android.car;
 
-import static com.android.internal.util.Preconditions.checkNotNull;
-
 import android.annotation.IntDef;
 import android.annotation.MainThread;
 import android.annotation.NonNull;
@@ -39,6 +37,7 @@
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.ref.WeakReference;
+import java.util.Objects;
 
 /**
  * The service that must be implemented by USB AOAP handler system apps. The app must hold the
@@ -203,7 +202,7 @@
             switch (msg.what) {
                 case MSG_NEW_DEVICE_ATTACHED: {
                     int res = service.isDeviceSupported(
-                            checkNotNull(data.getParcelable(KEY_DEVICE)));
+                            Objects.requireNonNull(data.getParcelable(KEY_DEVICE)));
                     if (res != RESULT_OK && res != RESULT_DEVICE_NOT_SUPPORTED) {
                         throw new IllegalArgumentException("Result can not be " + res);
                     }
@@ -213,7 +212,7 @@
 
                 case MSG_CAN_SWITCH_TO_AOAP: {
                     int res = service.canSwitchToAoap(
-                            checkNotNull(data.getParcelable(KEY_DEVICE)));
+                            Objects.requireNonNull(data.getParcelable(KEY_DEVICE)));
                     if (res != RESULT_OK && res != RESULT_DEVICE_NOT_SUPPORTED
                             && res != RESULT_DO_NOT_SWITCH_TO_AOAP) {
                         throw new IllegalArgumentException("Result can not be " + res);
diff --git a/car-lib/src/android/car/Car.java b/car-lib/src/android/car/Car.java
index 086e5c8..72825a6 100644
--- a/car-lib/src/android/car/Car.java
+++ b/car-lib/src/android/car/Car.java
@@ -21,11 +21,16 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
 import android.annotation.SdkConstant;
 import android.annotation.SdkConstant.SdkConstantType;
+import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
+import android.annotation.TestApi;
 import android.app.Activity;
 import android.app.Service;
+import android.car.annotation.MandatoryFeature;
+import android.car.annotation.OptionalFeature;
 import android.car.cluster.CarInstrumentClusterManager;
 import android.car.cluster.ClusterActivityState;
 import android.car.content.pm.CarPackageManager;
@@ -42,11 +47,15 @@
 import android.car.media.CarAudioManager;
 import android.car.media.CarMediaManager;
 import android.car.navigation.CarNavigationStatusManager;
+import android.car.occupantawareness.OccupantAwarenessManager;
 import android.car.settings.CarConfigurationManager;
 import android.car.storagemonitoring.CarStorageMonitoringManager;
 import android.car.test.CarTestManagerBinderWrapper;
 import android.car.trust.CarTrustAgentEnrollmentManager;
+import android.car.user.CarUserManager;
+import android.car.vms.VmsClientManager;
 import android.car.vms.VmsSubscriberManager;
+import android.car.watchdog.CarWatchdogManager;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.ContextWrapper;
@@ -65,13 +74,16 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.Preconditions;
 
 import java.lang.annotation.ElementType;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;
+import java.lang.reflect.Constructor;
+import java.util.Collections;
 import java.util.HashMap;
+import java.util.List;
+import java.util.Objects;
 
 /**
  *   Top level car API for embedded Android Auto deployments.
@@ -86,35 +98,91 @@
      * @hide
      */
     public static final String CAR_SERVICE_BINDER_SERVICE_NAME = "car_service";
+
+    /**
+     * This represents AndroidManifest meta-data to tell that {@code Activity} is optimized for
+     * driving distraction.
+     *
+     * <p>Activities without this meta-data can be blocked while car is in moving / driving state.
+     *
+     * <p>Note that having this flag does not guarantee that the {@code Activity} will be always
+     * allowed for all driving states.
+     *
+     * <p>For this meta-data, android:value can be {@code true} (=optimized) or {@code false}.
+     *
+     * <p>Example usage:
+     * <xml><meta-data android:name="distractionOptimized" android:value="true"/></xml>
+     */
+    @SuppressLint("IntentName")
+    public static final String META_DATA_DISTRACTION_OPTIMIZED = "distractionOptimized";
+
+    /**
+     * This represents AndroidManifest meta-data to tell that {@code Application} requires specific
+     * car features to work.
+     *
+     * <p>Apps like launcher or installer app can use this information to filter out apps
+     * not usable in a specific car. This meta-data is not necessary for mandatory features.
+     *
+     * <p>For this meta-data, android:value should contain the feature name string defined by
+     * (@link android.car.annotation.OptionalFeature} or
+     * {@link android.car.annotation.ExperimentalFeature} annotations.
+     *
+     * <p>Example usage:
+     * <xml><meta-data android:name="requires-car-feature" android:value="diagnostic"/></xml>
+     */
+    @SuppressLint("IntentName")
+    public static final String META_DATA_REQUIRES_CAR_FEATURE = "requires-car-feature";
+
     /**
      * Service name for {@link CarSensorManager}, to be used in {@link #getCarManager(String)}.
      *
      * @deprecated  {@link CarSensorManager} is deprecated. Use {@link CarPropertyManager} instead.
      */
+    @MandatoryFeature
     @Deprecated
     public static final String SENSOR_SERVICE = "sensor";
 
     /** Service name for {@link CarInfoManager}, to be used in {@link #getCarManager(String)}. */
+    @MandatoryFeature
     public static final String INFO_SERVICE = "info";
 
     /** Service name for {@link CarAppFocusManager}. */
+    @MandatoryFeature
     public static final String APP_FOCUS_SERVICE = "app_focus";
 
     /** Service name for {@link CarPackageManager} */
+    @MandatoryFeature
     public static final String PACKAGE_SERVICE = "package";
 
     /** Service name for {@link CarAudioManager} */
+    @MandatoryFeature
     public static final String AUDIO_SERVICE = "audio";
 
     /** Service name for {@link CarNavigationStatusManager} */
+    @MandatoryFeature
     public static final String CAR_NAVIGATION_SERVICE = "car_navigation_service";
 
+    /** Service name for {@link CarOccupantZoneManager} */
+    @MandatoryFeature
+    public static final String CAR_OCCUPANT_ZONE_SERVICE = "car_occupant_zone_service";
+
+    /**
+     * Service name for {@link CarUserManager}
+     *
+     * @hide
+     */
+    @MandatoryFeature
+    @SystemApi
+    @TestApi
+    public static final String CAR_USER_SERVICE = "car_user_service";
+
     /**
      * Service name for {@link CarInstrumentClusterManager}
      *
      * @deprecated CarInstrumentClusterManager is being deprecated
      * @hide
      */
+    @MandatoryFeature
     @Deprecated
     public static final String CAR_INSTRUMENT_CLUSTER_SERVICE = "cluster_service";
 
@@ -124,6 +192,7 @@
      * @deprecated {@link CarCabinManager} is deprecated. Use {@link CarPropertyManager} instead.
      * @hide
      */
+    @MandatoryFeature
     @Deprecated
     @SystemApi
     public static final String CABIN_SERVICE = "cabin";
@@ -131,6 +200,7 @@
     /**
      * @hide
      */
+    @OptionalFeature
     @SystemApi
     public static final String DIAGNOSTIC_SERVICE = "diagnostic";
 
@@ -139,6 +209,7 @@
      * @deprecated {@link CarHvacManager} is deprecated. Use {@link CarPropertyManager} instead.
      * @hide
      */
+    @MandatoryFeature
     @Deprecated
     @SystemApi
     public static final String HVAC_SERVICE = "hvac";
@@ -146,18 +217,21 @@
     /**
      * @hide
      */
+    @MandatoryFeature
     @SystemApi
     public static final String POWER_SERVICE = "power";
 
     /**
      * @hide
      */
+    @MandatoryFeature
     @SystemApi
     public static final String PROJECTION_SERVICE = "projection";
 
     /**
      * Service name for {@link CarPropertyManager}
      */
+    @MandatoryFeature
     public static final String PROPERTY_SERVICE = "property";
 
     /**
@@ -167,6 +241,7 @@
      * Use {@link CarPropertyManager} instead.
      * @hide
      */
+    @MandatoryFeature
     @Deprecated
     @SystemApi
     public static final String VENDOR_EXTENSION_SERVICE = "vendor_extension";
@@ -174,11 +249,26 @@
     /**
      * @hide
      */
+    @MandatoryFeature
     public static final String BLUETOOTH_SERVICE = "car_bluetooth";
 
     /**
+     * Service name for {@link VmsClientManager}
+     *
      * @hide
      */
+    @OptionalFeature
+    @SystemApi
+    public static final String VEHICLE_MAP_SERVICE = "vehicle_map_service";
+
+    /**
+     * Service name for {@link VmsSubscriberManager}
+     *
+     * @deprecated {@link VmsSubscriberManager} is deprecated. Use {@link VmsClientManager} instead.
+     * @hide
+     */
+    @OptionalFeature
+    @Deprecated
     @SystemApi
     public static final String VMS_SUBSCRIBER_SERVICE = "vehicle_map_subscriber_service";
 
@@ -186,6 +276,7 @@
      * Service name for {@link CarDrivingStateManager}
      * @hide
      */
+    @MandatoryFeature
     @SystemApi
     public static final String CAR_DRIVING_STATE_SERVICE = "drivingstate";
 
@@ -194,6 +285,11 @@
      */
     public static final String CAR_UX_RESTRICTION_SERVICE = "uxrestriction";
 
+    /** @hide */
+    @OptionalFeature
+    @SystemApi
+    public static final String OCCUPANT_AWARENESS_SERVICE = "occupant_awareness";
+
     /**
      * Service name for {@link android.car.settings.CarConfigurationManager}
      */
@@ -203,6 +299,7 @@
      * Service name for {@link android.car.media.CarMediaManager}
      * @hide
      */
+    @MandatoryFeature
     public static final String CAR_MEDIA_SERVICE = "car_media";
 
     /**
@@ -210,11 +307,13 @@
      * Service name for {@link android.car.CarBugreportManager}
      * @hide
      */
+    @MandatoryFeature
     public static final String CAR_BUGREPORT_SERVICE = "car_bugreport";
 
     /**
      * @hide
      */
+    @OptionalFeature
     @SystemApi
     public static final String STORAGE_MONITORING_SERVICE = "storage_monitoring";
 
@@ -226,10 +325,19 @@
     public static final String CAR_TRUST_AGENT_ENROLLMENT_SERVICE = "trust_enroll";
 
     /**
+     * Service name for {@link android.car.watchdog.CarWatchdogManager}
+     * @hide
+     */
+    @MandatoryFeature
+    @SystemApi
+    public static final String CAR_WATCHDOG_SERVICE = "car_watchdog";
+
+    /**
      * Service for testing. This is system app only feature.
      * Service name for {@link CarTestManager}, to be used in {@link #getCarManager(String)}.
      * @hide
      */
+    @MandatoryFeature
     @SystemApi
     public static final String TEST_SERVICE = "car-service-test";
 
@@ -242,6 +350,14 @@
     /** Permission necessary to access car's energy information. */
     public static final String PERMISSION_ENERGY = "android.car.permission.CAR_ENERGY";
 
+    /**
+     * Permission necessary to change value of car's range remaining.
+     * @hide
+     */
+    @SystemApi
+    public static final String PERMISSION_ADJUST_RANGE_REMAINING =
+            "android.car.permission.ADJUST_RANGE_REMAINING";
+
     /** Permission necessary to access car's VIN information */
     public static final String PERMISSION_IDENTIFICATION =
             "android.car.permission.CAR_IDENTIFICATION";
@@ -259,7 +375,15 @@
     /** Permission necessary to access car's fuel door and ev charge port. */
     public static final String PERMISSION_ENERGY_PORTS = "android.car.permission.CAR_ENERGY_PORTS";
 
-    /** Permission necessary to read car's exterior lights information.
+    /**
+     * Permission necessary to control car's fuel door and ev charge port.
+     * @hide
+     */
+    @SystemApi
+    public static final String PERMISSION_CONTROL_ENERGY_PORTS =
+            "android.car.permission.CONTROL_CAR_ENERGY_PORTS";
+    /**
+     * Permission necessary to read car's exterior lights information.
      *  @hide
      */
     @SystemApi
@@ -337,6 +461,14 @@
     /** Permission necessary to use {@link CarInfoManager}. */
     public static final String PERMISSION_CAR_INFO = "android.car.permission.CAR_INFO";
 
+    /**
+     * Permission necessary to read information of vendor properties' permissions.
+     * @hide
+     */
+    @SystemApi
+    public static final String PERMISSION_READ_CAR_VENDOR_PERMISSION_INFO =
+            "android.car.permission.READ_CAR_VENDOR_PERMISSION_INFO";
+
     /** Permission necessary to read temperature of car's exterior environment. */
     public static final String PERMISSION_EXTERIOR_ENVIRONMENT =
             "android.car.permission.CAR_EXTERIOR_ENVIRONMENT";
@@ -528,6 +660,24 @@
             "android.car.permission.CAR_UX_RESTRICTIONS_CONFIGURATION";
 
     /**
+     * Permission necessary to listen to occupant awareness state {@link OccupantAwarenessManager}.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final String PERMISSION_READ_CAR_OCCUPANT_AWARENESS_STATE =
+            "android.car.permission.READ_CAR_OCCUPANT_AWARENESS_STATE";
+
+    /**
+     * Permission necessary to modify occupant awareness graph.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final String PERMISSION_CONTROL_CAR_OCCUPANT_AWARENESS_SYSTEM =
+            "android.car.permission.CONTROL_CAR_OCCUPANT_AWARENESS_SYSTEM";
+
+    /**
      * Permissions necessary to clear diagnostic information.
      *
      * @hide
@@ -545,6 +695,24 @@
     public static final String PERMISSION_CAR_ENROLL_TRUST =
             "android.car.permission.CAR_ENROLL_TRUST";
 
+    /**
+     * Permission necessary to dynamically enable / disable optional car features.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final String PERMISSION_CONTROL_CAR_FEATURES =
+            "android.car.permission.CONTROL_CAR_FEATURES";
+
+    /**
+     * Permission necessary to be car watchdog clients.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final String PERMISSION_USE_CAR_WATCHDOG =
+            "android.car.permission.USE_CAR_WATCHDOG";
+
     /** Type of car connection: platform runs directly in car. */
     public static final int CONNECTION_TYPE_EMBEDDED = 5;
 
@@ -568,25 +736,22 @@
     /**
      * Used as a string extra field with {@link #CAR_INTENT_ACTION_MEDIA_TEMPLATE} to specify the
      * MediaBrowserService that user wants to start the media on.
-     *
-     * @hide
      */
     public static final String CAR_EXTRA_MEDIA_COMPONENT =
             "android.car.intent.extra.MEDIA_COMPONENT";
 
     /**
-     * Used as a string extra field with {@link #CAR_INTENT_ACTION_MEDIA_TEMPLATE} to specify the
-     * media app that user wants to start the media on. Note: this is not the templated media app.
      *
-     * This is being deprecated. Use {@link #CAR_EXTRA_MEDIA_COMPONENT} instead.
+     * @deprecated Use{@link #CAR_EXTRA_MEDIA_COMPONENT} instead.
+     * @removed Using this for specifying MediaBrowserService was not supported since API level 29
+     * and above. Apps must use {@link #CAR_EXTRA_MEDIA_COMPONENT} instead.
      */
+    @Deprecated
     public static final String CAR_EXTRA_MEDIA_PACKAGE = "android.car.intent.extra.MEDIA_PACKAGE";
 
     /**
      * Used as a string extra field of media session to specify the service corresponding to the
      * session.
-     *
-     * @hide
      */
     public static final String CAR_EXTRA_BROWSE_SERVICE_FOR_SESSION =
             "android.media.session.BROWSE_SERVICE";
@@ -630,7 +795,6 @@
      * called with ready set to false, access to car service should stop until car service is ready
      * again from {@link CarServiceLifecycleListener#onLifecycleChanged(Car, boolean)} call
      * with {@code ready} set to {@code true}.</p>
-     * @hide
      */
     public interface CarServiceLifecycleListener {
         /**
@@ -650,14 +814,12 @@
     /**
      * {@link #createCar(Context, Handler, long, CarServiceLifecycleListener)}'s
      * waitTimeoutMs value to use to wait forever inside the call until car service is ready.
-     * @hide
      */
     public static final long CAR_WAIT_TIMEOUT_WAIT_FOREVER = -1;
 
     /**
      * {@link #createCar(Context, Handler, long, CarServiceLifecycleListener)}'s
      * waitTimeoutMs value to use to skip any waiting inside the call.
-     * @hide
      */
     public static final long CAR_WAIT_TIMEOUT_DO_NOT_WAIT = 0;
 
@@ -681,6 +843,43 @@
     @Target({ElementType.TYPE_USE})
     public @interface StateTypeEnum {}
 
+    /**
+     * The enabling request was successful and requires reboot to take effect.
+     * @hide
+     */
+    @SystemApi
+    public static final int FEATURE_REQUEST_SUCCESS = 0;
+    /**
+     * The requested feature is already enabled or disabled as requested. No need to reboot the
+     * system.
+     * @hide
+     */
+    @SystemApi
+    public static final int FEATURE_REQUEST_ALREADY_IN_THE_STATE = 1;
+    /**
+     * The requested feature is mandatory cannot be enabled or disabled. It is always enabled.
+     * @hide
+     */
+    @SystemApi
+    public static final int FEATURE_REQUEST_MANDATORY = 2;
+    /**
+     * The requested feature is not available and cannot be enabled or disabled.
+     * @hide
+     */
+    @SystemApi
+    public static final int FEATURE_REQUEST_NOT_EXISTING = 3;
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = "FEATURE_REQUEST_", value = {
+            FEATURE_REQUEST_SUCCESS,
+            FEATURE_REQUEST_ALREADY_IN_THE_STATE,
+            FEATURE_REQUEST_MANDATORY,
+            FEATURE_REQUEST_NOT_EXISTING,
+    })
+    @Target({ElementType.TYPE_USE})
+    public @interface FeaturerRequestEnum {}
+
     private static final boolean DBG = false;
 
     private final Context mContext;
@@ -741,6 +940,8 @@
 
         @Override
         public void onServiceDisconnected(ComponentName name) {
+            // Car service can pick up feature changes after restart.
+            mFeatures.resetCache();
             synchronized (mLock) {
                 if (mConnectionState  == STATE_DISCONNECTED) {
                     // can happen when client calls disconnect before onServiceDisconnected call.
@@ -774,6 +975,8 @@
 
     private final Handler mMainThreadEventHandler;
 
+    private final CarFeatures mFeatures = new CarFeatures();
+
     /**
      * A factory method that creates Car instance for all Car API access.
      * @param context App's Context. This should not be null. If you are passing
@@ -932,15 +1135,13 @@
      *                      to {@link #CAR_WAIT_TIMEOUT_WAIT_FOREVER} will block the call forever
      *                      until the car service is ready. Setting any positive value will be
      *                      interpreted as timeout value.
-     *
-     * @hide
      */
     @NonNull
     public static Car createCar(@NonNull Context context,
             @Nullable Handler handler, long waitTimeoutMs,
             @NonNull CarServiceLifecycleListener statusChangeListener) {
         assertNonNullContext(context);
-        Preconditions.checkNotNull(statusChangeListener);
+        Objects.requireNonNull(statusChangeListener);
         Car car = null;
         IBinder service = null;
         boolean started = false;
@@ -1021,7 +1222,7 @@
     }
 
     private static void assertNonNullContext(Context context) {
-        Preconditions.checkNotNull(context);
+        Objects.requireNonNull(context);
         if (context instanceof ContextWrapper
                 && ((ContextWrapper) context).getBaseContext() == null) {
             throw new NullPointerException(
@@ -1185,7 +1386,7 @@
                                 + serviceName);
                         return null;
                     }
-                    manager = createCarManager(serviceName, binder);
+                    manager = createCarManagerLocked(serviceName, binder);
                     if (manager == null) {
                         Log.w(TAG_CAR, "getCarManager could not create manager for service:"
                                         + serviceName);
@@ -1209,6 +1410,146 @@
         return CONNECTION_TYPE_EMBEDDED;
     }
 
+    /**
+     * Checks if {code featureName} is enabled in this car.
+     *
+     * <p>For optional features, this can return false if the car cannot support it. Optional
+     * features should be used only when they are supported.</p>
+     *
+     * <p>For mandatory features, this will always return true.
+     */
+    public boolean isFeatureEnabled(@NonNull String featureName) {
+        ICar service;
+        synchronized (mLock) {
+            if (mService == null) {
+                return false;
+            }
+            service = mService;
+        }
+        return mFeatures.isFeatureEnabled(service, featureName);
+    }
+
+    /**
+     * Enables the requested car feature. It becomes no-op if the feature is already enabled. The
+     * change take effects after reboot.
+     *
+     * @return true if the feature is enabled or was enabled before.
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(PERMISSION_CONTROL_CAR_FEATURES)
+    @FeaturerRequestEnum
+    public int enableFeature(@NonNull String featureName) {
+        ICar service;
+        synchronized (mLock) {
+            if (mService == null) {
+                return FEATURE_REQUEST_NOT_EXISTING;
+            }
+            service = mService;
+        }
+        try {
+            return service.enableFeature(featureName);
+        } catch (RemoteException e) {
+            return handleRemoteExceptionFromCarService(e, FEATURE_REQUEST_NOT_EXISTING);
+        }
+    }
+
+    /**
+     * Disables the requested car feature. It becomes no-op if the feature is already disabled. The
+     * change take effects after reboot.
+     *
+     * @return true if the request succeeds or if it was already disabled.
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(PERMISSION_CONTROL_CAR_FEATURES)
+    @FeaturerRequestEnum
+    public int disableFeature(@NonNull String featureName) {
+        ICar service;
+        synchronized (mLock) {
+            if (mService == null) {
+                return FEATURE_REQUEST_NOT_EXISTING;
+            }
+            service = mService;
+        }
+        try {
+            return service.disableFeature(featureName);
+        } catch (RemoteException e) {
+            return handleRemoteExceptionFromCarService(e, FEATURE_REQUEST_NOT_EXISTING);
+        }
+    }
+
+    /**
+     * Returns all =enabled features at the moment including mandatory, optional, and
+     * experimental features.
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(PERMISSION_CONTROL_CAR_FEATURES)
+    @NonNull public List<String> getAllEnabledFeatures() {
+        ICar service;
+        synchronized (mLock) {
+            if (mService == null) {
+                return Collections.EMPTY_LIST;
+            }
+            service = mService;
+        }
+        try {
+            return service.getAllEnabledFeatures();
+        } catch (RemoteException e) {
+            return handleRemoteExceptionFromCarService(e, Collections.EMPTY_LIST);
+        }
+    }
+
+    /**
+     * Returns the list of disabled features which are not effective yet. Those features will be
+     * disabled when system restarts later.
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(PERMISSION_CONTROL_CAR_FEATURES)
+    @NonNull public List<String> getAllPendingDisabledFeatures() {
+        ICar service;
+        synchronized (mLock) {
+            if (mService == null) {
+                return Collections.EMPTY_LIST;
+            }
+            service = mService;
+        }
+        try {
+            return service.getAllPendingDisabledFeatures();
+        } catch (RemoteException e) {
+            return handleRemoteExceptionFromCarService(e, Collections.EMPTY_LIST);
+        }
+    }
+
+    /**
+     * Returns the list of enabled features which are not effective yet. Those features will be
+     * enabled when system restarts later.
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(PERMISSION_CONTROL_CAR_FEATURES)
+    @NonNull public List<String> getAllPendingEnabledFeatures() {
+        ICar service;
+        synchronized (mLock) {
+            if (mService == null) {
+                return Collections.EMPTY_LIST;
+            }
+            service = mService;
+        }
+        try {
+            return service.getAllPendingEnabledFeatures();
+        } catch (RemoteException e) {
+            return handleRemoteExceptionFromCarService(e, Collections.EMPTY_LIST);
+        }
+    }
+
     /** @hide */
     Context getContext() {
         return mContext;
@@ -1290,7 +1631,7 @@
     }
 
     @Nullable
-    private CarManagerBase createCarManager(String serviceName, IBinder binder) {
+    private CarManagerBase createCarManagerLocked(String serviceName, IBinder binder) {
         CarManagerBase manager = null;
         switch (serviceName) {
             case AUDIO_SERVICE:
@@ -1308,6 +1649,9 @@
             case PACKAGE_SERVICE:
                 manager = new CarPackageManager(this, binder);
                 break;
+            case CAR_OCCUPANT_ZONE_SERVICE:
+                manager = new CarOccupantZoneManager(this, binder);
+                break;
             case CAR_NAVIGATION_SERVICE:
                 manager = new CarNavigationStatusManager(this, binder);
                 break;
@@ -1340,8 +1684,12 @@
                  * only pass binder wrapper so that CarTestManager can be constructed outside. */
                 manager = new CarTestManagerBinderWrapper(this, binder);
                 break;
+            case VEHICLE_MAP_SERVICE:
+                manager = new VmsClientManager(this, binder);
+                break;
             case VMS_SUBSCRIBER_SERVICE:
-                manager = new VmsSubscriberManager(this, binder);
+                manager = VmsSubscriberManager.wrap(this,
+                        (VmsClientManager) getCarManager(VEHICLE_MAP_SERVICE));
                 break;
             case BLUETOOTH_SERVICE:
                 manager = new CarBluetoothManager(this, binder);
@@ -1355,6 +1703,9 @@
             case CAR_UX_RESTRICTION_SERVICE:
                 manager = new CarUxRestrictionsManager(this, binder);
                 break;
+            case OCCUPANT_AWARENESS_SERVICE:
+                manager = new OccupantAwarenessManager(this, binder);
+                break;
             case CAR_CONFIGURATION_SERVICE:
                 manager = new CarConfigurationManager(this, binder);
                 break;
@@ -1367,12 +1718,47 @@
             case CAR_BUGREPORT_SERVICE:
                 manager = new CarBugreportManager(this, binder);
                 break;
+            case CAR_USER_SERVICE:
+                manager = new CarUserManager(this, binder);
+                break;
+            case CAR_WATCHDOG_SERVICE:
+                manager = new CarWatchdogManager(this, binder);
+                break;
             default:
+                // Experimental or non-existing
+                String className = null;
+                try {
+                    className = mService.getCarManagerClassForFeature(serviceName);
+                } catch (RemoteException e) {
+                    handleRemoteExceptionFromCarService(e);
+                    return null;
+                }
+                if (className == null) {
+                    Log.e(TAG_CAR, "Cannot construct CarManager for service:" + serviceName
+                            + " : no class defined");
+                    return null;
+                }
+                manager = constructCarManager(className, binder);
                 break;
         }
         return manager;
     }
 
+    private CarManagerBase constructCarManager(String className, IBinder binder) {
+        try {
+            // Should use class loader for the Context as class loader for car api does not
+            // see the class.
+            ClassLoader loader = mContext.getClassLoader();
+            Class managerClass = loader.loadClass(className);
+            Constructor constructor = managerClass.getConstructor(Car.class, IBinder.class);
+            CarManagerBase manager = (CarManagerBase) constructor.newInstance(this, binder);
+            return manager;
+        } catch (Exception e) {
+            Log.e(TAG_CAR, "Cannot construct CarManager, class:" + className, e);
+            return null;
+        }
+    }
+
     private void startCarService() {
         Intent intent = new Intent();
         intent.setPackage(CAR_SERVICE_PACKAGE);
diff --git a/car-lib/src/android/car/CarAppFocusManager.java b/car-lib/src/android/car/CarAppFocusManager.java
index b8936af..ef9ddf4 100644
--- a/car-lib/src/android/car/CarAppFocusManager.java
+++ b/car-lib/src/android/car/CarAppFocusManager.java
@@ -17,6 +17,7 @@
 package android.car;
 
 import android.annotation.IntDef;
+import android.annotation.TestApi;
 import android.os.IBinder;
 import android.os.RemoteException;
 
@@ -204,6 +205,7 @@
      * Returns application types currently active in the system.
      * @hide
      */
+    @TestApi
     public int[] getActiveAppTypes() {
         try {
             return mService.getActiveAppTypes();
diff --git a/car-lib/src/android/car/CarBugreportManager.java b/car-lib/src/android/car/CarBugreportManager.java
index 99f2c7c..68c11d3 100644
--- a/car-lib/src/android/car/CarBugreportManager.java
+++ b/car-lib/src/android/car/CarBugreportManager.java
@@ -25,13 +25,12 @@
 import android.os.ParcelFileDescriptor;
 import android.os.RemoteException;
 
-import com.android.internal.util.Preconditions;
-
 import libcore.io.IoUtils;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.ref.WeakReference;
+import java.util.Objects;
 
 /**
  * Car specific bugreport manager. Only available for userdebug and eng builds.
@@ -178,9 +177,9 @@
             @NonNull ParcelFileDescriptor output,
             @NonNull ParcelFileDescriptor extraOutput,
             @NonNull CarBugreportManagerCallback callback) {
-        Preconditions.checkNotNull(output);
-        Preconditions.checkNotNull(extraOutput);
-        Preconditions.checkNotNull(callback);
+        Objects.requireNonNull(output);
+        Objects.requireNonNull(extraOutput);
+        Objects.requireNonNull(callback);
         try {
             CarBugreportManagerCallbackWrapper wrapper =
                     new CarBugreportManagerCallbackWrapper(callback, getEventHandler());
diff --git a/car-lib/src/android/car/CarFeatures.java b/car-lib/src/android/car/CarFeatures.java
new file mode 100644
index 0000000..3fe0f1b
--- /dev/null
+++ b/car-lib/src/android/car/CarFeatures.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.car;
+
+import android.annotation.NonNull;
+import android.car.annotation.OptionalFeature;
+import android.os.RemoteException;
+import android.util.ArrayMap;
+
+import com.android.internal.annotations.GuardedBy;
+
+/**
+ * This class declares all car features which does not have API as {@code String}.
+ *
+ * <p>Note that all {@code Car*Managers'} feature string is their service name
+ * {@code Car.*_SERVICE.}
+ * For features with APIs, subfeature {@code String} will be also defined inside the API.
+ *
+ * <p>To prevent potential conflict in feature / subfeature name, all feature name should use
+ * implementation package name space like {@code com.android.car.user.FeatureA} unless it is
+ * {@code Car.*_SERVICE}.
+ *
+ * <p>To define a subfeature, main feature should be already declared and sub feature name should
+ * have the format of "main_feature/sub_feature_name". Note that feature name cannot use '/' and
+ * should use only alphabet, digit, '_', '-' and '.'.
+ *
+ * @hide
+ */
+public final class CarFeatures {
+    /**
+     * Service to show initial user notice screen. This feature has no API and thus defined here.
+     * @hide */
+    @OptionalFeature
+    public static String FEATURE_CAR_USER_NOTICE_SERVICE =
+            "com.android.car.user.CarUserNoticeService";
+
+    // Local cache for making feature query fast.
+    // Key: feature name, value: supported or not.
+    @GuardedBy("mCachedFeatures")
+    private final ArrayMap<String, Boolean> mCachedFeatures = new ArrayMap<>();
+
+    /** @hide */
+    boolean isFeatureEnabled(@NonNull ICar service, @NonNull String featureName) {
+        synchronized (mCachedFeatures) {
+            Boolean supported = mCachedFeatures.get(featureName);
+            if (supported != null) {
+                return supported;
+            }
+        }
+        // Need to fetch from car service. This should happen only once
+        try {
+            boolean supported = service.isFeatureEnabled(featureName);
+            synchronized (mCachedFeatures) {
+                mCachedFeatures.put(featureName, Boolean.valueOf(supported));
+            }
+            return supported;
+        } catch (RemoteException e) {
+            // car service has crashed. return false.
+        }
+        return false;
+    }
+
+    /** @hide */
+    void resetCache() {
+        synchronized (mCachedFeatures) {
+            mCachedFeatures.clear();
+        }
+    }
+}
diff --git a/car-lib/src/android/car/CarInfoManager.java b/car-lib/src/android/car/CarInfoManager.java
index e9a170d..6b2ddd7 100644
--- a/car-lib/src/android/car/CarInfoManager.java
+++ b/car-lib/src/android/car/CarInfoManager.java
@@ -61,7 +61,6 @@
     public static final String BASIC_INFO_KEY_VEHICLE_ID = "android.car.vehicle-id";
     /**
      * Key for product configuration info.
-     * @FutureFeature Cannot drop due to usage in non-flag protected place.
      * @hide
      */
     @ValueTypeDef(type = String.class)
@@ -203,7 +202,7 @@
                     connectorTypes[i] = EvConnectorType.MENNEKES;
                     break;
                 case 3: // IEC_TYPE_3_AC
-                    connectorTypes[i] = 11;
+                    connectorTypes[i] = EvConnectorType.SCAME;
                     break;
                 case 4: // IEC_TYPE_4_DC
                     connectorTypes[i] = EvConnectorType.CHADEMO;
@@ -227,7 +226,7 @@
                     connectorTypes[i] = EvConnectorType.GBT;
                     break;
                 case 11: // GBT_DC
-                    connectorTypes[i] = 10;
+                    connectorTypes[i] = EvConnectorType.GBT_DC;
                     break;
                 case 101: // OTHER
                     connectorTypes[i] = EvConnectorType.OTHER;
@@ -261,7 +260,7 @@
     }
 
     /** @hide */
-    CarInfoManager(Car car, IBinder service) {
+    public CarInfoManager(Car car, IBinder service) {
         super(car);
         ICarProperty mCarPropertyService = ICarProperty.Stub.asInterface(service);
         mCarPropertyMgr = new CarPropertyManager(car, mCarPropertyService);
@@ -271,6 +270,4 @@
     public void onCarDisconnected() {
         mCarPropertyMgr.onCarDisconnected();
     }
-
-
 }
diff --git a/car-lib/src/android/car/CarOccupantZoneManager.aidl b/car-lib/src/android/car/CarOccupantZoneManager.aidl
new file mode 100644
index 0000000..a03c9af
--- /dev/null
+++ b/car-lib/src/android/car/CarOccupantZoneManager.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.car;
+
+parcelable CarOccupantZoneManager.OccupantZoneInfo;
diff --git a/car-lib/src/android/car/CarOccupantZoneManager.java b/car-lib/src/android/car/CarOccupantZoneManager.java
new file mode 100644
index 0000000..1667a05
--- /dev/null
+++ b/car-lib/src/android/car/CarOccupantZoneManager.java
@@ -0,0 +1,502 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.car;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.annotation.UserIdInt;
+import android.hardware.display.DisplayManager;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.util.Log;
+import android.view.Display;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+/**
+ * API to get information on displays and users in the car.
+ */
+public class CarOccupantZoneManager extends CarManagerBase {
+
+    private static final String TAG = CarOccupantZoneManager.class.getSimpleName();
+
+    /** Display type is not known. In some system, some displays may be just public display without
+     *  any additional information and such displays will be treated as unknown.
+     */
+    public static final int DISPLAY_TYPE_UNKNOWN = 0;
+
+    /** Main display users are interacting with. UI for the user will be launched to this display by
+     *  default. {@link Display#DEFAULT_DISPLAY} will be always have this type. But there can be
+     *  multiple of this type as each passenger can have their own main display.
+     */
+    public static final int DISPLAY_TYPE_MAIN = 1;
+
+    /** Instrument cluster display. This may exist only for driver. */
+    public static final int DISPLAY_TYPE_INSTRUMENT_CLUSTER = 2;
+
+    /** Head Up Display. This may exist only for driver. */
+    public static final int DISPLAY_TYPE_HUD = 3;
+
+    /** Dedicated display for showing IME for {@link #DISPLAY_TYPE_MAIN} */
+    public static final int DISPLAY_TYPE_INPUT = 4;
+
+    /** Auxiliary display which can provide additional screen for {@link #DISPLAY_TYPE_MAIN}.
+     *  Activity running in {@link #DISPLAY_TYPE_MAIN} may use {@link android.app.Presentation} to
+     *  show additional information.
+     */
+    public static final int DISPLAY_TYPE_AUXILIARY = 5;
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = "DISPLAY_TYPE_", value = {
+            DISPLAY_TYPE_UNKNOWN,
+            DISPLAY_TYPE_MAIN,
+            DISPLAY_TYPE_INSTRUMENT_CLUSTER,
+            DISPLAY_TYPE_HUD,
+            DISPLAY_TYPE_INPUT,
+            DISPLAY_TYPE_AUXILIARY,
+    })
+    @Target({ElementType.TYPE_USE})
+    public @interface DisplayTypeEnum {}
+
+    /** @hide */
+    public static final int OCCUPANT_TYPE_INVALID = -1;
+
+    /** Represents driver. There can be only one driver for the system. */
+    public static final int OCCUPANT_TYPE_DRIVER = 0;
+
+    /** Represents front passengers who sits in front side of car. Most cars will have only
+     *  one passenger of this type but this can be multiple. */
+    public static final int OCCUPANT_TYPE_FRONT_PASSENGER = 1;
+
+    /** Represents passengers in rear seats. There can be multiple passengers of this type. */
+    public static final int OCCUPANT_TYPE_REAR_PASSENGER = 2;
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = "OCCUPANT_TYPE_", value = {
+            OCCUPANT_TYPE_DRIVER,
+            OCCUPANT_TYPE_FRONT_PASSENGER,
+            OCCUPANT_TYPE_REAR_PASSENGER,
+    })
+    @Target({ElementType.TYPE_USE})
+    public @interface OccupantTypeEnum {}
+
+    /**
+     * Represents an occupant zone in a car.
+     *
+     * <p>Each occupant does not necessarily represent single person but it is for mapping to one
+     * set of displays. For example, for display located in center rear seat, both left and right
+     * side passengers may use it but it is abstracted as a single occupant zone.</p>
+     */
+    public static final class OccupantZoneInfo implements Parcelable {
+        /** @hide */
+        public static final int INVALID_ZONE_ID = -1;
+        /**
+         * This is an unique id to distinguish each occupant zone.
+         *
+         * <p>This can be helpful to distinguish different zones when {@link #occupantType} and
+         * {@link #seat} are the same for multiple occupant / passenger zones.</p>
+         *
+         * <p>This id will remain the same for the same zone across configuration changes like
+         * user switching or display changes</p>
+         */
+        public int zoneId;
+        /** Represents type of passenger */
+        @OccupantTypeEnum
+        public final int occupantType;
+        /**
+         * Represents seat assigned for the occupant. In some system, this can have value of
+         * {@link VehicleAreaSeat#SEAT_UNKNOWN}.
+         */
+        @VehicleAreaSeat.Enum
+        public final int seat;
+
+        /** @hide */
+        public OccupantZoneInfo(int zoneId, @OccupantTypeEnum int occupantType,
+                @VehicleAreaSeat.Enum int seat) {
+            this.zoneId = zoneId;
+            this.occupantType = occupantType;
+            this.seat = seat;
+        }
+
+        /** @hide */
+        public OccupantZoneInfo(Parcel in) {
+            zoneId = in.readInt();
+            occupantType = in.readInt();
+            seat = in.readInt();
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            dest.writeInt(zoneId);
+            dest.writeInt(occupantType);
+            dest.writeInt(seat);
+        }
+
+        @Override
+        public boolean equals(Object other) {
+            if (this == other) {
+                return true;
+            }
+            if (!(other instanceof OccupantZoneInfo)) {
+                return false;
+            }
+            OccupantZoneInfo that = (OccupantZoneInfo) other;
+            return zoneId == that.zoneId && occupantType == that.occupantType
+                    && seat == that.seat;
+        }
+
+        @Override
+        public int hashCode() {
+            int hash = 23;
+            hash = hash * 17 + zoneId;
+            hash = hash * 17 + occupantType;
+            hash = hash * 17 + seat;
+            return hash;
+        }
+
+        public static final Parcelable.Creator<OccupantZoneInfo> CREATOR =
+                new Parcelable.Creator<OccupantZoneInfo>() {
+            public OccupantZoneInfo createFromParcel(Parcel in) {
+                return new OccupantZoneInfo(in);
+            }
+
+            public OccupantZoneInfo[] newArray(int size) {
+                return new OccupantZoneInfo[size];
+            }
+        };
+
+        @Override
+        public String toString() {
+            StringBuilder b = new StringBuilder(64);
+            b.append("OccupantZoneInfo{zoneId=");
+            b.append(zoneId);
+            b.append(" type=");
+            b.append(occupantType);
+            b.append(" seat=");
+            b.append(Integer.toHexString(seat));
+            b.append("}");
+            return b.toString();
+        }
+    }
+
+    /** Zone config change caused by display changes. A display could have been added / removed.
+     *  Besides change in display itself. this can lead into removal / addition of passenger zones.
+     */
+    public static final int ZONE_CONFIG_CHANGE_FLAG_DISPLAY = 0x1;
+
+    /** Zone config change caused by user change. Assigned user for passenger zones have changed. */
+    public static final int ZONE_CONFIG_CHANGE_FLAG_USER = 0x2;
+
+    /** Zone config change caused by audio zone change.
+     * Assigned audio zone for passenger zones have changed.
+     **/
+    public static final int ZONE_CONFIG_CHANGE_FLAG_AUDIO = 0x4;
+
+    /** @hide */
+    @IntDef(flag = true, prefix = { "ZONE_CONFIG_CHANGE_FLAG_" }, value = {
+            ZONE_CONFIG_CHANGE_FLAG_DISPLAY,
+            ZONE_CONFIG_CHANGE_FLAG_USER,
+            ZONE_CONFIG_CHANGE_FLAG_AUDIO,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @interface ZoneConfigChangeFlags {}
+
+    /**
+     * Listener to monitor any Occupant Zone configuration change. The configuration change can
+     * involve some displays removed or new displays added. Also it can happen when assigned user
+     * for any zone changes.
+     */
+    public interface OccupantZoneConfigChangeListener {
+
+        /**
+         * Configuration for occupant zones has changed. Apps should re-check all
+         * occupant zone configs. This can be caused by events like user switching and
+         * display addition / removal.
+         *
+         * @param changeFlags Reason for the zone change.
+         */
+        void onOccupantZoneConfigChanged(@ZoneConfigChangeFlags int changeFlags);
+    }
+
+    private final DisplayManager mDisplayManager;
+    private final EventHandler mEventHandler;
+
+    private final ICarOccupantZone mService;
+
+    private final ICarOccupantZoneCallbackImpl mBinderCallback;
+
+    private final CopyOnWriteArrayList<OccupantZoneConfigChangeListener> mListeners =
+            new CopyOnWriteArrayList<>();
+
+    /** @hide */
+    @VisibleForTesting
+    public CarOccupantZoneManager(Car car, IBinder service) {
+        super(car);
+        mService = ICarOccupantZone.Stub.asInterface(service);
+        mBinderCallback = new ICarOccupantZoneCallbackImpl(this);
+        mDisplayManager = getContext().getSystemService(DisplayManager.class);
+        mEventHandler = new EventHandler(getEventHandler().getLooper());
+    }
+
+    /**
+     * Returns all available occupants in the system. If no occupant zone is defined in the system
+     * or none is available at the moment, it will return empty list.
+     */
+    @NonNull
+    public List<OccupantZoneInfo> getAllOccupantZones() {
+        try {
+            return mService.getAllOccupantZones();
+        } catch (RemoteException e) {
+            return handleRemoteExceptionFromCarService(e, Collections.emptyList());
+        }
+    }
+
+    /**
+     * Returns all displays assigned for the given occupant zone. If no display is available for
+     * the passenger, it will return empty list.
+     */
+    @NonNull
+    public List<Display> getAllDisplaysForOccupant(@NonNull OccupantZoneInfo occupantZone) {
+        assertNonNullOccupant(occupantZone);
+        try {
+            int[] displayIds = mService.getAllDisplaysForOccupantZone(occupantZone.zoneId);
+            ArrayList<Display> displays = new ArrayList<>(displayIds.length);
+            for (int i = 0; i < displayIds.length; i++) {
+                // quick sanity check while getDisplay can still handle invalid display
+                if (displayIds[i] == Display.INVALID_DISPLAY) {
+                    continue;
+                }
+                Display display = mDisplayManager.getDisplay(displayIds[i]);
+                if (display != null) { // necessary as display list could have changed.
+                    displays.add(display);
+                }
+            }
+            return displays;
+        } catch (RemoteException e) {
+            return handleRemoteExceptionFromCarService(e, Collections.emptyList());
+        }
+    }
+
+    /**
+     * Gets the display for the occupant for the specified display type, or returns {@code null}
+     * if no display matches the requirements.
+     *
+     * @param displayType This should be a valid display type and passing
+     *                    {@link #DISPLAY_TYPE_UNKNOWN} will always lead into {@link null} return.
+     */
+    @Nullable
+    public Display getDisplayForOccupant(@NonNull OccupantZoneInfo occupantZone,
+            @DisplayTypeEnum int displayType) {
+        assertNonNullOccupant(occupantZone);
+        try {
+            int displayId = mService.getDisplayForOccupant(occupantZone.zoneId, displayType);
+            // quick sanity check while getDisplay can still handle invalid display
+            if (displayId == Display.INVALID_DISPLAY) {
+                return null;
+            }
+            return mDisplayManager.getDisplay(displayId);
+        } catch (RemoteException e) {
+            return handleRemoteExceptionFromCarService(e, null);
+        }
+    }
+
+    /**
+     * Gets the audio zone id for the occupant, or returns
+     * {@code CarAudioManager.INVALID_AUDIO_ZONE} if no audio zone matches the requirements.
+     * throws InvalidArgumentException if occupantZone does not exist.
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS)
+    public int getAudioZoneIdForOccupant(@NonNull OccupantZoneInfo occupantZone) {
+        assertNonNullOccupant(occupantZone);
+        try {
+            return mService.getAudioZoneIdForOccupant(occupantZone.zoneId);
+        } catch (RemoteException e) {
+            return handleRemoteExceptionFromCarService(e, null);
+        }
+    }
+
+    /**
+     * Gets occupant for the audio zone id, or returns {@code null}
+     * if no audio zone matches the requirements.
+     *
+     * @hide
+     */
+    @Nullable
+    @SystemApi
+    @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS)
+    public OccupantZoneInfo getOccupantForAudioZoneId(int audioZoneId) {
+        try {
+            return mService.getOccupantForAudioZoneId(audioZoneId);
+        } catch (RemoteException e) {
+            return handleRemoteExceptionFromCarService(e, null);
+        }
+    }
+
+    /**
+     * Returns assigned display type for the display. It will return {@link #DISPLAY_TYPE_UNKNOWN}
+     * if type is not specified or if display is no longer available.
+     */
+    @DisplayTypeEnum
+    public int getDisplayType(@NonNull Display display) {
+        assertNonNullDisplay(display);
+        try {
+            return mService.getDisplayType(display.getDisplayId());
+        } catch (RemoteException e) {
+            return handleRemoteExceptionFromCarService(e, DISPLAY_TYPE_UNKNOWN);
+        }
+    }
+
+    /**
+     * Returns android user id assigned for the given zone. It will return
+     * {@link UserHandle#USER_NULL} if user is not assigned or if zone is not available.
+     */
+    @UserIdInt
+    public int getUserForOccupant(@NonNull OccupantZoneInfo occupantZone) {
+        assertNonNullOccupant(occupantZone);
+        try {
+            return mService.getUserForOccupant(occupantZone.zoneId);
+        } catch (RemoteException e) {
+            return handleRemoteExceptionFromCarService(e, UserHandle.USER_NULL);
+        }
+    }
+
+    private void assertNonNullOccupant(OccupantZoneInfo occupantZone) {
+        if (occupantZone == null) {
+            throw new IllegalArgumentException("null OccupantZoneInfo");
+        }
+    }
+
+    private void assertNonNullDisplay(Display display) {
+        if (display == null) {
+            throw new IllegalArgumentException("null Display");
+        }
+    }
+
+    /**
+     * Registers the listener for occupant zone config change. Registering multiple listeners are
+     * allowed.
+     */
+    public void registerOccupantZoneConfigChangeListener(
+            @NonNull OccupantZoneConfigChangeListener listener) {
+        if (mListeners.addIfAbsent(listener)) {
+            if (mListeners.size() == 1) {
+                try {
+                    mService.registerCallback(mBinderCallback);
+                } catch (RemoteException e) {
+                    handleRemoteExceptionFromCarService(e);
+                }
+            }
+        }
+    }
+
+    /**
+     * Unregisters the listener. Listeners not registered before will be ignored.
+     */
+    public void unregisterOccupantZoneConfigChangeListener(
+            @NonNull OccupantZoneConfigChangeListener listener) {
+        if (mListeners.remove(listener)) {
+            if (mListeners.size() == 0) {
+                try {
+                    mService.unregisterCallback(mBinderCallback);
+                } catch (RemoteException ignored) {
+                    // ignore for unregistering
+                }
+            }
+        }
+    }
+
+    private void handleOnOccupantZoneConfigChanged(int flags) {
+        for (OccupantZoneConfigChangeListener listener : mListeners) {
+            listener.onOccupantZoneConfigChanged(flags);
+        }
+    }
+
+    private final class EventHandler extends Handler {
+        private static final int MSG_ZONE_CHANGE = 1;
+
+        private EventHandler(Looper looper) {
+            super(looper);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_ZONE_CHANGE:
+                    handleOnOccupantZoneConfigChanged(msg.arg1);
+                    break;
+                default:
+                    Log.e(TAG, "Unknown msg not handdled:" + msg.what);
+                    break;
+            }
+        }
+
+        private void dispatchOnOccupantZoneConfigChanged(int flags) {
+            sendMessage(obtainMessage(MSG_ZONE_CHANGE, flags, 0));
+        }
+    }
+
+    private static class ICarOccupantZoneCallbackImpl extends ICarOccupantZoneCallback.Stub {
+        private final WeakReference<CarOccupantZoneManager> mManager;
+
+        private ICarOccupantZoneCallbackImpl(CarOccupantZoneManager manager) {
+            mManager = new WeakReference<>(manager);
+        }
+
+        @Override
+        public void onOccupantZoneConfigChanged(int flags) {
+            CarOccupantZoneManager manager = mManager.get();
+            if (manager != null) {
+                manager.mEventHandler.dispatchOnOccupantZoneConfigChanged(flags);
+            }
+        }
+    }
+
+    /** @hide */
+    @Override
+    public void onCarDisconnected() {
+        // nothing to do
+    }
+}
diff --git a/car-lib/src/android/car/CarProjectionManager.java b/car-lib/src/android/car/CarProjectionManager.java
index bc4107f..9356a24 100644
--- a/car-lib/src/android/car/CarProjectionManager.java
+++ b/car-lib/src/android/car/CarProjectionManager.java
@@ -27,6 +27,7 @@
 import android.car.projection.ProjectionStatus;
 import android.car.projection.ProjectionStatus.ProjectionState;
 import android.content.Intent;
+import android.net.wifi.SoftApConfiguration;
 import android.net.wifi.WifiConfiguration;
 import android.os.Binder;
 import android.os.Bundle;
@@ -56,6 +57,7 @@
 import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Set;
 import java.util.concurrent.Executor;
 
@@ -264,7 +266,7 @@
     @RequiresPermission(Car.PERMISSION_CAR_PROJECTION)
     public void registerProjectionListener(@NonNull CarProjectionListener listener,
             int voiceSearchFilter) {
-        Preconditions.checkNotNull(listener, "listener cannot be null");
+        Objects.requireNonNull(listener, "listener cannot be null");
         synchronized (mLock) {
             if (mListener == null || mVoiceSearchFilter != voiceSearchFilter) {
                 addKeyEventHandler(
@@ -462,7 +464,7 @@
      */
     @RequiresPermission(Car.PERMISSION_CAR_PROJECTION)
     public void registerProjectionRunner(@NonNull Intent serviceIntent) {
-        Preconditions.checkNotNull("serviceIntent cannot be null");
+        Objects.requireNonNull("serviceIntent cannot be null");
         synchronized (mLock) {
             try {
                 mService.registerProjectionRunner(serviceIntent);
@@ -479,7 +481,7 @@
      */
     @RequiresPermission(Car.PERMISSION_CAR_PROJECTION)
     public void unregisterProjectionRunner(@NonNull Intent serviceIntent) {
-        Preconditions.checkNotNull("serviceIntent cannot be null");
+        Objects.requireNonNull("serviceIntent cannot be null");
         synchronized (mLock) {
             try {
                 mService.unregisterProjectionRunner(serviceIntent);
@@ -506,7 +508,7 @@
      */
     @RequiresPermission(Car.PERMISSION_CAR_PROJECTION)
     public void startProjectionAccessPoint(@NonNull ProjectionAccessPointCallback callback) {
-        Preconditions.checkNotNull(callback, "callback cannot be null");
+        Objects.requireNonNull(callback, "callback cannot be null");
         synchronized (mLock) {
             Looper looper = getEventHandler().getLooper();
             ProjectionAccessPointCallbackProxy proxy =
@@ -572,7 +574,7 @@
     @RequiresPermission(Car.PERMISSION_CAR_PROJECTION)
     public boolean requestBluetoothProfileInhibit(
             @NonNull BluetoothDevice device, int profile) {
-        Preconditions.checkNotNull(device, "device cannot be null");
+        Objects.requireNonNull(device, "device cannot be null");
         try {
             return mService.requestBluetoothProfileInhibit(device, profile, mToken);
         } catch (RemoteException e) {
@@ -590,7 +592,7 @@
      */
     @RequiresPermission(Car.PERMISSION_CAR_PROJECTION)
     public boolean releaseBluetoothProfileInhibit(@NonNull BluetoothDevice device, int profile) {
-        Preconditions.checkNotNull(device, "device cannot be null");
+        Objects.requireNonNull(device, "device cannot be null");
         try {
             return mService.releaseBluetoothProfileInhibit(device, profile, mToken);
         } catch (RemoteException e) {
@@ -608,7 +610,7 @@
      */
     @RequiresPermission(Car.PERMISSION_CAR_PROJECTION)
     public void updateProjectionStatus(@NonNull ProjectionStatus status) {
-        Preconditions.checkNotNull(status, "status cannot be null");
+        Objects.requireNonNull(status, "status cannot be null");
         try {
             mService.updateProjectionStatus(status, mToken);
         } catch (RemoteException e) {
@@ -626,7 +628,7 @@
      */
     @RequiresPermission(Car.PERMISSION_CAR_PROJECTION_STATUS)
     public void registerProjectionStatusListener(@NonNull ProjectionStatusListener listener) {
-        Preconditions.checkNotNull(listener, "listener cannot be null");
+        Objects.requireNonNull(listener, "listener cannot be null");
         synchronized (mLock) {
             mProjectionStatusListeners.add(listener);
 
@@ -657,7 +659,7 @@
      */
     @RequiresPermission(Car.PERMISSION_CAR_PROJECTION_STATUS)
     public void unregisterProjectionStatusListener(@NonNull ProjectionStatusListener listener) {
-        Preconditions.checkNotNull(listener, "listener cannot be null");
+        Objects.requireNonNull(listener, "listener cannot be null");
         synchronized (mLock) {
             if (!mProjectionStatusListeners.remove(listener)
                     || !mProjectionStatusListeners.isEmpty()) {
@@ -709,8 +711,33 @@
         public static final int ERROR_INCOMPATIBLE_MODE = 3;
         public static final int ERROR_TETHERING_DISALLOWED = 4;
 
-        /** Called when access point started successfully. */
-        public void onStarted(WifiConfiguration wifiConfiguration) {}
+        /**
+         * Called when access point started successfully.
+         * <p>
+         * Note that AP detail may contain configuration which is cannot be represented
+         * by the legacy WifiConfiguration, in such cases a null will be returned.
+         * For example:
+         * <li> SoftAp band in {@link WifiConfiguration.apBand} only supports
+         * 2GHz, 5GHz, 2GHz+5GHz bands, so conversion is limited to these bands. </li>
+         * <li> SoftAp security type in {@link WifiConfiguration.KeyMgmt} only supports
+         * NONE, WPA2_PSK, so conversion is limited to these security type.</li>
+         *
+         * @param wifiConfiguration  the {@link WifiConfiguration} of the current hotspot.
+         * @deprecated This callback is deprecated. Use {@link #onStarted(SoftApConfiguration))}
+         * instead.
+         */
+        @Deprecated
+        public void onStarted(@Nullable WifiConfiguration wifiConfiguration) {}
+
+        /**
+         * Called when access point started successfully.
+         *
+         * @param softApConfiguration the {@link SoftApConfiguration} of the current hotspot.
+         */
+        public void onStarted(@NonNull SoftApConfiguration softApConfiguration) {
+            onStarted(softApConfiguration.toWifiConfiguration());
+        }
+
         /** Called when access point is stopped. No events will be sent after that. */
         public void onStopped() {}
         /** Called when access point failed to start. No events will be sent after that. */
@@ -745,7 +772,7 @@
 
                     switch (msg.what) {
                         case PROJECTION_AP_STARTED:
-                            WifiConfiguration config = (WifiConfiguration) msg.obj;
+                            SoftApConfiguration config = (SoftApConfiguration) msg.obj;
                             if (config == null) {
                                 Log.e(TAG, LOG_PREFIX + "config cannot be null.");
                                 callback.onFailed(ProjectionAccessPointCallback.ERROR_GENERIC);
diff --git a/car-lib/src/android/car/EvConnectorType.java b/car-lib/src/android/car/EvConnectorType.java
index 81b9e1f..a3f2c3a 100644
--- a/car-lib/src/android/car/EvConnectorType.java
+++ b/car-lib/src/android/car/EvConnectorType.java
@@ -26,9 +26,6 @@
 public final class EvConnectorType {
     /**
      * List of EV Connector Types used in {@link CarInfoManager#getEvConnectorTypes()}.
-     * Beside connector types are listed here, there are two more EvConnectorTypes.
-     * The value of GBT_DC faster charging standard is 10.
-     * The value of IEC_TYPE_3_AC standard is 11.
      * If a vehicle does not know the type, it will return UNKNOWN.
      * The vehicle returns OTHER when no other types apply.
      * <b>Note:</b> The connector types in Java API have different values than the ones in VHAL.
@@ -52,15 +49,9 @@
     public static final int TESLA_SUPERCHARGER = 8;
     /** GBT_AC Fast Charging Standard */
     public static final int GBT = 9;
-    /**
-     * Map to GBT_DC in VHAL
-     * @hide
-     */
+    /** GBT_DC Fast Charging Standard */
     public static final int GBT_DC = 10;
-    /**
-     * Map to IEC_TYPE_3_AC in VHAL
-     * @hide
-     */
+    /** IEC_TYPE_3_AC connector */
     public static final int SCAME = 11;
 
     /**
@@ -80,6 +71,8 @@
         TESLA_HPWC,
         TESLA_SUPERCHARGER,
         GBT,
+        GBT_DC,
+        SCAME,
         OTHER
     })
     @Retention(RetentionPolicy.SOURCE)
diff --git a/car-lib/src/android/car/ICar.aidl b/car-lib/src/android/car/ICar.aidl
index 811034f..a62d8fa 100644
--- a/car-lib/src/android/car/ICar.aidl
+++ b/car-lib/src/android/car/ICar.aidl
@@ -19,34 +19,65 @@
 /** @hide */
 interface ICar {
     // All oneway methods are called from system server and should be placed in top positions.
-    // Do not change the order of oneway methods as system server make binder call based on this
-    // order.
+    // Do not change the number of oneway methods as system server make binder call based on this
+    // order - if you change them, you need to change the constants on CarServiceHelperService.
 
     /**
      * IBinder is ICarServiceHelper but passed as IBinder due to aidl hidden.
-     *
-     * This should be the 1st method. Do not change the order.
      */
     oneway void setCarServiceHelper(in IBinder helper) = 0;
+
+    /**
+     * Notify of user lifecycle events.
+     *
+     * @param eventType - type as defined by CarUserManager.UserLifecycleEventType
+     * @param timestampMs - when the event happened
+     * @param fromUserId - user id of previous user when type is SWITCHING (or UserHandle.USER_NULL)
+     * @param toUserId - user id of new user.
+     */
+    oneway void onUserLifecycleEvent(int eventType, long timestampMs, int fromUserId,
+            int toUserId) = 1;
+
+    /**
+     * Notify when first user was unlocked, for metrics (and lifecycle) purposes.
+     *
+     * @param userId - id of first non-system user locked
+     * @param timestampMs - when the user was unlocked
+     * @param duration - how long it took to unlock (from SystemServer start)
+     */
+    oneway void onFirstUserUnlocked(int userId, long timestampMs, long duration) = 2;
+
+
+    // TODO(b/145689885): 2 method below are deprecated (onUserLifecycleEvent covers then) so
+    // they're have higher codes to make it easier to add other
+
     /**
      * Notify lock / unlock of user id to car service.
      * unlocked: 1 if unlocked 0 otherwise.
-     *
-     * This should be the 2nd method. Do not change the order.
      */
-    oneway void setUserLockStatus(in int userHandle, in int unlocked) = 1;
+    oneway void setUserLockStatus(in int userId, in int unlocked) = 9;
 
     /**
      * Notify of user switching.  This is called only for foreground users when the user is starting
      * to boot.
      *
-     * @param userHandle -  user handle of new user.
-     *
-     * This should be the 3rd method. Do not change the order.
+     * @param userId - user id of new user.
      */
-    oneway void onSwitchUser(in int userHandle) = 2;
+    oneway void onSwitchUser(in int userId) = 10;
 
-    IBinder getCarService(in String serviceName) = 3;
-    int getCarConnectionType() = 4;
 
+    // Methods below start on 11 to make it easier to add more oneway methods above
+    IBinder getCarService(in String serviceName) = 11;
+    int getCarConnectionType() = 12;
+    boolean isFeatureEnabled(in String featureName) = 13;
+    int enableFeature(in String featureName) = 14;
+    int disableFeature(in String featureName) = 15;
+    List<String> getAllEnabledFeatures() = 16;
+    List<String> getAllPendingDisabledFeatures() = 17;
+    List<String> getAllPendingEnabledFeatures() = 18;
+    /**
+     * Get class name for experimental feature. Class should have constructor taking (Car, IBinder)
+     * and should inherit CarManagerBase.
+     */
+    String getCarManagerClassForFeature(in String featureName) = 19;
 }
diff --git a/car-lib/src/android/car/ICarOccupantZone.aidl b/car-lib/src/android/car/ICarOccupantZone.aidl
new file mode 100644
index 0000000..904bc84
--- /dev/null
+++ b/car-lib/src/android/car/ICarOccupantZone.aidl
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.car;
+
+import android.car.CarOccupantZoneManager;
+import android.car.ICarOccupantZoneCallback;
+import android.view.DisplayInfo;
+
+/** @hide */
+interface ICarOccupantZone {
+    List<CarOccupantZoneManager.OccupantZoneInfo> getAllOccupantZones();
+    int getAudioZoneIdForOccupant(in int occupantZoneId);
+    int[] getAllDisplaysForOccupantZone(in int occupantZoneId);
+    int getDisplayForOccupant(in int occupantZoneId, in int displayType);
+    CarOccupantZoneManager.OccupantZoneInfo getOccupantForAudioZoneId(in int audioZoneId);
+    int getDisplayType(in int displayId);
+    int getUserForOccupant(in int occupantZoneId);
+    int getOccupantZoneIdForUserId(in int userId);
+    void setAudioZoneIdsForOccupantZoneIds(in int[] audioZoneIds, in int[] occupantZoneIds);
+    void registerCallback(in ICarOccupantZoneCallback callback);
+    void unregisterCallback(in ICarOccupantZoneCallback callback);
+}
diff --git a/car-lib/src/android/car/ICarOccupantZoneCallback.aidl b/car-lib/src/android/car/ICarOccupantZoneCallback.aidl
new file mode 100644
index 0000000..c87c48b
--- /dev/null
+++ b/car-lib/src/android/car/ICarOccupantZoneCallback.aidl
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.car;
+
+/** @hide */
+oneway interface ICarOccupantZoneCallback {
+    void onOccupantZoneConfigChanged(in int flags);
+}
\ No newline at end of file
diff --git a/car-lib/src/android/car/ICarUserService.aidl b/car-lib/src/android/car/ICarUserService.aidl
index 82eab72..d59c17b 100644
--- a/car-lib/src/android/car/ICarUserService.aidl
+++ b/car-lib/src/android/car/ICarUserService.aidl
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2017 The Android Open Source Project
+ * Copyright (C) 2019 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -15,11 +15,20 @@
  */
 
 package android.car;
-import android.car.ICarBluetoothUserService;
-import android.car.ILocationManagerProxy;
+
+import android.content.pm.UserInfo;
+import com.android.internal.os.IResultReceiver;
 
 /** @hide */
 interface ICarUserService {
-    ICarBluetoothUserService getBluetoothUserService();
-    ILocationManagerProxy getLocationManagerProxy();
+    UserInfo createDriver(in String name, boolean admin);
+    UserInfo createPassenger(in String name, int driverId);
+    boolean switchDriver(int driverId);
+    List<UserInfo> getAllDrivers();
+    List<UserInfo> getPassengers(int driverId);
+    boolean startPassenger(int passengerId, int zoneId);
+    boolean stopPassenger(int passengerId);
+    oneway void setLifecycleListenerForUid(in IResultReceiver listener);
+    oneway void resetLifecycleListenerForUid();
+    oneway void getInitialUserInfo(int requestType, int timeoutMs, in IResultReceiver receiver);
 }
diff --git a/car-lib/src/android/car/IExperimentalCar.aidl b/car-lib/src/android/car/IExperimentalCar.aidl
new file mode 100644
index 0000000..18cbb0d
--- /dev/null
+++ b/car-lib/src/android/car/IExperimentalCar.aidl
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.car;
+
+import android.car.IExperimentalCarHelper;
+
+/** @hide */
+oneway interface IExperimentalCar {
+    /**
+     * Initialize the experimental car service.
+     * @param helper After init, experimental car service should return binder, class names for
+     *               enabled features with all features available through helper.onInitComplete().
+     * @param enabledFeatures Currently enabled features. If this feature is not available any more,
+     *                        helper.onInitComplete should drop it from started features.
+     */
+    void init(in IExperimentalCarHelper helper, in List<String> enabledFeatures);
+}
diff --git a/car-lib/src/android/car/IExperimentalCarHelper.aidl b/car-lib/src/android/car/IExperimentalCarHelper.aidl
new file mode 100644
index 0000000..4275c51
--- /dev/null
+++ b/car-lib/src/android/car/IExperimentalCarHelper.aidl
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.car;
+
+/** @hide */
+interface IExperimentalCarHelper {
+    /**
+     * Notify the completion of init to car service.
+     * @param allAvailableFeatures All features available in the package.
+     * @param startedFeatures Started features. This is a subset of enabledFeatures passed through
+     *        IExperimentalCar.init(..). Only available features should be started.
+     * @param classNames Car*Manager class names for all started experimental features. Class name
+     *        can be null if the feature does not have Car*Manager (=internal feature).
+     * @param binders Car*Manager binders for all started experimental features. Binder can be null
+     *        if the feature does not have Car*Manager.
+     */
+    void onInitComplete(in List<String> allAvailableFeatures, in List<String> startedFeatures,
+            in List<String> classNames, in List<IBinder> binders);
+}
diff --git a/car-lib/src/android/car/IPerUserCarService.aidl b/car-lib/src/android/car/IPerUserCarService.aidl
new file mode 100644
index 0000000..fa66173
--- /dev/null
+++ b/car-lib/src/android/car/IPerUserCarService.aidl
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.car;
+
+import android.car.ICarBluetoothUserService;
+import android.car.ILocationManagerProxy;
+
+/** @hide */
+interface IPerUserCarService {
+    ICarBluetoothUserService getBluetoothUserService();
+    ILocationManagerProxy getLocationManagerProxy();
+}
diff --git a/car-lib/src/android/car/VehicleAreaSeat.java b/car-lib/src/android/car/VehicleAreaSeat.java
index e1c6720..792ca27 100644
--- a/car-lib/src/android/car/VehicleAreaSeat.java
+++ b/car-lib/src/android/car/VehicleAreaSeat.java
@@ -66,4 +66,31 @@
     public @interface Enum {}
     private VehicleAreaSeat() {}
 
+    /** @hide */
+    public static final int SIDE_LEFT = -1;
+    /** @hide */
+    public static final int SIDE_CENTER = 0;
+    /** @hide */
+    public static final int SIDE_RIGHT = 1;
+    /**
+     * Convert row number and side into {@link Enum}.
+     *
+     * @param rowNumber should be 1, 2 or 3
+     * @param side {@link #SIDE_LEFT}. {@link #SIDE_CENTER}, {@link #SIDE_RIGHT}.
+     *
+     * @hide */
+    @Enum
+    public static int fromRowAndSide(int rowNumber, int side) {
+        if (rowNumber < 1 || rowNumber > 3) {
+            return SEAT_UNKNOWN;
+        }
+        if (side < -1 || side > 1) {
+            return SEAT_UNKNOWN;
+        }
+        int seat = 0x1;
+        seat = seat << ((rowNumber - 1) * 4);
+        seat = seat << (side + 1);
+        return seat;
+    }
+
 }
diff --git a/car-lib/src/android/car/VehicleAreaWheel.java b/car-lib/src/android/car/VehicleAreaWheel.java
index 9e66ed5..d58381d 100644
--- a/car-lib/src/android/car/VehicleAreaWheel.java
+++ b/car-lib/src/android/car/VehicleAreaWheel.java
@@ -15,21 +15,50 @@
  */
 package android.car;
 
-import android.annotation.SystemApi;
+import android.annotation.IntDef;
+import android.car.hardware.CarPropertyConfig;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 
 /**
- * VehicleAreaWheel is an abstraction for the wheels on a car.  It exists to isolate the java APIs
- * from the VHAL definitions.
- * @hide
+ * Object used to indicate area value for car properties which have area type
+ * {@link VehicleAreaType#VEHICLE_AREA_TYPE_WHEEL}.
+ * <p>
+ * The constants defined by {@link VehicleAreaWheel} indicate the position for area type
+ * {@link VehicleAreaType#VEHICLE_AREA_TYPE_WHEEL}. A property can have a single or a combination of
+ * positions. Developers can query the position using
+ * {@link android.car.hardware.property.CarPropertyManager#getAreaId(int, int)}.
+ * </p><p>
+ * Refer to {@link CarPropertyConfig#getAreaIds()} for more information about areaId.
+ * </p>
  */
-@SystemApi
+
+// This class is only designed to provide constants for VehicleAreaWheel. The constants should
+// exactly be same as VehicleAreaWheel in /hardware/interfaces/automotive/vehicle/2.0/types.hal.
 public final class VehicleAreaWheel {
+    /** Unknown wheel*/
     public static final int WHEEL_UNKNOWN = 0x00;
+    /** Constant for left front wheel.*/
     public static final int WHEEL_LEFT_FRONT = 0x01;
+    /** Constant for right front wheel.*/
     public static final int WHEEL_RIGHT_FRONT = 0x02;
+    /** Constant for left rear wheel.*/
     public static final int WHEEL_LEFT_REAR = 0x04;
+    /** Constant for right rear wheel.*/
     public static final int WHEEL_RIGHT_REAR = 0x08;
 
+    /** @hide */
+    @IntDef(prefix = {"WHEEL_"}, value = {
+            WHEEL_UNKNOWN,
+            WHEEL_LEFT_FRONT,
+            WHEEL_RIGHT_FRONT,
+            WHEEL_LEFT_REAR,
+            WHEEL_RIGHT_REAR
+    })
+    @Retention(RetentionPolicy.SOURCE)
+
+    public  @interface Enum {}
     private VehicleAreaWheel() {}
 }
 
diff --git a/car-lib/src/android/car/VehicleGear.java b/car-lib/src/android/car/VehicleGear.java
new file mode 100644
index 0000000..8634aac
--- /dev/null
+++ b/car-lib/src/android/car/VehicleGear.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.car;
+
+import android.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * List of enums for vehicle gears.
+ */
+public final class VehicleGear {
+
+    /**
+     *  GEAR_* represents meaning of value for {@link VehiclePropertyIds#GEAR_SELECTION} and
+     *  {@link VehiclePropertyIds#CURRENT_GEAR}. {@link VehicleGear#GEAR_PARK} and
+     *  {@link VehicleGear#GEAR_DRIVE} only apply to the {@link VehiclePropertyIds#GEAR_SELECTION}
+     *  property for a vehicle with an automatic transmission.
+     */
+    public static final int GEAR_UNKNOWN    = 0x0000;
+    public static final int GEAR_NEUTRAL    = 0x0001;
+    public static final int GEAR_REVERSE    = 0x0002;
+    public static final int GEAR_PARK       = 0x0004;
+    public static final int GEAR_DRIVE      = 0x0008;
+    public static final int GEAR_FIRST      = 0x0010;
+    public static final int GEAR_SECOND     = 0x0020;
+    public static final int GEAR_THIRD      = 0x0040;
+    public static final int GEAR_FOURTH     = 0x0080;
+    public static final int GEAR_FIFTH      = 0x0100;
+    public static final int GEAR_SIXTH      = 0x0200;
+    public static final int GEAR_SEVENTH    = 0x0400;
+    public static final int GEAR_EIGHTH     = 0x0800;
+    public static final int GEAR_NINTH      = 0x1000;
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({
+        GEAR_UNKNOWN,
+        GEAR_NEUTRAL,
+        GEAR_REVERSE,
+        GEAR_PARK,
+        GEAR_DRIVE,
+        GEAR_FIRST,
+        GEAR_SECOND,
+        GEAR_THIRD,
+        GEAR_FOURTH,
+        GEAR_FIFTH,
+        GEAR_SIXTH,
+        GEAR_SEVENTH,
+        GEAR_EIGHTH,
+        GEAR_NINTH,
+    })
+    public @interface Enum {}
+    private VehicleGear() {}
+
+    /**
+     * @param o Integer
+     * @return String
+     */
+    public static  String toString(int o) {
+        if (o == GEAR_UNKNOWN) {
+            return "GEAR_UNKNOWN";
+        }
+        if (o == GEAR_NEUTRAL) {
+            return "GEAR_NEUTRAL";
+        }
+        if (o == GEAR_REVERSE) {
+            return "GEAR_REVERSE";
+        }
+        if (o == GEAR_PARK) {
+            return "GEAR_PARK";
+        }
+        if (o == GEAR_DRIVE) {
+            return "GEAR_DRIVE";
+        }
+        if (o == GEAR_FIRST) {
+            return "GEAR_FIRST";
+        }
+        if (o == GEAR_SECOND) {
+            return "GEAR_SECOND";
+        }
+        if (o == GEAR_THIRD) {
+            return "GEAR_THIRD";
+        }
+        if (o == GEAR_FOURTH) {
+            return "GEAR_FOURTH";
+        }
+        if (o == GEAR_FIFTH) {
+            return "GEAR_FIFTH";
+        }
+        if (o == GEAR_SIXTH) {
+            return "GEAR_SIXTH";
+        }
+        if (o == GEAR_SEVENTH) {
+            return "GEAR_SEVENTH";
+        }
+        if (o == GEAR_EIGHTH) {
+            return "GEAR_EIGHTH";
+        }
+        if (o == GEAR_NINTH) {
+            return "GEAR_NINTH";
+        }
+        return "0x" + Integer.toHexString(o);
+    }
+}
diff --git a/car-lib/src/android/car/VehiclePropertyIds.java b/car-lib/src/android/car/VehiclePropertyIds.java
index ada2b2d..60b3b0a 100644
--- a/car-lib/src/android/car/VehiclePropertyIds.java
+++ b/car-lib/src/android/car/VehiclePropertyIds.java
@@ -16,6 +16,8 @@
 
 package android.car;
 
+import android.annotation.RequiresPermission;
+
 /**
  * Copy from android.hardware.automotive.vehicle-V2.0-java_gen_java/gen/android/hardware/automotive
  * /vehicle/V2_0. Need to update this file when vehicle propertyId is changed in VHAL.
@@ -30,67 +32,112 @@
      * VIN of vehicle
      * Requires permission: {@link Car#PERMISSION_IDENTIFICATION}.
      */
+    @RequiresPermission(Car.PERMISSION_IDENTIFICATION)
     public static final int INFO_VIN = 286261504;
     /**
      * Manufacturer of vehicle
      * Requires permission: {@link Car#PERMISSION_CAR_INFO}.
      */
+    @RequiresPermission(Car.PERMISSION_CAR_INFO)
     public static final int INFO_MAKE = 286261505;
     /**
      * Model of vehicle
      * Requires permission: {@link Car#PERMISSION_CAR_INFO}.
      */
+    @RequiresPermission(Car.PERMISSION_CAR_INFO)
     public static final int INFO_MODEL = 286261506;
     /**
      * Model year of vehicle.
      * Requires permission: {@link Car#PERMISSION_CAR_INFO}.
      */
+    @RequiresPermission(Car.PERMISSION_CAR_INFO)
     public static final int INFO_MODEL_YEAR = 289407235;
     /**
      * Fuel capacity of the vehicle in milliliters
      * Requires permission: {@link Car#PERMISSION_CAR_INFO}.
      */
+    @RequiresPermission(Car.PERMISSION_CAR_INFO)
     public static final int INFO_FUEL_CAPACITY = 291504388;
     /**
      * List of fuels the vehicle may use
      * Requires permission: {@link Car#PERMISSION_CAR_INFO}.
      */
+    @RequiresPermission(Car.PERMISSION_CAR_INFO)
     public static final int INFO_FUEL_TYPE = 289472773;
     /**
      * Battery capacity of the vehicle, if EV or hybrid.  This is the nominal
      * battery capacity when the vehicle is new.
      * Requires permission: {@link Car#PERMISSION_CAR_INFO}.
      */
+    @RequiresPermission(Car.PERMISSION_CAR_INFO)
     public static final int INFO_EV_BATTERY_CAPACITY = 291504390;
     /**
-     * List of connectors this EV may use
+     * List of connectors this vehicle may use
+     *
+     * <p>Applications can query the property value by
+     * {@link android.car.hardware.property.CarPropertyManager#getIntArrayProperty(int, int)}. The
+     * return value is an integer array containing enums in {@link EvConnectorType}
+     *
      * Requires permission: {@link Car#PERMISSION_CAR_INFO}.
      */
+    @RequiresPermission(Car.PERMISSION_CAR_INFO)
     public static final int INFO_EV_CONNECTOR_TYPE = 289472775;
     /**
      * Fuel door location
+     *
+     * <p> Applications can query the property value by
+     * {@link android.car.hardware.property.CarPropertyManager#getIntProperty(int, int)}. The return
+     * value is one of enums in {@link PortLocationType}.
+     *
      * Requires permission: {@link Car#PERMISSION_CAR_INFO}.
      */
+    @RequiresPermission(Car.PERMISSION_CAR_INFO)
     public static final int INFO_FUEL_DOOR_LOCATION = 289407240;
     /**
      * EV port location
+     *
+     * <p> Applications can query the property value by
+     * {@link android.car.hardware.property.CarPropertyManager#getIntProperty(int, int)}. The return
+     * value is one of enums in {@link PortLocationType}.
+     *
      * Requires permission: {@link Car#PERMISSION_CAR_INFO}.
      */
+    @RequiresPermission(Car.PERMISSION_CAR_INFO)
     public static final int INFO_EV_PORT_LOCATION = 289407241;
     /**
+     * Multiple EV port locations
+     *
+     * <p> Applications can query the property value by
+     * {@link android.car.hardware.property.CarPropertyManager#getIntArrayProperty(int, int)}. The
+     * return value is an integer array containing enums in {@link PortLocationType}.
+     *
+     * Requires permission: {@link Car#PERMISSION_CAR_INFO}.
+     */
+    @RequiresPermission(Car.PERMISSION_CAR_INFO)
+    public static final int INFO_MULTI_EV_PORT_LOCATIONS = 289472780;
+    /**
      * Driver's seat location
      * Requires permission: {@link Car#PERMISSION_CAR_INFO}.
      */
+    @RequiresPermission(Car.PERMISSION_CAR_INFO)
     public static final int INFO_DRIVER_SEAT = 356516106;
     /**
+     * Vehicle's exterior dimensions.
+     * Requires permission: {@link Car#PERMISSION_CAR_INFO}.
+     */
+    @RequiresPermission(Car.PERMISSION_CAR_INFO)
+    public static final int INFO_EXTERIOR_DIMENSIONS = 289472779;
+    /**
      * Current odometer value of the vehicle
      * Requires permission: {@link Car#PERMISSION_MILEAGE}.
      */
+    @RequiresPermission(Car.PERMISSION_MILEAGE)
     public static final int PERF_ODOMETER = 291504644;
     /**
      * Speed of the vehicle
      * Requires permission: {@link Car#PERMISSION_SPEED}.
      */
+    @RequiresPermission(Car.PERMISSION_SPEED)
     public static final int PERF_VEHICLE_SPEED = 291504647;
     /**
      * Speed of the vehicle for displays
@@ -99,73 +146,105 @@
      * usually displayed on the speedometer.
      * Requires permission: {@link Car#PERMISSION_SPEED}.
      */
+    @RequiresPermission(Car.PERMISSION_SPEED)
     public static final int PERF_VEHICLE_SPEED_DISPLAY = 291504648;
     /**
-     * Steering angle of the vehicle
+     * Front bicycle model steering angle for vehicle
      *
      * Angle is in degrees. Left is negative.
      * Requires permission: {@link Car#PERMISSION_READ_STEERING_STATE}.
      */
+    @RequiresPermission(Car.PERMISSION_READ_STEERING_STATE)
     public static final int PERF_STEERING_ANGLE = 291504649;
     /**
+     * Rear bicycle model steering angle for vehicle
+     *
+     * Angle is in degrees. Left is negative.
+     * Requires permission: {@link Car#PERMISSION_READ_STEERING_STATE}.
+     */
+    @RequiresPermission(Car.PERMISSION_READ_STEERING_STATE)
+    public static final int PERF_REAR_STEERING_ANGLE = 291504656;
+    /**
      * Temperature of engine coolant
      * Requires permission: {@link Car#PERMISSION_CAR_ENGINE_DETAILED}.
      */
+    @RequiresPermission(Car.PERMISSION_CAR_ENGINE_DETAILED)
     public static final int ENGINE_COOLANT_TEMP = 291504897;
     /**
      * Engine oil level
      * Requires permission: {@link Car#PERMISSION_CAR_ENGINE_DETAILED}.
      */
+    @RequiresPermission(Car.PERMISSION_CAR_ENGINE_DETAILED)
     public static final int ENGINE_OIL_LEVEL = 289407747;
     /**
      * Temperature of engine oil
      * Requires permission: {@link Car#PERMISSION_CAR_ENGINE_DETAILED}.
      */
+    @RequiresPermission(Car.PERMISSION_CAR_ENGINE_DETAILED)
     public static final int ENGINE_OIL_TEMP = 291504900;
     /**
      * Engine rpm
      * Requires permission: {@link Car#PERMISSION_CAR_ENGINE_DETAILED}.
      */
+    @RequiresPermission(Car.PERMISSION_CAR_ENGINE_DETAILED)
     public static final int ENGINE_RPM = 291504901;
     /**
      * Reports wheel ticks
      * Requires permission: {@link Car#PERMISSION_SPEED}.
      */
+    @RequiresPermission(Car.PERMISSION_SPEED)
     public static final int WHEEL_TICK = 290521862;
     /**
      * Fuel remaining in the the vehicle, in milliliters
      * Requires permission: {@link Car#PERMISSION_ENERGY}.
      */
+    @RequiresPermission(Car.PERMISSION_ENERGY)
     public static final int FUEL_LEVEL = 291504903;
     /**
      * Fuel door open
-     * Requires permission: {@link Car#PERMISSION_ENERGY_PORTS}.
+     * Requires permission: {@link Car#PERMISSION_ENERGY_PORTS} to read the property.
+     * Requires permission: {@link Car#PERMISSION_CONTROL_ENERGY_PORTS} to control the property.
      */
+    @RequiresPermission.Read(@RequiresPermission(Car.PERMISSION_ENERGY_PORTS))
+    @RequiresPermission.Write(@RequiresPermission(Car.PERMISSION_CONTROL_ENERGY_PORTS))
     public static final int FUEL_DOOR_OPEN = 287310600;
     /**
      * EV battery level in WH, if EV or hybrid
      * Requires permission: {@link Car#PERMISSION_ENERGY}.
      */
+    @RequiresPermission(Car.PERMISSION_ENERGY)
     public static final int EV_BATTERY_LEVEL = 291504905;
     /**
      * EV charge port open
-     * Requires permission: {@link Car#PERMISSION_ENERGY_PORTS}.
+     * Requires permission: {@link Car#PERMISSION_ENERGY_PORTS} to read the property.
+     * Requires permission: {@link Car#PERMISSION_CONTROL_ENERGY_PORTS} to control the property.
      */
+    @RequiresPermission.Read(@RequiresPermission(Car.PERMISSION_ENERGY_PORTS))
+    @RequiresPermission.Write(@RequiresPermission(Car.PERMISSION_CONTROL_ENERGY_PORTS))
     public static final int EV_CHARGE_PORT_OPEN = 287310602;
     /**
      * EV charge port connected
      * Requires permission: {@link Car#PERMISSION_ENERGY_PORTS}.
      */
+    @RequiresPermission(Car.PERMISSION_ENERGY_PORTS)
     public static final int EV_CHARGE_PORT_CONNECTED = 287310603;
     /**
      * EV instantaneous charge rate in milliwatts
      * Requires permission: {@link Car#PERMISSION_ENERGY}.
      */
+    @RequiresPermission(Car.PERMISSION_ENERGY)
     public static final int EV_BATTERY_INSTANTANEOUS_CHARGE_RATE = 291504908;
     /**
      * Range remaining
-     * Requires permission: {@link Car#PERMISSION_ENERGY}.
+     *
+     * Meters remaining of fuel and charge.  Range remaining shall account for
+     * all energy sources in a vehicle.  For example, a hybrid car's range will
+     * be the sum of the ranges based on fuel and battery.
+     * Requires permission: {@link Car#PERMISSION_ENERGY} to read the property.
+     * Requires permission: {@link Car#PERMISSION_ADJUST_RANGE_REMAINING} to write the property.
      */
+    @RequiresPermission.Read(@RequiresPermission(Car.PERMISSION_ENERGY))
+    @RequiresPermission.Write(@RequiresPermission(Car.PERMISSION_ADJUST_RANGE_REMAINING))
     public static final int RANGE_REMAINING = 291504904;
     /**
      * Tire pressure
@@ -174,6 +253,7 @@
      * value denoted by its areaConfig.areaId.
      * Requires permission: {@link Car#PERMISSION_TIRES}.
      */
+    @RequiresPermission(Car.PERMISSION_TIRES)
     public static final int TIRE_PRESSURE = 392168201;
     /**
      * Currently selected gear
@@ -181,6 +261,7 @@
      * This is the gear selected by the user.
      * Requires permission: {@link Car#PERMISSION_POWERTRAIN}.
      */
+    @RequiresPermission(Car.PERMISSION_POWERTRAIN)
     public static final int GEAR_SELECTION = 289408000;
     /**
      * Current gear. In non-manual case, selected gear may not
@@ -189,126 +270,151 @@
      * the actual gear the transmission is currently running in.
      * Requires permission: {@link Car#PERMISSION_POWERTRAIN}.
      */
+    @RequiresPermission(Car.PERMISSION_POWERTRAIN)
     public static final int CURRENT_GEAR = 289408001;
     /**
      * Parking brake state.
      * Requires permission: {@link Car#PERMISSION_POWERTRAIN}.
      */
+    @RequiresPermission(Car.PERMISSION_POWERTRAIN)
     public static final int PARKING_BRAKE_ON = 287310850;
     /**
      * Auto-apply parking brake.
      * Requires permission: {@link Car#PERMISSION_POWERTRAIN}.
      */
+    @RequiresPermission(Car.PERMISSION_POWERTRAIN)
     public static final int PARKING_BRAKE_AUTO_APPLY = 287310851;
     /**
      * Warning for fuel low level.
      * Requires permission: {@link Car#PERMISSION_ENERGY}.
      */
+    @RequiresPermission(Car.PERMISSION_ENERGY)
     public static final int FUEL_LEVEL_LOW = 287310853;
     /**
      * Night mode
      * Requires permission: {@link Car#PERMISSION_EXTERIOR_ENVIRONMENT}.
      */
+    @RequiresPermission(Car.PERMISSION_EXTERIOR_ENVIRONMENT)
     public static final int NIGHT_MODE = 287310855;
     /**
      * State of the vehicles turn signals
      * Requires permission: {@link Car#PERMISSION_EXTERIOR_LIGHTS}.
      */
+    @RequiresPermission(Car.PERMISSION_EXTERIOR_LIGHTS)
     public static final int TURN_SIGNAL_STATE = 289408008;
     /**
      * Represents ignition state
      * Requires permission: {@link Car#PERMISSION_POWERTRAIN}.
      */
+    @RequiresPermission(Car.PERMISSION_POWERTRAIN)
     public static final int IGNITION_STATE = 289408009;
     /**
      * ABS is active
      * Requires permission: {@link Car#PERMISSION_CAR_DYNAMICS_STATE}.
      */
+    @RequiresPermission(Car.PERMISSION_CAR_DYNAMICS_STATE)
     public static final int ABS_ACTIVE = 287310858;
     /**
      * Traction Control is active
      * Requires permission: {@link Car#PERMISSION_CAR_DYNAMICS_STATE}.
      */
+    @RequiresPermission(Car.PERMISSION_CAR_DYNAMICS_STATE)
     public static final int TRACTION_CONTROL_ACTIVE = 287310859;
     /**
      * Fan speed setting
      * Requires permission: {@link Car#PERMISSION_CONTROL_CAR_CLIMATE}.
      */
+    @RequiresPermission(Car.PERMISSION_CONTROL_CAR_CLIMATE)
     public static final int HVAC_FAN_SPEED = 356517120;
     /**
      * Fan direction setting
      * Requires permission: {@link Car#PERMISSION_CONTROL_CAR_CLIMATE}.
      */
+    @RequiresPermission(Car.PERMISSION_CONTROL_CAR_CLIMATE)
     public static final int HVAC_FAN_DIRECTION = 356517121;
     /**
      * HVAC current temperature.
      * Requires permission: {@link Car#PERMISSION_CONTROL_CAR_CLIMATE}.
      */
+    @RequiresPermission(Car.PERMISSION_CONTROL_CAR_CLIMATE)
     public static final int HVAC_TEMPERATURE_CURRENT = 358614274;
     /**
      * HVAC, target temperature set.
      * Requires permission: {@link Car#PERMISSION_CONTROL_CAR_CLIMATE}.
      */
+    @RequiresPermission(Car.PERMISSION_CONTROL_CAR_CLIMATE)
     public static final int HVAC_TEMPERATURE_SET = 358614275;
     /**
      * On/off defrost for designated window
      * Requires permission: {@link Car#PERMISSION_CONTROL_CAR_CLIMATE}.
      */
+    @RequiresPermission(Car.PERMISSION_CONTROL_CAR_CLIMATE)
     public static final int HVAC_DEFROSTER = 320865540;
     /**
      * On/off AC for designated areaId
      * Requires permission: {@link Car#PERMISSION_CONTROL_CAR_CLIMATE}.
      */
+    @RequiresPermission(Car.PERMISSION_CONTROL_CAR_CLIMATE)
     public static final int HVAC_AC_ON = 354419973;
     /**
      * On/off max AC
      * Requires permission: {@link Car#PERMISSION_CONTROL_CAR_CLIMATE}.
      */
+    @RequiresPermission(Car.PERMISSION_CONTROL_CAR_CLIMATE)
     public static final int HVAC_MAX_AC_ON = 354419974;
     /**
      * On/off max defrost
      * Requires permission: {@link Car#PERMISSION_CONTROL_CAR_CLIMATE}.
      */
+    @RequiresPermission(Car.PERMISSION_CONTROL_CAR_CLIMATE)
     public static final int HVAC_MAX_DEFROST_ON = 354419975;
     /**
      * Recirculation on/off
      * Requires permission: {@link Car#PERMISSION_CONTROL_CAR_CLIMATE}.
      */
+    @RequiresPermission(Car.PERMISSION_CONTROL_CAR_CLIMATE)
     public static final int HVAC_RECIRC_ON = 354419976;
     /**
      * Enable temperature coupling between areas.
      * Requires permission: {@link Car#PERMISSION_CONTROL_CAR_CLIMATE}.
      */
+    @RequiresPermission(Car.PERMISSION_CONTROL_CAR_CLIMATE)
     public static final int HVAC_DUAL_ON = 354419977;
     /**
      * On/off automatic mode
      * Requires permission: {@link Car#PERMISSION_CONTROL_CAR_CLIMATE}.
      */
+    @RequiresPermission(Car.PERMISSION_CONTROL_CAR_CLIMATE)
     public static final int HVAC_AUTO_ON = 354419978;
     /**
      * Seat heating/cooling
      * Requires permission: {@link Car#PERMISSION_CONTROL_CAR_CLIMATE}.
      */
+    @RequiresPermission(Car.PERMISSION_CONTROL_CAR_CLIMATE)
     public static final int HVAC_SEAT_TEMPERATURE = 356517131;
     /**
      * Side Mirror Heat
      * Requires permission: {@link Car#PERMISSION_CONTROL_CAR_CLIMATE}.
      */
+    @RequiresPermission(Car.PERMISSION_CONTROL_CAR_CLIMATE)
     public static final int HVAC_SIDE_MIRROR_HEAT = 339739916;
     /**
      * Steering Wheel Heating/Cooling
      * Requires permission: {@link Car#PERMISSION_CONTROL_CAR_CLIMATE}.
      */
+    @RequiresPermission(Car.PERMISSION_CONTROL_CAR_CLIMATE)
     public static final int HVAC_STEERING_WHEEL_HEAT = 289408269;
     /**
      * Temperature units for display
      * Requires permission: {@link Car#PERMISSION_CONTROL_CAR_CLIMATE}.
      */
+    @RequiresPermission(Car.PERMISSION_CONTROL_CAR_CLIMATE)
     public static final int HVAC_TEMPERATURE_DISPLAY_UNITS = 289408270;
     /**
      * Actual fan speed
      * Requires permission: {@link Car#PERMISSION_CONTROL_CAR_CLIMATE}.
      */
+    @RequiresPermission(Car.PERMISSION_CONTROL_CAR_CLIMATE)
     public static final int HVAC_ACTUAL_FAN_SPEED_RPM = 356517135;
     /**
      * Represents global power state for HVAC. Setting this property to false
@@ -319,35 +425,52 @@
      * merits).
      * Requires permission: {@link Car#PERMISSION_CONTROL_CAR_CLIMATE}.
      */
+    @RequiresPermission(Car.PERMISSION_CONTROL_CAR_CLIMATE)
     public static final int HVAC_POWER_ON = 354419984;
     /**
      * Fan Positions Available
      * Requires permission: {@link Car#PERMISSION_CONTROL_CAR_CLIMATE}.
      */
+    @RequiresPermission(Car.PERMISSION_CONTROL_CAR_CLIMATE)
     public static final int HVAC_FAN_DIRECTION_AVAILABLE = 356582673;
     /**
      * Automatic recirculation on/off
      * Requires permission: {@link Car#PERMISSION_CONTROL_CAR_CLIMATE}.
      */
+    @RequiresPermission(Car.PERMISSION_CONTROL_CAR_CLIMATE)
     public static final int HVAC_AUTO_RECIRC_ON = 354419986;
     /**
      * Seat ventilation
      * Requires permission: {@link Car#PERMISSION_CONTROL_CAR_CLIMATE}.
      */
+    @RequiresPermission(Car.PERMISSION_CONTROL_CAR_CLIMATE)
     public static final int HVAC_SEAT_VENTILATION = 356517139;
     /**
+     * ELECTRIC DEFROSTER
+     * Requires permission: {@link Car#PERMISSION_CONTROL_CAR_CLIMATE}.
+     * @hide
+     */
+    @RequiresPermission(Car.PERMISSION_CONTROL_CAR_CLIMATE)
+    public static final int HVAC_ELECTRIC_DEFROSTER_ON = 320865556;
+    /**
      * Distance units for display
      * Requires permission {@link Car#PERMISSION_READ_DISPLAY_UNITS} to read the property.
      * Requires permission {@link Car#PERMISSION_CONTROL_DISPLAY_UNITS} and
      * {@link Car#PERMISSION_VENDOR_EXTENSION}to write the property.
      */
+    @RequiresPermission.Read(@RequiresPermission(Car.PERMISSION_READ_DISPLAY_UNITS))
+    @RequiresPermission.Write(@RequiresPermission(allOf = {Car.PERMISSION_CONTROL_DISPLAY_UNITS,
+            Car.PERMISSION_VENDOR_EXTENSION}))
     public static final int DISTANCE_DISPLAY_UNITS = 289408512;
     /**
      * Fuel volume units for display
      * Requires permission {@link Car#PERMISSION_READ_DISPLAY_UNITS} to read the property.
-     * Requires permission {@link Car#PERMISSION_CONTROL_DISPLAY_UNITS} and
-     * {@link Car#PERMISSION_VENDOR_EXTENSION}to write the property.
+     * Requires permission {@link Car#PERMISSION_CONTROL_DISPLAY_UNITS}
+     * and {@link Car#PERMISSION_VENDOR_EXTENSION}to write the property.
      */
+    @RequiresPermission.Read(@RequiresPermission(Car.PERMISSION_READ_DISPLAY_UNITS))
+    @RequiresPermission.Write(@RequiresPermission(allOf = {Car.PERMISSION_CONTROL_DISPLAY_UNITS,
+            Car.PERMISSION_VENDOR_EXTENSION}))
     public static final int FUEL_VOLUME_DISPLAY_UNITS = 289408513;
     /**
      * Tire pressure units for display
@@ -355,6 +478,9 @@
      * Requires permission {@link Car#PERMISSION_CONTROL_DISPLAY_UNITS} and
      * {@link Car#PERMISSION_VENDOR_EXTENSION}to write the property.
      */
+    @RequiresPermission.Read(@RequiresPermission(Car.PERMISSION_READ_DISPLAY_UNITS))
+    @RequiresPermission.Write(@RequiresPermission(allOf = {Car.PERMISSION_CONTROL_DISPLAY_UNITS,
+            Car.PERMISSION_VENDOR_EXTENSION}))
     public static final int TIRE_PRESSURE_DISPLAY_UNITS = 289408514;
     /**
      * EV battery units for display
@@ -362,6 +488,9 @@
      * Requires permission {@link Car#PERMISSION_CONTROL_DISPLAY_UNITS} and
      * {@link Car#PERMISSION_VENDOR_EXTENSION}to write the property.
      */
+    @RequiresPermission.Read(@RequiresPermission(Car.PERMISSION_READ_DISPLAY_UNITS))
+    @RequiresPermission.Write(@RequiresPermission(allOf = {Car.PERMISSION_CONTROL_DISPLAY_UNITS,
+            Car.PERMISSION_VENDOR_EXTENSION}))
     public static final int EV_BATTERY_DISPLAY_UNITS = 289408515;
     /**
      * Speed Units for display
@@ -370,6 +499,9 @@
      * {@link Car#PERMISSION_VENDOR_EXTENSION}to write the property.
      * @hide
      */
+    @RequiresPermission.Read(@RequiresPermission(Car.PERMISSION_READ_DISPLAY_UNITS))
+    @RequiresPermission.Write(@RequiresPermission(allOf = {Car.PERMISSION_CONTROL_DISPLAY_UNITS,
+            Car.PERMISSION_VENDOR_EXTENSION}))
     public static final int VEHICLE_SPEED_DISPLAY_UNITS = 289408516;
     /**
      * Fuel consumption units for display
@@ -377,11 +509,15 @@
      * Requires permission {@link Car#PERMISSION_CONTROL_DISPLAY_UNITS} and
      * {@link Car#PERMISSION_VENDOR_EXTENSION}to write the property.
      */
+    @RequiresPermission.Read(@RequiresPermission(Car.PERMISSION_READ_DISPLAY_UNITS))
+    @RequiresPermission.Write(@RequiresPermission(allOf = {Car.PERMISSION_CONTROL_DISPLAY_UNITS,
+            Car.PERMISSION_VENDOR_EXTENSION}))
     public static final int FUEL_CONSUMPTION_UNITS_DISTANCE_OVER_VOLUME = 287311364;
     /**
      * Outside temperature
      * Requires permission: {@link Car#PERMISSION_EXTERIOR_ENVIRONMENT}.
      */
+    @RequiresPermission(Car.PERMISSION_EXTERIOR_ENVIRONMENT)
     public static final int ENV_OUTSIDE_TEMPERATURE = 291505923;
     /**
      * Property to control power state of application processor
@@ -390,6 +526,7 @@
      * controller.
      * Requires permission: {@link Car#PERMISSION_CAR_POWER}.
      */
+    @RequiresPermission(Car.PERMISSION_CAR_POWER)
     public static final int AP_POWER_STATE_REQ = 289475072;
     /**
      * Property to report power state of application processor
@@ -398,6 +535,7 @@
      * controller.
      * Requires permission: {@link Car#PERMISSION_CAR_POWER}.
      */
+    @RequiresPermission(Car.PERMISSION_CAR_POWER)
     public static final int AP_POWER_STATE_REPORT = 289475073;
     /**
      * Property to report bootup reason for the current power on. This is a
@@ -407,6 +545,7 @@
      * VehicleApPowerBootupReason#USER_UNLOCK.
      * Requires permission: {@link Car#PERMISSION_CAR_POWER}.
      */
+    @RequiresPermission(Car.PERMISSION_CAR_POWER)
     public static final int AP_POWER_BOOTUP_REASON = 289409538;
     /**
      * Property to represent brightness of the display. Some cars have single
@@ -414,6 +553,7 @@
      * change in that control.
      * Requires permission: {@link Car#PERMISSION_CAR_POWER}.
      */
+    @RequiresPermission(Car.PERMISSION_CAR_POWER)
     public static final int DISPLAY_BRIGHTNESS = 289409539;
     /**
      * Property to feed H/W input events to android
@@ -426,46 +566,55 @@
      * Max value indicates fully open, min value (0) indicates fully closed.
      * Requires permission: {@link Car#PERMISSION_CONTROL_CAR_DOORS}.
      */
+    @RequiresPermission(Car.PERMISSION_CONTROL_CAR_DOORS)
     public static final int DOOR_POS = 373295872;
     /**
      * Door move
      * Requires permission: {@link Car#PERMISSION_CONTROL_CAR_DOORS}.
      */
+    @RequiresPermission(Car.PERMISSION_CONTROL_CAR_DOORS)
     public static final int DOOR_MOVE = 373295873;
     /**
      * Door lock
      * Requires permission: {@link Car#PERMISSION_CONTROL_CAR_DOORS}.
      */
+    @RequiresPermission(Car.PERMISSION_CONTROL_CAR_DOORS)
     public static final int DOOR_LOCK = 371198722;
     /**
      * Mirror Z Position
      * Requires permission: {@link Car#PERMISSION_CONTROL_CAR_MIRRORS}.
      */
+    @RequiresPermission(Car.PERMISSION_CONTROL_CAR_MIRRORS)
     public static final int MIRROR_Z_POS = 339741504;
     /**
      * Mirror Z Move
      * Requires permission: {@link Car#PERMISSION_CONTROL_CAR_MIRRORS}.
      */
+    @RequiresPermission(Car.PERMISSION_CONTROL_CAR_MIRRORS)
     public static final int MIRROR_Z_MOVE = 339741505;
     /**
      * Mirror Y Position
      * Requires permission: {@link Car#PERMISSION_CONTROL_CAR_MIRRORS}.
      */
+    @RequiresPermission(Car.PERMISSION_CONTROL_CAR_MIRRORS)
     public static final int MIRROR_Y_POS = 339741506;
     /**
      * Mirror Y Move
      * Requires permission: {@link Car#PERMISSION_CONTROL_CAR_MIRRORS}.
      */
+    @RequiresPermission(Car.PERMISSION_CONTROL_CAR_MIRRORS)
     public static final int MIRROR_Y_MOVE = 339741507;
     /**
      * Mirror Lock
      * Requires permission: {@link Car#PERMISSION_CONTROL_CAR_MIRRORS}.
      */
+    @RequiresPermission(Car.PERMISSION_CONTROL_CAR_MIRRORS)
     public static final int MIRROR_LOCK = 287312708;
     /**
      * Mirror Fold
      * Requires permission: {@link Car#PERMISSION_CONTROL_CAR_MIRRORS}.
      */
+    @RequiresPermission(Car.PERMISSION_CONTROL_CAR_MIRRORS)
     public static final int MIRROR_FOLD = 287312709;
     /**
      * Seat memory select
@@ -475,6 +624,7 @@
      * number of seat positions available.
      * Requires permission: {@link Car#PERMISSION_CONTROL_CAR_SEATS}.
      */
+    @RequiresPermission(Car.PERMISSION_CONTROL_CAR_SEATS)
     public static final int SEAT_MEMORY_SELECT = 356518784;
     /**
      * Seat memory set
@@ -484,6 +634,7 @@
      * must match the maxValue for SEAT_MEMORY_SELECT.
      * Requires permission: {@link Car#PERMISSION_CONTROL_CAR_SEATS}.
      */
+    @RequiresPermission(Car.PERMISSION_CONTROL_CAR_SEATS)
     public static final int SEAT_MEMORY_SET = 356518785;
     /**
      * Seatbelt buckled
@@ -491,31 +642,37 @@
      * True indicates belt is buckled.
      * Requires permission: {@link Car#PERMISSION_CONTROL_CAR_SEATS}.
      */
+    @RequiresPermission(Car.PERMISSION_CONTROL_CAR_SEATS)
     public static final int SEAT_BELT_BUCKLED = 354421634;
     /**
      * Seatbelt height position
      * Requires permission: {@link Car#PERMISSION_CONTROL_CAR_SEATS}.
      */
+    @RequiresPermission(Car.PERMISSION_CONTROL_CAR_SEATS)
     public static final int SEAT_BELT_HEIGHT_POS = 356518787;
     /**
      * Seatbelt height move
      * Requires permission: {@link Car#PERMISSION_CONTROL_CAR_SEATS}.
      */
+    @RequiresPermission(Car.PERMISSION_CONTROL_CAR_SEATS)
     public static final int SEAT_BELT_HEIGHT_MOVE = 356518788;
     /**
      * Seat fore/aft position
      * Requires permission: {@link Car#PERMISSION_CONTROL_CAR_SEATS}.
      */
+    @RequiresPermission(Car.PERMISSION_CONTROL_CAR_SEATS)
     public static final int SEAT_FORE_AFT_POS = 356518789;
     /**
      * Seat fore/aft move
      * Requires permission: {@link Car#PERMISSION_CONTROL_CAR_SEATS}.
      */
+    @RequiresPermission(Car.PERMISSION_CONTROL_CAR_SEATS)
     public static final int SEAT_FORE_AFT_MOVE = 356518790;
     /**
      * Seat backrest angle 1 position
      * Requires permission: {@link Car#PERMISSION_CONTROL_CAR_SEATS}.
      */
+    @RequiresPermission(Car.PERMISSION_CONTROL_CAR_SEATS)
     public static final int SEAT_BACKREST_ANGLE_1_POS = 356518791;
     /**
      * Seat backrest angle 1 move
@@ -523,122 +680,146 @@
      * Moves the backrest forward or recline.
      * Requires permission: {@link Car#PERMISSION_CONTROL_CAR_SEATS}.
      */
+    @RequiresPermission(Car.PERMISSION_CONTROL_CAR_SEATS)
     public static final int SEAT_BACKREST_ANGLE_1_MOVE = 356518792;
     /**
      * Seat backrest angle 2 position
      * Requires permission: {@link Car#PERMISSION_CONTROL_CAR_SEATS}.
      */
+    @RequiresPermission(Car.PERMISSION_CONTROL_CAR_SEATS)
     public static final int SEAT_BACKREST_ANGLE_2_POS = 356518793;
     /**
      * Seat backrest angle 2 move
      * Requires permission: {@link Car#PERMISSION_CONTROL_CAR_SEATS}.
      */
+    @RequiresPermission(Car.PERMISSION_CONTROL_CAR_SEATS)
     public static final int SEAT_BACKREST_ANGLE_2_MOVE = 356518794;
     /**
      * Seat height position
      * Requires permission: {@link Car#PERMISSION_CONTROL_CAR_SEATS}.
      */
+    @RequiresPermission(Car.PERMISSION_CONTROL_CAR_SEATS)
     public static final int SEAT_HEIGHT_POS = 356518795;
     /**
      * Seat height move
      * Requires permission: {@link Car#PERMISSION_CONTROL_CAR_SEATS}.
      */
+    @RequiresPermission(Car.PERMISSION_CONTROL_CAR_SEATS)
     public static final int SEAT_HEIGHT_MOVE = 356518796;
     /**
      * Seat depth position
      * Requires permission: {@link Car#PERMISSION_CONTROL_CAR_SEATS}.
      */
+    @RequiresPermission(Car.PERMISSION_CONTROL_CAR_SEATS)
     public static final int SEAT_DEPTH_POS = 356518797;
     /**
      * Seat depth move
      * Requires permission: {@link Car#PERMISSION_CONTROL_CAR_SEATS}.
      */
+    @RequiresPermission(Car.PERMISSION_CONTROL_CAR_SEATS)
     public static final int SEAT_DEPTH_MOVE = 356518798;
     /**
      * Seat tilt position
      * Requires permission: {@link Car#PERMISSION_CONTROL_CAR_SEATS}.
      */
+    @RequiresPermission(Car.PERMISSION_CONTROL_CAR_SEATS)
     public static final int SEAT_TILT_POS = 356518799;
     /**
      * Seat tilt move
      * Requires permission: {@link Car#PERMISSION_CONTROL_CAR_SEATS}.
      */
+    @RequiresPermission(Car.PERMISSION_CONTROL_CAR_SEATS)
     public static final int SEAT_TILT_MOVE = 356518800;
     /**
      * Lumber fore/aft position
      * Requires permission: {@link Car#PERMISSION_CONTROL_CAR_SEATS}.
      */
+    @RequiresPermission(Car.PERMISSION_CONTROL_CAR_SEATS)
     public static final int SEAT_LUMBAR_FORE_AFT_POS = 356518801;
     /**
      * Lumbar fore/aft move
      * Requires permission: {@link Car#PERMISSION_CONTROL_CAR_SEATS}.
      */
+    @RequiresPermission(Car.PERMISSION_CONTROL_CAR_SEATS)
     public static final int SEAT_LUMBAR_FORE_AFT_MOVE = 356518802;
     /**
      * Lumbar side support position
      * Requires permission: {@link Car#PERMISSION_CONTROL_CAR_SEATS}.
      */
+    @RequiresPermission(Car.PERMISSION_CONTROL_CAR_SEATS)
     public static final int SEAT_LUMBAR_SIDE_SUPPORT_POS = 356518803;
     /**
      * Lumbar side support move
      * Requires permission: {@link Car#PERMISSION_CONTROL_CAR_SEATS}.
      */
+    @RequiresPermission(Car.PERMISSION_CONTROL_CAR_SEATS)
     public static final int SEAT_LUMBAR_SIDE_SUPPORT_MOVE = 356518804;
     /**
      * Headrest height position
      * Requires permission: {@link Car#PERMISSION_CONTROL_CAR_SEATS}.
      */
+    @RequiresPermission(Car.PERMISSION_CONTROL_CAR_SEATS)
     public static final int SEAT_HEADREST_HEIGHT_POS = 289409941;
     /**
      * Headrest height move
      * Requires permission: {@link Car#PERMISSION_CONTROL_CAR_SEATS}.
      */
+    @RequiresPermission(Car.PERMISSION_CONTROL_CAR_SEATS)
     public static final int SEAT_HEADREST_HEIGHT_MOVE = 356518806;
     /**
      * Headrest angle position
      * Requires permission: {@link Car#PERMISSION_CONTROL_CAR_SEATS}.
      */
+    @RequiresPermission(Car.PERMISSION_CONTROL_CAR_SEATS)
     public static final int SEAT_HEADREST_ANGLE_POS = 356518807;
     /**
      * Headrest angle move
      * Requires permission: {@link Car#PERMISSION_CONTROL_CAR_SEATS}.
      */
+    @RequiresPermission(Car.PERMISSION_CONTROL_CAR_SEATS)
     public static final int SEAT_HEADREST_ANGLE_MOVE = 356518808;
     /**
      * Headrest fore/aft position
      * Requires permission: {@link Car#PERMISSION_CONTROL_CAR_SEATS}.
      */
+    @RequiresPermission(Car.PERMISSION_CONTROL_CAR_SEATS)
     public static final int SEAT_HEADREST_FORE_AFT_POS = 356518809;
     /**
      * Headrest fore/aft move
      * Requires permission: {@link Car#PERMISSION_CONTROL_CAR_SEATS}.
      */
+    @RequiresPermission(Car.PERMISSION_CONTROL_CAR_SEATS)
     public static final int SEAT_HEADREST_FORE_AFT_MOVE = 356518810;
     /**
      * Seat Occupancy
      * Requires permission: {@link Car#PERMISSION_CONTROL_CAR_SEATS}.
      */
+    @RequiresPermission(Car.PERMISSION_CONTROL_CAR_SEATS)
     public static final int SEAT_OCCUPANCY = 356518832;
     /**
      * Window Position
      * Requires permission: {@link Car#PERMISSION_CONTROL_CAR_WINDOWS}.
      */
+    @RequiresPermission(Car.PERMISSION_CONTROL_CAR_WINDOWS)
     public static final int WINDOW_POS = 322964416;
     /**
      * Window Move
      * Requires permission: {@link Car#PERMISSION_CONTROL_CAR_WINDOWS}.
      */
+    @RequiresPermission(Car.PERMISSION_CONTROL_CAR_WINDOWS)
     public static final int WINDOW_MOVE = 322964417;
     /**
      * Window Lock
      * Requires permission: {@link Car#PERMISSION_CONTROL_CAR_WINDOWS}.
      */
+    @RequiresPermission(Car.PERMISSION_CONTROL_CAR_WINDOWS)
     public static final int WINDOW_LOCK = 320867268;
     /**
      * Vehicle Maps Service (VMS) message
      * Requires one of permissions in {@link Car#PERMISSION_VMS_PUBLISHER},
      * {@link Car#PERMISSION_VMS_SUBSCRIBER}.
      */
+    @RequiresPermission(anyOf = {Car.PERMISSION_VMS_PUBLISHER, Car.PERMISSION_VMS_SUBSCRIBER})
     public static final int VEHICLE_MAP_SERVICE = 299895808;
     /**
      * OBD2 Live Sensor Data
@@ -646,6 +827,7 @@
      * Reports a snapshot of the current (live) values of the OBD2 sensors available.
      * Requires permission: {@link Car#PERMISSION_CAR_DIAGNOSTIC_READ_ALL}.
      */
+    @RequiresPermission(Car.PERMISSION_CAR_DIAGNOSTIC_READ_ALL)
     public static final int OBD2_LIVE_FRAME = 299896064;
     /**
      * OBD2 Freeze Frame Sensor Data
@@ -654,11 +836,13 @@
      * occurred and was detected.
      * Requires permission: {@link Car#PERMISSION_CAR_DIAGNOSTIC_READ_ALL}.
      */
+    @RequiresPermission(Car.PERMISSION_CAR_DIAGNOSTIC_READ_ALL)
     public static final int OBD2_FREEZE_FRAME = 299896065;
     /**
      * OBD2 Freeze Frame Information
      * Requires permission: {@link Car#PERMISSION_CAR_DIAGNOSTIC_READ_ALL}.
      */
+    @RequiresPermission(Car.PERMISSION_CAR_DIAGNOSTIC_READ_ALL)
     public static final int OBD2_FREEZE_FRAME_INFO = 299896066;
     /**
      * OBD2 Freeze Frame Clear
@@ -667,457 +851,361 @@
      * vehicle memory, as described by OBD2_FREEZE_FRAME_INFO.
      * Requires permission: {@link Car#PERMISSION_CAR_DIAGNOSTIC_CLEAR}.
      */
+    @RequiresPermission(Car.PERMISSION_CAR_DIAGNOSTIC_CLEAR)
     public static final int OBD2_FREEZE_FRAME_CLEAR = 299896067;
     /**
      * Headlights State
      * Requires permission: {@link Car#PERMISSION_EXTERIOR_LIGHTS}.
      */
+    @RequiresPermission(Car.PERMISSION_EXTERIOR_LIGHTS)
     public static final int HEADLIGHTS_STATE = 289410560;
     /**
      * High beam lights state
      * Requires permission: {@link Car#PERMISSION_EXTERIOR_LIGHTS}.
      */
+    @RequiresPermission(Car.PERMISSION_EXTERIOR_LIGHTS)
     public static final int HIGH_BEAM_LIGHTS_STATE = 289410561;
     /**
      * Fog light state
      * Requires permission: {@link Car#PERMISSION_EXTERIOR_LIGHTS}.
      */
+    @RequiresPermission(Car.PERMISSION_EXTERIOR_LIGHTS)
     public static final int FOG_LIGHTS_STATE = 289410562;
     /**
      * Hazard light status
      * Requires permission: {@link Car#PERMISSION_EXTERIOR_LIGHTS}.
      */
+    @RequiresPermission(Car.PERMISSION_EXTERIOR_LIGHTS)
     public static final int HAZARD_LIGHTS_STATE = 289410563;
     /**
      * Headlight switch
      * Requires permission: {@link Car#PERMISSION_CONTROL_EXTERIOR_LIGHTS}.
      */
+    @RequiresPermission(Car.PERMISSION_CONTROL_EXTERIOR_LIGHTS)
     public static final int HEADLIGHTS_SWITCH = 289410576;
     /**
      * High beam light switch
      * Requires permission: {@link Car#PERMISSION_CONTROL_EXTERIOR_LIGHTS}.
      */
+    @RequiresPermission(Car.PERMISSION_CONTROL_EXTERIOR_LIGHTS)
     public static final int HIGH_BEAM_LIGHTS_SWITCH = 289410577;
     /**
      * Fog light switch
      * Requires permission: {@link Car#PERMISSION_CONTROL_EXTERIOR_LIGHTS}.
      */
+    @RequiresPermission(Car.PERMISSION_CONTROL_EXTERIOR_LIGHTS)
     public static final int FOG_LIGHTS_SWITCH = 289410578;
     /**
      * Hazard light switch
      * Requires permission: {@link Car#PERMISSION_CONTROL_EXTERIOR_LIGHTS}.
      */
+    @RequiresPermission(Car.PERMISSION_CONTROL_EXTERIOR_LIGHTS)
     public static final int HAZARD_LIGHTS_SWITCH = 289410579;
     /**
      * Cabin lights
      * Requires permission: {@link Car#PERMISSION_READ_INTERIOR_LIGHTS}.
      */
+    @RequiresPermission(Car.PERMISSION_READ_INTERIOR_LIGHTS)
     public static final int CABIN_LIGHTS_STATE = 289410817;
     /**
      * Cabin lights switch
      * Requires permission: {@link Car#PERMISSION_CONTROL_INTERIOR_LIGHTS}.
      */
+    @RequiresPermission(Car.PERMISSION_CONTROL_INTERIOR_LIGHTS)
     public static final int CABIN_LIGHTS_SWITCH = 289410818;
     /**
      * Reading lights
      * Requires permission: {@link Car#PERMISSION_READ_INTERIOR_LIGHTS}.
      */
+    @RequiresPermission(Car.PERMISSION_READ_INTERIOR_LIGHTS)
     public static final int READING_LIGHTS_STATE = 356519683;
     /**
      * Reading lights switch
      * Requires permission: {@link Car#PERMISSION_CONTROL_INTERIOR_LIGHTS}.
      */
+    @RequiresPermission(Car.PERMISSION_CONTROL_INTERIOR_LIGHTS)
     public static final int READING_LIGHTS_SWITCH = 356519684;
 
     /**
-     * @param o Integer
-     * @return String
+     * Property to get the initial settings for multi-user management (such as initial user).
+     *
+     * <p>Doesn't require permission because it's not exposed externally.
      */
-    public static  String toString(int o) {
-        if (o == INVALID) {
-            return "INVALID";
-        }
-        if (o == INFO_VIN) {
-            return "INFO_VIN";
-        }
-        if (o == INFO_MAKE) {
-            return "INFO_MAKE";
-        }
-        if (o == INFO_MODEL) {
-            return "INFO_MODEL";
-        }
-        if (o == INFO_MODEL_YEAR) {
-            return "INFO_MODEL_YEAR";
-        }
-        if (o == INFO_FUEL_CAPACITY) {
-            return "INFO_FUEL_CAPACITY";
-        }
-        if (o == INFO_FUEL_TYPE) {
-            return "INFO_FUEL_TYPE";
-        }
-        if (o == INFO_EV_BATTERY_CAPACITY) {
-            return "INFO_EV_BATTERY_CAPACITY";
-        }
-        if (o == INFO_EV_CONNECTOR_TYPE) {
-            return "INFO_EV_CONNECTOR_TYPE";
-        }
-        if (o == INFO_FUEL_DOOR_LOCATION) {
-            return "INFO_FUEL_DOOR_LOCATION";
-        }
-        if (o == INFO_EV_PORT_LOCATION) {
-            return "INFO_EV_PORT_LOCATION";
-        }
-        if (o == INFO_DRIVER_SEAT) {
-            return "INFO_DRIVER_SEAT";
-        }
-        if (o == PERF_ODOMETER) {
-            return "PERF_ODOMETER";
-        }
-        if (o == PERF_VEHICLE_SPEED) {
-            return "PERF_VEHICLE_SPEED";
-        }
-        if (o == PERF_VEHICLE_SPEED_DISPLAY) {
-            return "PERF_VEHICLE_SPEED_DISPLAY";
-        }
-        if (o == PERF_STEERING_ANGLE) {
-            return "PERF__STEERING_ANGLE";
-        }
-        if (o == ENGINE_COOLANT_TEMP) {
-            return "ENGINE_COOLANT_TEMP";
-        }
-        if (o == ENGINE_OIL_LEVEL) {
-            return "ENGINE_OIL_LEVEL";
-        }
-        if (o == ENGINE_OIL_TEMP) {
-            return "ENGINE_OIL_TEMP";
-        }
-        if (o == ENGINE_RPM) {
-            return "ENGINE_RPM";
-        }
-        if (o == WHEEL_TICK) {
-            return "WHEEL_TICK";
-        }
-        if (o == FUEL_LEVEL) {
-            return "FUEL_LEVEL";
-        }
-        if (o == FUEL_DOOR_OPEN) {
-            return "FUEL_DOOR_OPEN";
-        }
-        if (o == EV_BATTERY_LEVEL) {
-            return "EV_BATTERY_LEVEL";
-        }
-        if (o == EV_CHARGE_PORT_OPEN) {
-            return "EV_CHARGE_PORT_OPEN";
-        }
-        if (o == EV_CHARGE_PORT_CONNECTED) {
-            return "EV_CHARGE_PORT_CONNECTED";
-        }
-        if (o == EV_BATTERY_INSTANTANEOUS_CHARGE_RATE) {
-            return "EV_BATTERY_INSTANTANEOUS_CHARGE_RATE";
-        }
-        if (o == RANGE_REMAINING) {
-            return "RANGE_REMAINING";
-        }
-        if (o == TIRE_PRESSURE) {
-            return "TIRE_PRESSURE";
-        }
-        if (o == GEAR_SELECTION) {
-            return "GEAR_SELECTION";
-        }
-        if (o == CURRENT_GEAR) {
-            return "CURRENT_GEAR";
-        }
-        if (o == PARKING_BRAKE_ON) {
-            return "PARKING_BRAKE_ON";
-        }
-        if (o == PARKING_BRAKE_AUTO_APPLY) {
-            return "PARKING_BRAKE_AUTO_APPLY";
-        }
-        if (o == FUEL_LEVEL_LOW) {
-            return "FUEL_LEVEL_LOW";
-        }
-        if (o == NIGHT_MODE) {
-            return "NIGHT_MODE";
-        }
-        if (o == TURN_SIGNAL_STATE) {
-            return "TURN_SIGNAL_STATE";
-        }
-        if (o == IGNITION_STATE) {
-            return "IGNITION_STATE";
-        }
-        if (o == ABS_ACTIVE) {
-            return "ABS_ACTIVE";
-        }
-        if (o == TRACTION_CONTROL_ACTIVE) {
-            return "TRACTION_CONTROL_ACTIVE";
-        }
-        if (o == HVAC_FAN_SPEED) {
-            return "HVAC_FAN_SPEED";
-        }
-        if (o == HVAC_FAN_DIRECTION) {
-            return "HVAC_FAN_DIRECTION";
-        }
-        if (o == HVAC_TEMPERATURE_CURRENT) {
-            return "HVAC_TEMPERATURE_CURRENT";
-        }
-        if (o == HVAC_TEMPERATURE_SET) {
-            return "HVAC_TEMPERATURE_SET";
-        }
-        if (o == HVAC_DEFROSTER) {
-            return "HVAC_DEFROSTER";
-        }
-        if (o == HVAC_AC_ON) {
-            return "HVAC_AC_ON";
-        }
-        if (o == HVAC_MAX_AC_ON) {
-            return "HVAC_MAX_AC_ON";
-        }
-        if (o == HVAC_MAX_DEFROST_ON) {
-            return "HVAC_MAX_DEFROST_ON";
-        }
-        if (o == HVAC_RECIRC_ON) {
-            return "HVAC_RECIRC_ON";
-        }
-        if (o == HVAC_DUAL_ON) {
-            return "HVAC_DUAL_ON";
-        }
-        if (o == HVAC_AUTO_ON) {
-            return "HVAC_AUTO_ON";
-        }
-        if (o == HVAC_SEAT_TEMPERATURE) {
-            return "HVAC_SEAT_TEMPERATURE";
-        }
-        if (o == HVAC_SIDE_MIRROR_HEAT) {
-            return "HVAC_SIDE_MIRROR_HEAT";
-        }
-        if (o == HVAC_STEERING_WHEEL_HEAT) {
-            return "HVAC_STEERING_WHEEL_HEAT";
-        }
-        if (o == HVAC_TEMPERATURE_DISPLAY_UNITS) {
-            return "HVAC_TEMPERATURE_DISPLAY_UNITS";
-        }
-        if (o == HVAC_ACTUAL_FAN_SPEED_RPM) {
-            return "HVAC_ACTUAL_FAN_SPEED_RPM";
-        }
-        if (o == HVAC_POWER_ON) {
-            return "HVAC_POWER_ON";
-        }
-        if (o == HVAC_FAN_DIRECTION_AVAILABLE) {
-            return "HVAC_FAN_DIRECTION_AVAILABLE";
-        }
-        if (o == HVAC_AUTO_RECIRC_ON) {
-            return "HVAC_AUTO_RECIRC_ON";
-        }
-        if (o == HVAC_SEAT_VENTILATION) {
-            return "HVAC_SEAT_VENTILATION";
-        }
-        if (o == DISTANCE_DISPLAY_UNITS) {
-            return "DISTANCE_DISPLAY_UNITS";
-        }
-        if (o == FUEL_VOLUME_DISPLAY_UNITS) {
-            return "FUEL_VOLUME_DISPLAY_UNITS";
-        }
-        if (o == TIRE_PRESSURE_DISPLAY_UNITS) {
-            return "TIRE_PRESSURE_DISPLAY_UNITS";
-        }
-        if (o == EV_BATTERY_DISPLAY_UNITS) {
-            return "EV_BATTERY_DISPLAY_UNITS";
-        }
-        if (o == FUEL_CONSUMPTION_UNITS_DISTANCE_OVER_VOLUME) {
-            return "FUEL_CONSUMPTION_UNITS_DISTANCE_OVER_VOLUME";
-        }
-        if (o == ENV_OUTSIDE_TEMPERATURE) {
-            return "ENV_OUTSIDE_TEMPERATURE";
-        }
-        if (o == AP_POWER_STATE_REQ) {
-            return "AP_POWER_STATE_REQ";
-        }
-        if (o == AP_POWER_STATE_REPORT) {
-            return "AP_POWER_STATE_REPORT";
-        }
-        if (o == AP_POWER_BOOTUP_REASON) {
-            return "AP_POWER_BOOTUP_REASON";
-        }
-        if (o == DISPLAY_BRIGHTNESS) {
-            return "DISPLAY_BRIGHTNESS";
-        }
-        if (o == HW_KEY_INPUT) {
-            return "HW_KEY_INPUT";
-        }
-        if (o == DOOR_POS) {
-            return "DOOR_POS";
-        }
-        if (o == DOOR_MOVE) {
-            return "DOOR_MOVE";
-        }
-        if (o == DOOR_LOCK) {
-            return "DOOR_LOCK";
-        }
-        if (o == MIRROR_Z_POS) {
-            return "MIRROR_Z_POS";
-        }
-        if (o == MIRROR_Z_MOVE) {
-            return "MIRROR_Z_MOVE";
-        }
-        if (o == MIRROR_Y_POS) {
-            return "MIRROR_Y_POS";
-        }
-        if (o == MIRROR_Y_MOVE) {
-            return "MIRROR_Y_MOVE";
-        }
-        if (o == MIRROR_LOCK) {
-            return "MIRROR_LOCK";
-        }
-        if (o == MIRROR_FOLD) {
-            return "MIRROR_FOLD";
-        }
-        if (o == SEAT_MEMORY_SELECT) {
-            return "SEAT_MEMORY_SELECT";
-        }
-        if (o == SEAT_MEMORY_SET) {
-            return "SEAT_MEMORY_SET";
-        }
-        if (o == SEAT_BELT_BUCKLED) {
-            return "SEAT_BELT_BUCKLED";
-        }
-        if (o == SEAT_BELT_HEIGHT_POS) {
-            return "SEAT_BELT_HEIGHT_POS";
-        }
-        if (o == SEAT_BELT_HEIGHT_MOVE) {
-            return "SEAT_BELT_HEIGHT_MOVE";
-        }
-        if (o == SEAT_FORE_AFT_POS) {
-            return "SEAT_FORE_AFT_POS";
-        }
-        if (o == SEAT_FORE_AFT_MOVE) {
-            return "SEAT_FORE_AFT_MOVE";
-        }
-        if (o == SEAT_BACKREST_ANGLE_1_POS) {
-            return "SEAT_BACKREST_ANGLE_1_POS";
-        }
-        if (o == SEAT_BACKREST_ANGLE_1_MOVE) {
-            return "SEAT_BACKREST_ANGLE_1_MOVE";
-        }
-        if (o == SEAT_BACKREST_ANGLE_2_POS) {
-            return "SEAT_BACKREST_ANGLE_2_POS";
-        }
-        if (o == SEAT_BACKREST_ANGLE_2_MOVE) {
-            return "SEAT_BACKREST_ANGLE_2_MOVE";
-        }
-        if (o == SEAT_HEIGHT_POS) {
-            return "SEAT_HEIGHT_POS";
-        }
-        if (o == SEAT_HEIGHT_MOVE) {
-            return "SEAT_HEIGHT_MOVE";
-        }
-        if (o == SEAT_DEPTH_POS) {
-            return "SEAT_DEPTH_POS";
-        }
-        if (o == SEAT_DEPTH_MOVE) {
-            return "SEAT_DEPTH_MOVE";
-        }
-        if (o == SEAT_TILT_POS) {
-            return "SEAT_TILT_POS";
-        }
-        if (o == SEAT_TILT_MOVE) {
-            return "SEAT_TILT_MOVE";
-        }
-        if (o == SEAT_LUMBAR_FORE_AFT_POS) {
-            return "SEAT_LUMBAR_FORE_AFT_POS";
-        }
-        if (o == SEAT_LUMBAR_FORE_AFT_MOVE) {
-            return "SEAT_LUMBAR_FORE_AFT_MOVE";
-        }
-        if (o == SEAT_LUMBAR_SIDE_SUPPORT_POS) {
-            return "SEAT_LUMBAR_SIDE_SUPPORT_POS";
-        }
-        if (o == SEAT_LUMBAR_SIDE_SUPPORT_MOVE) {
-            return "SEAT_LUMBAR_SIDE_SUPPORT_MOVE";
-        }
-        if (o == SEAT_HEADREST_HEIGHT_POS) {
-            return "SEAT_HEADREST_HEIGHT_POS";
-        }
-        if (o == SEAT_HEADREST_HEIGHT_MOVE) {
-            return "SEAT_HEADREST_HEIGHT_MOVE";
-        }
-        if (o == SEAT_HEADREST_ANGLE_POS) {
-            return "SEAT_HEADREST_ANGLE_POS";
-        }
-        if (o == SEAT_HEADREST_ANGLE_MOVE) {
-            return "SEAT_HEADREST_ANGLE_MOVE";
-        }
-        if (o == SEAT_HEADREST_FORE_AFT_POS) {
-            return "SEAT_HEADREST_FORE_AFT_POS";
-        }
-        if (o == SEAT_HEADREST_FORE_AFT_MOVE) {
-            return "SEAT_HEADREST_FORE_AFT_MOVE";
-        }
-        if (o == SEAT_OCCUPANCY) {
-            return "SEAT_OCCUPANCY";
-        }
-        if (o == WINDOW_POS) {
-            return "WINDOW_POS";
-        }
-        if (o == WINDOW_MOVE) {
-            return "WINDOW_MOVE";
-        }
-        if (o == WINDOW_LOCK) {
-            return "WINDOW_LOCK";
-        }
-        if (o == VEHICLE_MAP_SERVICE) {
-            return "VEHICLE_MAP_SERVICE";
-        }
-        if (o == OBD2_LIVE_FRAME) {
-            return "OBD2_LIVE_FRAME";
-        }
-        if (o == OBD2_FREEZE_FRAME) {
-            return "OBD2_FREEZE_FRAME";
-        }
-        if (o == OBD2_FREEZE_FRAME_INFO) {
-            return "OBD2_FREEZE_FRAME_INFO";
-        }
-        if (o == OBD2_FREEZE_FRAME_CLEAR) {
-            return "OBD2_FREEZE_FRAME_CLEAR";
-        }
-        if (o == HEADLIGHTS_STATE) {
-            return "HEADLIGHTS_STATE";
-        }
-        if (o == HIGH_BEAM_LIGHTS_STATE) {
-            return "HIGH_BEAM_LIGHTS_STATE";
-        }
-        if (o == FOG_LIGHTS_STATE) {
-            return "FOG_LIGHTS_STATE";
-        }
-        if (o == HAZARD_LIGHTS_STATE) {
-            return "HAZARD_LIGHTS_STATE";
-        }
-        if (o == HEADLIGHTS_SWITCH) {
-            return "HEADLIGHTS_SWITCH";
-        }
-        if (o == HIGH_BEAM_LIGHTS_SWITCH) {
-            return "HIGH_BEAM_LIGHTS_SWITCH";
-        }
-        if (o == FOG_LIGHTS_SWITCH) {
-            return "FOG_LIGHTS_SWITCH";
-        }
-        if (o == HAZARD_LIGHTS_SWITCH) {
-            return "HAZARD_LIGHTS_SWITCH";
-        }
-        if (o == CABIN_LIGHTS_STATE) {
-            return "CABIN_LIGHTS_STATE";
-        }
-        if (o == CABIN_LIGHTS_SWITCH) {
-            return "CABIN_LIGHTS_SWITCH";
-        }
-        if (o == READING_LIGHTS_STATE) {
-            return "READING_LIGHTS_STATE";
-        }
-        if (o == READING_LIGHTS_SWITCH) {
-            return "READING_LIGHTS_SWITCH";
-        }
-        if (o == VEHICLE_SPEED_DISPLAY_UNITS) {
-            return "VEHICLE_SPEED_DISPLAY_UNITS";
+    public static final int INITIAL_USER_INFO = 299896583;
+
+    /**
+     * Gets a user-friendly representation of a property.
+     */
+    public static String toString(int property) {
+        switch (property) {
+            case INVALID:
+                return "INVALID";
+            case INFO_VIN:
+                return "INFO_VIN";
+            case INFO_MAKE:
+                return "INFO_MAKE";
+            case INFO_MODEL:
+                return "INFO_MODEL";
+            case INFO_MODEL_YEAR:
+                return "INFO_MODEL_YEAR";
+            case INFO_FUEL_CAPACITY:
+                return "INFO_FUEL_CAPACITY";
+            case INFO_FUEL_TYPE:
+                return "INFO_FUEL_TYPE";
+            case INFO_EV_BATTERY_CAPACITY:
+                return "INFO_EV_BATTERY_CAPACITY";
+            case INFO_MULTI_EV_PORT_LOCATIONS:
+                return "INFO_MULTI_EV_PORT_LOCATIONS";
+            case INFO_EV_CONNECTOR_TYPE:
+                return "INFO_EV_CONNECTOR_TYPE";
+            case INFO_FUEL_DOOR_LOCATION:
+                return "INFO_FUEL_DOOR_LOCATION";
+            case INFO_EV_PORT_LOCATION:
+                return "INFO_EV_PORT_LOCATION";
+            case INFO_DRIVER_SEAT:
+                return "INFO_DRIVER_SEAT";
+            case INFO_EXTERIOR_DIMENSIONS:
+                return "INFO_EXTERIOR_DIMENSIONS";
+            case PERF_ODOMETER:
+                return "PERF_ODOMETER";
+            case PERF_VEHICLE_SPEED:
+                return "PERF_VEHICLE_SPEED";
+            case PERF_VEHICLE_SPEED_DISPLAY:
+                return "PERF_VEHICLE_SPEED_DISPLAY";
+            case PERF_STEERING_ANGLE:
+                return "PERF_STEERING_ANGLE";
+            case PERF_REAR_STEERING_ANGLE:
+                return "PERF_REAR_STEERING_ANGLE";
+            case ENGINE_COOLANT_TEMP:
+                return "ENGINE_COOLANT_TEMP";
+            case ENGINE_OIL_LEVEL:
+                return "ENGINE_OIL_LEVEL";
+            case ENGINE_OIL_TEMP:
+                return "ENGINE_OIL_TEMP";
+            case ENGINE_RPM:
+                return "ENGINE_RPM";
+            case WHEEL_TICK:
+                return "WHEEL_TICK";
+            case FUEL_LEVEL:
+                return "FUEL_LEVEL";
+            case FUEL_DOOR_OPEN:
+                return "FUEL_DOOR_OPEN";
+            case EV_BATTERY_LEVEL:
+                return "EV_BATTERY_LEVEL";
+            case EV_CHARGE_PORT_OPEN:
+                return "EV_CHARGE_PORT_OPEN";
+            case EV_CHARGE_PORT_CONNECTED:
+                return "EV_CHARGE_PORT_CONNECTED";
+            case EV_BATTERY_INSTANTANEOUS_CHARGE_RATE:
+                return "EV_BATTERY_INSTANTANEOUS_CHARGE_RATE";
+            case RANGE_REMAINING:
+                return "RANGE_REMAINING";
+            case TIRE_PRESSURE:
+                return "TIRE_PRESSURE";
+            case GEAR_SELECTION:
+                return "GEAR_SELECTION";
+            case CURRENT_GEAR:
+                return "CURRENT_GEAR";
+            case PARKING_BRAKE_ON:
+                return "PARKING_BRAKE_ON";
+            case PARKING_BRAKE_AUTO_APPLY:
+                return "PARKING_BRAKE_AUTO_APPLY";
+            case FUEL_LEVEL_LOW:
+                return "FUEL_LEVEL_LOW";
+            case NIGHT_MODE:
+                return "NIGHT_MODE";
+            case TURN_SIGNAL_STATE:
+                return "TURN_SIGNAL_STATE";
+            case IGNITION_STATE:
+                return "IGNITION_STATE";
+            case ABS_ACTIVE:
+                return "ABS_ACTIVE";
+            case TRACTION_CONTROL_ACTIVE:
+                return "TRACTION_CONTROL_ACTIVE";
+            case HVAC_FAN_SPEED:
+                return "HVAC_FAN_SPEED";
+            case HVAC_FAN_DIRECTION:
+                return "HVAC_FAN_DIRECTION";
+            case HVAC_TEMPERATURE_CURRENT:
+                return "HVAC_TEMPERATURE_CURRENT";
+            case HVAC_TEMPERATURE_SET:
+                return "HVAC_TEMPERATURE_SET";
+            case HVAC_DEFROSTER:
+                return "HVAC_DEFROSTER";
+            case HVAC_AC_ON:
+                return "HVAC_AC_ON";
+            case HVAC_MAX_AC_ON:
+                return "HVAC_MAX_AC_ON";
+            case HVAC_MAX_DEFROST_ON:
+                return "HVAC_MAX_DEFROST_ON";
+            case HVAC_RECIRC_ON:
+                return "HVAC_RECIRC_ON";
+            case HVAC_DUAL_ON:
+                return "HVAC_DUAL_ON";
+            case HVAC_AUTO_ON:
+                return "HVAC_AUTO_ON";
+            case HVAC_SEAT_TEMPERATURE:
+                return "HVAC_SEAT_TEMPERATURE";
+            case HVAC_SIDE_MIRROR_HEAT:
+                return "HVAC_SIDE_MIRROR_HEAT";
+            case HVAC_STEERING_WHEEL_HEAT:
+                return "HVAC_STEERING_WHEEL_HEAT";
+            case HVAC_TEMPERATURE_DISPLAY_UNITS:
+                return "HVAC_TEMPERATURE_DISPLAY_UNITS";
+            case HVAC_ACTUAL_FAN_SPEED_RPM:
+                return "HVAC_ACTUAL_FAN_SPEED_RPM";
+            case HVAC_POWER_ON:
+                return "HVAC_POWER_ON";
+            case HVAC_FAN_DIRECTION_AVAILABLE:
+                return "HVAC_FAN_DIRECTION_AVAILABLE";
+            case HVAC_AUTO_RECIRC_ON:
+                return "HVAC_AUTO_RECIRC_ON";
+            case HVAC_SEAT_VENTILATION:
+                return "HVAC_SEAT_VENTILATION";
+            case HVAC_ELECTRIC_DEFROSTER_ON:
+                return "HVAC_ELECTRIC_DEFROSTER_ON";
+            case DISTANCE_DISPLAY_UNITS:
+                return "DISTANCE_DISPLAY_UNITS";
+            case FUEL_VOLUME_DISPLAY_UNITS:
+                return "FUEL_VOLUME_DISPLAY_UNITS";
+            case TIRE_PRESSURE_DISPLAY_UNITS:
+                return "TIRE_PRESSURE_DISPLAY_UNITS";
+            case EV_BATTERY_DISPLAY_UNITS:
+                return "EV_BATTERY_DISPLAY_UNITS";
+            case FUEL_CONSUMPTION_UNITS_DISTANCE_OVER_VOLUME:
+                return "FUEL_CONSUMPTION_UNITS_DISTANCE_OVER_VOLUME";
+            case ENV_OUTSIDE_TEMPERATURE:
+                return "ENV_OUTSIDE_TEMPERATURE";
+            case AP_POWER_STATE_REQ:
+                return "AP_POWER_STATE_REQ";
+            case AP_POWER_STATE_REPORT:
+                return "AP_POWER_STATE_REPORT";
+            case AP_POWER_BOOTUP_REASON:
+                return "AP_POWER_BOOTUP_REASON";
+            case DISPLAY_BRIGHTNESS:
+                return "DISPLAY_BRIGHTNESS";
+            case HW_KEY_INPUT:
+                return "HW_KEY_INPUT";
+            case DOOR_POS:
+                return "DOOR_POS";
+            case DOOR_MOVE:
+                return "DOOR_MOVE";
+            case DOOR_LOCK:
+                return "DOOR_LOCK";
+            case MIRROR_Z_POS:
+                return "MIRROR_Z_POS";
+            case MIRROR_Z_MOVE:
+                return "MIRROR_Z_MOVE";
+            case MIRROR_Y_POS:
+                return "MIRROR_Y_POS";
+            case MIRROR_Y_MOVE:
+                return "MIRROR_Y_MOVE";
+            case MIRROR_LOCK:
+                return "MIRROR_LOCK";
+            case MIRROR_FOLD:
+                return "MIRROR_FOLD";
+            case SEAT_MEMORY_SELECT:
+                return "SEAT_MEMORY_SELECT";
+            case SEAT_MEMORY_SET:
+                return "SEAT_MEMORY_SET";
+            case SEAT_BELT_BUCKLED:
+                return "SEAT_BELT_BUCKLED";
+            case SEAT_BELT_HEIGHT_POS:
+                return "SEAT_BELT_HEIGHT_POS";
+            case SEAT_BELT_HEIGHT_MOVE:
+                return "SEAT_BELT_HEIGHT_MOVE";
+            case SEAT_FORE_AFT_POS:
+                return "SEAT_FORE_AFT_POS";
+            case SEAT_FORE_AFT_MOVE:
+                return "SEAT_FORE_AFT_MOVE";
+            case SEAT_BACKREST_ANGLE_1_POS:
+                return "SEAT_BACKREST_ANGLE_1_POS";
+            case SEAT_BACKREST_ANGLE_1_MOVE:
+                return "SEAT_BACKREST_ANGLE_1_MOVE";
+            case SEAT_BACKREST_ANGLE_2_POS:
+                return "SEAT_BACKREST_ANGLE_2_POS";
+            case SEAT_BACKREST_ANGLE_2_MOVE:
+                return "SEAT_BACKREST_ANGLE_2_MOVE";
+            case SEAT_HEIGHT_POS:
+                return "SEAT_HEIGHT_POS";
+            case SEAT_HEIGHT_MOVE:
+                return "SEAT_HEIGHT_MOVE";
+            case SEAT_DEPTH_POS:
+                return "SEAT_DEPTH_POS";
+            case SEAT_DEPTH_MOVE:
+                return "SEAT_DEPTH_MOVE";
+            case SEAT_TILT_POS:
+                return "SEAT_TILT_POS";
+            case SEAT_TILT_MOVE:
+                return "SEAT_TILT_MOVE";
+            case SEAT_LUMBAR_FORE_AFT_POS:
+                return "SEAT_LUMBAR_FORE_AFT_POS";
+            case SEAT_LUMBAR_FORE_AFT_MOVE:
+                return "SEAT_LUMBAR_FORE_AFT_MOVE";
+            case SEAT_LUMBAR_SIDE_SUPPORT_POS:
+                return "SEAT_LUMBAR_SIDE_SUPPORT_POS";
+            case SEAT_LUMBAR_SIDE_SUPPORT_MOVE:
+                return "SEAT_LUMBAR_SIDE_SUPPORT_MOVE";
+            case SEAT_HEADREST_HEIGHT_POS:
+                return "SEAT_HEADREST_HEIGHT_POS";
+            case SEAT_HEADREST_HEIGHT_MOVE:
+                return "SEAT_HEADREST_HEIGHT_MOVE";
+            case SEAT_HEADREST_ANGLE_POS:
+                return "SEAT_HEADREST_ANGLE_POS";
+            case SEAT_HEADREST_ANGLE_MOVE:
+                return "SEAT_HEADREST_ANGLE_MOVE";
+            case SEAT_HEADREST_FORE_AFT_POS:
+                return "SEAT_HEADREST_FORE_AFT_POS";
+            case SEAT_HEADREST_FORE_AFT_MOVE:
+                return "SEAT_HEADREST_FORE_AFT_MOVE";
+            case SEAT_OCCUPANCY:
+                return "SEAT_OCCUPANCY";
+            case WINDOW_POS:
+                return "WINDOW_POS";
+            case WINDOW_MOVE:
+                return "WINDOW_MOVE";
+            case WINDOW_LOCK:
+                return "WINDOW_LOCK";
+            case VEHICLE_MAP_SERVICE:
+                return "VEHICLE_MAP_SERVICE";
+            case OBD2_LIVE_FRAME:
+                return "OBD2_LIVE_FRAME";
+            case OBD2_FREEZE_FRAME:
+                return "OBD2_FREEZE_FRAME";
+            case OBD2_FREEZE_FRAME_INFO:
+                return "OBD2_FREEZE_FRAME_INFO";
+            case OBD2_FREEZE_FRAME_CLEAR:
+                return "OBD2_FREEZE_FRAME_CLEAR";
+            case HEADLIGHTS_STATE:
+                return "HEADLIGHTS_STATE";
+            case HIGH_BEAM_LIGHTS_STATE:
+                return "HIGH_BEAM_LIGHTS_STATE";
+            case FOG_LIGHTS_STATE:
+                return "FOG_LIGHTS_STATE";
+            case HAZARD_LIGHTS_STATE:
+                return "HAZARD_LIGHTS_STATE";
+            case HEADLIGHTS_SWITCH:
+                return "HEADLIGHTS_SWITCH";
+            case HIGH_BEAM_LIGHTS_SWITCH:
+                return "HIGH_BEAM_LIGHTS_SWITCH";
+            case FOG_LIGHTS_SWITCH:
+                return "FOG_LIGHTS_SWITCH";
+            case HAZARD_LIGHTS_SWITCH:
+                return "HAZARD_LIGHTS_SWITCH";
+            case CABIN_LIGHTS_STATE:
+                return "CABIN_LIGHTS_STATE";
+            case CABIN_LIGHTS_SWITCH:
+                return "CABIN_LIGHTS_SWITCH";
+            case READING_LIGHTS_STATE:
+                return "READING_LIGHTS_STATE";
+            case READING_LIGHTS_SWITCH:
+                return "READING_LIGHTS_SWITCH";
+            case VEHICLE_SPEED_DISPLAY_UNITS:
+                return "VEHICLE_SPEED_DISPLAY_UNITS";
+            case INITIAL_USER_INFO:
+                return "INITIAL_USER_INFO";
+            default:
+                return "0x" + Integer.toHexString(property);
         }
-        return "0x" + Integer.toHexString(o);
     }
 }
diff --git a/car-lib/src/android/car/VehiclePropertyType.java b/car-lib/src/android/car/VehiclePropertyType.java
index b236d2d..d7b86e3 100644
--- a/car-lib/src/android/car/VehiclePropertyType.java
+++ b/car-lib/src/android/car/VehiclePropertyType.java
@@ -17,6 +17,7 @@
 package android.car;
 
 import android.annotation.IntDef;
+import android.annotation.TestApi;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -26,6 +27,7 @@
  * Value type of VehicleProperty
  * @hide
  */
+@TestApi
 public class VehiclePropertyType {
     public static final int STRING          = 0x00100000;
     public static final int BOOLEAN         = 0x00200000;
diff --git a/car-lib/src/android/car/annotation/ExperimentalFeature.java b/car-lib/src/android/car/annotation/ExperimentalFeature.java
new file mode 100644
index 0000000..76e6b08
--- /dev/null
+++ b/car-lib/src/android/car/annotation/ExperimentalFeature.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.car.annotation;
+
+import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * This is for experimental features. Note that experimental feature will not be allowed for user
+ * build and experiental features will not be part of car API. But this annotation is provided
+ * to mark it in separate library, which will be typically static library.
+ *
+ * <p>Note that experimental feature can become official feature later.
+ *
+ * @hide
+ */
+@Retention(SOURCE)
+@Target({ANNOTATION_TYPE, FIELD})
+public @interface ExperimentalFeature {
+}
diff --git a/car-lib/src/android/car/annotation/FutureFeature.java b/car-lib/src/android/car/annotation/FutureFeature.java
deleted file mode 100644
index 1854771..0000000
--- a/car-lib/src/android/car/annotation/FutureFeature.java
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.car.annotation;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-/**
- * Annotation to represent future feature which is not ready for the current platform release.
- * Any API marked with this is for future development and should not be used for product.
- *
- * @hide
- */
-@Target({ElementType.TYPE, ElementType.FIELD, ElementType.METHOD, ElementType.CONSTRUCTOR,
-        ElementType.LOCAL_VARIABLE})
-@Retention(RetentionPolicy.CLASS)
-public @interface FutureFeature {
-    Class type() default Object.class;
-}
diff --git a/car-lib/src/android/car/annotation/MandatoryFeature.java b/car-lib/src/android/car/annotation/MandatoryFeature.java
new file mode 100644
index 0000000..3aca13d
--- /dev/null
+++ b/car-lib/src/android/car/annotation/MandatoryFeature.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.car.annotation;
+
+import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * This is for mandatory features. Features marked with this will be always available in all car
+ * products.
+ *
+ * @hide
+ */
+@Retention(SOURCE)
+@Target({ANNOTATION_TYPE, FIELD})
+public @interface MandatoryFeature {
+}
diff --git a/car-lib/src/android/car/annotation/OptionalFeature.java b/car-lib/src/android/car/annotation/OptionalFeature.java
new file mode 100644
index 0000000..342f696
--- /dev/null
+++ b/car-lib/src/android/car/annotation/OptionalFeature.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.car.annotation;
+
+import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * This is for optional features. Features marked with this should be first checked if it is
+ * supported using {@link android.car.Car#isFeatureSupported(featureName)}.
+ *
+ * @hide
+ */
+@Retention(SOURCE)
+@Target({ANNOTATION_TYPE, FIELD})
+public @interface OptionalFeature {
+}
diff --git a/car-lib/src/android/car/annotation/RequiredFeature.java b/car-lib/src/android/car/annotation/RequiredFeature.java
new file mode 100644
index 0000000..a5a3d14
--- /dev/null
+++ b/car-lib/src/android/car/annotation/RequiredFeature.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.car.annotation;
+
+import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
+import static java.lang.annotation.ElementType.CONSTRUCTOR;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * Annotation to represent a feature required to use the specified method.
+ * Ex: @RequiredFeature(Car.STORAGE_MONITORING_SERVICE)
+ *
+ * @hide
+ */
+@Target({ANNOTATION_TYPE, CONSTRUCTOR, METHOD, TYPE})
+@Retention(SOURCE)
+public @interface RequiredFeature {
+    String value();
+}
diff --git a/car-lib/src/android/car/app/CarActivityView.java b/car-lib/src/android/car/app/CarActivityView.java
new file mode 100644
index 0000000..efefbe1
--- /dev/null
+++ b/car-lib/src/android/car/app/CarActivityView.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.car.app;
+
+import android.annotation.Nullable;
+import android.app.ActivityView;
+import android.car.Car;
+import android.car.drivingstate.CarUxRestrictionsManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.Display;
+
+/**
+ * CarActivityView is a special kind of ActivityView that can track which display the ActivityView
+ * is placed.  This information can be used to enforce the driving safety.
+ *
+ * @hide
+ */
+public final class CarActivityView extends ActivityView {
+
+    private static final String TAG = CarActivityView.class.getSimpleName();
+
+    // volatile, since mUserActivityViewCallback can be accessed from Main and Binder thread.
+    @Nullable private volatile StateCallback mUserActivityViewCallback;
+
+    @Nullable private CarUxRestrictionsManager mUxRestrictionsManager;
+
+    private int mVirtualDisplayId = Display.INVALID_DISPLAY;
+
+    public CarActivityView(Context context) {
+        this(context, /*attrs=*/ null);
+    }
+
+    public CarActivityView(Context context, AttributeSet attrs) {
+        this(context, attrs, /*defStyle=*/ 0);
+    }
+
+    public CarActivityView(Context context, AttributeSet attrs, int defStyle) {
+        this(context, attrs, defStyle,  /*singleTaskInstance=*/ false);
+    }
+
+    public CarActivityView(
+            Context context, AttributeSet attrs, int defStyle, boolean singleTaskInstance) {
+        super(context, attrs, defStyle, singleTaskInstance);
+        super.setCallback(new CarActivityViewCallback());
+        Car.createCar(mContext, /*handler=*/ null,
+                Car.CAR_WAIT_TIMEOUT_DO_NOT_WAIT,
+                (car, ready) -> {
+                    // Expect to be called in the main thread, since passed a 'null' handler
+                    // in Car.createCar().
+                    if (!ready) return;
+                    mUxRestrictionsManager = (CarUxRestrictionsManager) car.getCarManager(
+                            Car.CAR_UX_RESTRICTION_SERVICE);
+                    if (mVirtualDisplayId != Display.INVALID_DISPLAY) {
+                        // When the CarService is reconnected, we'd like to report the physical
+                        // display id again, since the previously reported mapping could be gone.
+                        reportPhysicalDisplayId(
+                                mUxRestrictionsManager, mVirtualDisplayId, mContext.getDisplayId());
+                    }
+                });
+    }
+
+    @Override
+    public void setCallback(StateCallback callback) {
+        mUserActivityViewCallback = callback;
+        if (getVirtualDisplayId() != Display.INVALID_DISPLAY && callback != null) {
+            callback.onActivityViewReady(this);
+        }
+    }
+
+    private static void reportPhysicalDisplayId(CarUxRestrictionsManager manager,
+            int virtualDisplayId, int physicalDisplayId) {
+        Log.d(TAG, "reportPhysicalDisplayId: virtualDisplayId=" + virtualDisplayId
+                + ", physicalDisplayId=" + physicalDisplayId);
+        if (virtualDisplayId == Display.INVALID_DISPLAY) {
+            throw new RuntimeException("Has no virtual display to report.");
+        }
+        if (manager == null) {
+            Log.w(TAG, "CarUxRestrictionsManager is not ready yet");
+            return;
+        }
+        manager.reportVirtualDisplayToPhysicalDisplay(virtualDisplayId, physicalDisplayId);
+    }
+
+    // Intercepts ActivityViewCallback and reports it's display-id changes.
+    private class CarActivityViewCallback extends StateCallback {
+        // onActivityViewReady() and onActivityViewDestroyed() are called in the main thread.
+        @Override
+        public void onActivityViewReady(ActivityView activityView) {
+            // Stores the virtual display id to use it onActivityViewDestroyed().
+            mVirtualDisplayId = getVirtualDisplayId();
+            reportPhysicalDisplayId(
+                    mUxRestrictionsManager, mVirtualDisplayId, mContext.getDisplayId());
+
+            StateCallback stateCallback = mUserActivityViewCallback;
+            if (stateCallback != null) {
+                stateCallback.onActivityViewReady(activityView);
+            }
+        }
+
+        @Override
+        public void onActivityViewDestroyed(ActivityView activityView) {
+            // getVirtualDisplayId() will return INVALID_DISPLAY inside onActivityViewDestroyed(),
+            // because AV.mVirtualDisplay was already released during AV.performRelease().
+            int virtualDisplayId = mVirtualDisplayId;
+            mVirtualDisplayId = Display.INVALID_DISPLAY;
+            reportPhysicalDisplayId(
+                    mUxRestrictionsManager, virtualDisplayId, Display.INVALID_DISPLAY);
+
+            StateCallback stateCallback = mUserActivityViewCallback;
+            if (stateCallback != null) {
+                stateCallback.onActivityViewDestroyed(activityView);
+            }
+        }
+
+        @Override
+        public void onTaskCreated(int taskId, ComponentName componentName) {
+            StateCallback stateCallback = mUserActivityViewCallback;
+            if (stateCallback != null) {
+                stateCallback.onTaskCreated(taskId, componentName);
+            }
+        }
+
+        @Override
+        public void onTaskMovedToFront(int taskId) {
+            StateCallback stateCallback = mUserActivityViewCallback;
+            if (stateCallback != null) {
+                stateCallback.onTaskMovedToFront(taskId);
+            }
+        }
+
+        @Override
+        public void onTaskRemovalStarted(int taskId) {
+            StateCallback stateCallback = mUserActivityViewCallback;
+            if (stateCallback != null) {
+                stateCallback.onTaskRemovalStarted(taskId);
+            }
+        }
+    }
+}
diff --git a/car-lib/src/android/car/cluster/renderer/InstrumentClusterRenderer.java b/car-lib/src/android/car/cluster/renderer/InstrumentClusterRenderer.java
index be4cfd4..a66258c 100644
--- a/car-lib/src/android/car/cluster/renderer/InstrumentClusterRenderer.java
+++ b/car-lib/src/android/car/cluster/renderer/InstrumentClusterRenderer.java
@@ -20,6 +20,8 @@
 import android.annotation.UiThread;
 import android.content.Context;
 
+import com.android.internal.annotations.GuardedBy;
+
 /**
  * @deprecated This class is unused. Refer to {@link InstrumentClusterRenderingService} for
  * documentation on how to build a instrument cluster renderer.
@@ -30,6 +32,9 @@
 @SystemApi
 public abstract class InstrumentClusterRenderer {
 
+    private final Object mLock = new Object();
+
+    @GuardedBy("mLock")
     @Nullable private NavigationRenderer mNavigationRenderer;
 
     /**
@@ -45,8 +50,10 @@
 
     /** The method is thread-safe, callers should cache returned object. */
     @Nullable
-    public synchronized NavigationRenderer getNavigationRenderer() {
-        return mNavigationRenderer;
+    public NavigationRenderer getNavigationRenderer() {
+        synchronized (mLock) {
+            return mNavigationRenderer;
+        }
     }
 
     /**
@@ -54,7 +61,10 @@
      * method should not be overridden by subclasses.
      */
     @UiThread
-    public synchronized final void initialize() {
-        mNavigationRenderer = createNavigationRenderer();
+    public final void initialize() {
+        synchronized (mLock) {
+            mNavigationRenderer = createNavigationRenderer();
+        }
     }
 }
+
diff --git a/car-lib/src/android/car/cluster/renderer/InstrumentClusterRenderingService.java b/car-lib/src/android/car/cluster/renderer/InstrumentClusterRenderingService.java
index 3e463d1..5098ee7 100644
--- a/car-lib/src/android/car/cluster/renderer/InstrumentClusterRenderingService.java
+++ b/car-lib/src/android/car/cluster/renderer/InstrumentClusterRenderingService.java
@@ -94,13 +94,6 @@
 
     private static final String TAG = CarLibLog.TAG_CLUSTER;
 
-    /**
-     * This constant is kept here for backwards compatibility only.
-     * @deprecated TODO (b/130255007): Remove this along {@link NavigationRenderer#onEvent(int,
-     * Bundle)}
-     */
-    @Deprecated
-    private static final int NAVIGATION_STATE_EVENT_ID = 1;
     private static final String BITMAP_QUERY_WIDTH = "w";
     private static final String BITMAP_QUERY_HEIGHT = "h";
     private static final String BITMAP_QUERY_OFFLANESALPHA = "offLanesAlpha";
@@ -262,10 +255,8 @@
      *         target Activity is in normal state and client should retry when it fails. Once it is
      *         successfully launched, car service will guarantee that it is running across crash or
      *         other events.
-     *
-     * @hide
      */
-    protected boolean startFixedActivityModeForDisplayAndUser(@NonNull Intent intent,
+    public boolean startFixedActivityModeForDisplayAndUser(@NonNull Intent intent,
             @NonNull ActivityOptions options, @UserIdInt int userId) {
         IInstrumentClusterHelper helper = getClusterHelper();
         if (helper == null) {
@@ -285,10 +276,8 @@
     /**
      * Stop fixed mode for top Activity in the display. Crashing or launching other Activity
      * will not re-launch the top Activity any more.
-     *
-     * @hide
      */
-    protected void stopFixedActivityMode(int displayId) {
+    public void stopFixedActivityMode(int displayId) {
         IInstrumentClusterHelper helper = getClusterHelper();
         if (helper == null) {
             return;
@@ -430,9 +419,8 @@
     }
 
     /**
-     * @deprecated Use {@link #setClusterActivityLaunchOptions(ActivityOptions)} instead.
-     *
      * @hide
+     * @deprecated Use {@link #setClusterActivityLaunchOptions(ActivityOptions)} instead.
      */
     @Deprecated
     public void setClusterActivityLaunchOptions(String category, ActivityOptions activityOptions) {
@@ -453,9 +441,8 @@
     }
 
     /**
-     * @deprecated Use {@link #setClusterActivityState(ClusterActivityState)} instead.
-     *
      * @hide
+     * @deprecated Use {@link #setClusterActivityState(ClusterActivityState)} instead.
      */
     @Deprecated
     public void setClusterActivityState(String category, Bundle state) {
@@ -535,8 +522,6 @@
             assertContextOwnership();
             mUiHandler.post(() -> {
                 if (mNavigationRenderer != null) {
-                    // Invoking deprecated method to maintain backwards compatibility
-                    mNavigationRenderer.onEvent(NAVIGATION_STATE_EVENT_ID, bundle);
                     mNavigationRenderer.onNavigationStateChanged(bundle);
                 }
             });
@@ -596,8 +581,13 @@
      * This is a costly operation. Returned bitmaps should be cached and fetching should be done on
      * a secondary thread.
      *
-     * Will be deprecated. Replaced by {@link #getBitmap(Uri, int, int)}.
+     * @param uri The URI of the bitmap
+     *
+     * @throws IllegalArgumentException if {@code uri} does not have width and height query params.
+     *
+     * @deprecated Replaced by {@link #getBitmap(Uri, int, int)}.
      */
+    @Deprecated
     @Nullable
     public Bitmap getBitmap(Uri uri) {
         try {
@@ -649,11 +639,9 @@
 
     /**
      * See {@link #getBitmap(Uri, int, int, float)}
-     *
-     * @hide
      */
     @Nullable
-    public Bitmap getBitmap(Uri uri, int width, int height) {
+    public Bitmap getBitmap(@NonNull Uri uri, int width, int height) {
         return getBitmap(uri, width, height, 1f);
     }
 
@@ -668,11 +656,14 @@
      * </ul>
      * This is a costly operation. Returned bitmaps should be fetched on a secondary thread.
      *
+     * @param uri           The URI of the bitmap
+     * @param width         Requested width
+     * @param height        Requested height
+     * @param offLanesAlpha Opacity value of the off-lane images. Only used for lane guidance images
      * @throws IllegalArgumentException if width, height <= 0, or 0 > offLanesAlpha > 1
-     * @hide
      */
     @Nullable
-    public Bitmap getBitmap(Uri uri, int width, int height, float offLanesAlpha) {
+    public Bitmap getBitmap(@NonNull Uri uri, int width, int height, float offLanesAlpha) {
         if (width <= 0 || height <= 0) {
             throw new IllegalArgumentException("Width and height must be > 0");
         }
diff --git a/car-lib/src/android/car/cluster/renderer/NavigationRenderer.java b/car-lib/src/android/car/cluster/renderer/NavigationRenderer.java
index 429dada..95e1d48 100644
--- a/car-lib/src/android/car/cluster/renderer/NavigationRenderer.java
+++ b/car-lib/src/android/car/cluster/renderer/NavigationRenderer.java
@@ -37,9 +37,8 @@
     /**
      * Called when a navigation state change is received.
      *
-     * @deprecated use {@link #onNavigationStateChanged(Bundle)} instead.
+     * @removed Replaced by {@link #onNavigationStateChanged(Bundle)}
      */
-    @Deprecated
     public void onEvent(int eventType, Bundle bundle) {}
 
     /**
diff --git a/car-lib/src/android/car/content/pm/CarPackageManager.java b/car-lib/src/android/car/content/pm/CarPackageManager.java
index 7498c65..5d5936b 100644
--- a/car-lib/src/android/car/content/pm/CarPackageManager.java
+++ b/car-lib/src/android/car/content/pm/CarPackageManager.java
@@ -17,8 +17,10 @@
 package android.car.content.pm;
 
 import android.annotation.IntDef;
+import android.annotation.NonNull;
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
+import android.app.PendingIntent;
 import android.car.Car;
 import android.car.CarManagerBase;
 import android.content.ComponentName;
@@ -169,12 +171,13 @@
     }
 
     /**
-     * Check if given activity is distraction optimized, i.e, allowed in a
-     * restricted driving state
+     * Returns whether an activity is distraction optimized, i.e, allowed in a restricted
+     * driving state.
      *
-     * @param packageName
-     * @param className
-     * @return
+     * @param packageName the activity's {@link android.content.pm.ActivityInfo#packageName}.
+     * @param className the activity's {@link android.content.pm.ActivityInfo#name}.
+     * @return true if the activity is distraction optimized, false if it isn't or if the value
+     *         could not be determined.
      */
     public boolean isActivityDistractionOptimized(String packageName, String className) {
         try {
@@ -185,6 +188,22 @@
     }
 
     /**
+     * Returns whether the given {@link PendingIntent} represents an activity that is distraction
+     * optimized, i.e, allowed in a restricted driving state.
+     *
+     * @param pendingIntent the {@link PendingIntent} to check.
+     * @return true if the pending intent represents an activity that is distraction optimized,
+     *         false if it isn't or if the value could not be determined.
+     */
+    public boolean isPendingIntentDistractionOptimized(@NonNull PendingIntent pendingIntent) {
+        try {
+            return mService.isPendingIntentDistractionOptimized(pendingIntent);
+        } catch (RemoteException e) {
+            return handleRemoteExceptionFromCarService(e, false);
+        }
+    }
+
+    /**
      * Check if given service is distraction optimized, i.e, allowed in a restricted
      * driving state.
      *
diff --git a/car-lib/src/android/car/content/pm/ICarPackageManager.aidl b/car-lib/src/android/car/content/pm/ICarPackageManager.aidl
index 7210f90..b8261f2 100644
--- a/car-lib/src/android/car/content/pm/ICarPackageManager.aidl
+++ b/car-lib/src/android/car/content/pm/ICarPackageManager.aidl
@@ -16,6 +16,7 @@
 
 package android.car.content.pm;
 
+import android.app.PendingIntent;
 import android.car.content.pm.CarAppBlockingPolicy;
 import android.content.ComponentName;
 
@@ -27,4 +28,5 @@
     boolean isActivityBackedBySafeActivity(in ComponentName activityName) = 3;
     void setEnableActivityBlocking(boolean enable) = 4;
     void restartTask(int taskId) = 5;
+    boolean isPendingIntentDistractionOptimized(in PendingIntent pendingIntent) = 6;
 }
diff --git a/car-lib/src/android/car/drivingstate/CarUxRestrictionsManager.java b/car-lib/src/android/car/drivingstate/CarUxRestrictionsManager.java
index 1557e6e..79bd8e7 100644
--- a/car-lib/src/android/car/drivingstate/CarUxRestrictionsManager.java
+++ b/car-lib/src/android/car/drivingstate/CarUxRestrictionsManager.java
@@ -21,8 +21,10 @@
 import android.annotation.RequiresPermission;
 import android.car.Car;
 import android.car.CarManagerBase;
+import android.os.Bundle;
 import android.os.Handler;
 import android.os.IBinder;
+import android.os.IRemoteCallback;
 import android.os.Looper;
 import android.os.Message;
 import android.os.RemoteException;
@@ -381,4 +383,47 @@
 
         return mDisplayId;
     }
+
+    // Dummy Callback to identify the requester of reportVirtualDisplayToPhysicalDisplay() and
+    // to clean up the internal data when the requester is crashed.
+    private final IRemoteCallback mRequester = new IRemoteCallback.Stub() {
+        @Override
+        public void sendResult(Bundle data) {
+            // Unused
+        }
+    };
+
+    /**
+     * Reports the mapping the virtual display to the physical display.
+     *
+     * @param virtualDisplayId the display id of the embedded virtual display.
+     * @parom physicalDisplayId the display id where the ActivityView is placed in.
+     * @hide
+     */
+    public void reportVirtualDisplayToPhysicalDisplay(int virtualDisplayId, int physicalDisplayId) {
+        try {
+            mUxRService.reportVirtualDisplayToPhysicalDisplay(mRequester,
+                    virtualDisplayId, physicalDisplayId);
+        } catch (RemoteException e) {
+            handleRemoteExceptionFromCarService(e);
+        }
+    }
+
+    /**
+     * Finds out the physical display id where ActivityView is actually located in.
+     * If the given ActivityView is placed inside of another ActivityView, then it will return
+     * the display id where the parent ActivityView is located in.
+     *
+     * @param displayId the display id of the embedded virtual display of ActivityView.
+     * @return the physical display id where ActivityView is actually located in.
+     * @hide
+     */
+    public int getMappedPhysicalDisplayOfVirtualDisplay(int displayId) {
+        try {
+            return mUxRService.getMappedPhysicalDisplayOfVirtualDisplay(displayId);
+        } catch (RemoteException e) {
+            // When CarService isn't ready, we'll return DEFAULT_DISPLAY defensively.
+            return handleRemoteExceptionFromCarService(e, Display.DEFAULT_DISPLAY);
+        }
+    }
 }
diff --git a/car-lib/src/android/car/drivingstate/ICarUxRestrictionsManager.aidl b/car-lib/src/android/car/drivingstate/ICarUxRestrictionsManager.aidl
index 8c9afd7..dd4dc72 100644
--- a/car-lib/src/android/car/drivingstate/ICarUxRestrictionsManager.aidl
+++ b/car-lib/src/android/car/drivingstate/ICarUxRestrictionsManager.aidl
@@ -19,6 +19,7 @@
 import android.car.drivingstate.CarUxRestrictions;
 import android.car.drivingstate.CarUxRestrictionsConfiguration;
 import android.car.drivingstate.ICarUxRestrictionsChangeListener;
+import android.os.IRemoteCallback;
 
 /**
  * Binder interface for {@link android.car.drivingstate.CarUxRestrictionsManager}.
@@ -36,6 +37,8 @@
     List<CarUxRestrictionsConfiguration> getConfigs() = 5;
     // 6 removed. Do not use - boolean setRestrictionMode(int mode) = 6;
     // 7 removed. Do not use - int getRestrictionMode() = 7;
+    void reportVirtualDisplayToPhysicalDisplay(in IRemoteCallback binder, int virtualDisplayId, int physicalDisplayId) = 8;
+    int getMappedPhysicalDisplayOfVirtualDisplay(int displayId) = 9;
     boolean setRestrictionMode(String mode) = 10;
     String getRestrictionMode() = 11;
 }
diff --git a/car-lib/src/android/car/hardware/CarHvacFanDirection.java b/car-lib/src/android/car/hardware/CarHvacFanDirection.java
new file mode 100644
index 0000000..c55dd13
--- /dev/null
+++ b/car-lib/src/android/car/hardware/CarHvacFanDirection.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.car.hardware;
+
+import android.annotation.IntDef;
+import android.annotation.SystemApi;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * CarHvacFanDirection is an abstraction for car's fan directions.
+ * <p>
+ * {@link android.car.VehiclePropertyIds#HVAC_FAN_DIRECTION} and
+ * {@link android.car.VehiclePropertyIds#HVAC_FAN_DIRECTION_AVAILABLE} use constants in
+ * {@link CarHvacFanDirection} as their property value.
+ * Developers can compare the property value with constants in this class to know which fan
+ * direction is used in cars.
+ * </p>
+ * @hide
+ */
+// This class is only designed to provide constants for car's fan direction. The constants should
+// exactly be same as VehicleHvacFanDirection in file
+// hardware/interfaces/automotive/vehicle/2.0/types.hal.
+@SystemApi
+public final class CarHvacFanDirection {
+    /** Constant for unknown fan direction. */
+    public static final int UNKNOWN = 0x0;
+    /** Constant for face direction. */
+    public static final int FACE = 0x01;
+    /** Constant for floor direction. */
+    public static final int FLOOR = 0x02;
+    /** Constant for face and floor direction. */
+    public static final int FACE_AND_FLOOR = 0x03; // FACE_AND_FLOOR = FACE | FLOOR
+    /** Constant for defrost direction. */
+    public static final int DEFROST = 0x04;
+    /** Constant for defrost and floor direction.*/
+    public static final int DEFROST_AND_FLOOR = 0x06; // DEFROST_AND_FLOOR = DEFROST | FLOOR
+
+    /**@hide*/
+    @IntDef(value = {
+            UNKNOWN,
+            FACE,
+            FLOOR,
+            FACE_AND_FLOOR,
+            DEFROST,
+            DEFROST_AND_FLOOR
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Enum {}
+    private CarHvacFanDirection() {}
+}
diff --git a/car-lib/src/android/car/hardware/CarPropertyConfig.java b/car-lib/src/android/car/hardware/CarPropertyConfig.java
index 87c4b46..a74a361 100644
--- a/car-lib/src/android/car/hardware/CarPropertyConfig.java
+++ b/car-lib/src/android/car/hardware/CarPropertyConfig.java
@@ -203,10 +203,23 @@
     }
 
     /**
-     * @return Value type of VehicleProperty.
-     * @hide
+     * Returns the value type of the vehicle property.
+     * <p>The value type could be one of the following:
+     * <ul>
+     *   <li>Boolean</li>
+     *   <li>Float</li>
+     *   <li>Float[]</li>
+     *   <li>Integer</li>
+     *   <li>Integer[]</li>
+     *   <li>Long</li>
+     *   <li>Long[]</li>
+     *   <li>String</li>
+     *   <li>byte[]</li>
+     *   <li>Object[]</li>
+     * </ul>
+     *
+     * @return the value type of the vehicle property.
      */
-    @SystemApi
     @NonNull
     public Class<T> getPropertyType() {
         return mType;
diff --git a/car-lib/src/android/car/hardware/hvac/CarHvacManager.java b/car-lib/src/android/car/hardware/hvac/CarHvacManager.java
index 3ab7631..f6a2d53 100644
--- a/car-lib/src/android/car/hardware/hvac/CarHvacManager.java
+++ b/car-lib/src/android/car/hardware/hvac/CarHvacManager.java
@@ -212,16 +212,19 @@
 
 
     /**
+     * Use {@link android.car.hardware.CarHvacFanDirection#FACE} instead.
      * Represents fan direction when air flows through face directed vents.
      * This constant must be used with {@link #ID_ZONED_FAN_DIRECTION} property.
      */
     public static final int FAN_DIRECTION_FACE = 0x1;
     /**
+     * Use {@link android.car.hardware.CarHvacFanDirection#FLOOR} instead.
      * Represents fan direction when air flows through floor directed vents.
      * This constant must be used with {@link #ID_ZONED_FAN_DIRECTION} property.
      */
     public static final int FAN_DIRECTION_FLOOR = 0x2;
     /**
+     * Use {@link android.car.hardware.CarHvacFanDirection#DEFROST} instead.
      * Represents fan direction when air flows through defrost vents.
      * This constant must be used with {@link #ID_ZONED_FAN_DIRECTION} property.
      */
diff --git a/car-lib/src/android/car/hardware/power/CarPowerManager.java b/car-lib/src/android/car/hardware/power/CarPowerManager.java
index 4b0e8cf..7c2a323 100644
--- a/car-lib/src/android/car/hardware/power/CarPowerManager.java
+++ b/car-lib/src/android/car/hardware/power/CarPowerManager.java
@@ -220,14 +220,18 @@
                             future = mFuture;
                         }
                         // Notify user that the state has changed and supply a future
-                        listenerWithCompletion.onStateChanged(state, future);
+                        if (listenerWithCompletion != null) {
+                            listenerWithCompletion.onStateChanged(state, future);
+                        }
                     } else {
                         CarPowerStateListener listener;
                         synchronized (mLock) {
                             listener = mListener;
                         }
                         // Notify the user without supplying a future
-                        listener.onStateChanged(state);
+                        if (listener != null) {
+                            listener.onStateChanged(state);
+                        }
                     }
                 }
             };
diff --git a/car-lib/src/android/car/hardware/property/CarInternalErrorException.java b/car-lib/src/android/car/hardware/property/CarInternalErrorException.java
new file mode 100644
index 0000000..7fc3f52
--- /dev/null
+++ b/car-lib/src/android/car/hardware/property/CarInternalErrorException.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.car.hardware.property;
+
+import static java.lang.Integer.toHexString;
+
+/**
+ * Exception thrown when something unexpected happened in cars.
+ */
+public class CarInternalErrorException extends RuntimeException {
+    CarInternalErrorException(int property, int areaId) {
+        super("Property 0x" + toHexString(property) + " with area: " + toHexString(areaId)
+                + " raised an internal error in cars.");
+    }
+}
diff --git a/car-lib/src/android/car/hardware/property/CarPropertyEvent.java b/car-lib/src/android/car/hardware/property/CarPropertyEvent.java
index 4cb7e92..c423c2f 100644
--- a/car-lib/src/android/car/hardware/property/CarPropertyEvent.java
+++ b/car-lib/src/android/car/hardware/property/CarPropertyEvent.java
@@ -25,11 +25,15 @@
 public class CarPropertyEvent implements Parcelable {
     public static final int PROPERTY_EVENT_PROPERTY_CHANGE = 0;
     public static final int PROPERTY_EVENT_ERROR = 1;
-
     /**
      * EventType of this message
      */
     private final int mEventType;
+
+    /**
+     * ErrorCode of this message.
+     */
+    private final @CarPropertyManager.CarSetPropertyErrorCode int mErrorCode;
     private final CarPropertyValue<?> mCarPropertyValue;
 
     // Use it as default value for error events.
@@ -53,6 +57,7 @@
     @Override
     public void writeToParcel(Parcel dest, int flags) {
         dest.writeInt(mEventType);
+        dest.writeInt(mErrorCode);
         dest.writeParcelable(mCarPropertyValue, flags);
     }
 
@@ -72,32 +77,52 @@
      */
     public CarPropertyEvent(int eventType, @NonNull CarPropertyValue<?> carPropertyValue) {
         mEventType  = eventType;
+        mErrorCode = CarPropertyManager.CAR_SET_PROPERTY_ERROR_CODE_UNKNOWN;
         mCarPropertyValue = carPropertyValue;
     }
 
     /**
+     * Constructor for {@link CarPropertyEvent} with an error code.
+     */
+    public CarPropertyEvent(int eventType, @NonNull CarPropertyValue<?> carPropertyValue,
+            @CarPropertyManager.CarSetPropertyErrorCode int errorCode) {
+        mEventType  = eventType;
+        mErrorCode = errorCode;
+        mCarPropertyValue = carPropertyValue;
+    }
+
+
+    /**
      * Constructor for {@link CarPropertyEvent} when it is an error event.
      *
      * The status of {@link CarPropertyValue} should be {@link CarPropertyValue#STATUS_ERROR}.
      * In {@link CarPropertyManager}, the value of {@link CarPropertyValue} will be dropped.
      */
-    public static CarPropertyEvent createErrorEvent(int propertyId, int areaId) {
-        // valueWithErrorCode will not be propagated to listeners
+    public static CarPropertyEvent createErrorEventWithErrorCode(int propertyId, int areaId,
+            @CarPropertyManager.CarSetPropertyErrorCode int errorCode) {
         CarPropertyValue<Integer> valueWithErrorCode = new CarPropertyValue<>(propertyId, areaId,
-                    CarPropertyValue.STATUS_ERROR, 0, ERROR_EVENT_VALUE);
-        return new CarPropertyEvent(PROPERTY_EVENT_ERROR, valueWithErrorCode);
+                CarPropertyValue.STATUS_ERROR, 0, ERROR_EVENT_VALUE);
+        CarPropertyEvent event = new CarPropertyEvent(PROPERTY_EVENT_ERROR, valueWithErrorCode,
+                errorCode);
+        return event;
+    }
+
+    public @CarPropertyManager.CarSetPropertyErrorCode int getErrorCode() {
+        return mErrorCode;
     }
 
     private CarPropertyEvent(Parcel in) {
         mEventType  = in.readInt();
+        mErrorCode = in.readInt();
         mCarPropertyValue = in.readParcelable(CarPropertyValue.class.getClassLoader());
     }
 
     @Override
     public String toString() {
-        return "CarPropertyEvent{" +
-                "mEventType=" + mEventType +
-                ", mCarPropertyValue=" + mCarPropertyValue +
-                '}';
+        return "CarPropertyEvent{"
+                + "mEventType=" + mEventType
+                + ", mErrorCode=" + mErrorCode
+                + ", mCarPropertyValue=" + mCarPropertyValue
+                + '}';
     }
 }
diff --git a/car-lib/src/android/car/hardware/property/CarPropertyManager.java b/car-lib/src/android/car/hardware/property/CarPropertyManager.java
index be4766b..dafa8bc 100644
--- a/car-lib/src/android/car/hardware/property/CarPropertyManager.java
+++ b/car-lib/src/android/car/hardware/property/CarPropertyManager.java
@@ -19,14 +19,17 @@
 import static java.lang.Integer.toHexString;
 
 import android.annotation.FloatRange;
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.car.Car;
 import android.car.CarManagerBase;
+import android.car.VehicleAreaType;
 import android.car.hardware.CarPropertyConfig;
 import android.car.hardware.CarPropertyValue;
 import android.os.Handler;
 import android.os.RemoteException;
+import android.os.ServiceSpecificException;
 import android.util.ArraySet;
 import android.util.Log;
 import android.util.SparseArray;
@@ -34,6 +37,8 @@
 import com.android.car.internal.CarRatedFloatListeners;
 import com.android.car.internal.SingleMessageHandler;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
 import java.util.List;
@@ -72,11 +77,35 @@
         void onChangeEvent(CarPropertyValue value);
 
         /**
-         * Called when an error is detected with a property
+         * Called when an error is detected when setting a property.
+         *
          * @param propId Property ID which is detected an error.
          * @param zone Zone which is detected an error.
+         *
+         * @see CarPropertyEventCallback#onErrorEvent(int, int, int)
          */
         void onErrorEvent(int propId, int zone);
+
+        /**
+         * Called when an error is detected when setting a property.
+         *
+         * <p>Clients which changed the property value in the areaId most recently will receive
+         * this callback. If multiple clients set a property for the same area id simultaneously,
+         * which one takes precedence is undefined. Typically, the last set operation
+         * (in the order that they are issued to car's ECU) overrides the previous set operations.
+         * The delivered error reflects the error happened in the last set operation.
+         *
+         * @param propId Property ID which is detected an error.
+         * @param areaId AreaId which is detected an error.
+         * @param errorCode Error code is raised in the car.
+         */
+        default void onErrorEvent(int propId, int areaId, @CarSetPropertyErrorCode int errorCode) {
+            if (DBG) {
+                Log.d(TAG, "onErrorEvent propertyId: 0x" + toHexString(propId) + " areaId:0x"
+                        + toHexString(areaId) + " ErrorCode: " + errorCode);
+            }
+            onErrorEvent(propId, areaId);
+        }
     }
 
     /** Read ON_CHANGE sensors */
@@ -90,6 +119,44 @@
     /** Read sensors at the rate of 100 hertz */
     public static final float SENSOR_RATE_FASTEST = 100f;
 
+
+
+    /**
+     * Status to indicate that set operation failed. Try it again.
+     */
+    public static final int CAR_SET_PROPERTY_ERROR_CODE_TRY_AGAIN = 1;
+
+    /**
+     * Status to indicate that set operation failed because of an invalid argument.
+     */
+    public static final int CAR_SET_PROPERTY_ERROR_CODE_INVALID_ARG = 2;
+
+    /**
+     * Status to indicate that set operation failed because the property is not available.
+     */
+    public static final int CAR_SET_PROPERTY_ERROR_CODE_PROPERTY_NOT_AVAILABLE = 3;
+
+    /**
+     * Status to indicate that set operation failed because car denied access to the property.
+     */
+    public static final int CAR_SET_PROPERTY_ERROR_CODE_ACCESS_DENIED = 4;
+
+    /**
+     * Status to indicate that set operation failed because of an general error in cars.
+     */
+    public static final int CAR_SET_PROPERTY_ERROR_CODE_UNKNOWN = 5;
+
+    /** @hide */
+    @IntDef(prefix = {"CAR_SET_PROPERTY_ERROR_CODE_"}, value = {
+            CAR_SET_PROPERTY_ERROR_CODE_TRY_AGAIN,
+            CAR_SET_PROPERTY_ERROR_CODE_INVALID_ARG,
+            CAR_SET_PROPERTY_ERROR_CODE_PROPERTY_NOT_AVAILABLE,
+            CAR_SET_PROPERTY_ERROR_CODE_ACCESS_DENIED,
+            CAR_SET_PROPERTY_ERROR_CODE_UNKNOWN,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface CarSetPropertyErrorCode {}
+
     /**
      * Get an instance of the CarPropertyManager.
      *
@@ -317,6 +384,47 @@
     }
 
     /**
+     * Get CarPropertyConfig by property Id.
+     *
+     * @param propId Property ID
+     * @return {@link CarPropertyConfig} for the selected property.
+     * Null if the property is not available.
+     */
+    @Nullable
+    public CarPropertyConfig<?> getCarPropertyConfig(int propId) {
+        return  mConfigMap.get(propId);
+    }
+
+    /**
+     * Returns areaId contains the seletcted area for the property.
+     *
+     * @param propId Property ID
+     * @param area Area enum such as Enums in {@link android.car.VehicleAreaSeat}.
+     * @throws IllegalArgumentException if the property is not available in the vehicle for
+     * the selected area.
+     * @return AreaId contains the selected area for the property.
+     */
+    public int getAreaId(int propId, int area) {
+        CarPropertyConfig<?> propConfig = getCarPropertyConfig(propId);
+        if (propConfig == null) {
+            throw new IllegalArgumentException("The property propId: 0x" + toHexString(propId)
+                    + " is not available");
+        }
+        // For the global property, areaId is 0
+        if (propConfig.isGlobalProperty()) {
+            return VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL;
+        }
+        for (int areaId : propConfig.getAreaIds()) {
+            if ((area & areaId) == area) {
+                return areaId;
+            }
+        }
+
+        throw new IllegalArgumentException("The property propId: 0x" + toHexString(propId)
+                + " is not available at the area: 0x" + toHexString(area));
+    }
+
+    /**
      * Return read permission string for given property ID.
      *
      * @param propId Property ID to query
@@ -432,7 +540,11 @@
      * @param clazz The class object for the CarPropertyValue
      * @param propId Property ID to get
      * @param areaId Zone of the property to get
-     * @throws IllegalArgumentException if there is invalid property type.
+     * @throws {@link CarInternalErrorException} when there is an error detected in cars.
+     * @throws {@link PropertyAccessDeniedSecurityException} when cars denied the access of the
+     * property.
+     * @throws {@link PropertyNotAvailableException} when the property is temporarily not available.
+     * @throws {@link IllegalArgumentException} when the property in the areaId is not supplied.
      * @return CarPropertyValue. Null if property's id is invalid.
      */
     @SuppressWarnings("unchecked")
@@ -454,6 +566,8 @@
             return propVal;
         } catch (RemoteException e) {
             return handleRemoteExceptionFromCarService(e, null);
+        } catch (ServiceSpecificException e) {
+            return handleCarServiceSpecificException(e.errorCode, propId, areaId, null);
         }
     }
 
@@ -461,7 +575,12 @@
      * Query CarPropertyValue with property id and areaId.
      * @param propId Property Id
      * @param areaId areaId
-     * @param <E>
+     * @param <E> Value type of the property
+     * @throws {@link CarInternalErrorException} when there is an error detected in cars.
+     * @throws {@link PropertyAccessDeniedSecurityException} when cars denied the access of the
+     * property.
+     * @throws {@link PropertyNotAvailableException} when the property is temporarily not available.
+     * @throws {@link IllegalArgumentException} when the property in the areaId is not supplied.
      * @return CarPropertyValue. Null if property's id is invalid.
      */
     @Nullable
@@ -471,18 +590,33 @@
             return propVal;
         } catch (RemoteException e) {
             return handleRemoteExceptionFromCarService(e, null);
+        } catch (ServiceSpecificException e) {
+            return handleCarServiceSpecificException(e.errorCode, propId, areaId, null);
         }
     }
 
     /**
      * Set value of car property by areaId.
+     *
+     * <p>If multiple clients set a property for the same area id simultaneously, which one takes
+     * precedence is undefined. Typically, the last set operation (in the order that they are issued
+     * to the car's ECU) overrides the previous set operations.
+     *
      * @param clazz The class object for the CarPropertyValue
      * @param propId Property ID
      * @param areaId areaId
      * @param val Value of CarPropertyValue
      * @param <E> data type of the given property, for example property that was
      * defined as {@code VEHICLE_VALUE_TYPE_INT32} in vehicle HAL could be accessed using
-     * {@code Integer.class}
+     * {@code Integer.class}.
+     * @throws {@link CarInternalErrorException} when there is an error detected in cars.
+     * @throws {@link PropertyAccessDeniedSecurityException} when cars denied the access of the
+     * property.
+     * @throws {@link PropertyNotAvailableException} when the property is temporarily not available.
+     * @throws {@link PropertyNotAvailableAndRetryException} when the property is temporarily
+     * not available and likely that retrying will be successful.
+     * @throws {@link IllegalStateException} when get an unexpected error code.
+     * @throws {@link IllegalArgumentException} when the property in the areaId is not supplied.
      */
     public <E> void setProperty(@NonNull Class<E> clazz, int propId, int areaId, @NonNull E val) {
         if (DBG) {
@@ -490,9 +624,15 @@
                     + ", areaId: 0x" + toHexString(areaId) + ", class: " + clazz + ", val: " + val);
         }
         try {
-            mService.setProperty(new CarPropertyValue<>(propId, areaId, val));
+            if (mCarPropertyEventToService == null) {
+                mCarPropertyEventToService = new CarPropertyEventListenerToService(this);
+            }
+            mService.setProperty(new CarPropertyValue<>(propId, areaId, val),
+                    mCarPropertyEventToService);
         } catch (RemoteException e) {
             handleRemoteExceptionFromCarService(e);
+        } catch (ServiceSpecificException e) {
+            handleCarServiceSpecificException(e.errorCode, propId, areaId, null);
         }
     }
 
@@ -530,6 +670,22 @@
         setProperty(Integer.class, prop, areaId, val);
     }
 
+    private <T> T handleCarServiceSpecificException(int errorCode, int propId, int areaId,
+            T returnValue) {
+        switch (errorCode) {
+            case VehicleHalStatusCode.STATUS_NOT_AVAILABLE:
+                throw new PropertyNotAvailableException(propId, areaId);
+            case VehicleHalStatusCode.STATUS_TRY_AGAIN:
+                throw new PropertyNotAvailableAndRetryException(propId, areaId);
+            case VehicleHalStatusCode.STATUS_ACCESS_DENIED:
+                throw new PropertyAccessDeniedSecurityException(propId, areaId);
+            case VehicleHalStatusCode.STATUS_INTERNAL_ERROR:
+                throw new CarInternalErrorException(propId, areaId);
+            default:
+                Log.e(TAG, "Invalid errorCode: " + errorCode + " in CarService");
+        }
+        return returnValue;
+    }
 
     private class CarPropertyListeners extends CarRatedFloatListeners<CarPropertyEventCallback> {
         CarPropertyListeners(float rate) {
@@ -570,9 +726,13 @@
                         Log.d(TAG, new StringBuilder().append("onErrorEvent for ")
                                         .append("property: ").append(value.getPropertyId())
                                         .append(" areaId: ").append(value.getAreaId())
+                                        .append(" errorCode: ").append(event.getErrorCode())
                                         .toString());
                     }
-                    listener.onErrorEvent(value.getPropertyId(), value.getAreaId());
+
+                    listener.onErrorEvent(value.getPropertyId(), value.getAreaId(),
+                            event.getErrorCode());
+
                 }
             });
         }
diff --git a/car-lib/src/android/car/hardware/property/ICarProperty.aidl b/car-lib/src/android/car/hardware/property/ICarProperty.aidl
index ab5ad7e..f2496a2 100644
--- a/car-lib/src/android/car/hardware/property/ICarProperty.aidl
+++ b/car-lib/src/android/car/hardware/property/ICarProperty.aidl
@@ -33,7 +33,7 @@
 
     CarPropertyValue getProperty(int prop, int zone) = 3;
 
-    void setProperty(in CarPropertyValue prop) = 4;
+    void setProperty(in CarPropertyValue prop, in ICarPropertyEventListener callback) = 4;
 
     String getReadPermission(int propId) = 5;
 
diff --git a/car-lib/src/android/car/hardware/property/PropertyAccessDeniedSecurityException.java b/car-lib/src/android/car/hardware/property/PropertyAccessDeniedSecurityException.java
new file mode 100644
index 0000000..dcfb284
--- /dev/null
+++ b/car-lib/src/android/car/hardware/property/PropertyAccessDeniedSecurityException.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.car.hardware.property;
+
+import static java.lang.Integer.toHexString;
+
+/**
+ * Exception thrown when cars denied the access of properties.
+ */
+public class PropertyAccessDeniedSecurityException extends SecurityException {
+    PropertyAccessDeniedSecurityException(int property, int areaId) {
+        super("Cars denied the access of property 0x"
+                + toHexString(property) + " in area: " + toHexString(areaId));
+    }
+}
diff --git a/car-lib/src/android/car/hardware/property/PropertyNotAvailableAndRetryException.java b/car-lib/src/android/car/hardware/property/PropertyNotAvailableAndRetryException.java
new file mode 100644
index 0000000..a04b9b2
--- /dev/null
+++ b/car-lib/src/android/car/hardware/property/PropertyNotAvailableAndRetryException.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.car.hardware.property;
+
+import static java.lang.Integer.toHexString;
+
+/**
+ * Exception thrown when device that associated with the vehicle property is temporarily
+ * not available. It's likely that retrying will be successful.
+ */
+public class PropertyNotAvailableAndRetryException extends IllegalStateException {
+    PropertyNotAvailableAndRetryException(int property, int areaId) {
+        super("Property 0x" + toHexString(property) + " with area: " + toHexString(areaId)
+                + " is temporarily not available. Try the operation later.");
+    }
+}
diff --git a/car-lib/src/android/car/hardware/property/PropertyNotAvailableException.java b/car-lib/src/android/car/hardware/property/PropertyNotAvailableException.java
new file mode 100644
index 0000000..1edfd67
--- /dev/null
+++ b/car-lib/src/android/car/hardware/property/PropertyNotAvailableException.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.car.hardware.property;
+
+import static java.lang.Integer.toHexString;
+
+/**
+ * Exception thrown when device that associated with the vehicle property is temporarily
+ * not available because of the current state of cars. For example, applications try to change
+ * HVAC fan speed when the HVAC system is power off.
+ */
+public class PropertyNotAvailableException extends IllegalStateException {
+    PropertyNotAvailableException(int property, int areaId) {
+        super("Property 0x" + toHexString(property) + " with area: " + toHexString(areaId)
+                + " is temporarily not available because the current state of cars.");
+    }
+}
diff --git a/car-lib/src/android/car/hardware/property/VehicleHalStatusCode.java b/car-lib/src/android/car/hardware/property/VehicleHalStatusCode.java
new file mode 100644
index 0000000..983c2ad
--- /dev/null
+++ b/car-lib/src/android/car/hardware/property/VehicleHalStatusCode.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.car.hardware.property;
+
+/**
+ * Error codes used in vehicle HAL interface.
+ * @hide
+ */
+public final class VehicleHalStatusCode {
+
+    /** No error detected in HAL.*/
+    public static final int STATUS_OK = 0;
+    /** Try again. */
+    public static final int STATUS_TRY_AGAIN = 1;
+    /** Invalid argument provide. */
+    public static final int STATUS_INVALID_ARG = 2;
+    /**
+     * This code must be returned when device that associated with the vehicle
+     * property is not available. For example, when client tries to set HVAC
+     * temperature when the whole HVAC unit is turned OFF.
+     */
+    public static final int STATUS_NOT_AVAILABLE = 3;
+    /** Access denied */
+    public static final int STATUS_ACCESS_DENIED = 4;
+    /** Something unexpected has happened in Vehicle HAL */
+    public static final int STATUS_INTERNAL_ERROR = 5;
+
+    private VehicleHalStatusCode() {}
+}
diff --git a/car-lib/src/android/car/hardware/property/VehicleVendorPermission.java b/car-lib/src/android/car/hardware/property/VehicleVendorPermission.java
new file mode 100644
index 0000000..3ad6b9c
--- /dev/null
+++ b/car-lib/src/android/car/hardware/property/VehicleVendorPermission.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.car.hardware.property;
+
+import android.annotation.SystemApi;
+
+/**
+ * VehicleVendorPermission list all vendor permissions for vehicle. Vendors can map the vendor
+ * properties with any vendor permission.
+ * @hide
+ */
+@SystemApi
+public final class VehicleVendorPermission {
+
+    // permissions for the property related with window
+    public static final String PERMISSION_GET_CAR_VENDOR_CATEGORY_WINDOW =
+            "android.car.permission.GET_CAR_VENDOR_CATEGORY_WINDOW";
+    public static final String PERMISSION_SET_CAR_VENDOR_CATEGORY_WINDOW =
+            "android.car.permission.SET_CAR_VENDOR_CATEGORY_WINDOW";
+
+    // permissions for the property related with door
+    public static final String PERMISSION_GET_CAR_VENDOR_CATEGORY_DOOR =
+            "android.car.permission.GET_CAR_VENDOR_CATEGORY_DOOR";
+    public static final String PERMISSION_SET_CAR_VENDOR_CATEGORY_DOOR =
+            "android.car.permission.SET_CAR_VENDOR_CATEGORY_DOOR";
+
+    // permissions for the property related with seat
+    public static final String PERMISSION_GET_CAR_VENDOR_CATEGORY_SEAT =
+            "android.car.permission.GET_CAR_VENDOR_CATEGORY_SEAT";
+    public static final String PERMISSION_SET_CAR_VENDOR_CATEGORY_SEAT =
+            "android.car.permission.SET_CAR_VENDOR_CATEGORY_SEAT";
+
+    // permissions for the property related with mirror
+    public static final String PERMISSION_GET_CAR_VENDOR_CATEGORY_MIRROR =
+            "android.car.permission.GET_CAR_VENDOR_CATEGORY_MIRROR";
+    public static final String PERMISSION_SET_CAR_VENDOR_CATEGORY_MIRROR =
+            "android.car.permission.SET_CAR_VENDOR_CATEGORY_MIRROR";
+
+    // permissions for the property related with car's information
+    public static final String PERMISSION_GET_CAR_VENDOR_CATEGORY_INFO =
+            "android.car.permission.GET_CAR_VENDOR_CATEGORY_INFO";
+    public static final String PERMISSION_SET_CAR_VENDOR_CATEGORY_INFO =
+            "android.car.permission.SET_CAR_VENDOR_CATEGORY_INFO";
+
+    // permissions for the property related with car's engine
+    public static final String PERMISSION_GET_CAR_VENDOR_CATEGORY_ENGINE =
+            "android.car.permission.GET_CAR_VENDOR_CATEGORY_ENGINE";
+    public static final String PERMISSION_SET_CAR_VENDOR_CATEGORY_ENGINE =
+            "android.car.permission.SET_CAR_VENDOR_CATEGORY_ENGINE";
+
+    // permissions for the property related with car's HVAC
+    public static final String PERMISSION_GET_CAR_VENDOR_CATEGORY_HVAC =
+            "android.car.permission.GET_CAR_VENDOR_CATEGORY_HVAC";
+    public static final String PERMISSION_SET_CAR_VENDOR_CATEGORY_HVAC =
+            "android.car.permission.SET_CAR_VENDOR_CATEGORY_HVAC";
+
+    // permissions for the property related with car's light
+    public static final String PERMISSION_GET_CAR_VENDOR_CATEGORY_LIGHT =
+            "android.car.permission.GET_CAR_VENDOR_CATEGORY_LIGHT";
+    public static final String PERMISSION_SET_CAR_VENDOR_CATEGORY_LIGHT =
+            "android.car.permission.SET_CAR_VENDOR_CATEGORY_LIGHT";
+
+
+    // permissions reserved for other vendor permission
+    public static final String PERMISSION_GET_CAR_VENDOR_CATEGORY_1 =
+            "android.car.permission.GET_CAR_VENDOR_CATEGORY_1";
+    public static final String PERMISSION_SET_CAR_VENDOR_CATEGORY_1 =
+            "android.car.permission.SET_CAR_VENDOR_CATEGORY_1";
+    public static final String PERMISSION_GET_CAR_VENDOR_CATEGORY_2 =
+            "android.car.permission.GET_CAR_VENDOR_CATEGORY_2";
+    public static final String PERMISSION_SET_CAR_VENDOR_CATEGORY_2 =
+            "android.car.permission.SET_CAR_VENDOR_CATEGORY_2";
+    public static final String PERMISSION_GET_CAR_VENDOR_CATEGORY_3 =
+            "android.car.permission.GET_CAR_VENDOR_CATEGORY_3";
+    public static final String PERMISSION_SET_CAR_VENDOR_CATEGORY_3 =
+            "android.car.permission.SET_CAR_VENDOR_CATEGORY_3";
+    public static final String PERMISSION_GET_CAR_VENDOR_CATEGORY_4 =
+            "android.car.permission.GET_CAR_VENDOR_CATEGORY_4";
+    public static final String PERMISSION_SET_CAR_VENDOR_CATEGORY_4 =
+            "android.car.permission.SET_CAR_VENDOR_CATEGORY_4";
+    public static final String PERMISSION_GET_CAR_VENDOR_CATEGORY_5 =
+            "android.car.permission.GET_CAR_VENDOR_CATEGORY_5";
+    public static final String PERMISSION_SET_CAR_VENDOR_CATEGORY_5 =
+            "android.car.permission.SET_CAR_VENDOR_CATEGORY_5";
+    public static final String PERMISSION_GET_CAR_VENDOR_CATEGORY_6 =
+            "android.car.permission.GET_CAR_VENDOR_CATEGORY_6";
+    public static final String PERMISSION_SET_CAR_VENDOR_CATEGORY_6 =
+            "android.car.permission.SET_CAR_VENDOR_CATEGORY_6";
+    public static final String PERMISSION_GET_CAR_VENDOR_CATEGORY_7 =
+            "android.car.permission.GET_CAR_VENDOR_CATEGORY_7";
+    public static final String PERMISSION_SET_CAR_VENDOR_CATEGORY_7 =
+            "android.car.permission.SET_CAR_VENDOR_CATEGORY_7";
+    public static final String PERMISSION_GET_CAR_VENDOR_CATEGORY_8 =
+            "android.car.permission.GET_CAR_VENDOR_CATEGORY_8";
+    public static final String PERMISSION_SET_CAR_VENDOR_CATEGORY_8 =
+            "android.car.permission.SET_CAR_VENDOR_CATEGORY_8";
+    public static final String PERMISSION_GET_CAR_VENDOR_CATEGORY_9 =
+            "android.car.permission.GET_CAR_VENDOR_CATEGORY_9";
+    public static final String PERMISSION_SET_CAR_VENDOR_CATEGORY_9 =
+            "android.car.permission.SET_CAR_VENDOR_CATEGORY_9";
+    public static final String PERMISSION_GET_CAR_VENDOR_CATEGORY_10 =
+            "android.car.permission.GET_CAR_VENDOR_CATEGORY_10";
+    public static final String PERMISSION_SET_CAR_VENDOR_CATEGORY_10 =
+            "android.car.permission.SET_CAR_VENDOR_CATEGORY_10";
+
+    private VehicleVendorPermission() {}
+
+}
diff --git a/car-lib/src/android/car/media/CarAudioManager.java b/car-lib/src/android/car/media/CarAudioManager.java
index 2bc8fd7..5b5f07b 100644
--- a/car-lib/src/android/car/media/CarAudioManager.java
+++ b/car-lib/src/android/car/media/CarAudioManager.java
@@ -16,6 +16,7 @@
 package android.car.media;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
@@ -23,6 +24,10 @@
 import android.car.CarLibLog;
 import android.car.CarManagerBase;
 import android.media.AudioAttributes;
+import android.media.AudioDeviceAttributes;
+import android.media.AudioDeviceInfo;
+import android.media.AudioManager;
+import android.media.AudioManager.AudioDeviceRole;
 import android.os.Bundle;
 import android.os.IBinder;
 import android.os.RemoteException;
@@ -31,7 +36,10 @@
 import android.view.DisplayAddress;
 
 import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
 
 /**
  * APIs for handling audio in a car.
@@ -60,6 +68,13 @@
     public static final int PRIMARY_AUDIO_ZONE = 0x0;
 
     /**
+     * Zone id of the invalid audio zone.
+     * @hide
+     */
+    @SystemApi
+    public static final int INVALID_AUDIO_ZONE = 0xffffffff;
+
+    /**
      * Extra for {@link android.media.AudioAttributes.Builder#addBundle(Bundle)}: when used in an
      * {@link android.media.AudioFocusRequest}, the requester should receive all audio focus events,
      * including {@link android.media.AudioManager#AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK}.
@@ -86,6 +101,7 @@
 
     private final ICarAudio mService;
     private final List<CarVolumeCallback> mCarVolumeCallbacks;
+    private final AudioManager mAudioManager;
 
     private final ICarVolumeCallback mCarVolumeCallbackImpl = new ICarVolumeCallback.Stub() {
         @Override
@@ -430,12 +446,18 @@
      * @return audio zone ids
      * @hide
      */
+    @SystemApi
     @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS)
-    public @NonNull int[] getAudioZoneIds() {
+    public @NonNull List<Integer> getAudioZoneIds() {
         try {
-            return mService.getAudioZoneIds();
+            int[] zoneIdArray = mService.getAudioZoneIds();
+            List<Integer> zoneIdList = new ArrayList<Integer>(zoneIdArray.length);
+            for (int zoneIdValue : zoneIdArray) {
+                zoneIdList.add(zoneIdValue);
+            }
+            return zoneIdList;
         } catch (RemoteException e) {
-            return handleRemoteExceptionFromCarService(e, new int[0]);
+            return handleRemoteExceptionFromCarService(e, Collections.emptyList());
         }
     }
 
@@ -544,6 +566,63 @@
         }
     }
 
+    /**
+     * Gets the output device for a given {@link AudioAttributes} usage in zoneId.
+     *
+     * <p><b>Note:</b> To be used for routing to a specific device. Most applications should
+     * use the regular routing mechanism, which is to set audio attribute usage to
+     * an audio track.
+     *
+     * @param zoneId zone id to query for device
+     * @param usage usage where audio is routed
+     * @return Audio device info, returns {@code null} if audio device usage fails to map to
+     * an active audio device. This is different from the using an invalid value for
+     * {@link AudioAttributes} usage. In the latter case the query will fail with a
+     * RuntimeException indicating the issue.
+     *
+     * @hide
+     */
+    @SystemApi
+    @Nullable
+    @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS)
+    public AudioDeviceInfo getOutputDeviceForUsage(int zoneId,
+            @AudioAttributes.AttributeUsage int usage) {
+        try {
+            String deviceAddress = mService.getOutputDeviceAddressForUsage(zoneId, usage);
+            if (deviceAddress == null) {
+                return null;
+            }
+            AudioDeviceInfo[] outputDevices =
+                    mAudioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS);
+            for (AudioDeviceInfo info : outputDevices) {
+                if (info.getAddress().equals(deviceAddress)) {
+                    return info;
+                }
+            }
+            return null;
+        } catch (RemoteException e) {
+            return handleRemoteExceptionFromCarService(e, null);
+        }
+    }
+
+    /**
+     * Gets the input devices for an audio zone
+     *
+     * @return list of input devices
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS)
+    public @NonNull List<AudioDeviceInfo> getInputDevicesForZoneId(int zoneId) {
+        try {
+            return convertInputDevicesToDeviceInfos(
+                    mService.getInputDevicesForZoneId(zoneId),
+                    AudioManager.GET_DEVICES_INPUTS);
+        } catch (RemoteException e) {
+            return handleRemoteExceptionFromCarService(e, new ArrayList<>());
+        }
+    }
+
     /** @hide */
     @Override
     public void onCarDisconnected() {
@@ -560,6 +639,7 @@
     public CarAudioManager(Car car, IBinder service) {
         super(car);
         mService = ICarAudio.Stub.asInterface(service);
+        mAudioManager = getContext().getSystemService(AudioManager.class);
         mCarVolumeCallbacks = new ArrayList<>();
         try {
             mService.registerVolumeCallback(mCarVolumeCallbackImpl.asBinder());
@@ -584,6 +664,25 @@
         mCarVolumeCallbacks.remove(callback);
     }
 
+    private List<AudioDeviceInfo> convertInputDevicesToDeviceInfos(
+            List<AudioDeviceAttributes> devices, @AudioDeviceRole int flag) {
+        int addressesSize = devices.size();
+        Set<String> deviceAddressMap = new HashSet<>(addressesSize);
+        for (int i = 0; i < addressesSize; ++i) {
+            AudioDeviceAttributes device = devices.get(i);
+            deviceAddressMap.add(device.getAddress());
+        }
+        List<AudioDeviceInfo> deviceInfoList = new ArrayList<>(devices.size());
+        AudioDeviceInfo[] inputDevices = mAudioManager.getDevices(flag);
+        for (int i = 0; i < inputDevices.length; ++i) {
+            AudioDeviceInfo info = inputDevices[i];
+            if (info.isSource() && deviceAddressMap.contains(info.getAddress())) {
+                deviceInfoList.add(info);
+            }
+        }
+        return deviceInfoList;
+    }
+
     /**
      * Callback interface to receive volume change events in a car.
      * Extend this class and register it with {@link #registerCarVolumeCallback(CarVolumeCallback)}
diff --git a/car-lib/src/android/car/media/CarAudioPatchHandle.java b/car-lib/src/android/car/media/CarAudioPatchHandle.java
index 77dfc23..0ff5246 100644
--- a/car-lib/src/android/car/media/CarAudioPatchHandle.java
+++ b/car-lib/src/android/car/media/CarAudioPatchHandle.java
@@ -42,6 +42,8 @@
      * Construct a audio patch handle container given the system level handle
      * NOTE: Assumes (as it true today), that there is exactly one device port in the source
      * and sink arrays.
+     *
+     * @hide
      */
     public CarAudioPatchHandle(AudioPatch patch) {
         Preconditions.checkArgument(patch.sources().length == 1
diff --git a/car-lib/src/android/car/media/CarMediaManager.java b/car-lib/src/android/car/media/CarMediaManager.java
index 8537ed6..d286403 100644
--- a/car-lib/src/android/car/media/CarMediaManager.java
+++ b/car-lib/src/android/car/media/CarMediaManager.java
@@ -15,27 +15,45 @@
  */
 package android.car.media;
 
+import android.annotation.IntDef;
+import android.annotation.NonNull;
 import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
 import android.car.Car;
 import android.car.CarManagerBase;
 import android.content.ComponentName;
 import android.os.IBinder;
 import android.os.RemoteException;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 
 /**
  * API for updating and receiving updates to the primary media source in the car.
  * @hide
  */
+@SystemApi
 public final class CarMediaManager extends CarManagerBase {
 
+    public static final int MEDIA_SOURCE_MODE_PLAYBACK = 0;
+    public static final int MEDIA_SOURCE_MODE_BROWSE = 1;
+
+    /** @hide */
+    @IntDef(prefix = { "MEDIA_SOURCE_MODE_" }, value = {
+            MEDIA_SOURCE_MODE_PLAYBACK,
+            MEDIA_SOURCE_MODE_BROWSE
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface MediaSourceMode {}
+
     private final ICarMedia mService;
     private Map<MediaSourceChangedListener, ICarMediaSourceListener> mCallbackMap = new HashMap();
 
     /**
-     * Get an instance of the CarPowerManager.
+     * Get an instance of the CarMediaManager.
      *
      * Should not be obtained directly by clients, use {@link Car#getCarManager(String)} instead.
      * @hide
@@ -47,15 +65,13 @@
 
     /**
      * Listener for updates to the primary media source
-     * @hide
      */
     public interface MediaSourceChangedListener {
 
         /**
          * Called when the primary media source is changed
-         * @hide
          */
-        void onMediaSourceChanged(ComponentName componentName);
+        void onMediaSourceChanged(@NonNull ComponentName componentName);
     }
 
     /**
@@ -121,6 +137,49 @@
             handleRemoteExceptionFromCarService(e);
         }
     }
+    /**
+     * Gets the currently active media source for the provided mode
+     */
+    @RequiresPermission(value = android.Manifest.permission.MEDIA_CONTENT_CONTROL)
+    public @NonNull ComponentName getMediaSource(@MediaSourceMode int mode) {
+        // STUB
+        return null;
+    }
+
+    /**
+     * Sets the currently active media source for the provided mode
+     */
+    @RequiresPermission(value = android.Manifest.permission.MEDIA_CONTENT_CONTROL)
+    public void setMediaSource(@NonNull ComponentName componentName, @MediaSourceMode int mode) {
+        // STUB
+    }
+
+    /**
+     * Register a callback that receives updates to the active media source.
+     */
+    @RequiresPermission(value = android.Manifest.permission.MEDIA_CONTENT_CONTROL)
+    public void addMediaSourceListener(@NonNull MediaSourceChangedListener callback,
+            @MediaSourceMode int mode) {
+        // STUB
+    }
+
+    /**
+     * Unregister a callback that receives updates to the active media source.
+     */
+    @RequiresPermission(value = android.Manifest.permission.MEDIA_CONTENT_CONTROL)
+    public void removeMediaSourceListener(@NonNull MediaSourceChangedListener callback,
+            @MediaSourceMode int mode) {
+        // STUB
+    }
+
+    /**
+     * Retrieve a list of media sources, ordered by most recently used.
+     */
+    @RequiresPermission(value = android.Manifest.permission.MEDIA_CONTENT_CONTROL)
+    public @NonNull List<ComponentName> getLastMediaSources(@MediaSourceMode int mode) {
+        // STUB
+        return null;
+    }
 
     /** @hide */
     @Override
diff --git a/car-lib/src/android/car/media/ICarAudio.aidl b/car-lib/src/android/car/media/ICarAudio.aidl
index 2614a2e..7491a19 100644
--- a/car-lib/src/android/car/media/ICarAudio.aidl
+++ b/car-lib/src/android/car/media/ICarAudio.aidl
@@ -17,6 +17,7 @@
 package android.car.media;
 
 import android.car.media.CarAudioPatchHandle;
+import android.media.AudioDeviceAttributes;
 /**
  * Binder interface for {@link android.car.media.CarAudioManager}.
  * Check {@link android.car.media.CarAudioManager} APIs for expected behavior of each call.
@@ -49,6 +50,9 @@
 
     int getZoneIdForDisplayPortId(byte displayPortId);
 
+    String getOutputDeviceAddressForUsage(int zoneId, int usage);
+
+    List<AudioDeviceAttributes> getInputDevicesForZoneId(int zoneId);
     /**
      * IBinder is ICarVolumeCallback but passed as IBinder due to aidl hidden.
      */
diff --git a/car-lib/src/android/car/occupantawareness/DriverMonitoringDetection.aidl b/car-lib/src/android/car/occupantawareness/DriverMonitoringDetection.aidl
new file mode 100644
index 0000000..61779a3
--- /dev/null
+++ b/car-lib/src/android/car/occupantawareness/DriverMonitoringDetection.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.car.occupantawareness;
+
+parcelable DriverMonitoringDetection;
diff --git a/car-lib/src/android/car/occupantawareness/DriverMonitoringDetection.java b/car-lib/src/android/car/occupantawareness/DriverMonitoringDetection.java
new file mode 100644
index 0000000..043204e
--- /dev/null
+++ b/car-lib/src/android/car/occupantawareness/DriverMonitoringDetection.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.car.occupantawareness;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.car.occupantawareness.OccupantAwarenessDetection.ConfidenceLevel;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Detection state data for monitoring driver attention.
+ *
+ * @hide
+ */
+@SystemApi
+public final class DriverMonitoringDetection implements Parcelable {
+
+    /** {@link OccupantAwarenessDetection.ConfidenceLevel} for the driver monitoring detection. */
+    public final @ConfidenceLevel int confidenceLevel;
+
+    /** Indicates whether the driver is looking on-road. */
+    public final boolean isLookingOnRoad;
+
+    /**
+     * Duration that the driver has been looking on or off-road, in milliseconds.
+     *
+     * <p>If 'isLookingOnRoad' is true, this indicates the continuous duration of time that the
+     * driver has been looking on-road (in milliseconds). Otherwise, this indicates the continuous
+     * duration of time that the driver is looking off-road (in milliseconds).
+     */
+    public final long gazeDurationMillis;
+
+    public DriverMonitoringDetection() {
+        confidenceLevel = OccupantAwarenessDetection.CONFIDENCE_LEVEL_NONE;
+        isLookingOnRoad = false;
+        gazeDurationMillis = 0;
+    }
+
+    public DriverMonitoringDetection(
+            @ConfidenceLevel int confidenceLevel,
+            boolean isLookingOnRoad,
+            long gazeDurationMillis) {
+        this.confidenceLevel = confidenceLevel;
+        this.isLookingOnRoad = isLookingOnRoad;
+        this.gazeDurationMillis = gazeDurationMillis;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public String toString() {
+        return "DriverMonitoringDetection{"
+                + "confidenceLevel="
+                + confidenceLevel
+                + ", isLookingOnRoad="
+                + isLookingOnRoad
+                + ", gazeDurationMillis="
+                + gazeDurationMillis;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeInt(confidenceLevel);
+        dest.writeBoolean(isLookingOnRoad);
+        dest.writeLong(gazeDurationMillis);
+    }
+
+    public static final @NonNull Parcelable.Creator<DriverMonitoringDetection> CREATOR =
+            new Parcelable.Creator<DriverMonitoringDetection>() {
+                public DriverMonitoringDetection createFromParcel(Parcel in) {
+                    return new DriverMonitoringDetection(in);
+                }
+
+                public DriverMonitoringDetection[] newArray(int size) {
+                    return new DriverMonitoringDetection[size];
+                }
+            };
+
+    private DriverMonitoringDetection(Parcel in) {
+        confidenceLevel = in.readInt();
+        isLookingOnRoad = in.readBoolean();
+        gazeDurationMillis = in.readLong();
+    }
+}
diff --git a/car-lib/src/android/car/occupantawareness/GazeDetection.aidl b/car-lib/src/android/car/occupantawareness/GazeDetection.aidl
new file mode 100644
index 0000000..3ef6b9c
--- /dev/null
+++ b/car-lib/src/android/car/occupantawareness/GazeDetection.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.car.occupantawareness;
+
+parcelable GazeDetection;
diff --git a/car-lib/src/android/car/occupantawareness/GazeDetection.java b/car-lib/src/android/car/occupantawareness/GazeDetection.java
new file mode 100644
index 0000000..3724a76
--- /dev/null
+++ b/car-lib/src/android/car/occupantawareness/GazeDetection.java
@@ -0,0 +1,198 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.car.occupantawareness;
+
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.annotation.Retention;
+
+/**
+ * Detection result for gaze detection for the respective {@link VehicleOccupantRole}.
+ *
+ * @hide
+ */
+@SystemApi
+public final class GazeDetection implements Parcelable {
+
+    /** A unknown gaze region, not otherwise specified. */
+    public static final int VEHICLE_REGION_UNKNOWN = 0;
+
+    /** Center instrument cluster in front of the driver. */
+    public static final int VEHICLE_REGION_CENTER_INSTRUMENT_CLUSTER = 1;
+
+    /** The rear-view mirror. */
+    public static final int VEHICLE_REGION_REAR_VIEW_MIRROR = 2;
+
+    /** The left side mirror. */
+    public static final int VEHICLE_REGION_LEFT_SIDE_MIRROR = 3;
+
+    /** The right side mirror. */
+    public static final int VEHICLE_REGION_RIGHT_SIDE_MIRROR = 4;
+
+    /** The forward roadway. */
+    public static final int VEHICLE_REGION_FORWARD_ROADWAY = 5;
+
+    /** Out-the-window to the right. */
+    public static final int VEHICLE_REGION_LEFT_ROADWAY = 6;
+
+    /** Out-the-window to the right. */
+    public static final int VEHICLE_REGION_RIGHT_ROADWAY = 7;
+
+    /** Center head-unit display. */
+    public static final int VEHICLE_REGION_HEAD_UNIT_DISPLAY = 8;
+
+    /**
+     * Vehicle regions
+     *
+     * @hide
+     */
+    @Retention(SOURCE)
+    @IntDef({
+        VEHICLE_REGION_UNKNOWN,
+        VEHICLE_REGION_CENTER_INSTRUMENT_CLUSTER,
+        VEHICLE_REGION_REAR_VIEW_MIRROR,
+        VEHICLE_REGION_LEFT_SIDE_MIRROR,
+        VEHICLE_REGION_RIGHT_SIDE_MIRROR,
+        VEHICLE_REGION_FORWARD_ROADWAY,
+        VEHICLE_REGION_LEFT_ROADWAY,
+        VEHICLE_REGION_RIGHT_ROADWAY,
+        VEHICLE_REGION_HEAD_UNIT_DISPLAY
+    })
+    public @interface VehicleRegion {}
+
+    /** {@link OccupantAwarenessDetection.ConfidenceLevel} for the gaze detection. */
+    @OccupantAwarenessDetection.ConfidenceLevel public final int confidenceLevel;
+
+    /**
+     * Location of the subject's left eye, in millimeters.
+     *
+     * <p>Vehicle origin is defined as part of the Cabin Space API. All units in millimeters. +x is
+     * right (from driver perspective), +y is up, -z is forward.
+     *
+     * <p>May be {@code null} if the underlying detection system does not export eye position data.
+     */
+    public final @Nullable Point3D leftEyePosition;
+
+    /**
+     * Location of the subject's right eye, in millimeters.
+     *
+     * <p>Vehicle origin is defined as part of the Cabin Space API. All units in millimeters. +x is
+     * right (from driver perspective), +y is up, -z is forward.
+     *
+     * <p>May be {@code null} if the underlying detection system does not export eye position data.
+     */
+    public final @Nullable Point3D rightEyePosition;
+
+    /**
+     * Direction of the subject's head orientation, as a <a
+     * href="https://en.wikipedia.org/wiki/Unit_vector">unit-vector</a>.
+     *
+     * <p>May be {@code null} if the underlying system does not support head orientation vectors.
+     */
+    public final @Nullable Point3D headAngleUnitVector;
+
+    /**
+     * Direction of the gaze angle, as a <a
+     * href="https://en.wikipedia.org/wiki/Unit_vector">unit-vector</a>.
+     *
+     * <p>May be {@code null} if the underlying system does not support vectors.
+     */
+    public final @Nullable Point3D gazeAngleUnitVector;
+
+    /** {@link VehicleRegion} where the subject is currently looking. */
+    @VehicleRegion public final int gazeTarget;
+
+    /** Duration on the current gaze target, in milliseconds. */
+    public final long durationOnTargetMillis;
+
+    public GazeDetection(
+            @OccupantAwarenessDetection.ConfidenceLevel int confidenceLevel,
+            @Nullable Point3D leftEyePosition,
+            @Nullable Point3D rightEyePosition,
+            @Nullable Point3D headAngleUnitVector,
+            @Nullable Point3D gazeAngleUnitVector,
+            @VehicleRegion int gazeTarget,
+            long durationOnTargetMillis) {
+
+        this.confidenceLevel = confidenceLevel;
+        this.leftEyePosition = leftEyePosition;
+        this.rightEyePosition = rightEyePosition;
+        this.headAngleUnitVector = headAngleUnitVector;
+        this.gazeAngleUnitVector = gazeAngleUnitVector;
+        this.gazeTarget = gazeTarget;
+        this.durationOnTargetMillis = durationOnTargetMillis;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeInt(confidenceLevel);
+        dest.writeParcelable(leftEyePosition, flags);
+        dest.writeParcelable(rightEyePosition, flags);
+        dest.writeParcelable(headAngleUnitVector, flags);
+        dest.writeParcelable(gazeAngleUnitVector, flags);
+        dest.writeInt(gazeTarget);
+        dest.writeLong(durationOnTargetMillis);
+    }
+
+    @Override
+    public String toString() {
+        return "GazeDetection{"
+                + "confidenceLevel=" + confidenceLevel
+                + ", leftEyePosition=" + (leftEyePosition == null ? "(null)" : leftEyePosition)
+                + ", rightEyePosition=" + (rightEyePosition == null ? "(null)" : rightEyePosition)
+                + ", headAngleUnitVector="
+                + (headAngleUnitVector == null ? "(null)" : headAngleUnitVector)
+                + ", gazeAngleUnitVector="
+                + (gazeAngleUnitVector == null ? "(null)" : gazeAngleUnitVector)
+                + ", gazeTarget=" + gazeTarget
+                + ", durationOnTargetMillis=" + durationOnTargetMillis
+                + "}";
+    }
+
+    public static final @NonNull Parcelable.Creator<GazeDetection> CREATOR =
+            new Parcelable.Creator<GazeDetection>() {
+                public GazeDetection createFromParcel(Parcel in) {
+                    return new GazeDetection(in);
+                }
+
+                public GazeDetection[] newArray(int size) {
+                    return new GazeDetection[size];
+                }
+            };
+
+    private GazeDetection(Parcel in) {
+        confidenceLevel = in.readInt();
+        leftEyePosition = in.readParcelable(Point3D.class.getClassLoader());
+        rightEyePosition = in.readParcelable(Point3D.class.getClassLoader());
+        headAngleUnitVector = in.readParcelable(Point3D.class.getClassLoader());
+        gazeAngleUnitVector = in.readParcelable(Point3D.class.getClassLoader());
+        gazeTarget = in.readInt();
+        durationOnTargetMillis = in.readLong();
+    }
+}
diff --git a/car-lib/src/android/car/occupantawareness/IOccupantAwarenessEventCallback.aidl b/car-lib/src/android/car/occupantawareness/IOccupantAwarenessEventCallback.aidl
new file mode 100644
index 0000000..ad582d2
--- /dev/null
+++ b/car-lib/src/android/car/occupantawareness/IOccupantAwarenessEventCallback.aidl
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.car.occupantawareness;
+
+import android.car.occupantawareness.OccupantAwarenessDetection;
+import android.car.occupantawareness.SystemStatusEvent;
+
+/**
+ * Binder callbacks for events.
+   @hide
+ */
+oneway interface IOccupantAwarenessEventCallback {
+    void onStatusChanged(in SystemStatusEvent event) = 0;
+    void onDetectionEvent(in OccupantAwarenessDetection detectionEvent) = 1;
+}
diff --git a/car-lib/src/android/car/occupantawareness/IOccupantAwarenessManager.aidl b/car-lib/src/android/car/occupantawareness/IOccupantAwarenessManager.aidl
new file mode 100644
index 0000000..0994a09
--- /dev/null
+++ b/car-lib/src/android/car/occupantawareness/IOccupantAwarenessManager.aidl
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.car.occupantawareness;
+
+import android.car.occupantawareness.IOccupantAwarenessEventCallback;
+
+/**
+ * Binder interface for {@link android.car.occupantawareness.OccupantAwarenessManager}.
+ * Check {@link android.car.occupantawareness.OccupantAwarenessManager} APIs for expected behavior of
+ * each call.
+ *
+ * @hide
+ */
+interface IOccupantAwarenessManager {
+    int getCapabilityForRole(int role) = 0;
+    void registerEventListener(IOccupantAwarenessEventCallback listener) = 1;
+    void unregisterEventListener(IOccupantAwarenessEventCallback listener) = 2;
+}
diff --git a/car-lib/src/android/car/occupantawareness/OccupantAwarenessDetection.aidl b/car-lib/src/android/car/occupantawareness/OccupantAwarenessDetection.aidl
new file mode 100644
index 0000000..5e806a6
--- /dev/null
+++ b/car-lib/src/android/car/occupantawareness/OccupantAwarenessDetection.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.car.occupantawareness;
+
+parcelable OccupantAwarenessDetection;
diff --git a/car-lib/src/android/car/occupantawareness/OccupantAwarenessDetection.java b/car-lib/src/android/car/occupantawareness/OccupantAwarenessDetection.java
new file mode 100644
index 0000000..d449128
--- /dev/null
+++ b/car-lib/src/android/car/occupantawareness/OccupantAwarenessDetection.java
@@ -0,0 +1,229 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.car.occupantawareness;
+
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.car.Car;
+import android.car.annotation.RequiredFeature;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.annotation.Retention;
+
+/**
+ * Complete detection result for a single detected person. Includes presence detection, {@link
+ * GazeDetection} and {@link DriverMonitoringDetection}.
+ *
+ * <p>Register to listen to events via {@link OccupantAwarenessManager}.
+ *
+ * @hide
+ */
+@SystemApi
+@RequiredFeature(Car.OCCUPANT_AWARENESS_SERVICE)
+public final class OccupantAwarenessDetection implements Parcelable {
+    /** Empty occupant flag. */
+    public static final int VEHICLE_OCCUPANT_NONE = 0;
+
+    /** Occupants that the system detects as the driver. */
+    public static final int VEHICLE_OCCUPANT_DRIVER = 1 << 2;
+
+    /** Occupants that the system detects as front seat passengers. */
+    public static final int VEHICLE_OCCUPANT_FRONT_PASSENGER = 1 << 1;
+
+    /** Occupants that the system detects in the second vehicle row, on the left. */
+    public static final int VEHICLE_OCCUPANT_ROW_2_PASSENGER_LEFT = 1 << 3;
+
+    /** Occupants that the system detects in the second vehicle row, in the center. */
+    public static final int VEHICLE_OCCUPANT_ROW_2_PASSENGER_CENTER = 1 << 4;
+
+    /** Occupants that the system detects in the second vehicle row, on the right. */
+    public static final int VEHICLE_OCCUPANT_ROW_2_PASSENGER_RIGHT = 1 << 5;
+
+    /** Occupants that the system detects in the third vehicle row, on the left. */
+    public static final int VEHICLE_OCCUPANT_ROW_3_PASSENGER_LEFT = 1 << 6;
+
+    /** Occupants that the system detects in the third vehicle row, in the middle. */
+    public static final int VEHICLE_OCCUPANT_ROW_3_PASSENGER_CENTER = 1 << 7;
+
+    /** Occupants that the system detects in the third vehicle row, on the right. */
+    public static final int VEHICLE_OCCUPANT_ROW_3_PASSENGER_RIGHT = 1 << 8;
+
+    /** All occupants that the system detects in the front row of the vehicle. */
+    public static final int VEHICLE_OCCUPANT_ALL_FRONT_OCCUPANTS =
+            VEHICLE_OCCUPANT_DRIVER | VEHICLE_OCCUPANT_FRONT_PASSENGER;
+
+    /** All occupants that the system detects in the second row of the vehicle. */
+    public static final int VEHICLE_OCCUPANT_ALL_ROW_2_OCCUPANTS =
+            VEHICLE_OCCUPANT_ROW_2_PASSENGER_LEFT
+                    | VEHICLE_OCCUPANT_ROW_2_PASSENGER_RIGHT
+                    | VEHICLE_OCCUPANT_ROW_2_PASSENGER_CENTER;
+
+    /** All occupants that the system detects in the third row of the vehicle. */
+    public static final int VEHICLE_OCCUPANT_ALL_ROW_3_OCCUPANTS =
+            VEHICLE_OCCUPANT_ROW_3_PASSENGER_LEFT
+                    | VEHICLE_OCCUPANT_ROW_3_PASSENGER_RIGHT
+                    | VEHICLE_OCCUPANT_ROW_3_PASSENGER_CENTER;
+
+    /** All occupants that the system detects in the vehicle. */
+    public static final int VEHICLE_OCCUPANT_ALL_OCCUPANTS =
+            VEHICLE_OCCUPANT_ALL_FRONT_OCCUPANTS
+                    | VEHICLE_OCCUPANT_ALL_ROW_2_OCCUPANTS
+                    | VEHICLE_OCCUPANT_ALL_ROW_3_OCCUPANTS;
+
+    /**
+     * Vehicle occupant roles based on their location in the vehicle.
+     *
+     * @hide
+     */
+    @Retention(SOURCE)
+    @IntDef(
+            flag = true,
+            value = {
+                VEHICLE_OCCUPANT_NONE,
+                VEHICLE_OCCUPANT_DRIVER,
+                VEHICLE_OCCUPANT_FRONT_PASSENGER,
+                VEHICLE_OCCUPANT_ROW_2_PASSENGER_LEFT,
+                VEHICLE_OCCUPANT_ROW_2_PASSENGER_CENTER,
+                VEHICLE_OCCUPANT_ROW_2_PASSENGER_RIGHT,
+                VEHICLE_OCCUPANT_ROW_3_PASSENGER_LEFT,
+                VEHICLE_OCCUPANT_ROW_3_PASSENGER_CENTER,
+                VEHICLE_OCCUPANT_ROW_3_PASSENGER_RIGHT,
+                VEHICLE_OCCUPANT_ALL_FRONT_OCCUPANTS,
+                VEHICLE_OCCUPANT_ALL_ROW_2_OCCUPANTS,
+                VEHICLE_OCCUPANT_ALL_ROW_3_OCCUPANTS
+            })
+    public @interface VehicleOccupantRole {}
+
+    /** No prediction could be made. */
+    public static final int CONFIDENCE_LEVEL_NONE = 0;
+
+    /**
+     * Best-guess, low-confidence prediction. Predictions exceeding this threshold are adequate for
+     * non-critical applications.
+     */
+    public static final int CONFIDENCE_LEVEL_LOW = 1;
+
+    /**
+     * High-confidence prediction. Predictions exceeding this threshold are adequate for
+     * applications that require reliable predictions.
+     */
+    public static final int CONFIDENCE_LEVEL_HIGH = 2;
+
+    /** Highest confidence rate achievable. */
+    public static final int CONFIDENCE_LEVEL_MAX = 3;
+
+    /**
+     * Confidence scores for predictions.
+     *
+     * @hide
+     */
+    @Retention(SOURCE)
+    @IntDef(
+            value = {
+                CONFIDENCE_LEVEL_NONE,
+                CONFIDENCE_LEVEL_LOW,
+                CONFIDENCE_LEVEL_HIGH,
+                CONFIDENCE_LEVEL_MAX
+            })
+    public @interface ConfidenceLevel {}
+
+    /** The {@link VehicleOccupantRole} of the face associated with this event. */
+    public final @VehicleOccupantRole int role;
+
+    /** Timestamp when the underlying detection data was detected, in milliseconds since boot. */
+    public final long timestampMillis;
+
+    /** Indicates whether any person was detected for the given role. */
+    public final boolean isPresent;
+
+    /**
+     * {@link GazeDetection} data for the requested role, or {@code null} if no person was found.
+     */
+    public final @Nullable GazeDetection gazeDetection;
+
+    /**
+     * {@link DriverMonitoringDetection} data for the driver, or {@code null} if the role was
+     * non-driver or if the detection could not be computed.
+     */
+    public final @Nullable DriverMonitoringDetection driverMonitoringDetection;
+
+    public OccupantAwarenessDetection(
+            @VehicleOccupantRole int role,
+            long timestampMillis,
+            boolean isPresent,
+            @Nullable GazeDetection gazeDetection,
+            @Nullable DriverMonitoringDetection driverMonitoringDetection) {
+        this.role = role;
+        this.timestampMillis = timestampMillis;
+        this.isPresent = isPresent;
+        this.gazeDetection = gazeDetection;
+        this.driverMonitoringDetection = driverMonitoringDetection;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeInt(role);
+        dest.writeLong(timestampMillis);
+        dest.writeBoolean(isPresent);
+        dest.writeParcelable(gazeDetection, flags);
+        dest.writeParcelable(driverMonitoringDetection, flags);
+    }
+
+    @Override
+    public String toString() {
+        return "OccupantAwarenessDetection{"
+                + "role="  + role
+                + ", timestampMillis=" + timestampMillis
+                + ", isPresent=" + isPresent
+                + ", gazeDetection="
+                + (gazeDetection == null ? "(null)" : gazeDetection.toString())
+                + ", driverMonitoringDetection="
+                + (driverMonitoringDetection == null
+                        ? "(null)" : driverMonitoringDetection.toString())
+                + "}";
+    }
+
+    public static final @NonNull Parcelable.Creator<OccupantAwarenessDetection> CREATOR =
+            new Parcelable.Creator<OccupantAwarenessDetection>() {
+                public OccupantAwarenessDetection createFromParcel(Parcel in) {
+                    return new OccupantAwarenessDetection(in);
+                }
+
+                public OccupantAwarenessDetection[] newArray(int size) {
+                    return new OccupantAwarenessDetection[size];
+                }
+            };
+
+    private OccupantAwarenessDetection(Parcel in) {
+        role = in.readInt();
+        timestampMillis = in.readLong();
+        isPresent = in.readBoolean();
+        gazeDetection = in.readParcelable(GazeDetection.class.getClassLoader());
+        driverMonitoringDetection =
+                in.readParcelable(DriverMonitoringDetection.class.getClassLoader());
+    }
+}
diff --git a/car-lib/src/android/car/occupantawareness/OccupantAwarenessManager.java b/car-lib/src/android/car/occupantawareness/OccupantAwarenessManager.java
new file mode 100644
index 0000000..690d6cc
--- /dev/null
+++ b/car-lib/src/android/car/occupantawareness/OccupantAwarenessManager.java
@@ -0,0 +1,315 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.car.occupantawareness;
+
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.car.Car;
+import android.car.CarManagerBase;
+import android.car.annotation.RequiredFeature;
+import android.car.occupantawareness.OccupantAwarenessDetection.VehicleOccupantRole;
+import android.car.occupantawareness.SystemStatusEvent.DetectionTypeFlags;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.RemoteException;
+import android.util.Log;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.lang.ref.WeakReference;
+
+/**
+ * API exposing Occupant Awareness System data.
+ *
+ * <p>Supported detections include: presence detection, {@link GazeDetection} and {@link
+ * DriverMonitoringDetection}.
+ *
+ * @hide
+ */
+@RequiredFeature(Car.OCCUPANT_AWARENESS_SERVICE)
+@SystemApi
+public class OccupantAwarenessManager extends CarManagerBase {
+    private static final String TAG = "OAS.Manager";
+    private static final boolean DBG = false;
+
+    private static final int MSG_HANDLE_SYSTEM_STATUS_CHANGE = 0;
+    private static final int MSG_HANDLE_DETECTION_EVENT = 1;
+
+    private final android.car.occupantawareness.IOccupantAwarenessManager mOccupantAwarenessService;
+
+    private final Object mLock = new Object();
+
+    @GuardedBy("mLock")
+    private ChangeCallback mChangeCallback;
+
+    private final EventCallbackHandler mEventCallbackHandler;
+
+    @GuardedBy("mLock")
+    private ChangeListenerToService mListenerToService;
+
+    /** @hide */
+    public OccupantAwarenessManager(Car car, IBinder service) {
+        super(car);
+        mOccupantAwarenessService =
+                android.car.occupantawareness.IOccupantAwarenessManager.Stub.asInterface(service);
+
+        mEventCallbackHandler = new EventCallbackHandler(this, getEventHandler().getLooper());
+    }
+
+    /** @hide */
+    @Override
+    public void onCarDisconnected() {
+        synchronized (mLock) {
+            mChangeCallback = null;
+            mListenerToService = null;
+        }
+    }
+
+    /**
+     * Gets the detection capabilities for a given {@link VehicleOccupantRole} in this vehicle.
+     *
+     * <p>There may be different detection capabilities for different {@link VehicleOccupantRole}.
+     * Each role should be queried independently.
+     *
+     * <p>Capabilities are static for a given vehicle configuration and need only be queried once
+     * per vehicle. Once capability is determined, clients should query system status via {@link
+     * SystemStatusEvent} to see if the subsystem is currently online and ready to serve.
+     *
+     * @param role {@link VehicleOccupantRole} to query for.
+     * @return {@link SystemStatusEvent.DetectionTypeFlags} indicating supported capabilities for
+     *     the role.
+     */
+    @RequiresPermission(value = Car.PERMISSION_READ_CAR_OCCUPANT_AWARENESS_STATE)
+    public @DetectionTypeFlags int getCapabilityForRole(@VehicleOccupantRole int role) {
+        try {
+            return mOccupantAwarenessService.getCapabilityForRole(role);
+        } catch (RemoteException e) {
+            return handleRemoteExceptionFromCarService(e, 0);
+        }
+    }
+
+    /**
+     * Callbacks for listening to changes to {@link SystemStatusEvent} and {@link
+     * OccupantAwarenessDetection}.
+     */
+    public abstract static class ChangeCallback {
+        /**
+         * Called when the system state changes changes.
+         *
+         * @param systemStatus The new system state as a {@link SystemStatusEvent}.
+         */
+        public abstract void onSystemStateChanged(@NonNull SystemStatusEvent systemStatus);
+
+        /**
+         * Called when a detection event is generated.
+         *
+         * @param event The new detection state as a {@link OccupantAwarenessDetection}.
+         */
+        public abstract void onDetectionEvent(@NonNull OccupantAwarenessDetection event);
+    }
+
+    /**
+     * Registers a {@link ChangeCallback} for listening for events.
+     *
+     * <p>If a listener has already been registered, it has to be unregistered before registering
+     * the new one.
+     *
+     * @param callback {@link ChangeCallback} to register.
+     * @throws IllegalStateException if an existing callback is already registered.
+     */
+    @RequiresPermission(value = Car.PERMISSION_READ_CAR_OCCUPANT_AWARENESS_STATE)
+    public void registerChangeCallback(@NonNull ChangeCallback callback) {
+        if (DBG) {
+            Log.d(TAG, "Registering change listener");
+        }
+
+        synchronized (mLock) {
+            // Check if the listener has been already registered.
+            if (mChangeCallback != null) {
+                throw new IllegalStateException(
+                        "Attempting to register a new listener when an existing listener has"
+                                + " already been registered.");
+            }
+
+            mChangeCallback = callback;
+
+            try {
+                if (mListenerToService == null) {
+                    mListenerToService = new ChangeListenerToService(this);
+                }
+
+                mOccupantAwarenessService.registerEventListener(mListenerToService);
+            } catch (RemoteException e) {
+                handleRemoteExceptionFromCarService(e);
+            }
+        }
+    }
+
+    /** Unregisters a previously registered {@link ChangeCallback}. */
+    @RequiresPermission(value = Car.PERMISSION_READ_CAR_OCCUPANT_AWARENESS_STATE)
+    public void unregisterChangeCallback() {
+        if (DBG) {
+            Log.d(TAG, "Unregistering change listener");
+        }
+
+        synchronized (mLock) {
+            if (mChangeCallback == null) {
+                Log.e(TAG, "No listener exists to unregister.");
+                return;
+            }
+            mChangeCallback = null;
+        }
+
+        synchronized (mLock) {
+            try {
+                mOccupantAwarenessService.unregisterEventListener(mListenerToService);
+            } catch (RemoteException e) {
+                handleRemoteExceptionFromCarService(e);
+            }
+
+            mListenerToService = null;
+        }
+    }
+
+    /**
+     * Class that implements the listener interface and gets called back from the {@link
+     * com.android.car.IOccupantAwarenessEventCallback} across the binder interface.
+     */
+    private static class ChangeListenerToService extends IOccupantAwarenessEventCallback.Stub {
+        private final WeakReference<OccupantAwarenessManager> mOccupantAwarenessManager;
+
+        ChangeListenerToService(OccupantAwarenessManager manager) {
+            mOccupantAwarenessManager = new WeakReference<>(manager);
+        }
+
+        @Override
+        public void onStatusChanged(SystemStatusEvent systemStatus) {
+            OccupantAwarenessManager manager = mOccupantAwarenessManager.get();
+            if (manager != null) {
+                manager.handleSystemStatusChanged(systemStatus);
+            }
+        }
+
+        @Override
+        public void onDetectionEvent(OccupantAwarenessDetection event) {
+            OccupantAwarenessManager manager = mOccupantAwarenessManager.get();
+            if (manager != null) {
+                manager.handleDetectionEvent(event);
+            }
+        }
+    }
+
+    /**
+     * Gets the {@link SystemStatusEvent} from the service listener {@link
+     * SystemStateChangeListenerToService} and dispatches it to a handler provided to the manager.
+     *
+     * @param systemStatus {@link SystemStatusEvent} that has been registered to listen on
+     */
+    private void handleSystemStatusChanged(SystemStatusEvent systemStatus) {
+        // Send a message via the handler.
+        mEventCallbackHandler.sendMessage(
+                mEventCallbackHandler.obtainMessage(MSG_HANDLE_SYSTEM_STATUS_CHANGE, systemStatus));
+    }
+
+    /**
+     * Checks for the listeners to list of {@link SystemStatusEvent} and calls them back in the
+     * callback handler thread.
+     *
+     * @param systemStatus {@link SystemStatusEvent}
+     */
+    private void dispatchSystemStatusToClient(SystemStatusEvent systemStatus) {
+        if (systemStatus == null) {
+            return;
+        }
+
+        synchronized (mLock) {
+            if (mChangeCallback != null) {
+                mChangeCallback.onSystemStateChanged(systemStatus);
+            }
+        }
+    }
+
+    /**
+     * Gets the {@link OccupantAwarenessDetection} from the service listener {@link
+     * DetectionEventListenerToService} and dispatches it to a handler provided to the manager.
+     *
+     * @param detectionEvent {@link OccupantAwarenessDetection} that has been registered to listen
+     *     on
+     */
+    private void handleDetectionEvent(OccupantAwarenessDetection detectionEvent) {
+        // Send a message via the handler.
+        mEventCallbackHandler.sendMessage(
+                mEventCallbackHandler.obtainMessage(MSG_HANDLE_DETECTION_EVENT, detectionEvent));
+    }
+
+    /**
+     * Checks for the listeners to list of {@link
+     * android.car.occupantawareness.OccupantAwarenessDetection} and calls them back in the callback
+     * handler thread.
+     *
+     * @param detectionEvent {@link OccupantAwarenessDetection}
+     */
+    private void dispatchDetectionEventToClient(OccupantAwarenessDetection detectionEvent) {
+        if (detectionEvent == null) {
+            return;
+        }
+
+        ChangeCallback callback;
+
+        synchronized (mLock) {
+            callback = mChangeCallback;
+        }
+
+        if (callback != null) {
+
+            callback.onDetectionEvent(detectionEvent);
+        }
+    }
+
+    /** Callback handler to dispatch system status changes to the corresponding listeners. */
+    private static final class EventCallbackHandler extends Handler {
+        private final WeakReference<OccupantAwarenessManager> mOccupantAwarenessManager;
+
+        EventCallbackHandler(OccupantAwarenessManager manager, Looper looper) {
+            super(looper);
+            mOccupantAwarenessManager = new WeakReference<>(manager);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            OccupantAwarenessManager mgr = mOccupantAwarenessManager.get();
+            if (mgr != null) {
+
+                switch (msg.what) {
+                    case MSG_HANDLE_SYSTEM_STATUS_CHANGE:
+                        mgr.dispatchSystemStatusToClient((SystemStatusEvent) msg.obj);
+                        break;
+
+                    case MSG_HANDLE_DETECTION_EVENT:
+                        mgr.dispatchDetectionEventToClient((OccupantAwarenessDetection) msg.obj);
+                        break;
+
+                    default:
+                        throw new RuntimeException("Unknown message " + msg.what);
+                }
+            }
+        }
+    }
+}
diff --git a/car-lib/src/android/car/occupantawareness/Point3D.aidl b/car-lib/src/android/car/occupantawareness/Point3D.aidl
new file mode 100644
index 0000000..b9ee912
--- /dev/null
+++ b/car-lib/src/android/car/occupantawareness/Point3D.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.car.occupantawareness;
+
+parcelable Point3D;
diff --git a/car-lib/src/android/car/occupantawareness/Point3D.java b/car-lib/src/android/car/occupantawareness/Point3D.java
new file mode 100644
index 0000000..3af0f37
--- /dev/null
+++ b/car-lib/src/android/car/occupantawareness/Point3D.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.car.occupantawareness;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * A point in 3D space, in millimeters.
+ *
+ * @hide
+ */
+@SystemApi
+public final class Point3D implements Parcelable {
+    /** The x-component of the point. */
+    public final double x;
+
+    /** The y-component of the point. */
+    public final double y;
+
+    /** The z-component of the point. */
+    public final double z;
+
+    public Point3D(double valueX, double valueY, double valueZ) {
+        x = valueX;
+        y = valueY;
+        z = valueZ;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeDouble(x);
+        dest.writeDouble(y);
+        dest.writeDouble(z);
+    }
+
+    @Override
+    public String toString() {
+        return String.format("%f, %f, %f", x, y, z);
+    }
+
+    public static final @NonNull Parcelable.Creator<Point3D> CREATOR =
+            new Parcelable.Creator<Point3D>() {
+                public Point3D createFromParcel(Parcel in) {
+                    return new Point3D(in);
+                }
+
+                public Point3D[] newArray(int size) {
+                    return new Point3D[size];
+                }
+            };
+
+    private Point3D(Parcel in) {
+        x = in.readDouble();
+        y = in.readDouble();
+        z = in.readDouble();
+    }
+}
diff --git a/car-lib/src/android/car/occupantawareness/SystemStatusEvent.aidl b/car-lib/src/android/car/occupantawareness/SystemStatusEvent.aidl
new file mode 100644
index 0000000..62ca13c
--- /dev/null
+++ b/car-lib/src/android/car/occupantawareness/SystemStatusEvent.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.car.occupantawareness;
+
+parcelable SystemStatusEvent;
diff --git a/car-lib/src/android/car/occupantawareness/SystemStatusEvent.java b/car-lib/src/android/car/occupantawareness/SystemStatusEvent.java
new file mode 100644
index 0000000..802db59
--- /dev/null
+++ b/car-lib/src/android/car/occupantawareness/SystemStatusEvent.java
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.car.occupantawareness;
+
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.annotation.Retention;
+
+/**
+ * Current system status.
+ *
+ * <p>Detection system may be ready, not-supported, in a failure mode or not-yet-ready.
+ *
+ * @hide
+ */
+@SystemApi
+public final class SystemStatusEvent implements Parcelable {
+    /** The system is ready to provide data. */
+    public static final int SYSTEM_STATUS_READY = 0;
+
+    /**
+     * Detection is not supported in this vehicle due to a permanent lack of capabilities. Clients
+     * need not retry.
+     */
+    public static final int SYSTEM_STATUS_NOT_SUPPORTED = 1;
+
+    /** The system is not yet ready to serve requests. Clients should check back again later. */
+    public static final int SYSTEM_STATUS_NOT_READY = 2;
+
+    /**
+     * A permanent hardware failure has occurred. Clients need not retry until the underlying
+     * hardware has been fixed.
+     */
+    public static final int SYSTEM_STATUS_SYSTEM_FAILURE = 3;
+
+    /**
+     * System state status values.
+     *
+     * @hide
+     */
+    @Retention(SOURCE)
+    @IntDef({
+        SYSTEM_STATUS_READY,
+        SYSTEM_STATUS_NOT_SUPPORTED,
+        SYSTEM_STATUS_SYSTEM_FAILURE,
+        SYSTEM_STATUS_NOT_READY
+    })
+    public @interface SystemStatus {}
+
+    /** No detection types are supported. */
+    public static final int DETECTION_TYPE_NONE = 0;
+
+    /** Presence detection for occupants in the vehicle. */
+    public static final int DETECTION_TYPE_PRESENCE = 1 << 0;
+
+    /** Gaze data for occupant in the vehicle. */
+    public static final int DETECTION_TYPE_GAZE = 1 << 1;
+
+    /** Driver monitoring state for the driver in the vehicle. */
+    public static final int DETECTION_TYPE_DRIVER_MONITORING = 1 << 2;
+
+    /**
+     * Detection types.
+     *
+     * @hide
+     */
+    @Retention(SOURCE)
+    @IntDef(
+            flag = true,
+            value = {
+                DETECTION_TYPE_PRESENCE,
+                DETECTION_TYPE_GAZE,
+                DETECTION_TYPE_DRIVER_MONITORING
+            })
+    public @interface DetectionTypeFlags {}
+
+    public final @SystemStatus int systemStatus;
+    public final @DetectionTypeFlags int detectionType;
+
+    public SystemStatusEvent(@SystemStatus int status, @DetectionTypeFlags int type) {
+        systemStatus = status;
+        detectionType = type;
+    }
+
+    public SystemStatusEvent() {
+        systemStatus = SYSTEM_STATUS_NOT_READY;
+        detectionType = DETECTION_TYPE_NONE;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeInt(systemStatus);
+        dest.writeInt(detectionType);
+    }
+
+    @Override
+    public String toString() {
+        return "SystemStatusEvent{"
+                + "systemStatus="
+                + systemStatus
+                + ", detectionType="
+                + detectionType
+                + "}";
+    }
+
+    public static final @NonNull Parcelable.Creator<SystemStatusEvent> CREATOR =
+            new Parcelable.Creator<SystemStatusEvent>() {
+                public SystemStatusEvent createFromParcel(Parcel in) {
+                    return new SystemStatusEvent(in);
+                }
+
+                public SystemStatusEvent[] newArray(int size) {
+                    return new SystemStatusEvent[size];
+                }
+            };
+
+    private SystemStatusEvent(Parcel in) {
+        systemStatus = in.readInt();
+        detectionType = in.readInt();
+    }
+}
diff --git a/car-lib/src/android/car/settings/CarConfigurationManager.java b/car-lib/src/android/car/settings/CarConfigurationManager.java
index 626ad39..45b8767 100644
--- a/car-lib/src/android/car/settings/CarConfigurationManager.java
+++ b/car-lib/src/android/car/settings/CarConfigurationManager.java
@@ -23,7 +23,10 @@
 
 /**
  * Manager that exposes car configuration values that are stored on the system.
+ *
+ * @deprecated The {@code CarConfigurationManager} is no longer supported and will be removed.
  */
+@Deprecated
 public class CarConfigurationManager extends CarManagerBase {
     private static final String TAG = "CarConfigurationManager";
 
diff --git a/car-lib/src/android/car/settings/CarSettings.java b/car-lib/src/android/car/settings/CarSettings.java
index 3294071..e9af8eb 100644
--- a/car-lib/src/android/car/settings/CarSettings.java
+++ b/car-lib/src/android/car/settings/CarSettings.java
@@ -16,58 +16,35 @@
 
 package android.car.settings;
 
+import android.annotation.SystemApi;
+
 /**
- * System level car related settings.
+ * System-level, car-related settings.
+ *
+ * @hide
  */
 public class CarSettings {
 
+    private CarSettings() {
+        throw new UnsupportedOperationException("this class only provide constants");
+    }
+
     /**
      * Global car settings, containing preferences that always apply identically
      * to all defined users.  Applications can read these but are not allowed to write;
      * like the "Secure" settings, these are for preferences that the user must
      * explicitly modify through the system UI or specialized APIs for those values.
      *
-     * To read/write the global car settings, use {@link android.provider.Settings.Global}
+     * <p>To read/write the global car settings, use {@link android.provider.Settings.Global}
      * with the keys defined here.
+     *
+     * @hide
      */
     public static final class Global {
-        /**
-         * DEPRECATED. Will be removed in Q. Key for when to wake up to run garage mode.
-         * @deprecated not used by GarageMode anymore. Will be removed in Q.
-         */
-        @Deprecated
-        public static final String KEY_GARAGE_MODE_WAKE_UP_TIME =
-                "android.car.GARAGE_MODE_WAKE_UP_TIME";
-        /**
-         * DEPRECATED. Will be removed in Q. Key for whether garage mode is enabled.
-         * @deprecated not used by GarageMode anymore. Will be removed in Q.
-         */
-        @Deprecated
-        public static final String KEY_GARAGE_MODE_ENABLED = "android.car.GARAGE_MODE_ENABLED";
 
-        /**
-         * DEPRECATED. Will be removed in Q. Key for garage mode maintenance window.
-         * @deprecated not used by GarageMode anymore. Will be removed in Q.
-         */
-        @Deprecated
-        public static final String KEY_GARAGE_MODE_MAINTENANCE_WINDOW =
-                "android.car.GARAGE_MODE_MAINTENANCE_WINDOW";
-
-        /**
-         * Key for default user id to boot into.
-         *
-         * @hide
-         */
-        public static final String DEFAULT_USER_ID_TO_BOOT_INTO =
-                "android.car.DEFAULT_BOOT_INTO_USER_ID";
-
-        /**
-         * Key for user id that is last logged in to.
-         *
-         * @hide
-         */
-        public static final String LAST_ACTIVE_USER_ID =
-                "android.car.LAST_ACTIVE_USER_ID";
+        private Global() {
+            throw new UnsupportedOperationException("this class only provide constants");
+        }
 
         /**
          * Whether default restrictions for users have been set.
@@ -78,6 +55,15 @@
                 "android.car.DEFAULT_USER_RESTRICTIONS_SET";
 
         /**
+         * Developer settings String used to explicitly disable the instrumentation service (when
+         * set to {@code "true"}.
+         *
+         * @hide
+         */
+        public static final String DISABLE_INSTRUMENTATION_SERVICE =
+                "android.car.DISABLE_INSTRUMENTATION_SERVICE";
+
+        /**
          * Developer settings String used to explicitly enable the user switch message when
          * set to {@code "true"}.
          *
@@ -104,8 +90,33 @@
     /**
      * @hide
      */
+    @SystemApi
     public static final class Secure {
 
+        private Secure() {
+            throw new UnsupportedOperationException("this class only provide constants");
+        }
+
+        /**
+         * Key to indicate whether audio focus requests for
+         * {@link android.hardware.automotive.audiocontrol.V1_0.ContextNumber.NAVIGATION} should
+         * be rejected if focus is currently held by
+         * {@link android.hardware.automotive.audiocontrol.V1_0.ContextNumber.CALL}.
+         * <p>The value is a boolean (1 or 0) where:
+         * <ul>
+         * <li>1 indicates {@code NAVIGATION} should be rejected when a {@code CALL} is in progress.
+         * <li>0 indicates {@code NAVIGATION} and {@code CALL} should be allowed to hold focus
+         * concurrently.
+         * </ul>
+         *
+         * <p>Recommended {@code false} as default value.
+         *
+         * @hide
+         */
+        @SystemApi
+        public static final String KEY_AUDIO_FOCUS_NAVIGATION_REJECTED_DURING_CALL =
+                "android.car.KEY_AUDIO_FOCUS_NAVIGATION_REJECTED_DURING_CALL";
+
         /**
          * Key for a list of devices to automatically connect on Bluetooth A2DP Sink profile
          * Written to and read by {@link com.android.car.BluetoothDeviceConnectionPolicy}
diff --git a/car-lib/src/android/car/settings/ICarConfigurationManager.aidl b/car-lib/src/android/car/settings/ICarConfigurationManager.aidl
index 5fe4088..ea53998 100644
--- a/car-lib/src/android/car/settings/ICarConfigurationManager.aidl
+++ b/car-lib/src/android/car/settings/ICarConfigurationManager.aidl
@@ -22,6 +22,7 @@
  * Binder interface for {@link android.car.settings.CarConfigurationManager}.
  *
  * @hide
+ * @deprecated Configuration of speed bump is no longer a supported feature.
  */
 interface ICarConfigurationManager {
     /**
diff --git a/car-lib/src/android/car/settings/SpeedBumpConfiguration.java b/car-lib/src/android/car/settings/SpeedBumpConfiguration.java
index 39a1eab..ee090f5 100644
--- a/car-lib/src/android/car/settings/SpeedBumpConfiguration.java
+++ b/car-lib/src/android/car/settings/SpeedBumpConfiguration.java
@@ -25,7 +25,9 @@
  * A configuration struct that holds information for tweaking SpeedBump settings.
  *
  * @see androidx.car.moderator.SpeedBumpView
+ * @deprecated Speed bump configuration is no longer a support feature.
  */
+@Deprecated
 public final class SpeedBumpConfiguration implements Parcelable {
     private final double mAcquiredPermitsPerSecond;
     private final double mMaxPermitPool;
diff --git a/car-lib/src/android/car/storagemonitoring/CarStorageMonitoringManager.java b/car-lib/src/android/car/storagemonitoring/CarStorageMonitoringManager.java
index 69c092b..ac59bac 100644
--- a/car-lib/src/android/car/storagemonitoring/CarStorageMonitoringManager.java
+++ b/car-lib/src/android/car/storagemonitoring/CarStorageMonitoringManager.java
@@ -19,6 +19,7 @@
 import android.annotation.SystemApi;
 import android.car.Car;
 import android.car.CarManagerBase;
+import android.car.annotation.RequiredFeature;
 import android.os.IBinder;
 import android.os.RemoteException;
 
@@ -36,6 +37,7 @@
  * @hide
  */
 @SystemApi
+@RequiredFeature(Car.STORAGE_MONITORING_SERVICE)
 public final class CarStorageMonitoringManager extends CarManagerBase {
     private static final String TAG = CarStorageMonitoringManager.class.getSimpleName();
     private static final int MSG_IO_STATS_EVENT = 0;
diff --git a/car-lib/src/android/car/trust/CarTrustAgentEnrollmentManager.java b/car-lib/src/android/car/trust/CarTrustAgentEnrollmentManager.java
index 9881420..5087c17 100644
--- a/car-lib/src/android/car/trust/CarTrustAgentEnrollmentManager.java
+++ b/car-lib/src/android/car/trust/CarTrustAgentEnrollmentManager.java
@@ -66,7 +66,10 @@
  * </ol>
  *
  * @hide
+ *
+ * @deprecated Enrollment of a trusted device is no longer a supported feature.
  */
+@Deprecated
 @SystemApi
 public final class CarTrustAgentEnrollmentManager extends CarManagerBase {
     private static final String TAG = "CarTrustEnrollMgr";
diff --git a/car-lib/src/android/car/trust/ICarTrustAgentBleCallback.aidl b/car-lib/src/android/car/trust/ICarTrustAgentBleCallback.aidl
index f83cd23..7305542 100644
--- a/car-lib/src/android/car/trust/ICarTrustAgentBleCallback.aidl
+++ b/car-lib/src/android/car/trust/ICarTrustAgentBleCallback.aidl
@@ -22,6 +22,7 @@
  * Callback interface for BLE connection state changes during trusted device enrollment.
  *
  * @hide
+ * @deprecated Adding a trust agent is no longer a supported feature.
  */
 oneway interface ICarTrustAgentBleCallback {
     /**
diff --git a/car-lib/src/android/car/trust/ICarTrustAgentEnrollment.aidl b/car-lib/src/android/car/trust/ICarTrustAgentEnrollment.aidl
index 27b8a3f..08c2680 100644
--- a/car-lib/src/android/car/trust/ICarTrustAgentEnrollment.aidl
+++ b/car-lib/src/android/car/trust/ICarTrustAgentEnrollment.aidl
@@ -26,6 +26,7 @@
  * to communicate with the remote device securely to enroll the remote device as a trusted device.
  *
  * @hide
+ * @deprecated Enrolling a trusted device is no longer a supported feature.
  */
 interface ICarTrustAgentEnrollment {
     void startEnrollmentAdvertising();
diff --git a/car-lib/src/android/car/trust/ICarTrustAgentEnrollmentCallback.aidl b/car-lib/src/android/car/trust/ICarTrustAgentEnrollmentCallback.aidl
index 8cfc983..c8c3d65 100644
--- a/car-lib/src/android/car/trust/ICarTrustAgentEnrollmentCallback.aidl
+++ b/car-lib/src/android/car/trust/ICarTrustAgentEnrollmentCallback.aidl
@@ -22,6 +22,7 @@
  * Callback interface for state changes during Trusted device enrollment.
  *
  * @hide
+ * @deprecated Enrolling a trusted device is no longer a supported feature.
  */
 oneway interface ICarTrustAgentEnrollmentCallback {
     /**
diff --git a/car-lib/src/android/car/trust/TrustedDeviceInfo.aidl b/car-lib/src/android/car/trust/TrustedDeviceInfo.aidl
index 510dbc9..8dc558c 100644
--- a/car-lib/src/android/car/trust/TrustedDeviceInfo.aidl
+++ b/car-lib/src/android/car/trust/TrustedDeviceInfo.aidl
@@ -1,3 +1,3 @@
 package android.car.trust;
 
-parcelable TrustedDeviceInfo;
\ No newline at end of file
+parcelable TrustedDeviceInfo;
diff --git a/car-lib/src/android/car/trust/TrustedDeviceInfo.java b/car-lib/src/android/car/trust/TrustedDeviceInfo.java
index 418bede..b216874 100644
--- a/car-lib/src/android/car/trust/TrustedDeviceInfo.java
+++ b/car-lib/src/android/car/trust/TrustedDeviceInfo.java
@@ -28,7 +28,9 @@
  * Contains basic info of a trusted device.
  *
  * @hide
+ * @deprecated Adding a trusted device is no longer a supported feature.
  */
+@Deprecated
 @SystemApi
 public final class TrustedDeviceInfo implements Parcelable {
 
diff --git a/car-lib/src/android/car/user/CarUserManager.java b/car-lib/src/android/car/user/CarUserManager.java
new file mode 100644
index 0000000..afd60e3
--- /dev/null
+++ b/car-lib/src/android/car/user/CarUserManager.java
@@ -0,0 +1,563 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.car.user;
+
+import static android.Manifest.permission.INTERACT_ACROSS_USERS;
+import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.os.Process.myUid;
+
+import android.annotation.CallbackExecutor;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
+import android.annotation.UserIdInt;
+import android.car.Car;
+import android.car.CarManagerBase;
+import android.car.ICarUserService;
+import android.content.Context;
+import android.content.pm.UserInfo;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.util.ArrayMap;
+import android.util.Log;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.os.IResultReceiver;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.Executor;
+
+/**
+ * API to manage users related to car.
+ *
+ * @hide
+ */
+@SystemApi
+@TestApi
+public final class CarUserManager extends CarManagerBase {
+
+    private static final String TAG = CarUserManager.class.getSimpleName();
+
+    /**
+     *  User id representing invalid user.
+     *
+     * @hide
+     */
+    public static final int INVALID_USER_ID = UserHandle.USER_NULL;
+
+    // TODO(b/144120654): STOPSHIP - set to false
+    private static final boolean DBG = true;
+
+    /**
+     * {@link UserLifecycleEvent} called when the user is starting.
+     *
+     * @hide
+     */
+    @SystemApi
+    @TestApi
+    public static final int USER_LIFECYCLE_EVENT_TYPE_STARTING = 1;
+
+    /**
+     * {@link UserLifecycleEvent} called when the user is switching.
+     *
+     * @hide
+     */
+    @SystemApi
+    @TestApi
+    public static final int USER_LIFECYCLE_EVENT_TYPE_SWITCHING = 2;
+
+    /**
+     * {@link UserLifecycleEvent} called whe the user is unlocking.
+     *
+     * @hide
+     */
+    @SystemApi
+    @TestApi
+    public static final int USER_LIFECYCLE_EVENT_TYPE_UNLOCKING = 3;
+
+    /**
+     * {@link UserLifecycleEvent} called after the user was unlocked.
+     *
+     * @hide
+     */
+    @SystemApi
+    @TestApi
+    public static final int USER_LIFECYCLE_EVENT_TYPE_UNLOCKED = 4;
+
+    /**
+     * {@link UserLifecycleEvent} called when the user is stopping.
+     *
+     * @hide
+     */
+    @SystemApi
+    @TestApi
+    public static final int USER_LIFECYCLE_EVENT_TYPE_STOPPING = 5;
+
+    /**
+     * {@link UserLifecycleEvent} called after the user stoppped.
+     *
+     * @hide
+     */
+    @SystemApi
+    @TestApi
+    public static final int USER_LIFECYCLE_EVENT_TYPE_STOPPED = 6;
+
+    /** @hide */
+    @IntDef(prefix = { "USER_LIFECYCLE_EVENT_TYPE_" }, value = {
+            USER_LIFECYCLE_EVENT_TYPE_STARTING,
+            USER_LIFECYCLE_EVENT_TYPE_SWITCHING,
+            USER_LIFECYCLE_EVENT_TYPE_UNLOCKING,
+            USER_LIFECYCLE_EVENT_TYPE_UNLOCKED,
+            USER_LIFECYCLE_EVENT_TYPE_STOPPING,
+            USER_LIFECYCLE_EVENT_TYPE_STOPPED,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface UserLifecycleEventType{}
+
+    /** @hide */
+    public static final String BUNDLE_PARAM_ACTION = "action";
+    /** @hide */
+    public static final String BUNDLE_PARAM_PREVIOUS_USER_HANDLE = "previous_user";
+
+    private final Object mLock = new Object();
+    private final ICarUserService mService;
+
+    @Nullable
+    @GuardedBy("mLock")
+    private ArrayMap<UserLifecycleListener, Executor> mListeners;
+
+    @Nullable
+    @GuardedBy("mLock")
+    private LifecycleResultReceiver mReceiver;
+
+    /**
+     * @hide
+     */
+    @VisibleForTesting
+    public CarUserManager(@NonNull Car car, @NonNull IBinder service) {
+        super(car);
+        mService = ICarUserService.Stub.asInterface(service);
+    }
+
+    /**
+     * Creates a driver who is a regular user and is allowed to login to the driving occupant zone.
+     *
+     * @param name The name of the driver to be created.
+     * @param admin Whether the created driver will be an admin.
+     * @return user id of the created driver, or {@code INVALID_USER_ID} if the driver could
+     *         not be created.
+     *
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.MANAGE_USERS)
+    @Nullable
+    public int createDriver(@NonNull String name, boolean admin) {
+        try {
+            UserInfo ui = mService.createDriver(name, admin);
+            return ui != null ? ui.id : INVALID_USER_ID;
+        } catch (RemoteException e) {
+            return handleRemoteExceptionFromCarService(e, null);
+        }
+    }
+
+    /**
+     * Creates a passenger who is a profile of the given driver.
+     *
+     * @param name The name of the passenger to be created.
+     * @param driverId User id of the driver under whom a passenger is created.
+     * @return user id of the created passenger, or {@code INVALID_USER_ID} if the passenger
+     *         could not be created.
+     *
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.MANAGE_USERS)
+    @Nullable
+    public int createPassenger(@NonNull String name, @UserIdInt int driverId) {
+        try {
+            UserInfo ui = mService.createPassenger(name, driverId);
+            return ui != null ? ui.id : INVALID_USER_ID;
+        } catch (RemoteException e) {
+            return handleRemoteExceptionFromCarService(e, null);
+        }
+    }
+
+    /**
+     * Switches a driver to the given user.
+     *
+     * @param driverId User id of the driver to switch to.
+     * @return {@code true} if user switching succeeds, or {@code false} if it fails.
+     *
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.MANAGE_USERS)
+    public boolean switchDriver(@UserIdInt int driverId) {
+        try {
+            return mService.switchDriver(driverId);
+        } catch (RemoteException e) {
+            return handleRemoteExceptionFromCarService(e, false);
+        }
+    }
+
+    /**
+     * Returns all drivers who can occupy the driving zone. Guest users are included in the list.
+     *
+     * @return the list of user ids who can be a driver on the device.
+     *
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.MANAGE_USERS)
+    @NonNull
+    public List<Integer> getAllDrivers() {
+        try {
+            return getUserIdsFromUserInfos(mService.getAllDrivers());
+        } catch (RemoteException e) {
+            return handleRemoteExceptionFromCarService(e, Collections.emptyList());
+        }
+    }
+
+    /**
+     * Returns all passengers under the given driver.
+     *
+     * @param driverId User id of a driver.
+     * @return the list of user ids who are passengers under the given driver.
+     *
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.MANAGE_USERS)
+    @NonNull
+    public List<Integer> getPassengers(@UserIdInt int driverId) {
+        try {
+            return getUserIdsFromUserInfos(mService.getPassengers(driverId));
+        } catch (RemoteException e) {
+            return handleRemoteExceptionFromCarService(e, Collections.emptyList());
+        }
+    }
+
+    /**
+     * Assigns the passenger to the zone and starts the user if it is not started yet.
+     *
+     * @param passengerId User id of the passenger to be started.
+     * @param zoneId Zone id to which the passenger is assigned.
+     * @return {@code true} if the user is successfully started or the user is already running.
+     *         Otherwise, {@code false}.
+     *
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.MANAGE_USERS)
+    public boolean startPassenger(@UserIdInt int passengerId, int zoneId) {
+        try {
+            return mService.startPassenger(passengerId, zoneId);
+        } catch (RemoteException e) {
+            return handleRemoteExceptionFromCarService(e, false);
+        }
+    }
+
+    /**
+     * Stops the given passenger.
+     *
+     * @param passengerId User id of the passenger to be stopped.
+     * @return {@code true} if successfully stopped, or {@code false} if failed.
+     *
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.MANAGE_USERS)
+    public boolean stopPassenger(@UserIdInt int passengerId) {
+        try {
+            return mService.stopPassenger(passengerId);
+        } catch (RemoteException e) {
+            return handleRemoteExceptionFromCarService(e, false);
+        }
+    }
+
+    /**
+     * Adds a listener for {@link UserLifecycleEvent user lifecycle events}.
+     *
+     * @hide
+     */
+    @SystemApi
+    @TestApi
+    @RequiresPermission(anyOf = {INTERACT_ACROSS_USERS, INTERACT_ACROSS_USERS_FULL})
+    public void addListener(@NonNull @CallbackExecutor Executor executor,
+            @NonNull UserLifecycleListener listener) {
+        checkInteractAcrossUsersPermission();
+
+        // TODO(b/144120654): add unit tests to validate input
+        // - executor cannot be null
+        // - listener cannot be null
+        // - listener must not be added before
+
+        synchronized (mLock) {
+            if (mReceiver == null) {
+                mReceiver = new LifecycleResultReceiver();
+                try {
+                    Log.i(TAG, "Setting lifecycle receiver for uid " + myUid());
+                    mService.setLifecycleListenerForUid(mReceiver);
+                } catch (RemoteException e) {
+                    handleRemoteExceptionFromCarService(e);
+                }
+            } else {
+                if (DBG) Log.d(TAG, "Already set receiver for uid " + myUid());
+            }
+
+            if (mListeners == null) {
+                mListeners = new ArrayMap<>(1); // Most likely app will have just one listener
+            }
+            if (DBG) Log.d(TAG, "Adding listener: " + listener);
+            mListeners.put(listener, executor);
+        }
+    }
+
+    /**
+     * Removes a listener for {@link UserLifecycleEvent user lifecycle events}.
+     *
+     * @hide
+     */
+    @SystemApi
+    @TestApi
+    @RequiresPermission(anyOf = {INTERACT_ACROSS_USERS, INTERACT_ACROSS_USERS_FULL})
+    public void removeListener(@NonNull UserLifecycleListener listener) {
+        checkInteractAcrossUsersPermission();
+
+        // TODO(b/144120654): add unit tests to validate input
+        // - listener cannot be null
+        // - listener must not be added before
+        synchronized (mLock) {
+            if (mListeners == null) {
+                Log.w(TAG, "removeListener(): no listeners for uid " + myUid());
+                return;
+            }
+
+            mListeners.remove(listener);
+
+            if (!mListeners.isEmpty()) {
+                if (DBG) Log.d(TAG, "removeListeners(): still " + mListeners.size() + " left");
+                return;
+            }
+            mListeners = null;
+
+            if (mReceiver == null) {
+                Log.wtf(TAG, "removeListener(): receiver already null");
+                return;
+            }
+
+            Log.i(TAG, "Removing lifecycle receiver for uid=" + myUid());
+            try {
+                mService.resetLifecycleListenerForUid();
+                mReceiver = null;
+            } catch (RemoteException e) {
+                handleRemoteExceptionFromCarService(e);
+            }
+        }
+    }
+
+    /** @hide */
+    @TestApi
+    // TODO(b/144120654): temp method used by CTS; will eventually be refactored to take a listener
+    @UserIdInt
+    public int createUser(@Nullable String name) {
+        Log.i(TAG, "createUser()"); // name is PII
+        UserManager userManager = getContext().getSystemService(UserManager.class);
+        UserInfo info = userManager.createUser(name, /* flags= */ 0);
+        return info.id;
+    }
+
+    /** @hide */
+    @TestApi
+    // TODO(b/144120654): temp method used by CTS; will eventually be refactored to take a listener
+    public void removeUser(@UserIdInt int userId) {
+        Log.i(TAG, "removeUser(" + userId + ")");
+        UserManager userManager = getContext().getSystemService(UserManager.class);
+        userManager.removeUser(userId);
+    }
+
+    /**
+     * {@code IResultReceiver} used to receive lifecycle events and dispatch to the proper listener.
+     */
+    private class LifecycleResultReceiver extends IResultReceiver.Stub {
+        public void send(int resultCode, Bundle resultData) {
+            if (resultData == null) {
+                Log.w(TAG, "Received result (" + resultCode + ") without data");
+                return;
+            }
+            UserHandle toHandle = new UserHandle(resultCode);
+            UserHandle fromHandle = resultData.getParcelable(BUNDLE_PARAM_PREVIOUS_USER_HANDLE);
+            int eventType = resultData.getInt(BUNDLE_PARAM_ACTION);
+            UserLifecycleEvent event = new UserLifecycleEvent(eventType, fromHandle, toHandle);
+            ArrayMap<UserLifecycleListener, Executor> listeners;
+            synchronized (mLock) {
+                listeners = mListeners;
+            }
+            if (listeners == null) {
+                Log.w(TAG, "No listeners for event " + event);
+                return;
+            }
+            for (int i = 0; i < listeners.size(); i++) {
+                UserLifecycleListener listener = listeners.keyAt(i);
+                Executor executor = listeners.valueAt(i);
+                if (DBG) Log.d(TAG, "Calling listener " + listener + " for event " + event);
+                executor.execute(() -> listener.onEvent(event));
+            }
+        }
+    }
+
+    /** @hide */
+    @Override
+    public void onCarDisconnected() {
+        // nothing to do
+    }
+
+    /**
+     * @hide
+     */
+    @TestApi
+    public static String lifecycleEventTypeToString(@UserLifecycleEventType int type) {
+        switch (type) {
+            case USER_LIFECYCLE_EVENT_TYPE_STARTING:
+                return "STARTING";
+            case USER_LIFECYCLE_EVENT_TYPE_SWITCHING:
+                return "SWITCHING";
+            case USER_LIFECYCLE_EVENT_TYPE_UNLOCKING:
+                return "UNLOCKING";
+            case USER_LIFECYCLE_EVENT_TYPE_UNLOCKED:
+                return "UNLOCKED";
+            case USER_LIFECYCLE_EVENT_TYPE_STOPPING:
+                return "STOPPING";
+            case USER_LIFECYCLE_EVENT_TYPE_STOPPED:
+                return "STOPPED";
+            default:
+                return "UNKNOWN-" + type;
+        }
+    }
+
+    private List<Integer> getUserIdsFromUserInfos(List<UserInfo> infos) {
+        List<Integer> ids = new ArrayList<>(infos.size());
+        for (UserInfo ui : infos) {
+            ids.add(ui.id);
+        }
+        return ids;
+    }
+
+    private void checkInteractAcrossUsersPermission() {
+        Context context = getContext();
+        if (context.checkSelfPermission(INTERACT_ACROSS_USERS) != PERMISSION_GRANTED
+                && context.checkSelfPermission(INTERACT_ACROSS_USERS_FULL) != PERMISSION_GRANTED) {
+            throw new SecurityException(
+                    "Must have either " + android.Manifest.permission.INTERACT_ACROSS_USERS + " or "
+                            + android.Manifest.permission.INTERACT_ACROSS_USERS_FULL
+                            + " permission");
+        }
+    }
+
+    /**
+     * Defines a lifecycle event for an Android user.
+     *
+     * @hide
+     */
+    @SystemApi
+    @TestApi
+    public final class UserLifecycleEvent {
+        private final @UserLifecycleEventType int mEventType;
+        private final @NonNull UserHandle mUserHandle;
+        private final @Nullable UserHandle mPreviousUserHandle;
+
+        private UserLifecycleEvent(@UserLifecycleEventType int eventType,
+                @NonNull UserHandle from, @Nullable UserHandle to) {
+            mEventType = eventType;
+            mPreviousUserHandle = from;
+            mUserHandle = to;
+        }
+
+        /**
+         * Gets the event type.
+         *
+         * @return either {@link CarUserManager#USER_LIFECYCLE_EVENT_TYPE_STARTING},
+         * {@link CarUserManager#USER_LIFECYCLE_EVENT_TYPE_SWITCHING},
+         * {@link CarUserManager#USER_LIFECYCLE_EVENT_TYPE_UNLOCKING},
+         * {@link CarUserManager#USER_LIFECYCLE_EVENT_TYPE_UNLOCKED},
+         * {@link CarUserManager#USER_LIFECYCLE_EVENT_TYPE_STOPPING}, or
+         * {@link CarUserManager#USER_LIFECYCLE_EVENT_TYPE_STOPPED}.
+         */
+        @UserLifecycleEventType
+        public int getEventType() {
+            return mEventType;
+        }
+
+        /**
+         * Gets the handle of the user whose event is being reported.
+         */
+        @NonNull
+        public UserHandle getUserHandle() {
+            return mUserHandle;
+        }
+
+        /**
+         * Gets the handle of the user being switched from.
+         *
+         * <p>This method returns {@code null} for all event types but
+         * {@link CarUserManager#USER_LIFECYCLE_EVENT_TYPE_SWITCHING}.
+         */
+        @Nullable
+        public UserHandle getPreviousUserHandle() {
+            return mPreviousUserHandle;
+        }
+
+        @Override
+        public String toString() {
+            StringBuilder builder = new StringBuilder("Event[type=")
+                    .append(lifecycleEventTypeToString(mEventType));
+            if (mPreviousUserHandle != null) {
+                builder
+                    .append(",from=").append(mPreviousUserHandle)
+                    .append(",to=").append(mUserHandle);
+            } else {
+                builder.append(",user=").append(mUserHandle);
+            }
+
+            return builder.append(']').toString();
+        }
+    }
+
+    /**
+     * Listener for Android User lifecycle events.
+     *
+     * <p>Must be registered using {@link CarUserManager#addListener(UserLifecycleListener)} and
+     * unregistered through {@link CarUserManager#removeListener(UserLifecycleListener)}.
+     *
+     * @hide
+     */
+    @SystemApi
+    @TestApi
+    public interface UserLifecycleListener {
+
+        /**
+         * Called to notify the given {@code event}.
+         */
+        void onEvent(@NonNull UserLifecycleEvent event);
+    }
+}
diff --git a/car-lib/src/android/car/vms/IVmsBrokerService.aidl b/car-lib/src/android/car/vms/IVmsBrokerService.aidl
new file mode 100644
index 0000000..cb70323
--- /dev/null
+++ b/car-lib/src/android/car/vms/IVmsBrokerService.aidl
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.car.vms;
+
+import android.car.vms.IVmsClientCallback;
+import android.car.vms.VmsAssociatedLayer;
+import android.car.vms.VmsLayer;
+import android.car.vms.VmsLayerDependency;
+import android.car.vms.VmsProviderInfo;
+import android.car.vms.VmsRegistrationInfo;
+
+/**
+ * Hidden API for communicating with the Vehicle Map Service message broker.
+ *
+ * @hide
+ */
+interface IVmsBrokerService {
+    // Client operations
+    // Restricted to callers with android.car.permission.VMS_SUBSCRIBER
+    // or android.car.permission.VMS_PUBLISHER
+
+    VmsRegistrationInfo registerClient(
+    in IBinder token,
+    in IVmsClientCallback callback,
+    boolean legacyClient) = 0;
+
+    void unregisterClient(in IBinder token) = 1;
+
+    VmsProviderInfo getProviderInfo(in IBinder token, int providerId) = 2;
+
+    // Subscriber operations
+    // Restricted to callers with android.car.permission.VMS_SUBSCRIBER
+
+    void setSubscriptions(
+        in IBinder token,
+        in List<VmsAssociatedLayer> layers) = 3;
+
+    void setMonitoringEnabled(in IBinder token, boolean enabled) = 4;
+
+    // Publisher operations
+    // Restricted to callers with android.car.permission.VMS_PUBLISHER
+
+    int registerProvider(
+        in IBinder token,
+        in VmsProviderInfo providerInfo) = 5;
+
+    void setProviderOfferings(
+        in IBinder token,
+        int providerId,
+        in List<VmsLayerDependency> offerings) = 6;
+
+    void publishPacket(
+        in IBinder token,
+        int providerId,
+        in VmsLayer layer,
+        in byte[] packet) = 7;
+}
diff --git a/car-lib/src/android/car/vms/IVmsClientCallback.aidl b/car-lib/src/android/car/vms/IVmsClientCallback.aidl
new file mode 100644
index 0000000..5c8886d
--- /dev/null
+++ b/car-lib/src/android/car/vms/IVmsClientCallback.aidl
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.car.vms;
+
+import android.car.vms.VmsAvailableLayers;
+import android.car.vms.VmsLayer;
+import android.car.vms.VmsSubscriptionState;
+
+/**
+ * Hidden API for sending notifications to Vehicle Map Service clients.
+ *
+ * @hide
+ */
+oneway interface IVmsClientCallback {
+    void onLayerAvailabilityChanged(
+        in VmsAvailableLayers availableLayers) = 0;
+
+    void onSubscriptionStateChanged(
+        in VmsSubscriptionState subscriptionState) = 1;
+
+    void onPacketReceived(
+        int providerId,
+        in VmsLayer layer,
+        in byte[] packet) = 2;
+}
diff --git a/car-lib/src/android/car/vms/IVmsPublisherClient.aidl b/car-lib/src/android/car/vms/IVmsPublisherClient.aidl
deleted file mode 100644
index 96b993b..0000000
--- a/car-lib/src/android/car/vms/IVmsPublisherClient.aidl
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.car.vms;
-
-import android.car.vms.IVmsPublisherService;
-import android.car.vms.VmsSubscriptionState;
-
-/**
- * @hide
- */
-interface IVmsPublisherClient {
-    /**
-    * Once the VmsPublisherService is bound to the client, this callback is used to set the
-    * binder that the client can use to invoke publisher services. This also gives the client
-    * the token it should use when calling the service.
-    */
-    oneway void setVmsPublisherService(in IBinder token, IVmsPublisherService service) = 0;
-
-    /**
-     * The VmsPublisherService uses this callback to notify about subscription changes.
-     * @param subscriptionState all the layers that have subscribers and a sequence number,
-     *                          clients should ignore any packet with a sequence number that is less
-     *                          than the highest sequence number they have seen thus far.
-     */
-    oneway void onVmsSubscriptionChange(in VmsSubscriptionState subscriptionState) = 1;
-}
diff --git a/car-lib/src/android/car/vms/IVmsPublisherService.aidl b/car-lib/src/android/car/vms/IVmsPublisherService.aidl
deleted file mode 100644
index 3c26a1b..0000000
--- a/car-lib/src/android/car/vms/IVmsPublisherService.aidl
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.car.vms;
-
-import android.car.vms.VmsLayer;
-import android.car.vms.VmsLayersOffering;
-import android.car.vms.VmsSubscriptionState;
-
-/**
- * Exposes publisher services to VMS clients.
- *
- * @hide
- */
-interface IVmsPublisherService {
-    /**
-     * Client call to publish a message.
-     */
-    oneway void publish(in IBinder token, in VmsLayer layer, int publisherId, in byte[] message) = 0;
-
-    /**
-     * Returns the list of VmsLayers that has any clients subscribed to it.
-     */
-    VmsSubscriptionState getSubscriptions() = 1;
-
-    /**
-     * Sets which layers the publisher can publish under which dependencties.
-     */
-    oneway void setLayersOffering(in IBinder token, in VmsLayersOffering offering) = 2;
-
-    /**
-     * The first time a publisher calls this API it will store the publisher info and assigns the
-     * publisher an ID. Between reboots, subsequent calls with the same publisher info will
-     * return the same ID so that a restarting process can obtain the same ID as it had before.
-     */
-    int getPublisherId(in byte[] publisherInfo) = 3;
-}
diff --git a/car-lib/src/android/car/vms/IVmsSubscriberClient.aidl b/car-lib/src/android/car/vms/IVmsSubscriberClient.aidl
deleted file mode 100644
index 8f67cd5..0000000
--- a/car-lib/src/android/car/vms/IVmsSubscriberClient.aidl
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.car.vms;
-
-import android.car.vms.VmsAvailableLayers;
-import android.car.vms.VmsLayer;
-
-/**
- * @hide
- */
-oneway interface IVmsSubscriberClient {
-    /**
-     * A VmsService uses this callback to pass messages to subscribers.
-     */
-    void onVmsMessageReceived(in VmsLayer layer, in byte[] payload) = 0;
-
-    void onLayersAvailabilityChanged(in VmsAvailableLayers availableLayers) = 1;
-}
diff --git a/car-lib/src/android/car/vms/IVmsSubscriberService.aidl b/car-lib/src/android/car/vms/IVmsSubscriberService.aidl
deleted file mode 100644
index 661065a..0000000
--- a/car-lib/src/android/car/vms/IVmsSubscriberService.aidl
+++ /dev/null
@@ -1,97 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.car.vms;
-
-import android.car.vms.IVmsSubscriberClient;
-import android.car.vms.VmsLayer;
-import android.car.vms.VmsAvailableLayers;
-
-/**
- * @hide
- */
-interface IVmsSubscriberService {
-    /**
-     * Adds a subscriber to notifications only.
-     * Should be called when a subscriber registers its callback, and before any subscription to a
-     * layer is made.
-     */
-    void addVmsSubscriberToNotifications(
-            in IVmsSubscriberClient subscriber) = 0;
-
-    /**
-     * Adds a subscriber to a VMS layer.
-     */
-    void addVmsSubscriber(
-            in IVmsSubscriberClient subscriber,
-            in VmsLayer layer) = 1;
-
-    /**
-     * Adds a subscriber to all actively broadcasted layers.
-     * Publishers will not be notified regarding this request so the state of the service will not
-     * change.
-     */
-    void addVmsSubscriberPassive(in IVmsSubscriberClient subscriber) = 2;
-
-    /**
-     * Adds a subscriber to a VMS layer from a specific publisher.
-     */
-    void addVmsSubscriberToPublisher(
-            in IVmsSubscriberClient subscriber,
-            in VmsLayer layer,
-            int publisherId) = 3;
-
-    /**
-     * Removes a subscriber to notifications only.
-     * Should be called when a subscriber unregisters its callback, and after all subscriptions to
-     * layers are removed.
-     */
-    void removeVmsSubscriberToNotifications(
-            in IVmsSubscriberClient subscriber) = 4;
-
-    /**
-     * Removes a subscriber to a VMS layer.
-     */
-    void removeVmsSubscriber(
-            in IVmsSubscriberClient subscriber,
-            in VmsLayer layer) = 5;
-
-    /**
-     * Removes a subscriber to all actively broadcasted layers.
-     * Publishers will not be notified regarding this request so the state of the service will not
-     * change.
-     */
-    void removeVmsSubscriberPassive(
-            in IVmsSubscriberClient subscriber) = 6;
-
-    /**
-     * Removes a subscriber to a VMS layer from a specific publisher.
-     */
-    void removeVmsSubscriberToPublisher(
-            in IVmsSubscriberClient subscriber,
-            in VmsLayer layer,
-            int publisherId) = 7;
-
-    /**
-     * Returns a list of available layers from the closure of the publishers offerings.
-     */
-    VmsAvailableLayers getAvailableLayers() = 8;
-
-    /**
-     *  Returns a the publisher information for a publisher ID.
-     */
-    byte[] getPublisherInfo(in int publisherId) = 9;
-}
diff --git a/car-lib/src/android/car/vms/VmsAssociatedLayer.java b/car-lib/src/android/car/vms/VmsAssociatedLayer.java
index e522faa..d34aa5d 100644
--- a/car-lib/src/android/car/vms/VmsAssociatedLayer.java
+++ b/car-lib/src/android/car/vms/VmsAssociatedLayer.java
@@ -20,8 +20,9 @@
 import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.util.ArraySet;
 
-import com.android.internal.util.Preconditions;
+import com.android.internal.util.DataClass;
 
 import java.util.*;
 
@@ -31,85 +32,194 @@
  * @hide
  */
 @SystemApi
+@DataClass(genAidl = true, genEqualsHashCode = true, genToString = true)
 public final class VmsAssociatedLayer implements Parcelable {
-    private final VmsLayer mLayer;
-    private final Set<Integer> mPublisherIds;
+    /**
+     * Layer being offered
+     */
+    private final @NonNull VmsLayer mVmsLayer;
 
     /**
-     * Constructs a new layer offering.
+     * IDs of providers that publish the layer
+     */
+    private @NonNull Set<Integer> mProviderIds;
+
+    private void onConstructed() {
+        mProviderIds = Collections.unmodifiableSet(mProviderIds);
+    }
+
+    private void parcelProviderIds(Parcel dest, int flags) {
+        dest.writeArraySet(new ArraySet<>(mProviderIds));
+    }
+
+    @SuppressWarnings("unchecked")
+    private Set<Integer> unparcelProviderIds(Parcel in) {
+        return (Set<Integer>) in.readArraySet(Integer.class.getClassLoader());
+    }
+
+    /**
+     * IDs of publishers that publish the layer
      *
-     * @param layer layer being offered
-     * @param publisherIds IDs of publishers associated with the layer
+     * @deprecated Use {@link #getProviderIds()} instead
      */
-    public VmsAssociatedLayer(@NonNull VmsLayer layer, @NonNull Set<Integer> publisherIds) {
-        mLayer = Preconditions.checkNotNull(layer, "layer cannot be null");
-        mPublisherIds = Collections.unmodifiableSet(publisherIds);
-    }
-
-    /**
-     * @return layer being offered
-     */
-    @NonNull
-    public VmsLayer getVmsLayer() {
-        return mLayer;
-    }
-
-    /**
-     * @return IDs of publishers associated with the layer
-     */
+    @Deprecated
     @NonNull
     public Set<Integer> getPublisherIds() {
-        return mPublisherIds;
+        return mProviderIds;
+    }
+
+
+
+    // Code below generated by codegen v1.0.14.
+    //
+    // DO NOT MODIFY!
+    // CHECKSTYLE:OFF Generated code
+    //
+    // To regenerate run:
+    // $ codegen $ANDROID_BUILD_TOP/packages/services/Car/car-lib/src/android/car/vms/VmsAssociatedLayer.java
+    //
+    // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+    //   Settings > Editor > Code Style > Formatter Control
+    //@formatter:off
+
+
+    /**
+     * Creates a new VmsAssociatedLayer.
+     *
+     * @param vmsLayer
+     *   Layer being offered
+     * @param providerIds
+     *   IDs of providers that publish the layer
+     */
+    @DataClass.Generated.Member
+    public VmsAssociatedLayer(
+            @NonNull VmsLayer vmsLayer,
+            @NonNull Set<Integer> providerIds) {
+        this.mVmsLayer = vmsLayer;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mVmsLayer);
+        this.mProviderIds = providerIds;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mProviderIds);
+
+        onConstructed();
+    }
+
+    /**
+     * Layer being offered
+     */
+    @DataClass.Generated.Member
+    public @NonNull VmsLayer getVmsLayer() {
+        return mVmsLayer;
+    }
+
+    /**
+     * IDs of providers that publish the layer
+     */
+    @DataClass.Generated.Member
+    public @NonNull Set<Integer> getProviderIds() {
+        return mProviderIds;
     }
 
     @Override
+    @DataClass.Generated.Member
     public String toString() {
-        return "VmsAssociatedLayer{ VmsLayer: " + mLayer + ", Publishers: " + mPublisherIds + "}";
-    }
+        // You can override field toString logic by defining methods like:
+        // String fieldNameToString() { ... }
 
-    public static final Parcelable.Creator<VmsAssociatedLayer> CREATOR =
-            new Parcelable.Creator<VmsAssociatedLayer>() {
-                public VmsAssociatedLayer createFromParcel(Parcel in) {
-                    return new VmsAssociatedLayer(in);
-                }
-
-                public VmsAssociatedLayer[] newArray(int size) {
-                    return new VmsAssociatedLayer[size];
-                }
-            };
-
-    @Override
-    public void writeToParcel(Parcel out, int flags) {
-        out.writeParcelable(mLayer, flags);
-        out.writeArray(mPublisherIds.toArray());
+        return "VmsAssociatedLayer { " +
+                "vmsLayer = " + mVmsLayer + ", " +
+                "providerIds = " + mProviderIds +
+        " }";
     }
 
     @Override
-    public boolean equals(Object o) {
-        if (!(o instanceof VmsAssociatedLayer)) {
-            return false;
-        }
-        VmsAssociatedLayer p = (VmsAssociatedLayer) o;
-        return Objects.equals(p.mLayer, mLayer) && p.mPublisherIds.equals(mPublisherIds);
+    @DataClass.Generated.Member
+    public boolean equals(@android.annotation.Nullable Object o) {
+        // You can override field equality logic by defining either of the methods like:
+        // boolean fieldNameEquals(VmsAssociatedLayer other) { ... }
+        // boolean fieldNameEquals(FieldType otherValue) { ... }
+
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        @SuppressWarnings("unchecked")
+        VmsAssociatedLayer that = (VmsAssociatedLayer) o;
+        //noinspection PointlessBooleanExpression
+        return true
+                && Objects.equals(mVmsLayer, that.mVmsLayer)
+                && Objects.equals(mProviderIds, that.mProviderIds);
     }
 
     @Override
+    @DataClass.Generated.Member
     public int hashCode() {
-        return Objects.hash(mLayer, mPublisherIds);
+        // You can override field hashCode logic by defining methods like:
+        // int fieldNameHashCode() { ... }
+
+        int _hash = 1;
+        _hash = 31 * _hash + Objects.hashCode(mVmsLayer);
+        _hash = 31 * _hash + Objects.hashCode(mProviderIds);
+        return _hash;
     }
 
     @Override
-    public int describeContents() {
-        return 0;
+    @DataClass.Generated.Member
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        // You can override field parcelling by defining methods like:
+        // void parcelFieldName(Parcel dest, int flags) { ... }
+
+        dest.writeTypedObject(mVmsLayer, flags);
+        parcelProviderIds(dest, flags);
     }
 
-    private VmsAssociatedLayer(Parcel in) {
-        mLayer = in.readParcelable(VmsLayer.class.getClassLoader());
+    @Override
+    @DataClass.Generated.Member
+    public int describeContents() { return 0; }
 
-        Object[] objects = in.readArray(Integer.class.getClassLoader());
-        Integer[] integers = Arrays.copyOf(objects, objects.length, Integer[].class);
+    /** @hide */
+    @SuppressWarnings({"unchecked", "RedundantCast"})
+    @DataClass.Generated.Member
+    /* package-private */ VmsAssociatedLayer(@NonNull Parcel in) {
+        // You can override field unparcelling by defining methods like:
+        // static FieldType unparcelFieldName(Parcel in) { ... }
 
-        mPublisherIds = Collections.unmodifiableSet(
-                new HashSet<>(Arrays.asList(integers)));
+        VmsLayer vmsLayer = (VmsLayer) in.readTypedObject(VmsLayer.CREATOR);
+        Set<Integer> providerIds = unparcelProviderIds(in);
+
+        this.mVmsLayer = vmsLayer;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mVmsLayer);
+        this.mProviderIds = providerIds;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mProviderIds);
+
+        onConstructed();
     }
+
+    @DataClass.Generated.Member
+    public static final @NonNull Parcelable.Creator<VmsAssociatedLayer> CREATOR
+            = new Parcelable.Creator<VmsAssociatedLayer>() {
+        @Override
+        public VmsAssociatedLayer[] newArray(int size) {
+            return new VmsAssociatedLayer[size];
+        }
+
+        @Override
+        public VmsAssociatedLayer createFromParcel(@NonNull Parcel in) {
+            return new VmsAssociatedLayer(in);
+        }
+    };
+
+    @DataClass.Generated(
+            time = 1582065953302L,
+            codegenVersion = "1.0.14",
+            sourceFile = "packages/services/Car/car-lib/src/android/car/vms/VmsAssociatedLayer.java",
+            inputSignatures = "private final @android.annotation.NonNull android.car.vms.VmsLayer mVmsLayer\nprivate @android.annotation.NonNull android.car.vms.Set<java.lang.Integer> mProviderIds\nprivate  void onConstructed()\nprivate  void parcelProviderIds(android.os.Parcel,int)\nprivate @java.lang.SuppressWarnings(\"unchecked\") android.car.vms.Set<java.lang.Integer> unparcelProviderIds(android.os.Parcel)\npublic @java.lang.Deprecated @android.annotation.NonNull android.car.vms.Set<java.lang.Integer> getPublisherIds()\nclass VmsAssociatedLayer extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genAidl=true, genEqualsHashCode=true, genToString=true)")
+    @Deprecated
+    private void __metadata() {}
+
+
+    //@formatter:on
+    // End of generated code
+
 }
diff --git a/car-lib/src/android/car/vms/VmsAvailableLayers.java b/car-lib/src/android/car/vms/VmsAvailableLayers.java
index 3852a7f..cfd55bc 100644
--- a/car-lib/src/android/car/vms/VmsAvailableLayers.java
+++ b/car-lib/src/android/car/vms/VmsAvailableLayers.java
@@ -20,12 +20,11 @@
 import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.util.ArraySet;
 
-import java.util.ArrayList;
+import com.android.internal.util.DataClass;
+
 import java.util.Collections;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Objects;
 import java.util.Set;
 
 /**
@@ -42,88 +41,204 @@
  * @hide
  */
 @SystemApi
+@DataClass(genAidl = true, genEqualsHashCode = true, genToString = true)
 public final class VmsAvailableLayers implements Parcelable {
-
-    // A sequence number.
-    private final int mSeq;
-
-    // The list of AssociatedLayers
-    private final Set<VmsAssociatedLayer> mAssociatedLayers;
+    /**
+     * Sequence number of the availability state
+     */
+    private final int mSequenceNumber;
 
     /**
-     * Constructs a new layer availability.
+     * Set of layers available for subscription
+     */
+    private @NonNull Set<VmsAssociatedLayer> mAssociatedLayers;
+
+    private void onConstructed() {
+        mAssociatedLayers = Collections.unmodifiableSet(mAssociatedLayers);
+    }
+
+    private void parcelAssociatedLayers(Parcel dest, int flags) {
+        dest.writeArraySet(new ArraySet<>(mAssociatedLayers));
+    }
+
+    @SuppressWarnings("unchecked")
+    private Set<VmsAssociatedLayer> unparcelAssociatedLayers(Parcel in) {
+        return (Set<VmsAssociatedLayer>) in.readArraySet(VmsAssociatedLayer.class.getClassLoader());
+    }
+
+    /**
+     * Creates a new VmsAvailableLayers.
      *
-     * @param associatedLayers set of layers available for subscription
-     * @param sequence         sequence number of the availability state
+     * @param associatedLayers
+     *   Set of layers available for subscription
+     * @param sequenceNumber
+     *   Sequence number of the availability state
+     * @deprecated Use {@link #VmsAvailableLayers(int, Set)} instead
      */
-    public VmsAvailableLayers(@NonNull Set<VmsAssociatedLayer> associatedLayers, int sequence) {
-        mSeq = sequence;
-        mAssociatedLayers = Collections.unmodifiableSet(associatedLayers);
+    @Deprecated
+    public VmsAvailableLayers(@NonNull Set<VmsAssociatedLayer> associatedLayers,
+            int sequenceNumber) {
+        this(sequenceNumber, associatedLayers);
     }
 
     /**
-     * @return sequence number of the availability state
+     * Sequence number of the availability state
+     *
+     * @deprecated Use {@link #getSequenceNumber()} instead
      */
+    @Deprecated
     public int getSequence() {
-        return mSeq;
+        return mSequenceNumber;
+    }
+
+
+
+    // Code below generated by codegen v1.0.14.
+    //
+    // DO NOT MODIFY!
+    // CHECKSTYLE:OFF Generated code
+    //
+    // To regenerate run:
+    // $ codegen $ANDROID_BUILD_TOP/packages/services/Car/car-lib/src/android/car/vms/VmsAvailableLayers.java
+    //
+    // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+    //   Settings > Editor > Code Style > Formatter Control
+    //@formatter:off
+
+
+    /**
+     * Creates a new VmsAvailableLayers.
+     *
+     * @param sequenceNumber
+     *   Sequence number of the availability state
+     * @param associatedLayers
+     *   Set of layers available for subscription
+     */
+    @DataClass.Generated.Member
+    public VmsAvailableLayers(
+            int sequenceNumber,
+            @NonNull Set<VmsAssociatedLayer> associatedLayers) {
+        this.mSequenceNumber = sequenceNumber;
+        this.mAssociatedLayers = associatedLayers;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mAssociatedLayers);
+
+        onConstructed();
     }
 
     /**
-     * @return set of layers available for subscription
+     * Sequence number of the availability state
      */
-    @NonNull
-    public Set<VmsAssociatedLayer> getAssociatedLayers() {
+    @DataClass.Generated.Member
+    public int getSequenceNumber() {
+        return mSequenceNumber;
+    }
+
+    /**
+     * Set of layers available for subscription
+     */
+    @DataClass.Generated.Member
+    public @NonNull Set<VmsAssociatedLayer> getAssociatedLayers() {
         return mAssociatedLayers;
     }
 
     @Override
+    @DataClass.Generated.Member
     public String toString() {
-        return "VmsAvailableLayers{ seq: " +
-                mSeq +
-                ", AssociatedLayers: " +
-                mAssociatedLayers +
-                "}";
-    }
+        // You can override field toString logic by defining methods like:
+        // String fieldNameToString() { ... }
 
-
-    // Parcelable related methods.
-    public static final Parcelable.Creator<VmsAvailableLayers> CREATOR = new
-            Parcelable.Creator<VmsAvailableLayers>() {
-                public VmsAvailableLayers createFromParcel(Parcel in) {
-                    return new VmsAvailableLayers(in);
-                }
-
-                public VmsAvailableLayers[] newArray(int size) {
-                    return new VmsAvailableLayers[size];
-                }
-            };
-
-    @Override
-    public void writeToParcel(Parcel out, int flags) {
-        out.writeInt(mSeq);
-        out.writeParcelableList(new ArrayList(mAssociatedLayers), flags);
+        return "VmsAvailableLayers { " +
+                "sequenceNumber = " + mSequenceNumber + ", " +
+                "associatedLayers = " + mAssociatedLayers +
+        " }";
     }
 
     @Override
-    public boolean equals(Object o) {
-        if (!(o instanceof VmsAvailableLayers)) {
-            return false;
+    @DataClass.Generated.Member
+    public boolean equals(@android.annotation.Nullable Object o) {
+        // You can override field equality logic by defining either of the methods like:
+        // boolean fieldNameEquals(VmsAvailableLayers other) { ... }
+        // boolean fieldNameEquals(FieldType otherValue) { ... }
+
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        @SuppressWarnings("unchecked")
+        VmsAvailableLayers that = (VmsAvailableLayers) o;
+        //noinspection PointlessBooleanExpression
+        return true
+                && mSequenceNumber == that.mSequenceNumber
+                && java.util.Objects.equals(mAssociatedLayers, that.mAssociatedLayers);
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public int hashCode() {
+        // You can override field hashCode logic by defining methods like:
+        // int fieldNameHashCode() { ... }
+
+        int _hash = 1;
+        _hash = 31 * _hash + mSequenceNumber;
+        _hash = 31 * _hash + java.util.Objects.hashCode(mAssociatedLayers);
+        return _hash;
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        // You can override field parcelling by defining methods like:
+        // void parcelFieldName(Parcel dest, int flags) { ... }
+
+        dest.writeInt(mSequenceNumber);
+        parcelAssociatedLayers(dest, flags);
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public int describeContents() { return 0; }
+
+    /** @hide */
+    @SuppressWarnings({"unchecked", "RedundantCast"})
+    @DataClass.Generated.Member
+    /* package-private */ VmsAvailableLayers(@NonNull Parcel in) {
+        // You can override field unparcelling by defining methods like:
+        // static FieldType unparcelFieldName(Parcel in) { ... }
+
+        int sequenceNumber = in.readInt();
+        Set<VmsAssociatedLayer> associatedLayers = unparcelAssociatedLayers(in);
+
+        this.mSequenceNumber = sequenceNumber;
+        this.mAssociatedLayers = associatedLayers;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mAssociatedLayers);
+
+        onConstructed();
+    }
+
+    @DataClass.Generated.Member
+    public static final @NonNull Parcelable.Creator<VmsAvailableLayers> CREATOR
+            = new Parcelable.Creator<VmsAvailableLayers>() {
+        @Override
+        public VmsAvailableLayers[] newArray(int size) {
+            return new VmsAvailableLayers[size];
         }
-        VmsAvailableLayers p = (VmsAvailableLayers) o;
-        return Objects.equals(p.mAssociatedLayers, mAssociatedLayers) &&
-                p.mSeq == mSeq;
-    }
 
-    @Override
-    public int describeContents() {
-        return 0;
-    }
+        @Override
+        public VmsAvailableLayers createFromParcel(@NonNull Parcel in) {
+            return new VmsAvailableLayers(in);
+        }
+    };
 
-    private VmsAvailableLayers(Parcel in) {
-        mSeq = in.readInt();
+    @DataClass.Generated(
+            time = 1582066089314L,
+            codegenVersion = "1.0.14",
+            sourceFile = "packages/services/Car/car-lib/src/android/car/vms/VmsAvailableLayers.java",
+            inputSignatures = "private final  int mSequenceNumber\nprivate @android.annotation.NonNull java.util.Set<android.car.vms.VmsAssociatedLayer> mAssociatedLayers\nprivate  void onConstructed()\nprivate  void parcelAssociatedLayers(android.os.Parcel,int)\nprivate @java.lang.SuppressWarnings(\"unchecked\") java.util.Set<android.car.vms.VmsAssociatedLayer> unparcelAssociatedLayers(android.os.Parcel)\npublic @java.lang.Deprecated int getSequence()\nclass VmsAvailableLayers extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genAidl=true, genEqualsHashCode=true, genToString=true)")
+    @Deprecated
+    private void __metadata() {}
 
-        List<VmsAssociatedLayer> associatedLayers = new ArrayList<>();
-        in.readParcelableList(associatedLayers, VmsAssociatedLayer.class.getClassLoader());
-        mAssociatedLayers = Collections.unmodifiableSet(new HashSet(associatedLayers));
-    }
-}
\ No newline at end of file
+
+    //@formatter:on
+    // End of generated code
+
+}
diff --git a/car-lib/src/android/car/vms/VmsClient.java b/car-lib/src/android/car/vms/VmsClient.java
new file mode 100644
index 0000000..b580ced
--- /dev/null
+++ b/car-lib/src/android/car/vms/VmsClient.java
@@ -0,0 +1,335 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.car.vms;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.car.Car;
+import android.car.vms.VmsClientManager.VmsClientCallback;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.Executor;
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
+
+/**
+ * API implementation for use by Vehicle Map Service clients.
+ *
+ * This API can be obtained by registering a callback with {@link VmsClientManager}.
+ *
+ * @hide
+ */
+@SystemApi
+public final class VmsClient {
+    private static final boolean DBG = false;
+    private static final String TAG = VmsClient.class.getSimpleName();
+
+    private static final VmsAvailableLayers DEFAULT_AVAILABLE_LAYERS =
+            new VmsAvailableLayers(Collections.emptySet(), 0);
+    private static final VmsSubscriptionState DEFAULT_SUBSCRIPTIONS =
+            new VmsSubscriptionState(0, Collections.emptySet(), Collections.emptySet());
+
+    private final IVmsBrokerService mService;
+    private final Executor mExecutor;
+    private final VmsClientCallback mCallback;
+    private final boolean mLegacyClient;
+    private final Consumer<RemoteException> mExceptionHandler;
+    private final IBinder mClientToken;
+
+    private final Object mLock = new Object();
+    @GuardedBy("mLock")
+    private VmsAvailableLayers mAvailableLayers = DEFAULT_AVAILABLE_LAYERS;
+    @GuardedBy("mLock")
+    private VmsSubscriptionState mSubscriptionState = DEFAULT_SUBSCRIPTIONS;
+    @GuardedBy("mLock")
+    private boolean mMonitoringEnabled;
+
+    /**
+     * @hide
+     */
+    public VmsClient(IVmsBrokerService service, Executor executor, VmsClientCallback callback,
+            boolean legacyClient, Consumer<RemoteException> exceptionHandler) {
+        mService = service;
+        mExecutor = executor;
+        mCallback = callback;
+        mLegacyClient = legacyClient;
+        mExceptionHandler = exceptionHandler;
+        mClientToken = new Binder();
+    }
+
+    /**
+     * Retrieves registered information about a Vehicle Map Service data provider.
+     *
+     * <p>Data layers may be associated with multiple providers in updates received via
+     * {@link VmsClientCallback#onLayerAvailabilityChanged(VmsAvailableLayers)}, and clients can use
+     * this query to determine a provider or subset of providers to subscribe to.
+     *
+     * @param providerId Provider ID
+     * @return Provider's registration information, or null if unavailable
+     */
+    @Nullable
+    @RequiresPermission(anyOf = {Car.PERMISSION_VMS_PUBLISHER, Car.PERMISSION_VMS_SUBSCRIBER})
+    public byte[] getProviderDescription(int providerId) {
+        if (DBG) Log.d(TAG, "Getting provider information for " + providerId);
+        try {
+            return mService.getProviderInfo(mClientToken, providerId).getDescription();
+        } catch (RemoteException e) {
+            Log.e(TAG, "While getting publisher information for " + providerId, e);
+            mExceptionHandler.accept(e);
+            return null;
+        }
+    }
+
+    /**
+     * Sets this client's data layer subscriptions
+     *
+     * <p>Existing subscriptions for the client will be replaced.
+     *
+     * @param layers Data layers to be subscribed
+     */
+    @RequiresPermission(Car.PERMISSION_VMS_SUBSCRIBER)
+    public void setSubscriptions(@NonNull Set<VmsAssociatedLayer> layers) {
+        if (DBG) Log.d(TAG, "Setting subscriptions to " + layers);
+        try {
+            mService.setSubscriptions(mClientToken, new ArrayList<>(layers));
+        } catch (RemoteException e) {
+            Log.e(TAG, "While setting subscriptions", e);
+            mExceptionHandler.accept(e);
+        }
+    }
+
+    /**
+     * Enables the monitoring of Vehicle Map Service packets by this client.
+     *
+     * <p>If monitoring is enabled, the client will receive all packets, regardless of any
+     * subscriptions. Enabling monitoring does not affect the client's existing subscriptions.
+     */
+    @RequiresPermission(Car.PERMISSION_VMS_SUBSCRIBER)
+    public void setMonitoringEnabled(boolean enabled) {
+        if (DBG) Log.d(TAG, "Setting monitoring state to " + enabled);
+        try {
+            mService.setMonitoringEnabled(mClientToken, enabled);
+            synchronized (mLock) {
+                mMonitoringEnabled = enabled;
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "While setting monitoring state to " + enabled, e);
+            mExceptionHandler.accept(e);
+        }
+    }
+
+    /**
+     * Disables the monitoring of Vehicle Map Service packets by this client.
+     *
+     * <p>If monitoring is disabled, this client will receive only packets for its subscriptions.
+     * Disabling monitoring does not affect the client's existing subscriptions.
+     */
+    @RequiresPermission(Car.PERMISSION_VMS_SUBSCRIBER)
+    public boolean isMonitoringEnabled() {
+        synchronized (mLock) {
+            return mMonitoringEnabled;
+        }
+    }
+
+    /**
+     * Returns the most recently received data layer availability.
+     */
+    @NonNull
+    @RequiresPermission(anyOf = {Car.PERMISSION_VMS_PUBLISHER, Car.PERMISSION_VMS_SUBSCRIBER})
+    public VmsAvailableLayers getAvailableLayers() {
+        synchronized (mLock) {
+            return mAvailableLayers;
+        }
+    }
+
+    /**
+     * Registers a data provider with the Vehicle Map Service.
+     *
+     * @param providerDescription Identifying information about the data provider
+     * @return Provider ID to use for setting offerings and publishing packets, or -1 on
+     * connection error
+     */
+    @RequiresPermission(Car.PERMISSION_VMS_PUBLISHER)
+    public int registerProvider(@NonNull byte[] providerDescription) {
+        if (DBG) Log.d(TAG, "Registering provider");
+        Objects.requireNonNull(providerDescription, "providerDescription cannot be null");
+        try {
+            return mService.registerProvider(mClientToken,
+                    new VmsProviderInfo(providerDescription));
+        } catch (RemoteException e) {
+            Log.e(TAG, "While registering provider", e);
+            mExceptionHandler.accept(e);
+            return -1;
+        }
+    }
+
+    /**
+     * Unregisters a data provider with the Vehicle Map Service.
+     *
+     * @param providerId Provider ID
+     */
+    @RequiresPermission(Car.PERMISSION_VMS_PUBLISHER)
+    public void unregisterProvider(int providerId) {
+        if (DBG) Log.d(TAG, "Unregistering provider");
+        try {
+            setProviderOfferings(providerId, Collections.emptySet());
+        } catch (IllegalArgumentException e) {
+            Log.e(TAG, "While unregistering provider " + providerId, e);
+        }
+    }
+
+    /**
+     * Sets the data layer offerings for a provider registered through
+     * {@link #registerProvider(byte[])}.
+     *
+     * <p>Existing offerings for the provider ID will be replaced.
+     *
+     * @param providerId Provider ID
+     * @param offerings  Data layer offerings
+     * @throws IllegalArgumentException if the client has not registered the provider
+     */
+    @RequiresPermission(Car.PERMISSION_VMS_PUBLISHER)
+    public void setProviderOfferings(int providerId, @NonNull Set<VmsLayerDependency> offerings) {
+        if (DBG) Log.d(TAG, "Setting provider offerings for " + providerId);
+        Objects.requireNonNull(offerings, "offerings cannot be null");
+        try {
+            mService.setProviderOfferings(mClientToken, providerId, new ArrayList<>(offerings));
+        } catch (RemoteException e) {
+            Log.e(TAG, "While setting provider offerings for " + providerId, e);
+            mExceptionHandler.accept(e);
+        }
+    }
+
+    /**
+     * Publishes a Vehicle Maps Service packet.
+     *
+     * @param providerId Packet provider
+     * @param layer      Packet layer
+     * @param packet     Packet data
+     * @throws IllegalArgumentException if the client does not offer the layer as the provider
+     */
+    @RequiresPermission(Car.PERMISSION_VMS_PUBLISHER)
+    public void publishPacket(int providerId, @NonNull VmsLayer layer, @NonNull byte[] packet) {
+        if (DBG) Log.d(TAG, "Publishing packet as " + providerId);
+        Objects.requireNonNull(layer, "layer cannot be null");
+        Objects.requireNonNull(packet, "packet cannot be null");
+        try {
+            mService.publishPacket(mClientToken, providerId, layer, packet);
+        } catch (RemoteException e) {
+            Log.e(TAG, "While publishing packet as " + providerId);
+            mExceptionHandler.accept(e);
+        }
+    }
+
+    /**
+     * Returns the most recently received data layer subscription state.
+     */
+    @NonNull
+    @RequiresPermission(anyOf = {Car.PERMISSION_VMS_PUBLISHER, Car.PERMISSION_VMS_SUBSCRIBER})
+    public VmsSubscriptionState getSubscriptionState() {
+        synchronized (mLock) {
+            return mSubscriptionState;
+        }
+    }
+
+    /**
+     * Registers this client with the Vehicle Map Service.
+     *
+     * @hide
+     */
+    public void register() throws RemoteException {
+        VmsRegistrationInfo registrationInfo = mService.registerClient(mClientToken,
+                new IVmsClientCallbackImpl(this), mLegacyClient);
+        synchronized (mLock) {
+            mAvailableLayers = registrationInfo.getAvailableLayers();
+            mSubscriptionState = registrationInfo.getSubscriptionState();
+        }
+    }
+
+    /**
+     * Unregisters this client from the Vehicle Map Service.
+     *
+     * @hide
+     */
+    public void unregister() throws RemoteException {
+        mService.unregisterClient(mClientToken);
+    }
+
+    private static class IVmsClientCallbackImpl extends IVmsClientCallback.Stub {
+        private final WeakReference<VmsClient> mClient;
+
+        private IVmsClientCallbackImpl(VmsClient client) {
+            mClient = new WeakReference<>(client);
+        }
+
+        @Override
+        public void onLayerAvailabilityChanged(VmsAvailableLayers availableLayers) {
+            if (DBG) Log.d(TAG, "Received new layer availability: " + availableLayers);
+            executeCallback((client, callback) -> {
+                synchronized (client.mLock) {
+                    client.mAvailableLayers = availableLayers;
+                }
+                callback.onLayerAvailabilityChanged(availableLayers);
+            });
+        }
+
+        @Override
+        public void onSubscriptionStateChanged(VmsSubscriptionState subscriptionState) {
+            if (DBG) Log.d(TAG, "Received new subscription state: " + subscriptionState);
+            executeCallback((client, callback) -> {
+                synchronized (client.mLock) {
+                    client.mSubscriptionState = subscriptionState;
+                }
+                callback.onSubscriptionStateChanged(subscriptionState);
+            });
+        }
+
+        @Override
+        public void onPacketReceived(int providerId, VmsLayer layer, byte[] packet) {
+            if (DBG) Log.d(TAG, "Received packet from " + providerId + " for: " + layer);
+            executeCallback((client, callback) ->
+                    callback.onPacketReceived(providerId, layer, packet));
+        }
+
+        private void executeCallback(BiConsumer<VmsClient, VmsClientCallback> callbackOperation) {
+            final VmsClient client = mClient.get();
+            if (client == null) {
+                Log.w(TAG, "VmsClient unavailable");
+                return;
+            }
+
+            long token = Binder.clearCallingIdentity();
+            try {
+                client.mExecutor.execute(() -> callbackOperation.accept(client, client.mCallback));
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+    }
+}
diff --git a/car-lib/src/android/car/vms/VmsClientManager.java b/car-lib/src/android/car/vms/VmsClientManager.java
new file mode 100644
index 0000000..c6f8be8
--- /dev/null
+++ b/car-lib/src/android/car/vms/VmsClientManager.java
@@ -0,0 +1,193 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.car.vms;
+
+import android.annotation.CallbackExecutor;
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.car.Car;
+import android.car.CarManagerBase;
+import android.car.annotation.RequiredFeature;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.ArrayMap;
+import android.util.Log;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.Executor;
+
+/**
+ * Car Service manager for connecting clients to the Vehicle Map Service.
+ *
+ * @hide
+ */
+@RequiredFeature(Car.VEHICLE_MAP_SERVICE)
+@SystemApi
+public final class VmsClientManager extends CarManagerBase {
+    private static final boolean DBG = false;
+    private static final String TAG = VmsClientManager.class.getSimpleName();
+
+    /**
+     * Callback interface for Vehicle Map Service clients.
+     */
+    public interface VmsClientCallback {
+        /**
+         * Invoked when a Vehicle Map Service connection has been established for the callback.
+         *
+         * @param client API client
+         */
+        void onClientConnected(@NonNull VmsClient client);
+
+        /**
+         * Invoked when the availability of data layers has changed.
+         *
+         * @param availableLayers Current layer availability
+         */
+        void onLayerAvailabilityChanged(@NonNull VmsAvailableLayers availableLayers);
+
+        /**
+         * Invoked when any subscriptions to data layers have changed.
+         *
+         * @param subscriptionState Current subscription state
+         */
+        void onSubscriptionStateChanged(@NonNull VmsSubscriptionState subscriptionState);
+
+        /**
+         * Invoked whenever a packet is received for this client's subscriptions.
+         *
+         * @param providerId  Packet provider
+         * @param layer       Packet layer
+         * @param packet      Packet data
+         */
+        void onPacketReceived(int providerId, @NonNull VmsLayer layer, @NonNull byte[] packet);
+    }
+
+    private final IVmsBrokerService mBrokerService;
+
+    private final Object mLock = new Object();
+    @GuardedBy("mLock")
+    private final Map<VmsClientCallback, VmsClient> mClients = new ArrayMap<>();
+
+    /**
+     * @hide
+     */
+    public VmsClientManager(Car car, IBinder service) {
+        super(car);
+        mBrokerService = IVmsBrokerService.Stub.asInterface(service);
+    }
+
+    /**
+     * Registers new Vehicle Map Service client for the given callback.
+     *
+     * If the callback is already registered, no action is taken.
+     *
+     * @param executor Executor to run callback operations
+     * @param callback Callback to register for new client
+     */
+    @RequiresPermission(anyOf = {Car.PERMISSION_VMS_PUBLISHER, Car.PERMISSION_VMS_SUBSCRIBER})
+    public void registerVmsClientCallback(
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull VmsClientCallback callback) {
+        registerVmsClientCallback(executor, callback, false);
+    }
+
+    /**
+     * @hide
+     */
+    @RequiresPermission(anyOf = {Car.PERMISSION_VMS_PUBLISHER, Car.PERMISSION_VMS_SUBSCRIBER})
+    void registerVmsClientCallback(
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull VmsClientCallback callback,
+            boolean legacyClient) {
+        Objects.requireNonNull(executor, "executor cannot be null");
+        Objects.requireNonNull(callback, "callback cannot be null");
+        VmsClient client;
+        synchronized (mLock) {
+            if (mClients.containsKey(callback)) {
+                Log.w(TAG, "VmsClient already registered");
+                return;
+            }
+
+            client = new VmsClient(mBrokerService, executor, callback, legacyClient,
+                    this::handleRemoteExceptionFromCarService);
+            mClients.put(callback, client);
+            if (DBG) Log.d(TAG, "Client count: " + mClients.size());
+        }
+
+        try {
+            if (DBG) Log.d(TAG, "Registering VmsClient");
+            client.register();
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error while registering", e);
+            synchronized (mLock) {
+                mClients.remove(callback);
+            }
+            handleRemoteExceptionFromCarService(e);
+            return;
+        }
+
+        if (DBG) Log.d(TAG, "Triggering callbacks for new VmsClient");
+        executor.execute(() -> {
+            callback.onClientConnected(client);
+            if (!legacyClient) {
+                callback.onLayerAvailabilityChanged(client.getAvailableLayers());
+                callback.onSubscriptionStateChanged(client.getSubscriptionState());
+            }
+        });
+    }
+
+    /**
+     * Unregisters the Vehicle Map Service client associated with the given callback.
+     *
+     * If the callback is not registered, no action is taken.
+     *
+     * @param callback
+     */
+    @RequiresPermission(anyOf = {Car.PERMISSION_VMS_PUBLISHER, Car.PERMISSION_VMS_SUBSCRIBER})
+    public void unregisterVmsClientCallback(@NonNull VmsClientCallback callback) {
+        VmsClient client;
+        synchronized (mLock) {
+            client = mClients.remove(callback);
+        }
+        if (client == null) {
+            Log.w(TAG, "Unregister called for unknown callback");
+            return;
+        }
+
+        if (DBG) Log.d(TAG, "Unregistering VmsClient");
+        try {
+            client.unregister();
+        } catch (RemoteException e) {
+            handleRemoteExceptionFromCarService(e);
+        }
+    }
+
+    /**
+     * @hide
+     */
+    @Override
+    protected void onCarDisconnected() {
+        synchronized (mLock) {
+            Log.w(TAG, "Car disconnected with " + mClients.size() + " active clients");
+            mClients.clear();
+        }
+    }
+}
diff --git a/car-lib/src/android/car/vms/VmsLayer.java b/car-lib/src/android/car/vms/VmsLayer.java
index fb811fa..ebb068f 100644
--- a/car-lib/src/android/car/vms/VmsLayer.java
+++ b/car-lib/src/android/car/vms/VmsLayer.java
@@ -20,7 +20,7 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 
-import java.util.Objects;
+import com.android.internal.util.DataClass;
 
 /**
  * A Vehicle Map Service layer, which can be offered or subscribed to by clients.
@@ -40,97 +40,195 @@
  * @hide
  */
 @SystemApi
+@DataClass(genAidl = true, genEqualsHashCode = true, genToString = true)
 public final class VmsLayer implements Parcelable {
+    /**
+     * Type of data published on the layer
+     **/
     private int mType;
-    private int mSubtype;
+
+    /**
+     * Type of packet published on the layer
+     */
+    private int mChannel;
+
+    /**
+     * Major version of layer packet format
+     */
     private int mVersion;
 
     /**
-     * Constructs a new layer definition.
+     * Type of packet published on the layer
      *
-     * @param type    type of data published on the layer
-     * @param subtype type of packet published on the layer
-     * @param version major version of layer packet format
+     * @deprecated Use {@link #getChannel()} instead
      */
-    public VmsLayer(int type, int subtype, int version) {
-        mType = type;
-        mSubtype = subtype;
-        mVersion = version;
+    @Deprecated
+    public int getSubtype() {
+        return mChannel;
+    }
+
+
+
+    // Code below generated by codegen v1.0.14.
+    //
+    // DO NOT MODIFY!
+    // CHECKSTYLE:OFF Generated code
+    //
+    // To regenerate run:
+    // $ codegen $ANDROID_BUILD_TOP/packages/services/Car/car-lib/src/android/car/vms/VmsLayer.java
+    //
+    // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+    //   Settings > Editor > Code Style > Formatter Control
+    //@formatter:off
+
+
+    /**
+     * Creates a new VmsLayer.
+     *
+     * @param type
+     *   Type of data published on the layer
+     * @param channel
+     *   Type of packet published on the layer
+     * @param version
+     *   Major version of layer packet format
+     */
+    @DataClass.Generated.Member
+    public VmsLayer(
+            int type,
+            int channel,
+            int version) {
+        this.mType = type;
+        this.mChannel = channel;
+        this.mVersion = version;
+
+        // onConstructed(); // You can define this method to get a callback
     }
 
     /**
-     * @return type of data published on the layer
+     * Type of data published on the layer
      */
+    @DataClass.Generated.Member
     public int getType() {
         return mType;
     }
 
     /**
-     * @return type of packet published on the layer
+     * Type of packet published on the layer
      */
-    public int getSubtype() {
-        return mSubtype;
+    @DataClass.Generated.Member
+    public int getChannel() {
+        return mChannel;
     }
 
     /**
-     * @return major version of layer packet format
+     * Major version of layer packet format
      */
+    @DataClass.Generated.Member
     public int getVersion() {
         return mVersion;
     }
 
     @Override
-    public boolean equals(Object o) {
-        if (!(o instanceof VmsLayer)) {
-            return false;
-        }
-        VmsLayer p = (VmsLayer) o;
-        return Objects.equals(p.mType, mType)
-                && Objects.equals(p.mSubtype, mSubtype)
-                && Objects.equals(p.mVersion, mVersion);
-    }
-
-    @Override
-    public int hashCode() {
-        return Objects.hash(mType, mSubtype, mVersion);
-    }
-
-    @Override
+    @DataClass.Generated.Member
     public String toString() {
-        return "VmsLayer{ Type: " + mType + ", Sub type: " + mSubtype + ", Version: " + mVersion
-                + "}";
-    }
+        // You can override field toString logic by defining methods like:
+        // String fieldNameToString() { ... }
 
-    public static final Parcelable.Creator<VmsLayer> CREATOR = new
-            Parcelable.Creator<VmsLayer>() {
-                public VmsLayer createFromParcel(Parcel in) {
-                    return new VmsLayer(in);
-                }
-
-                public VmsLayer[] newArray(int size) {
-                    return new VmsLayer[size];
-                }
-            };
-
-    @Override
-    public void writeToParcel(Parcel out, int flags) {
-        out.writeInt(mType);
-        out.writeInt(mSubtype);
-        out.writeInt(mVersion);
+        return "VmsLayer { " +
+                "type = " + mType + ", " +
+                "channel = " + mChannel + ", " +
+                "version = " + mVersion +
+        " }";
     }
 
     @Override
-    public int describeContents() {
-        return 0;
+    @DataClass.Generated.Member
+    public boolean equals(@android.annotation.Nullable Object o) {
+        // You can override field equality logic by defining either of the methods like:
+        // boolean fieldNameEquals(VmsLayer other) { ... }
+        // boolean fieldNameEquals(FieldType otherValue) { ... }
+
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        @SuppressWarnings("unchecked")
+        VmsLayer that = (VmsLayer) o;
+        //noinspection PointlessBooleanExpression
+        return true
+                && mType == that.mType
+                && mChannel == that.mChannel
+                && mVersion == that.mVersion;
     }
 
-    private VmsLayer(Parcel in) {
-        readFromParcel(in);
+    @Override
+    @DataClass.Generated.Member
+    public int hashCode() {
+        // You can override field hashCode logic by defining methods like:
+        // int fieldNameHashCode() { ... }
+
+        int _hash = 1;
+        _hash = 31 * _hash + mType;
+        _hash = 31 * _hash + mChannel;
+        _hash = 31 * _hash + mVersion;
+        return _hash;
     }
 
-    private void readFromParcel(Parcel in) {
-        mType = in.readInt();
-        mSubtype = in.readInt();
-        mVersion = in.readInt();
+    @Override
+    @DataClass.Generated.Member
+    public void writeToParcel(@android.annotation.NonNull Parcel dest, int flags) {
+        // You can override field parcelling by defining methods like:
+        // void parcelFieldName(Parcel dest, int flags) { ... }
+
+        dest.writeInt(mType);
+        dest.writeInt(mChannel);
+        dest.writeInt(mVersion);
     }
-}
\ No newline at end of file
+
+    @Override
+    @DataClass.Generated.Member
+    public int describeContents() { return 0; }
+
+    /** @hide */
+    @SuppressWarnings({"unchecked", "RedundantCast"})
+    @DataClass.Generated.Member
+    /* package-private */ VmsLayer(@android.annotation.NonNull Parcel in) {
+        // You can override field unparcelling by defining methods like:
+        // static FieldType unparcelFieldName(Parcel in) { ... }
+
+        int type = in.readInt();
+        int channel = in.readInt();
+        int version = in.readInt();
+
+        this.mType = type;
+        this.mChannel = channel;
+        this.mVersion = version;
+
+        // onConstructed(); // You can define this method to get a callback
+    }
+
+    @DataClass.Generated.Member
+    public static final @android.annotation.NonNull Parcelable.Creator<VmsLayer> CREATOR
+            = new Parcelable.Creator<VmsLayer>() {
+        @Override
+        public VmsLayer[] newArray(int size) {
+            return new VmsLayer[size];
+        }
+
+        @Override
+        public VmsLayer createFromParcel(@android.annotation.NonNull Parcel in) {
+            return new VmsLayer(in);
+        }
+    };
+
+    @DataClass.Generated(
+            time = 1582065881190L,
+            codegenVersion = "1.0.14",
+            sourceFile = "packages/services/Car/car-lib/src/android/car/vms/VmsLayer.java",
+            inputSignatures = "private  int mType\nprivate  int mChannel\nprivate  int mVersion\npublic @java.lang.Deprecated int getSubtype()\nclass VmsLayer extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genAidl=true, genEqualsHashCode=true, genToString=true)")
+    @Deprecated
+    private void __metadata() {}
+
+
+    //@formatter:on
+    // End of generated code
+
+}
diff --git a/car-lib/src/android/car/vms/VmsLayerDependency.java b/car-lib/src/android/car/vms/VmsLayerDependency.java
index 8cf8722..06d2617 100644
--- a/car-lib/src/android/car/vms/VmsLayerDependency.java
+++ b/car-lib/src/android/car/vms/VmsLayerDependency.java
@@ -20,13 +20,11 @@
 import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.util.ArraySet;
 
-import com.android.internal.util.Preconditions;
+import com.android.internal.util.DataClass;
 
-import java.util.ArrayList;
 import java.util.Collections;
-import java.util.HashSet;
-import java.util.List;
 import java.util.Objects;
 import java.util.Set;
 
@@ -39,91 +37,193 @@
  * @hide
  */
 @SystemApi
+@DataClass(genAidl = true, genEqualsHashCode = true, genToString = true)
 public final class VmsLayerDependency implements Parcelable {
-    private final VmsLayer mLayer;
-    private final Set<VmsLayer> mDependency;
+    /**
+     * Layer that has dependencies
+     */
+    private final @NonNull VmsLayer mLayer;
 
     /**
-     * Constructs a layer with a dependency on other layers.
-     *
-     * @param layer layer that has dependencies
-     * @param dependencies layers that the given layer depends on
+     * Layers that the given layer depends on
      */
-    public VmsLayerDependency(@NonNull VmsLayer layer, @NonNull Set<VmsLayer> dependencies) {
-        mLayer = Preconditions.checkNotNull(layer, "layer cannot be null");
-        mDependency = Collections.unmodifiableSet(dependencies);
+    private @NonNull Set<VmsLayer> mDependencies;
+
+    private void onConstructed() {
+        mDependencies = Collections.unmodifiableSet(mDependencies);
+    }
+
+    private void parcelDependencies(Parcel dest, int flags) {
+        dest.writeArraySet(new ArraySet<>(mDependencies));
+    }
+
+    @SuppressWarnings("unchecked")
+    private Set<VmsLayer> unparcelDependencies(Parcel in) {
+        return (Set<VmsLayer>) in.readArraySet(VmsLayer.class.getClassLoader());
     }
 
     /**
-     * Constructs a layer without dependencies.
+     * Creates a new VmsLayerDependency without dependencies.
      *
-     * @param layer layer that has no dependencies
+     * @param layer
+     *   Layer that has no dependencies
      */
     public VmsLayerDependency(@NonNull VmsLayer layer) {
         this(layer, Collections.emptySet());
     }
 
+
+
+    // Code below generated by codegen v1.0.14.
+    //
+    // DO NOT MODIFY!
+    // CHECKSTYLE:OFF Generated code
+    //
+    // To regenerate run:
+    // $ codegen $ANDROID_BUILD_TOP/packages/services/Car/car-lib/src/android/car/vms/VmsLayerDependency.java
+    //
+    // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+    //   Settings > Editor > Code Style > Formatter Control
+    //@formatter:off
+
+
     /**
-     * @return layer that has zero or more dependencies
+     * Creates a new VmsLayerDependency.
+     *
+     * @param layer
+     *   Layer that has dependencies
+     * @param dependencies
+     *   Layers that the given layer depends on
      */
-    @NonNull
-    public VmsLayer getLayer() {
+    @DataClass.Generated.Member
+    public VmsLayerDependency(
+            @NonNull VmsLayer layer,
+            @NonNull Set<VmsLayer> dependencies) {
+        this.mLayer = layer;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mLayer);
+        this.mDependencies = dependencies;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mDependencies);
+
+        onConstructed();
+    }
+
+    /**
+     * Layer that has dependencies
+     */
+    @DataClass.Generated.Member
+    public @NonNull VmsLayer getLayer() {
         return mLayer;
     }
 
     /**
-     * @return all layers that the layer depends on
+     * Layers that the given layer depends on
      */
-    @NonNull
-    public Set<VmsLayer> getDependencies() {
-        return mDependency;
+    @DataClass.Generated.Member
+    public @NonNull Set<VmsLayer> getDependencies() {
+        return mDependencies;
     }
 
-    public static final Parcelable.Creator<VmsLayerDependency> CREATOR = new
-            Parcelable.Creator<VmsLayerDependency>() {
-                public VmsLayerDependency createFromParcel(Parcel in) {
-                    return new VmsLayerDependency(in);
-                }
-
-                public VmsLayerDependency[] newArray(int size) {
-                    return new VmsLayerDependency[size];
-                }
-            };
-
     @Override
+    @DataClass.Generated.Member
     public String toString() {
-        return "VmsLayerDependency{ Layer: " + mLayer + " Dependency: " + mDependency + "}";
+        // You can override field toString logic by defining methods like:
+        // String fieldNameToString() { ... }
+
+        return "VmsLayerDependency { " +
+                "layer = " + mLayer + ", " +
+                "dependencies = " + mDependencies +
+        " }";
     }
 
     @Override
-    public boolean equals(Object o) {
-        if (!(o instanceof VmsLayerDependency)) {
-            return false;
-        }
-        VmsLayerDependency p = (VmsLayerDependency) o;
-        return Objects.equals(p.mLayer, mLayer) && p.mDependency.equals(mDependency);
+    @DataClass.Generated.Member
+    public boolean equals(@android.annotation.Nullable Object o) {
+        // You can override field equality logic by defining either of the methods like:
+        // boolean fieldNameEquals(VmsLayerDependency other) { ... }
+        // boolean fieldNameEquals(FieldType otherValue) { ... }
+
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        @SuppressWarnings("unchecked")
+        VmsLayerDependency that = (VmsLayerDependency) o;
+        //noinspection PointlessBooleanExpression
+        return true
+                && Objects.equals(mLayer, that.mLayer)
+                && Objects.equals(mDependencies, that.mDependencies);
     }
 
     @Override
+    @DataClass.Generated.Member
     public int hashCode() {
-        return Objects.hash(mLayer, mDependency);
+        // You can override field hashCode logic by defining methods like:
+        // int fieldNameHashCode() { ... }
+
+        int _hash = 1;
+        _hash = 31 * _hash + Objects.hashCode(mLayer);
+        _hash = 31 * _hash + Objects.hashCode(mDependencies);
+        return _hash;
     }
 
     @Override
-    public void writeToParcel(Parcel out, int flags) {
-        out.writeParcelable(mLayer, flags);
-        out.writeParcelableList(new ArrayList<VmsLayer>(mDependency), flags);
+    @DataClass.Generated.Member
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        // You can override field parcelling by defining methods like:
+        // void parcelFieldName(Parcel dest, int flags) { ... }
+
+        dest.writeTypedObject(mLayer, flags);
+        parcelDependencies(dest, flags);
     }
 
     @Override
-    public int describeContents() {
-        return 0;
+    @DataClass.Generated.Member
+    public int describeContents() { return 0; }
+
+    /** @hide */
+    @SuppressWarnings({"unchecked", "RedundantCast"})
+    @DataClass.Generated.Member
+    /* package-private */ VmsLayerDependency(@NonNull Parcel in) {
+        // You can override field unparcelling by defining methods like:
+        // static FieldType unparcelFieldName(Parcel in) { ... }
+
+        VmsLayer layer = (VmsLayer) in.readTypedObject(VmsLayer.CREATOR);
+        Set<VmsLayer> dependencies = unparcelDependencies(in);
+
+        this.mLayer = layer;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mLayer);
+        this.mDependencies = dependencies;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mDependencies);
+
+        onConstructed();
     }
 
-    private VmsLayerDependency(Parcel in) {
-        mLayer = in.readParcelable(VmsLayer.class.getClassLoader());
-        List<VmsLayer> dependency = new ArrayList<>();
-        in.readParcelableList(dependency, VmsLayer.class.getClassLoader());
-        mDependency = Collections.unmodifiableSet(new HashSet<VmsLayer>(dependency));
-    }
+    @DataClass.Generated.Member
+    public static final @NonNull Parcelable.Creator<VmsLayerDependency> CREATOR
+            = new Parcelable.Creator<VmsLayerDependency>() {
+        @Override
+        public VmsLayerDependency[] newArray(int size) {
+            return new VmsLayerDependency[size];
+        }
+
+        @Override
+        public VmsLayerDependency createFromParcel(@NonNull Parcel in) {
+            return new VmsLayerDependency(in);
+        }
+    };
+
+    @DataClass.Generated(
+            time = 1582065875835L,
+            codegenVersion = "1.0.14",
+            sourceFile = "packages/services/Car/car-lib/src/android/car/vms/VmsLayerDependency.java",
+            inputSignatures = "private final @android.annotation.NonNull android.car.vms.VmsLayer mLayer\nprivate @android.annotation.NonNull java.util.Set<android.car.vms.VmsLayer> mDependencies\nprivate  void onConstructed()\nprivate  void parcelDependencies(android.os.Parcel,int)\nprivate @java.lang.SuppressWarnings(\"unchecked\") java.util.Set<android.car.vms.VmsLayer> unparcelDependencies(android.os.Parcel)\nclass VmsLayerDependency extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genAidl=true, genEqualsHashCode=true, genToString=true)")
+    @Deprecated
+    private void __metadata() {}
+
+
+    //@formatter:on
+    // End of generated code
+
 }
diff --git a/car-lib/src/android/car/vms/VmsLayersOffering.java b/car-lib/src/android/car/vms/VmsLayersOffering.java
index 9a79cdb..f201e39 100644
--- a/car-lib/src/android/car/vms/VmsLayersOffering.java
+++ b/car-lib/src/android/car/vms/VmsLayersOffering.java
@@ -20,12 +20,11 @@
 import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.util.ArraySet;
 
-import java.util.ArrayList;
+import com.android.internal.util.DataClass;
+
 import java.util.Collections;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Objects;
 import java.util.Set;
 
 /**
@@ -35,89 +34,185 @@
  *
  * A layer will not be advertised to subscribers unless all of its dependencies are met.
  *
+ * @deprecated Use {@link VmsClient#setProviderOfferings(int, Set)} instead
+ *
  * @hide
  */
+@Deprecated
 @SystemApi
+@DataClass(genAidl = true, genEqualsHashCode = true, genToString = true)
 public final class VmsLayersOffering implements Parcelable {
-
-    private final Set<VmsLayerDependency> mDependencies;
-
-    private final int mPublisherId;
+    /**
+     * Layers and dependencies in the offering
+     */
+    private @NonNull Set<VmsLayerDependency> mDependencies;
 
     /**
+     * ID of the publisher making the offering
+     */
+    private final int mPublisherId;
+
+    private void onConstructed() {
+        mDependencies = Collections.unmodifiableSet(mDependencies);
+    }
+
+    private void parcelDependencies(Parcel dest, int flags) {
+        dest.writeArraySet(new ArraySet<>(mDependencies));
+    }
+
+    @SuppressWarnings("unchecked")
+    private Set<VmsLayerDependency> unparcelDependencies(Parcel in) {
+        return (Set<VmsLayerDependency>) in.readArraySet(VmsLayerDependency.class.getClassLoader());
+    }
+
+
+
+    // Code below generated by codegen v1.0.14.
+    //
+    // DO NOT MODIFY!
+    // CHECKSTYLE:OFF Generated code
+    //
+    // To regenerate run:
+    // $ codegen $ANDROID_BUILD_TOP/packages/services/Car/car-lib/src/android/car/vms/VmsLayersOffering.java
+    //
+    // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+    //   Settings > Editor > Code Style > Formatter Control
+    //@formatter:off
+
+
+    /**
+     * Creates a new VmsLayersOffering.
      *
      * @param dependencies
+     *   Layers and dependencies in the offering
      * @param publisherId
+     *   ID of the publisher making the offering
      */
-    public VmsLayersOffering(@NonNull Set<VmsLayerDependency> dependencies, int publisherId) {
-        mDependencies = Collections.unmodifiableSet(dependencies);
-        mPublisherId = publisherId;
+    @DataClass.Generated.Member
+    public VmsLayersOffering(
+            @NonNull Set<VmsLayerDependency> dependencies,
+            int publisherId) {
+        this.mDependencies = dependencies;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mDependencies);
+        this.mPublisherId = publisherId;
+
+        onConstructed();
     }
 
     /**
-     * @return set of layer and dependencies in the offering
+     * Layers and dependencies in the offering
      */
-    public Set<VmsLayerDependency> getDependencies() {
+    @DataClass.Generated.Member
+    public @NonNull Set<VmsLayerDependency> getDependencies() {
         return mDependencies;
     }
 
     /**
-     * @return ID of the publisher making the offering
+     * ID of the publisher making the offering
      */
+    @DataClass.Generated.Member
     public int getPublisherId() {
         return mPublisherId;
     }
 
-    public static final Parcelable.Creator<VmsLayersOffering> CREATOR = new
-            Parcelable.Creator<VmsLayersOffering>() {
-                public VmsLayersOffering createFromParcel(Parcel in) {
-                    return new VmsLayersOffering(in);
-                }
-
-                public VmsLayersOffering[] newArray(int size) {
-                    return new VmsLayersOffering[size];
-                }
-            };
-
     @Override
+    @DataClass.Generated.Member
     public String toString() {
-        return "VmsLayersOffering{ Publisher: " +
-                mPublisherId +
-                " Dependencies: " +
-                mDependencies +
-                "}";
+        // You can override field toString logic by defining methods like:
+        // String fieldNameToString() { ... }
+
+        return "VmsLayersOffering { " +
+                "dependencies = " + mDependencies + ", " +
+                "publisherId = " + mPublisherId +
+        " }";
     }
 
     @Override
-    public void writeToParcel(Parcel out, int flags) {
-        out.writeParcelableList(new ArrayList<>(mDependencies), flags);
-        out.writeInt(mPublisherId);
+    @DataClass.Generated.Member
+    public boolean equals(@android.annotation.Nullable Object o) {
+        // You can override field equality logic by defining either of the methods like:
+        // boolean fieldNameEquals(VmsLayersOffering other) { ... }
+        // boolean fieldNameEquals(FieldType otherValue) { ... }
+
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        @SuppressWarnings("unchecked")
+        VmsLayersOffering that = (VmsLayersOffering) o;
+        //noinspection PointlessBooleanExpression
+        return true
+                && java.util.Objects.equals(mDependencies, that.mDependencies)
+                && mPublisherId == that.mPublisherId;
     }
 
     @Override
-    public boolean equals(Object o) {
-        if (!(o instanceof VmsLayersOffering)) {
-            return false;
-        }
-        VmsLayersOffering p = (VmsLayersOffering) o;
-        return Objects.equals(p.mPublisherId, mPublisherId)
-                && p.mDependencies.equals(mDependencies);
-    }
-
-    @Override
+    @DataClass.Generated.Member
     public int hashCode() {
-        return Objects.hash(mPublisherId, mDependencies);
+        // You can override field hashCode logic by defining methods like:
+        // int fieldNameHashCode() { ... }
+
+        int _hash = 1;
+        _hash = 31 * _hash + java.util.Objects.hashCode(mDependencies);
+        _hash = 31 * _hash + mPublisherId;
+        return _hash;
     }
 
     @Override
-    public int describeContents() {
-        return 0;
+    @DataClass.Generated.Member
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        // You can override field parcelling by defining methods like:
+        // void parcelFieldName(Parcel dest, int flags) { ... }
+
+        parcelDependencies(dest, flags);
+        dest.writeInt(mPublisherId);
     }
 
-    private VmsLayersOffering(Parcel in) {
-        List<VmsLayerDependency> dependencies = new ArrayList<>();
-        in.readParcelableList(dependencies, VmsLayerDependency.class.getClassLoader());
-        mDependencies = Collections.unmodifiableSet(new HashSet<>(dependencies));
-        mPublisherId = in.readInt();
+    @Override
+    @DataClass.Generated.Member
+    public int describeContents() { return 0; }
+
+    /** @hide */
+    @SuppressWarnings({"unchecked", "RedundantCast"})
+    @DataClass.Generated.Member
+    /* package-private */ VmsLayersOffering(@NonNull Parcel in) {
+        // You can override field unparcelling by defining methods like:
+        // static FieldType unparcelFieldName(Parcel in) { ... }
+
+        Set<VmsLayerDependency> dependencies = unparcelDependencies(in);
+        int publisherId = in.readInt();
+
+        this.mDependencies = dependencies;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mDependencies);
+        this.mPublisherId = publisherId;
+
+        onConstructed();
     }
-}
\ No newline at end of file
+
+    @DataClass.Generated.Member
+    public static final @NonNull Parcelable.Creator<VmsLayersOffering> CREATOR
+            = new Parcelable.Creator<VmsLayersOffering>() {
+        @Override
+        public VmsLayersOffering[] newArray(int size) {
+            return new VmsLayersOffering[size];
+        }
+
+        @Override
+        public VmsLayersOffering createFromParcel(@NonNull Parcel in) {
+            return new VmsLayersOffering(in);
+        }
+    };
+
+    @DataClass.Generated(
+            time = 1582065871728L,
+            codegenVersion = "1.0.14",
+            sourceFile = "packages/services/Car/car-lib/src/android/car/vms/VmsLayersOffering.java",
+            inputSignatures = "private @android.annotation.NonNull java.util.Set<android.car.vms.VmsLayerDependency> mDependencies\nprivate final  int mPublisherId\nprivate  void onConstructed()\nprivate  void parcelDependencies(android.os.Parcel,int)\nprivate @java.lang.SuppressWarnings(\"unchecked\") java.util.Set<android.car.vms.VmsLayerDependency> unparcelDependencies(android.os.Parcel)\nclass VmsLayersOffering extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genAidl=true, genEqualsHashCode=true, genToString=true)")
+    @Deprecated
+    private void __metadata() {}
+
+
+    //@formatter:on
+    // End of generated code
+
+}
diff --git a/car-lib/src/android/car/vms/VmsOperationRecorder.java b/car-lib/src/android/car/vms/VmsOperationRecorder.java
index 50a47c2..81265d2 100644
--- a/car-lib/src/android/car/vms/VmsOperationRecorder.java
+++ b/car-lib/src/android/car/vms/VmsOperationRecorder.java
@@ -22,8 +22,10 @@
  *   VmsOperationRecorder.get().subscribe(layer);
  * }</pre>
  *
+ * @deprecated VmsOperationRecorder is no longer used by VMS and will produce no output
  * @hide
  */
+@Deprecated
 @SystemApi
 public final class VmsOperationRecorder {
     private static final String TAG = "VmsOperationRecorder";
diff --git a/car-lib/src/android/car/vms/VmsProviderInfo.aidl b/car-lib/src/android/car/vms/VmsProviderInfo.aidl
new file mode 100644
index 0000000..6122834
--- /dev/null
+++ b/car-lib/src/android/car/vms/VmsProviderInfo.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.car.vms;
+
+parcelable VmsProviderInfo;
diff --git a/car-lib/src/android/car/vms/VmsProviderInfo.java b/car-lib/src/android/car/vms/VmsProviderInfo.java
new file mode 100644
index 0000000..db4f873
--- /dev/null
+++ b/car-lib/src/android/car/vms/VmsProviderInfo.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.car.vms;
+
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.DataClass;
+
+import java.util.Arrays;
+
+/**
+ * Hidden data object used to communicate Vehicle Map Service publisher information on registration.
+ *
+ * @hide
+ */
+@DataClass(
+        genEqualsHashCode = true,
+        genAidl = true)
+public class VmsProviderInfo implements Parcelable {
+    private @Nullable final byte[] mDescription;
+
+
+
+    // Code below generated by codegen v1.0.14.
+    //
+    // DO NOT MODIFY!
+    // CHECKSTYLE:OFF Generated code
+    //
+    // To regenerate run:
+    // $ codegen $ANDROID_BUILD_TOP/packages/services/Car/car-lib/src/android/car/vms/VmsProviderInfo.java
+    //
+    // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+    //   Settings > Editor > Code Style > Formatter Control
+    //@formatter:off
+
+
+    @DataClass.Generated.Member
+    public VmsProviderInfo(
+            @Nullable byte[] description) {
+        this.mDescription = description;
+
+        // onConstructed(); // You can define this method to get a callback
+    }
+
+    @DataClass.Generated.Member
+    public @Nullable byte[] getDescription() {
+        return mDescription;
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public boolean equals(@Nullable Object o) {
+        // You can override field equality logic by defining either of the methods like:
+        // boolean fieldNameEquals(VmsProviderInfo other) { ... }
+        // boolean fieldNameEquals(FieldType otherValue) { ... }
+
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        @SuppressWarnings("unchecked")
+        VmsProviderInfo that = (VmsProviderInfo) o;
+        //noinspection PointlessBooleanExpression
+        return true
+                && Arrays.equals(mDescription, that.mDescription);
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public int hashCode() {
+        // You can override field hashCode logic by defining methods like:
+        // int fieldNameHashCode() { ... }
+
+        int _hash = 1;
+        _hash = 31 * _hash + Arrays.hashCode(mDescription);
+        return _hash;
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public void writeToParcel(@android.annotation.NonNull Parcel dest, int flags) {
+        // You can override field parcelling by defining methods like:
+        // void parcelFieldName(Parcel dest, int flags) { ... }
+
+        byte flg = 0;
+        if (mDescription != null) flg |= 0x1;
+        dest.writeByte(flg);
+        if (mDescription != null) dest.writeByteArray(mDescription);
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public int describeContents() { return 0; }
+
+    /** @hide */
+    @SuppressWarnings({"unchecked", "RedundantCast"})
+    @DataClass.Generated.Member
+    protected VmsProviderInfo(@android.annotation.NonNull Parcel in) {
+        // You can override field unparcelling by defining methods like:
+        // static FieldType unparcelFieldName(Parcel in) { ... }
+
+        byte flg = in.readByte();
+        byte[] description = (flg & 0x1) == 0 ? null : in.createByteArray();
+
+        this.mDescription = description;
+
+        // onConstructed(); // You can define this method to get a callback
+    }
+
+    @DataClass.Generated.Member
+    public static final @android.annotation.NonNull Parcelable.Creator<VmsProviderInfo> CREATOR
+            = new Parcelable.Creator<VmsProviderInfo>() {
+        @Override
+        public VmsProviderInfo[] newArray(int size) {
+            return new VmsProviderInfo[size];
+        }
+
+        @Override
+        public VmsProviderInfo createFromParcel(@android.annotation.NonNull Parcel in) {
+            return new VmsProviderInfo(in);
+        }
+    };
+
+    @DataClass.Generated(
+            time = 1581406319319L,
+            codegenVersion = "1.0.14",
+            sourceFile = "packages/services/Car/car-lib/src/android/car/vms/VmsProviderInfo.java",
+            inputSignatures = "private final @android.annotation.Nullable byte[] mDescription\nclass VmsProviderInfo extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genAidl=true)")
+    @Deprecated
+    private void __metadata() {}
+
+
+    //@formatter:on
+    // End of generated code
+
+}
diff --git a/car-lib/src/android/car/vms/VmsPublisherClientService.java b/car-lib/src/android/car/vms/VmsPublisherClientService.java
index 70e0592..c603609 100644
--- a/car-lib/src/android/car/vms/VmsPublisherClientService.java
+++ b/car-lib/src/android/car/vms/VmsPublisherClientService.java
@@ -18,25 +18,22 @@
 
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.app.Service;
 import android.car.Car;
+import android.car.annotation.RequiredFeature;
+import android.car.vms.VmsClientManager.VmsClientCallback;
 import android.content.Intent;
 import android.os.Binder;
-import android.os.Build;
 import android.os.Handler;
+import android.os.HandlerExecutor;
 import android.os.IBinder;
 import android.os.Looper;
-import android.os.Message;
-import android.os.Process;
-import android.os.RemoteException;
 import android.util.Log;
 
 import com.android.internal.annotations.GuardedBy;
-import com.android.internal.util.Preconditions;
-
-import java.lang.ref.WeakReference;
-import java.util.Collections;
+import com.android.internal.annotations.VisibleForTesting;
 
 /**
  * API implementation of a Vehicle Map Service publisher client.
@@ -51,41 +48,68 @@
  *
  * Publishers must also register a publisher ID by calling {@link #getPublisherId(byte[])}.
  *
+ * @deprecated Use {@link VmsClientManager} instead
  * @hide
  */
+@RequiredFeature(Car.VEHICLE_MAP_SERVICE)
+@Deprecated
 @SystemApi
 public abstract class VmsPublisherClientService extends Service {
     private static final boolean DBG = false;
     private static final String TAG = "VmsPublisherClientService";
 
-    private static final VmsSubscriptionState DEFAULT_SUBSCRIPTIONS =
-            new VmsSubscriptionState(0, Collections.emptySet(),
-                    Collections.emptySet());
+
+    private final Handler mHandler = new Handler(Looper.getMainLooper());
+    private final VmsClientCallback mClientCallback = new PublisherClientCallback();
 
     private final Object mLock = new Object();
-
-    private Handler mHandler = new VmsEventHandler(this);
-    private final VmsPublisherClientBinder mVmsPublisherClient = new VmsPublisherClientBinder(this);
-    private volatile IVmsPublisherService mVmsPublisherService = null;
     @GuardedBy("mLock")
-    private IBinder mToken = null;
+    private @Nullable Car mCar;
+    @GuardedBy("mLock")
+    private @Nullable VmsClient mClient;
+
+    @Override
+    public void onCreate() {
+        if (DBG) Log.d(TAG, "Connecting to Car service");
+        synchronized (mLock) {
+            mCar = Car.createCar(this, mHandler, Car.CAR_WAIT_TIMEOUT_DO_NOT_WAIT,
+                    this::onCarLifecycleChanged);
+        }
+    }
+
+    @Override
+    public void onDestroy() {
+        if (DBG) Log.d(TAG, "Disconnecting from Car service");
+        synchronized (mLock) {
+            if (mCar != null) {
+                mCar.disconnect();
+                mCar = null;
+            }
+        }
+    }
 
     @Override
     public IBinder onBind(Intent intent) {
         if (DBG) Log.d(TAG, "onBind, intent: " + intent);
-        return mVmsPublisherClient.asBinder();
+        return new Binder();
     }
 
-    @Override
-    public boolean onUnbind(Intent intent) {
-        if (DBG) Log.d(TAG, "onUnbind, intent: " + intent);
-        stopSelf();
-        return super.onUnbind(intent);
-    }
-
-    private void setToken(IBinder token) {
-        synchronized (mLock) {
-            mToken = token;
+    /**
+     * @hide
+     */
+    @VisibleForTesting
+    protected void onCarLifecycleChanged(Car car, boolean ready) {
+        if (DBG) Log.d(TAG, "Car service ready: " + ready);
+        if (ready) {
+            VmsClientManager clientManager =
+                    (VmsClientManager) car.getCarManager(Car.VEHICLE_MAP_SERVICE);
+            if (DBG) Log.d(TAG, "VmsClientManager: " + clientManager);
+            if (clientManager == null) {
+                Log.e(TAG, "VmsClientManager is not available");
+                return;
+            }
+            clientManager.registerVmsClientCallback(new HandlerExecutor(mHandler), mClientCallback,
+                    /* legacyClient= */ true);
         }
     }
 
@@ -112,16 +136,7 @@
      * @throws IllegalStateException if publisher services are not available
      */
     public final void publish(@NonNull VmsLayer layer, int publisherId, byte[] payload) {
-        Preconditions.checkNotNull(layer, "layer cannot be null");
-        if (DBG) Log.d(TAG, "Publishing for layer : " + layer);
-
-        IBinder token = getTokenForPublisherServiceThreadSafe();
-
-        try {
-            mVmsPublisherService.publish(token, layer, publisherId, payload);
-        } catch (RemoteException e) {
-            Car.handleRemoteExceptionFromCarService(this, e);
-        }
+        getVmsClient().publishPacket(publisherId, layer, payload);
     }
 
     /**
@@ -131,32 +146,7 @@
      * @throws IllegalStateException if publisher services are not available
      */
     public final void setLayersOffering(@NonNull VmsLayersOffering offering) {
-        Preconditions.checkNotNull(offering, "offering cannot be null");
-        if (DBG) Log.d(TAG, "Setting layers offering : " + offering);
-
-        IBinder token = getTokenForPublisherServiceThreadSafe();
-
-        try {
-            mVmsPublisherService.setLayersOffering(token, offering);
-            VmsOperationRecorder.get().setLayersOffering(offering);
-        } catch (RemoteException e) {
-            Car.handleRemoteExceptionFromCarService(this, e);
-        }
-    }
-
-    private IBinder getTokenForPublisherServiceThreadSafe() {
-        if (mVmsPublisherService == null) {
-            throw new IllegalStateException("VmsPublisherService not set.");
-        }
-
-        IBinder token;
-        synchronized (mLock) {
-            token = mToken;
-        }
-        if (token == null) {
-            throw new IllegalStateException("VmsPublisherService does not have a valid token.");
-        }
-        return token;
+        getVmsClient().setProviderOfferings(offering.getPublisherId(), offering.getDependencies());
     }
 
     /**
@@ -170,19 +160,7 @@
      * @throws IllegalStateException if publisher services are not available
      */
     public final int getPublisherId(byte[] publisherInfo) {
-        if (mVmsPublisherService == null) {
-            throw new IllegalStateException("VmsPublisherService not set.");
-        }
-        int publisherId;
-        try {
-            publisherId = mVmsPublisherService.getPublisherId(publisherInfo);
-            Log.i(TAG, "Assigned publisher ID: " + publisherId);
-        } catch (RemoteException e) {
-            // This will crash. To prevent crash, safer invalid return value should be defined.
-            throw e.rethrowFromSystemServer();
-        }
-        VmsOperationRecorder.get().getPublisherId(publisherId);
-        return publisherId;
+        return getVmsClient().registerProvider(publisherInfo);
     }
 
     /**
@@ -192,114 +170,40 @@
      * @throws IllegalStateException if publisher services are not available
      */
     public final VmsSubscriptionState getSubscriptions() {
-        if (mVmsPublisherService == null) {
-            throw new IllegalStateException("VmsPublisherService not set.");
-        }
-        try {
-            return mVmsPublisherService.getSubscriptions();
-        } catch (RemoteException e) {
-            return Car.handleRemoteExceptionFromCarService(this, e, DEFAULT_SUBSCRIPTIONS);
+        return getVmsClient().getSubscriptionState();
+    }
+
+    private VmsClient getVmsClient() {
+        synchronized (mLock) {
+            if (mClient == null) {
+                throw new IllegalStateException("VMS client connection is not ready");
+            }
+            return mClient;
         }
     }
 
-    private void setVmsPublisherService(IVmsPublisherService service) {
-        mVmsPublisherService = service;
-        onVmsPublisherServiceReady();
-    }
-
-    /**
-     * Implements the interface that the VMS service uses to communicate with this client.
-     */
-    private static class VmsPublisherClientBinder extends IVmsPublisherClient.Stub {
-        private final WeakReference<VmsPublisherClientService> mVmsPublisherClientService;
-        @GuardedBy("mSequenceLock")
-        private long mSequence = -1;
-        private final Object mSequenceLock = new Object();
-
-        VmsPublisherClientBinder(VmsPublisherClientService vmsPublisherClientService) {
-            mVmsPublisherClientService = new WeakReference<>(vmsPublisherClientService);
+    private class PublisherClientCallback implements VmsClientCallback {
+        @Override
+        public void onClientConnected(VmsClient client) {
+            synchronized (mLock) {
+                mClient = client;
+            }
+            onVmsPublisherServiceReady();
         }
 
         @Override
-        public void setVmsPublisherService(IBinder token, IVmsPublisherService service) {
-            assertSystemOrSelf();
-
-            VmsPublisherClientService vmsPublisherClientService = mVmsPublisherClientService.get();
-            if (vmsPublisherClientService == null) return;
-            if (DBG) Log.d(TAG, "setting VmsPublisherService.");
-            Handler handler = vmsPublisherClientService.mHandler;
-            handler.sendMessage(
-                    handler.obtainMessage(VmsEventHandler.SET_SERVICE_CALLBACK, service));
-            vmsPublisherClientService.setToken(token);
+        public void onSubscriptionStateChanged(VmsSubscriptionState subscriptionState) {
+            onVmsSubscriptionChange(subscriptionState);
         }
 
         @Override
-        public void onVmsSubscriptionChange(VmsSubscriptionState subscriptionState) {
-            assertSystemOrSelf();
-
-            VmsPublisherClientService vmsPublisherClientService = mVmsPublisherClientService.get();
-            if (vmsPublisherClientService == null) return;
-            if (DBG) Log.d(TAG, "subscription event: " + subscriptionState);
-            synchronized (mSequenceLock) {
-                if (subscriptionState.getSequenceNumber() <= mSequence) {
-                    Log.w(TAG, "Sequence out of order. Current sequence = " + mSequence
-                            + "; expected new sequence = " + subscriptionState.getSequenceNumber());
-                    // Do not propagate old notifications.
-                    return;
-                } else {
-                    mSequence = subscriptionState.getSequenceNumber();
-                }
-            }
-            Handler handler = vmsPublisherClientService.mHandler;
-            handler.sendMessage(
-                    handler.obtainMessage(VmsEventHandler.ON_SUBSCRIPTION_CHANGE_EVENT,
-                            subscriptionState));
-        }
-
-        private void assertSystemOrSelf() {
-            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
-                if (DBG) Log.d(TAG, "Skipping system user check");
-                return;
-            }
-
-            if (!(Binder.getCallingUid() == Process.SYSTEM_UID
-                    || Binder.getCallingPid() == Process.myPid())) {
-                throw new SecurityException("Caller must be system user or same process");
-            }
-        }
-    }
-
-    /**
-     * Receives events from the binder thread and dispatches them.
-     */
-    private final static class VmsEventHandler extends Handler {
-        /** Constants handled in the handler */
-        private static final int ON_SUBSCRIPTION_CHANGE_EVENT = 0;
-        private static final int SET_SERVICE_CALLBACK = 1;
-
-        private final WeakReference<VmsPublisherClientService> mVmsPublisherClientService;
-
-        VmsEventHandler(VmsPublisherClientService service) {
-            super(Looper.getMainLooper());
-            mVmsPublisherClientService = new WeakReference<>(service);
+        public void onLayerAvailabilityChanged(VmsAvailableLayers availableLayers) {
+            // Ignored
         }
 
         @Override
-        public void handleMessage(Message msg) {
-            VmsPublisherClientService service = mVmsPublisherClientService.get();
-            if (service == null) return;
-            switch (msg.what) {
-                case ON_SUBSCRIPTION_CHANGE_EVENT:
-                    VmsSubscriptionState subscriptionState = (VmsSubscriptionState) msg.obj;
-                    service.onVmsSubscriptionChange(subscriptionState);
-                    break;
-                case SET_SERVICE_CALLBACK:
-                    service.setVmsPublisherService((IVmsPublisherService) msg.obj);
-                    break;
-                default:
-                    Log.e(TAG, "Event type not handled:  " + msg.what);
-                    break;
-            }
+        public void onPacketReceived(int providerId, VmsLayer layer, byte[] packet) {
+            // Does not subscribe to packets
         }
     }
 }
diff --git a/car-lib/src/android/car/vms/VmsRegistrationInfo.aidl b/car-lib/src/android/car/vms/VmsRegistrationInfo.aidl
new file mode 100644
index 0000000..eff5dc2
--- /dev/null
+++ b/car-lib/src/android/car/vms/VmsRegistrationInfo.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.car.vms;
+
+parcelable VmsRegistrationInfo;
diff --git a/car-lib/src/android/car/vms/VmsRegistrationInfo.java b/car-lib/src/android/car/vms/VmsRegistrationInfo.java
new file mode 100644
index 0000000..78c8273
--- /dev/null
+++ b/car-lib/src/android/car/vms/VmsRegistrationInfo.java
@@ -0,0 +1,168 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.car.vms;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.DataClass;
+
+import java.util.Objects;
+
+/**
+ * Hidden data object used to communicate Vehicle Map Service client tokens and system state on
+ * client registration.
+ *
+ * @hide
+ */
+@DataClass(
+        genEqualsHashCode = true,
+        genAidl = true)
+public class VmsRegistrationInfo implements Parcelable {
+    private @NonNull VmsAvailableLayers mAvailableLayers;
+    private @NonNull VmsSubscriptionState mSubscriptionState;
+
+
+
+    // Code below generated by codegen v1.0.14.
+    //
+    // DO NOT MODIFY!
+    // CHECKSTYLE:OFF Generated code
+    //
+    // To regenerate run:
+    // $ codegen $ANDROID_BUILD_TOP/packages/services/Car/car-lib/src/android/car/vms/VmsRegistrationInfo.java
+    //
+    // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+    //   Settings > Editor > Code Style > Formatter Control
+    //@formatter:off
+
+
+    @DataClass.Generated.Member
+    public VmsRegistrationInfo(
+            @NonNull VmsAvailableLayers availableLayers,
+            @NonNull VmsSubscriptionState subscriptionState) {
+        this.mAvailableLayers = availableLayers;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mAvailableLayers);
+        this.mSubscriptionState = subscriptionState;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mSubscriptionState);
+
+        // onConstructed(); // You can define this method to get a callback
+    }
+
+    @DataClass.Generated.Member
+    public @NonNull VmsAvailableLayers getAvailableLayers() {
+        return mAvailableLayers;
+    }
+
+    @DataClass.Generated.Member
+    public @NonNull VmsSubscriptionState getSubscriptionState() {
+        return mSubscriptionState;
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public boolean equals(@android.annotation.Nullable Object o) {
+        // You can override field equality logic by defining either of the methods like:
+        // boolean fieldNameEquals(VmsRegistrationInfo other) { ... }
+        // boolean fieldNameEquals(FieldType otherValue) { ... }
+
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        @SuppressWarnings("unchecked")
+        VmsRegistrationInfo that = (VmsRegistrationInfo) o;
+        //noinspection PointlessBooleanExpression
+        return true
+                && Objects.equals(mAvailableLayers, that.mAvailableLayers)
+                && Objects.equals(mSubscriptionState, that.mSubscriptionState);
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public int hashCode() {
+        // You can override field hashCode logic by defining methods like:
+        // int fieldNameHashCode() { ... }
+
+        int _hash = 1;
+        _hash = 31 * _hash + Objects.hashCode(mAvailableLayers);
+        _hash = 31 * _hash + Objects.hashCode(mSubscriptionState);
+        return _hash;
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        // You can override field parcelling by defining methods like:
+        // void parcelFieldName(Parcel dest, int flags) { ... }
+
+        dest.writeTypedObject(mAvailableLayers, flags);
+        dest.writeTypedObject(mSubscriptionState, flags);
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public int describeContents() { return 0; }
+
+    /** @hide */
+    @SuppressWarnings({"unchecked", "RedundantCast"})
+    @DataClass.Generated.Member
+    protected VmsRegistrationInfo(@NonNull Parcel in) {
+        // You can override field unparcelling by defining methods like:
+        // static FieldType unparcelFieldName(Parcel in) { ... }
+
+        VmsAvailableLayers availableLayers = (VmsAvailableLayers) in.readTypedObject(VmsAvailableLayers.CREATOR);
+        VmsSubscriptionState subscriptionState = (VmsSubscriptionState) in.readTypedObject(VmsSubscriptionState.CREATOR);
+
+        this.mAvailableLayers = availableLayers;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mAvailableLayers);
+        this.mSubscriptionState = subscriptionState;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mSubscriptionState);
+
+        // onConstructed(); // You can define this method to get a callback
+    }
+
+    @DataClass.Generated.Member
+    public static final @NonNull Parcelable.Creator<VmsRegistrationInfo> CREATOR
+            = new Parcelable.Creator<VmsRegistrationInfo>() {
+        @Override
+        public VmsRegistrationInfo[] newArray(int size) {
+            return new VmsRegistrationInfo[size];
+        }
+
+        @Override
+        public VmsRegistrationInfo createFromParcel(@NonNull Parcel in) {
+            return new VmsRegistrationInfo(in);
+        }
+    };
+
+    @DataClass.Generated(
+            time = 1581406323727L,
+            codegenVersion = "1.0.14",
+            sourceFile = "packages/services/Car/car-lib/src/android/car/vms/VmsRegistrationInfo.java",
+            inputSignatures = "private @android.annotation.NonNull android.car.vms.VmsAvailableLayers mAvailableLayers\nprivate @android.annotation.NonNull android.car.vms.VmsSubscriptionState mSubscriptionState\nclass VmsRegistrationInfo extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genAidl=true)")
+    @Deprecated
+    private void __metadata() {}
+
+
+    //@formatter:on
+    // End of generated code
+
+}
diff --git a/car-lib/src/android/car/vms/VmsSubscriberManager.java b/car-lib/src/android/car/vms/VmsSubscriberManager.java
index ce10b13..c077bcd 100644
--- a/car-lib/src/android/car/vms/VmsSubscriberManager.java
+++ b/car-lib/src/android/car/vms/VmsSubscriberManager.java
@@ -18,19 +18,20 @@
 
 import android.annotation.CallbackExecutor;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.car.Car;
 import android.car.CarManagerBase;
-import android.os.Binder;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.util.Log;
+import android.car.annotation.RequiredFeature;
+import android.car.vms.VmsClientManager.VmsClientCallback;
 
 import com.android.internal.annotations.GuardedBy;
-import com.android.internal.util.Preconditions;
 
-import java.util.Collections;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
 
 /**
  * API implementation for use by Vehicle Map Service subscribers.
@@ -38,23 +39,15 @@
  * Supports a single client callback that can subscribe and unsubscribe to different data layers.
  * {@link #setVmsSubscriberClientCallback} must be called before any subscription operations.
  *
+ * @deprecated Use {@link VmsClientManager} instead
  * @hide
  */
+@RequiredFeature(Car.VMS_SUBSCRIBER_SERVICE)
+@Deprecated
 @SystemApi
 public final class VmsSubscriberManager extends CarManagerBase {
-    private static final String TAG = "VmsSubscriberManager";
-
+    private static final long CLIENT_READY_TIMEOUT_MS = 500;
     private static final byte[] DEFAULT_PUBLISHER_INFO = new byte[0];
-    private static final VmsAvailableLayers DEFAULT_AVAILABLE_LAYERS =
-            new VmsAvailableLayers(Collections.emptySet(), 0);
-
-    private final IVmsSubscriberService mVmsSubscriberService;
-    private final IVmsSubscriberClient mSubscriberManagerClient;
-    private final Object mClientCallbackLock = new Object();
-    @GuardedBy("mClientCallbackLock")
-    private VmsSubscriberClientCallback mClientCallback;
-    @GuardedBy("mClientCallbackLock")
-    private Executor mExecutor;
 
     /**
      * Callback interface for Vehicle Map Service subscribers.
@@ -76,47 +69,32 @@
         void onLayersAvailabilityChanged(@NonNull VmsAvailableLayers availableLayers);
     }
 
+    private final VmsClientManager mClientManager;
+
+    private final Object mLock = new Object();
+
+    @GuardedBy("mLock")
+    private @Nullable VmsClient mClient;
+
+    @GuardedBy("mLock")
+    private @Nullable VmsClientCallback mClientCallback;
+
+    private final VmsSubscriptionHelper mSubscriptionHelper =
+            new VmsSubscriptionHelper(this::setSubscriptions);
+
     /**
-     * Hidden constructor - can only be used internally.
-     *
      * @hide
      */
-    public VmsSubscriberManager(Car car, IBinder service) {
-        super(car);
-        mVmsSubscriberService = IVmsSubscriberService.Stub.asInterface(service);
-        mSubscriberManagerClient = new IVmsSubscriberClient.Stub() {
-            @Override
-            public void onVmsMessageReceived(VmsLayer layer, byte[] payload) {
-                Executor executor;
-                synchronized (mClientCallbackLock) {
-                    executor = mExecutor;
-                }
-                if (executor == null) {
-                    Log.w(TAG, "Executor is unset in onVmsMessageReceived");
-                    return;
-                }
-                Binder.clearCallingIdentity();
-                executor.execute(() -> {
-                    dispatchOnReceiveMessage(layer, payload);
-                });
-            }
+    public static VmsSubscriberManager wrap(Car car, @Nullable VmsClientManager clientManager) {
+        if (clientManager == null) {
+            return null;
+        }
+        return new VmsSubscriberManager(car, clientManager);
+    }
 
-            @Override
-            public void onLayersAvailabilityChanged(VmsAvailableLayers availableLayers) {
-                Executor executor;
-                synchronized (mClientCallbackLock) {
-                    executor = mExecutor;
-                }
-                if (executor == null) {
-                    Log.w(TAG, "Executor is unset in onLayersAvailabilityChanged");
-                    return;
-                }
-                Binder.clearCallingIdentity();
-                executor.execute(() -> {
-                    dispatchOnAvailabilityChangeMessage(availableLayers);
-                });
-            }
-        };
+    private VmsSubscriberManager(Car car, VmsClientManager clientManager) {
+        super(car);
+        mClientManager = clientManager;
     }
 
     /**
@@ -129,38 +107,41 @@
     public void setVmsSubscriberClientCallback(
             @NonNull @CallbackExecutor Executor executor,
             @NonNull VmsSubscriberClientCallback clientCallback) {
-        synchronized (mClientCallbackLock) {
+        Objects.requireNonNull(clientCallback, "clientCallback cannot be null");
+        Objects.requireNonNull(executor, "executor cannot be null");
+        CountDownLatch clientReady;
+        synchronized (mLock) {
             if (mClientCallback != null) {
                 throw new IllegalStateException("Client callback is already configured.");
             }
-            mClientCallback = Preconditions.checkNotNull(clientCallback,
-                    "clientCallback cannot be null");
-            mExecutor = Preconditions.checkNotNull(executor, "executor cannot be null");
+            clientReady = new CountDownLatch(1);
+            mClientCallback = new SubscriberCallbackWrapper(clientCallback, clientReady);
+            // Register callback with broker service
+            mClientManager.registerVmsClientCallback(executor, mClientCallback,
+                    /* legacyClient= */ true);
         }
+
         try {
-            mVmsSubscriberService.addVmsSubscriberToNotifications(mSubscriberManagerClient);
-        } catch (RemoteException e) {
-            handleRemoteExceptionFromCarService(e);
+            // Wait for VmsClient to be available
+            if (!clientReady.await(CLIENT_READY_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
+                clearVmsSubscriberClientCallback();
+                throw new IllegalStateException("Subscriber client is not ready");
+            }
+        } catch (InterruptedException e) {
+            clearVmsSubscriberClientCallback();
+            Thread.currentThread().interrupt();
+            throw new IllegalStateException("Interrupted while waiting for subscriber client", e);
         }
     }
 
-
     /**
      * Clears the subscriber client's callback.
      */
     public void clearVmsSubscriberClientCallback() {
-        synchronized (mClientCallbackLock) {
-            if (mExecutor == null) return;
-        }
-        try {
-            mVmsSubscriberService.removeVmsSubscriberToNotifications(mSubscriberManagerClient);
-        } catch (RemoteException e) {
-            handleRemoteExceptionFromCarService(e);
-        } finally {
-            synchronized (mClientCallbackLock) {
-                mClientCallback = null;
-                mExecutor = null;
-            }
+        synchronized (mLock) {
+            mClientManager.unregisterVmsClientCallback(mClientCallback);
+            mClient = null;
+            mClientCallback = null;
         }
     }
 
@@ -172,11 +153,8 @@
      */
     @NonNull
     public byte[] getPublisherInfo(int publisherId) {
-        try {
-            return mVmsSubscriberService.getPublisherInfo(publisherId);
-        } catch (RemoteException e) {
-            return handleRemoteExceptionFromCarService(e, DEFAULT_PUBLISHER_INFO);
-        }
+        byte[] publisherInfo = getVmsClient().getProviderDescription(publisherId);
+        return publisherInfo != null ? publisherInfo : DEFAULT_PUBLISHER_INFO;
     }
 
     /**
@@ -186,11 +164,7 @@
      */
     @NonNull
     public VmsAvailableLayers getAvailableLayers() {
-        try {
-            return mVmsSubscriberService.getAvailableLayers();
-        } catch (RemoteException e) {
-            return handleRemoteExceptionFromCarService(e, DEFAULT_AVAILABLE_LAYERS);
-        }
+        return getVmsClient().getAvailableLayers();
     }
 
     /**
@@ -201,13 +175,7 @@
      *                               {@link #setVmsSubscriberClientCallback}.
      */
     public void subscribe(@NonNull VmsLayer layer) {
-        verifySubscriptionIsAllowed();
-        try {
-            mVmsSubscriberService.addVmsSubscriber(mSubscriberManagerClient, layer);
-            VmsOperationRecorder.get().subscribe(layer);
-        } catch (RemoteException e) {
-            handleRemoteExceptionFromCarService(e);
-        }
+        mSubscriptionHelper.subscribe(layer);
     }
 
     /**
@@ -219,27 +187,14 @@
      *                               {@link #setVmsSubscriberClientCallback}.
      */
     public void subscribe(@NonNull VmsLayer layer, int publisherId) {
-        verifySubscriptionIsAllowed();
-        try {
-            mVmsSubscriberService.addVmsSubscriberToPublisher(
-                    mSubscriberManagerClient, layer, publisherId);
-            VmsOperationRecorder.get().subscribe(layer, publisherId);
-        } catch (RemoteException e) {
-            handleRemoteExceptionFromCarService(e);
-        }
+        mSubscriptionHelper.subscribe(layer, publisherId);
     }
 
     /**
      * Start monitoring all messages for all layers, regardless of subscriptions.
      */
     public void startMonitoring() {
-        verifySubscriptionIsAllowed();
-        try {
-            mVmsSubscriberService.addVmsSubscriberPassive(mSubscriberManagerClient);
-            VmsOperationRecorder.get().startMonitoring();
-        } catch (RemoteException e) {
-            handleRemoteExceptionFromCarService(e);
-        }
+        getVmsClient().setMonitoringEnabled(true);
     }
 
     /**
@@ -250,13 +205,7 @@
      *                               {@link #setVmsSubscriberClientCallback}.
      */
     public void unsubscribe(@NonNull VmsLayer layer) {
-        verifySubscriptionIsAllowed();
-        try {
-            mVmsSubscriberService.removeVmsSubscriber(mSubscriberManagerClient, layer);
-            VmsOperationRecorder.get().unsubscribe(layer);
-        } catch (RemoteException e) {
-            handleRemoteExceptionFromCarService(e);
-        }
+        mSubscriptionHelper.unsubscribe(layer);
     }
 
     /**
@@ -268,74 +217,66 @@
      *                               {@link #setVmsSubscriberClientCallback}.
      */
     public void unsubscribe(@NonNull VmsLayer layer, int publisherId) {
-        try {
-            mVmsSubscriberService.removeVmsSubscriberToPublisher(
-                    mSubscriberManagerClient, layer, publisherId);
-            VmsOperationRecorder.get().unsubscribe(layer, publisherId);
-        } catch (RemoteException e) {
-            handleRemoteExceptionFromCarService(e);
-        }
+        mSubscriptionHelper.unsubscribe(layer, publisherId);
     }
 
     /**
      * Stop monitoring. Only receive messages for layers which have been subscribed to."
      */
     public void stopMonitoring() {
-        try {
-            mVmsSubscriberService.removeVmsSubscriberPassive(mSubscriberManagerClient);
-            VmsOperationRecorder.get().stopMonitoring();
-        } catch (RemoteException e) {
-            handleRemoteExceptionFromCarService(e);
-        }
-    }
-
-    private void dispatchOnReceiveMessage(VmsLayer layer, byte[] payload) {
-        VmsSubscriberClientCallback clientCallback = getClientCallbackThreadSafe();
-        if (clientCallback == null) {
-            Log.e(TAG, "Cannot dispatch received message.");
-            return;
-        }
-        clientCallback.onVmsMessageReceived(layer, payload);
-    }
-
-    private void dispatchOnAvailabilityChangeMessage(VmsAvailableLayers availableLayers) {
-        VmsSubscriberClientCallback clientCallback = getClientCallbackThreadSafe();
-        if (clientCallback == null) {
-            Log.e(TAG, "Cannot dispatch availability change message.");
-            return;
-        }
-        clientCallback.onLayersAvailabilityChanged(availableLayers);
-    }
-
-    private VmsSubscriberClientCallback getClientCallbackThreadSafe() {
-        VmsSubscriberClientCallback clientCallback;
-        synchronized (mClientCallbackLock) {
-            clientCallback = mClientCallback;
-        }
-        if (clientCallback == null) {
-            Log.e(TAG, "client callback not set.");
-        }
-        return clientCallback;
-    }
-
-    /*
-     * Verifies that the subscriber is in a state where it is allowed to subscribe.
-     */
-    private void verifySubscriptionIsAllowed() {
-        VmsSubscriberClientCallback clientCallback = getClientCallbackThreadSafe();
-        if (clientCallback == null) {
-            throw new IllegalStateException("Cannot subscribe.");
-        }
+        getVmsClient().setMonitoringEnabled(false);
     }
 
     /**
      * @hide
      */
     @Override
-    public void onCarDisconnected() {
-        synchronized (mClientCallbackLock) {
-            mClientCallback = null;
-            mExecutor = null;
+    public void onCarDisconnected() {}
+
+    private void setSubscriptions(Set<VmsAssociatedLayer> subscriptions) {
+        getVmsClient().setSubscriptions(subscriptions);
+    }
+
+    private VmsClient getVmsClient() {
+        synchronized (mLock) {
+            if (mClient == null) {
+                throw new IllegalStateException("VMS client connection is not ready");
+            }
+            return mClient;
+        }
+    }
+
+    private final class SubscriberCallbackWrapper implements VmsClientCallback {
+        private final VmsSubscriberClientCallback mCallback;
+        private final CountDownLatch mClientReady;
+
+        SubscriberCallbackWrapper(VmsSubscriberClientCallback callback,
+                CountDownLatch clientReady) {
+            mCallback = callback;
+            mClientReady = clientReady;
+        }
+
+        @Override
+        public void onClientConnected(VmsClient client) {
+            synchronized (mLock) {
+                mClient = client;
+            }
+            mClientReady.countDown();
+        }
+
+        @Override
+        public void onSubscriptionStateChanged(VmsSubscriptionState subscriptionState) {
+            // Ignored
+        }
+
+        @Override
+        public void onLayerAvailabilityChanged(VmsAvailableLayers availableLayers) {
+            mCallback.onLayersAvailabilityChanged(availableLayers);
+        }
+
+        @Override
+        public void onPacketReceived(int providerId, VmsLayer layer, byte[] packet) {
+            mCallback.onVmsMessageReceived(layer, packet);
         }
     }
 }
diff --git a/car-lib/src/android/car/vms/VmsSubscriptionHelper.java b/car-lib/src/android/car/vms/VmsSubscriptionHelper.java
new file mode 100644
index 0000000..500d8f9
--- /dev/null
+++ b/car-lib/src/android/car/vms/VmsSubscriptionHelper.java
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.car.vms;
+
+import android.annotation.NonNull;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.SparseBooleanArray;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.Collections;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.function.Consumer;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+/**
+ * Internal utility for computing subscription updates.
+ *
+ * @hide
+ */
+public final class VmsSubscriptionHelper {
+    private final Consumer<Set<VmsAssociatedLayer>> mUpdateHandler;
+
+    private final Object mLock = new Object();
+
+    @GuardedBy("mLock")
+    private final Set<VmsLayer> mLayerSubscriptions = new ArraySet<>();
+
+    @GuardedBy("mLock")
+    private final Map<VmsLayer, SparseBooleanArray> mPublisherSubscriptions = new ArrayMap<>();
+
+    @GuardedBy("mLock")
+    private boolean mPendingUpdate;
+
+    /**
+     * Constructor for subscription helper.
+     *
+     * @param updateHandler Consumer of subscription updates.
+     */
+    public VmsSubscriptionHelper(@NonNull Consumer<Set<VmsAssociatedLayer>> updateHandler) {
+        mUpdateHandler = Objects.requireNonNull(updateHandler, "updateHandler cannot be null");
+    }
+
+    /**
+     * Adds a subscription to a layer.
+     */
+    public void subscribe(@NonNull VmsLayer layer) {
+        Objects.requireNonNull(layer, "layer cannot be null");
+        synchronized (mLock) {
+            if (mLayerSubscriptions.add(layer)) {
+                mPendingUpdate = true;
+            }
+            publishSubscriptionUpdate();
+        }
+    }
+
+    /**
+     * Adds a subscription to a specific provider of a layer.
+     */
+    public void subscribe(@NonNull VmsLayer layer, int providerId) {
+        Objects.requireNonNull(layer, "layer cannot be null");
+        synchronized (mLock) {
+            SparseBooleanArray providerIds = mPublisherSubscriptions.computeIfAbsent(layer,
+                    ignored -> new SparseBooleanArray());
+            if (!providerIds.get(providerId)) {
+                providerIds.put(providerId, true);
+                mPendingUpdate = true;
+            }
+            publishSubscriptionUpdate();
+        }
+    }
+
+    /**
+     * Removes a subscription to a layer.
+     */
+    public void unsubscribe(@NonNull VmsLayer layer) {
+        Objects.requireNonNull(layer, "layer cannot be null");
+        synchronized (mLock) {
+            if (mLayerSubscriptions.remove(layer)) {
+                mPendingUpdate = true;
+            }
+            publishSubscriptionUpdate();
+        }
+    }
+
+    /**
+     * Removes a subscription to the specific provider of a layer.
+     */
+    public void unsubscribe(@NonNull VmsLayer layer, int providerId) {
+        Objects.requireNonNull(layer, "layer cannot be null");
+        synchronized (mLock) {
+            SparseBooleanArray providerIds = mPublisherSubscriptions.get(layer);
+            if (providerIds != null && providerIds.get(providerId)) {
+                providerIds.delete(providerId);
+                if (providerIds.size() == 0) {
+                    mPublisherSubscriptions.remove(layer);
+                }
+                mPendingUpdate = true;
+            }
+            publishSubscriptionUpdate();
+        }
+    }
+
+    /**
+     * Gets the current set of subscriptions.
+     */
+    @NonNull
+    public Set<VmsAssociatedLayer> getSubscriptions() {
+        return Stream.concat(
+                mLayerSubscriptions.stream().map(
+                        layer -> new VmsAssociatedLayer(layer, Collections.emptySet())),
+                mPublisherSubscriptions.entrySet().stream()
+                        .filter(entry -> !mLayerSubscriptions.contains(entry.getKey()))
+                        .map(VmsSubscriptionHelper::toAssociatedLayer))
+                .collect(Collectors.toSet());
+    }
+
+    private void publishSubscriptionUpdate() {
+        synchronized (mLock) {
+            if (mPendingUpdate) {
+                mUpdateHandler.accept(getSubscriptions());
+            }
+            mPendingUpdate = false;
+        }
+    }
+
+    private static VmsAssociatedLayer toAssociatedLayer(
+            Map.Entry<VmsLayer, SparseBooleanArray> entry) {
+        SparseBooleanArray providerIdArray = entry.getValue();
+        Set<Integer> providerIds = new ArraySet<>(providerIdArray.size());
+        for (int i = 0; i < providerIdArray.size(); i++) {
+            providerIds.add(providerIdArray.keyAt(i));
+        }
+        return new VmsAssociatedLayer(entry.getKey(), providerIds);
+    }
+}
diff --git a/car-lib/src/android/car/vms/VmsSubscriptionState.java b/car-lib/src/android/car/vms/VmsSubscriptionState.java
index 08e72ba..436744f 100644
--- a/car-lib/src/android/car/vms/VmsSubscriptionState.java
+++ b/car-lib/src/android/car/vms/VmsSubscriptionState.java
@@ -20,11 +20,11 @@
 import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.util.ArraySet;
 
-import java.util.ArrayList;
+import com.android.internal.util.DataClass;
+
 import java.util.Collections;
-import java.util.HashSet;
-import java.util.List;
 import java.util.Objects;
 import java.util.Set;
 
@@ -41,112 +41,216 @@
  * @hide
  */
 @SystemApi
+@DataClass(genAidl = true, genEqualsHashCode = true, genToString = true)
 public final class VmsSubscriptionState implements Parcelable {
+    /**
+     * Sequence number of the subscription state
+     */
     private final int mSequenceNumber;
-    private final Set<VmsLayer> mLayers;
-    private final Set<VmsAssociatedLayer> mSubscribedLayersFromPublishers;
 
     /**
-     * Constructs a summary of the state of the current subscriptions for publishers to consume
-     * and adjust which layers that the are publishing.
+     * Layers with subscriptions to all publishers
      */
-    public VmsSubscriptionState(int sequenceNumber,
-            @NonNull Set<VmsLayer> subscribedLayers,
-            @NonNull Set<VmsAssociatedLayer> layersFromPublishers) {
-        mSequenceNumber = sequenceNumber;
-        mLayers = Collections.unmodifiableSet(subscribedLayers);
-        mSubscribedLayersFromPublishers = Collections.unmodifiableSet(layersFromPublishers);
+    private @NonNull Set<VmsLayer> mLayers;
+
+    /**
+     * Layers with subscriptions to a subset of publishers
+     */
+    private @NonNull Set<VmsAssociatedLayer> mAssociatedLayers;
+
+    private void onConstructed() {
+        mLayers = Collections.unmodifiableSet(mLayers);
+        mAssociatedLayers = Collections.unmodifiableSet(mAssociatedLayers);
+    }
+
+    private void parcelLayers(Parcel dest, int flags) {
+        dest.writeArraySet(new ArraySet<>(mLayers));
+    }
+
+    @SuppressWarnings("unchecked")
+    private Set<VmsLayer> unparcelLayers(Parcel in) {
+        return (Set<VmsLayer>) in.readArraySet(VmsLayer.class.getClassLoader());
+    }
+
+    private void parcelAssociatedLayers(Parcel dest, int flags) {
+        dest.writeArraySet(new ArraySet<>(mAssociatedLayers));
+    }
+
+    @SuppressWarnings("unchecked")
+    private Set<VmsAssociatedLayer> unparcelAssociatedLayers(Parcel in) {
+        return (Set<VmsAssociatedLayer>) in.readArraySet(VmsAssociatedLayer.class.getClassLoader());
+    }
+
+
+
+    // Code below generated by codegen v1.0.14.
+    //
+    // DO NOT MODIFY!
+    // CHECKSTYLE:OFF Generated code
+    //
+    // To regenerate run:
+    // $ codegen $ANDROID_BUILD_TOP/packages/services/Car/car-lib/src/android/car/vms/VmsSubscriptionState.java
+    //
+    // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+    //   Settings > Editor > Code Style > Formatter Control
+    //@formatter:off
+
+
+    /**
+     * Creates a new VmsSubscriptionState.
+     *
+     * @param sequenceNumber
+     *   Sequence number of the subscription state
+     * @param layers
+     *   Layers with subscriptions to all publishers
+     * @param associatedLayers
+     *   Layers with subscriptions to a subset of publishers
+     */
+    @DataClass.Generated.Member
+    public VmsSubscriptionState(
+            int sequenceNumber,
+            @NonNull Set<VmsLayer> layers,
+            @NonNull Set<VmsAssociatedLayer> associatedLayers) {
+        this.mSequenceNumber = sequenceNumber;
+        this.mLayers = layers;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mLayers);
+        this.mAssociatedLayers = associatedLayers;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mAssociatedLayers);
+
+        onConstructed();
     }
 
     /**
-     * @return sequence number of the subscription state
+     * Sequence number of the subscription state
      */
+    @DataClass.Generated.Member
     public int getSequenceNumber() {
         return mSequenceNumber;
     }
 
     /**
-     * @return set of layers with subscriptions to all publishers
+     * Layers with subscriptions to all publishers
      */
-    @NonNull
-    public Set<VmsLayer> getLayers() {
+    @DataClass.Generated.Member
+    public @NonNull Set<VmsLayer> getLayers() {
         return mLayers;
     }
 
     /**
-     * @return set of layers with subscriptions to a subset of publishers
+     * Layers with subscriptions to a subset of publishers
      */
-    @NonNull
-    public Set<VmsAssociatedLayer> getAssociatedLayers() {
-        return mSubscribedLayersFromPublishers;
+    @DataClass.Generated.Member
+    public @NonNull Set<VmsAssociatedLayer> getAssociatedLayers() {
+        return mAssociatedLayers;
     }
 
     @Override
+    @DataClass.Generated.Member
     public String toString() {
-        StringBuilder sb = new StringBuilder();
-        sb.append("sequence number=").append(mSequenceNumber);
-        sb.append("; layers={");
-        for (VmsLayer layer : mLayers) {
-            sb.append(layer).append(",");
-        }
-        sb.append("}");
-        sb.append("; associatedLayers={");
-        for (VmsAssociatedLayer layer : mSubscribedLayersFromPublishers) {
-            sb.append(layer).append(",");
-        }
-        sb.append("}");
-        return sb.toString();
-    }
+        // You can override field toString logic by defining methods like:
+        // String fieldNameToString() { ... }
 
-    public static final Parcelable.Creator<VmsSubscriptionState> CREATOR = new
-            Parcelable.Creator<VmsSubscriptionState>() {
-                public VmsSubscriptionState createFromParcel(Parcel in) {
-                    return new VmsSubscriptionState(in);
-                }
-
-                public VmsSubscriptionState[] newArray(int size) {
-                    return new VmsSubscriptionState[size];
-                }
-            };
-
-    @Override
-    public void writeToParcel(Parcel out, int flags) {
-        out.writeInt(mSequenceNumber);
-        out.writeParcelableList(new ArrayList(mLayers), flags);
-        out.writeParcelableList(new ArrayList(mSubscribedLayersFromPublishers), flags);
+        return "VmsSubscriptionState { " +
+                "sequenceNumber = " + mSequenceNumber + ", " +
+                "layers = " + mLayers + ", " +
+                "associatedLayers = " + mAssociatedLayers +
+        " }";
     }
 
     @Override
-    public boolean equals(Object o) {
-        if (!(o instanceof VmsSubscriptionState)) {
-            return false;
-        }
-        VmsSubscriptionState p = (VmsSubscriptionState) o;
-        return Objects.equals(p.mSequenceNumber, mSequenceNumber) &&
-                p.mLayers.equals(mLayers) &&
-                p.mSubscribedLayersFromPublishers.equals(mSubscribedLayersFromPublishers);
+    @DataClass.Generated.Member
+    public boolean equals(@android.annotation.Nullable Object o) {
+        // You can override field equality logic by defining either of the methods like:
+        // boolean fieldNameEquals(VmsSubscriptionState other) { ... }
+        // boolean fieldNameEquals(FieldType otherValue) { ... }
+
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        @SuppressWarnings("unchecked")
+        VmsSubscriptionState that = (VmsSubscriptionState) o;
+        //noinspection PointlessBooleanExpression
+        return true
+                && mSequenceNumber == that.mSequenceNumber
+                && Objects.equals(mLayers, that.mLayers)
+                && Objects.equals(mAssociatedLayers, that.mAssociatedLayers);
     }
 
     @Override
+    @DataClass.Generated.Member
     public int hashCode() {
-        return Objects.hash(mSequenceNumber, mLayers, mSubscribedLayersFromPublishers);
+        // You can override field hashCode logic by defining methods like:
+        // int fieldNameHashCode() { ... }
+
+        int _hash = 1;
+        _hash = 31 * _hash + mSequenceNumber;
+        _hash = 31 * _hash + Objects.hashCode(mLayers);
+        _hash = 31 * _hash + Objects.hashCode(mAssociatedLayers);
+        return _hash;
     }
 
     @Override
-    public int describeContents() {
-        return 0;
+    @DataClass.Generated.Member
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        // You can override field parcelling by defining methods like:
+        // void parcelFieldName(Parcel dest, int flags) { ... }
+
+        dest.writeInt(mSequenceNumber);
+        parcelLayers(dest, flags);
+        parcelAssociatedLayers(dest, flags);
     }
 
-    private VmsSubscriptionState(Parcel in) {
-        mSequenceNumber = in.readInt();
+    @Override
+    @DataClass.Generated.Member
+    public int describeContents() { return 0; }
 
-        List<VmsLayer> layers = new ArrayList<>();
-        in.readParcelableList(layers, VmsLayer.class.getClassLoader());
-        mLayers = Collections.unmodifiableSet(new HashSet(layers));
+    /** @hide */
+    @SuppressWarnings({"unchecked", "RedundantCast"})
+    @DataClass.Generated.Member
+    /* package-private */ VmsSubscriptionState(@NonNull Parcel in) {
+        // You can override field unparcelling by defining methods like:
+        // static FieldType unparcelFieldName(Parcel in) { ... }
 
-        List<VmsAssociatedLayer> associatedLayers = new ArrayList<>();
-        in.readParcelableList(associatedLayers, VmsAssociatedLayer.class.getClassLoader());
-        mSubscribedLayersFromPublishers =
-                Collections.unmodifiableSet(new HashSet(associatedLayers));
+        int sequenceNumber = in.readInt();
+        Set<VmsLayer> layers = unparcelLayers(in);
+        Set<VmsAssociatedLayer> associatedLayers = unparcelAssociatedLayers(in);
+
+        this.mSequenceNumber = sequenceNumber;
+        this.mLayers = layers;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mLayers);
+        this.mAssociatedLayers = associatedLayers;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mAssociatedLayers);
+
+        onConstructed();
     }
-}
\ No newline at end of file
+
+    @DataClass.Generated.Member
+    public static final @NonNull Parcelable.Creator<VmsSubscriptionState> CREATOR
+            = new Parcelable.Creator<VmsSubscriptionState>() {
+        @Override
+        public VmsSubscriptionState[] newArray(int size) {
+            return new VmsSubscriptionState[size];
+        }
+
+        @Override
+        public VmsSubscriptionState createFromParcel(@NonNull Parcel in) {
+            return new VmsSubscriptionState(in);
+        }
+    };
+
+    @DataClass.Generated(
+            time = 1582061018243L,
+            codegenVersion = "1.0.14",
+            sourceFile = "packages/services/Car/car-lib/src/android/car/vms/VmsSubscriptionState.java",
+            inputSignatures = "private final  int mSequenceNumber\nprivate @android.annotation.NonNull java.util.Set<android.car.vms.VmsLayer> mLayers\nprivate @android.annotation.NonNull java.util.Set<android.car.vms.VmsAssociatedLayer> mAssociatedLayers\nprivate  void onConstructed()\nprivate  void parcelLayers(android.os.Parcel,int)\nprivate @java.lang.SuppressWarnings(\"unchecked\") java.util.Set<android.car.vms.VmsLayer> unparcelLayers(android.os.Parcel)\nprivate  void parcelAssociatedLayers(android.os.Parcel,int)\nprivate @java.lang.SuppressWarnings(\"unchecked\") java.util.Set<android.car.vms.VmsAssociatedLayer> unparcelAssociatedLayers(android.os.Parcel)\nclass VmsSubscriptionState extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genAidl=true, genEqualsHashCode=true, genToString=true)")
+    @Deprecated
+    private void __metadata() {}
+
+
+    //@formatter:on
+    // End of generated code
+
+}
diff --git a/car-lib/src/android/car/watchdog/CarWatchdogManager.java b/car-lib/src/android/car/watchdog/CarWatchdogManager.java
new file mode 100644
index 0000000..6f85982
--- /dev/null
+++ b/car-lib/src/android/car/watchdog/CarWatchdogManager.java
@@ -0,0 +1,241 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.car.watchdog;
+
+import android.annotation.CallbackExecutor;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.automotive.watchdog.ICarWatchdogClient;
+import android.car.Car;
+import android.car.CarManagerBase;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.lang.ref.WeakReference;
+import java.util.concurrent.Executor;
+
+/**
+ * Provides APIs and interfaces for client health checking.
+ *
+ * @hide
+ */
+@SystemApi
+public final class CarWatchdogManager extends CarManagerBase {
+
+    private static final String TAG = CarWatchdogManager.class.getSimpleName();
+
+    /** Timeout for services which should be responsive. The length is 3,000 milliseconds. */
+    public static final int TIMEOUT_CRITICAL = 0;
+
+    /** Timeout for services which are relatively responsive. The length is 5,000 milliseconds. */
+    public static final int TIMEOUT_MODERATE = 1;
+
+    /** Timeout for all other services. The length is 10,000 milliseconds. */
+    public static final int TIMEOUT_NORMAL = 2;
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = "TIMEOUT_", value = {
+            TIMEOUT_CRITICAL,
+            TIMEOUT_MODERATE,
+            TIMEOUT_NORMAL,
+    })
+    @Target({ElementType.TYPE_USE})
+    public @interface TimeoutLengthEnum {}
+
+    private final ICarWatchdogService mService;
+    private final ICarWatchdogClientImpl mClientImpl;
+    private final Object mLock = new Object();
+    @GuardedBy("mLock")
+    private CarWatchdogClientCallback mRegisteredClient;
+    @GuardedBy("mLock")
+    private Executor mCallbackExecutor;
+
+    /**
+     * CarWatchdogClientCallback is implemented by the clients which want to be health-checked by
+     * car watchdog server. Every time onCheckHealthStatus is called, they are expected to
+     * respond by calling {@link CarWatchdogManager.tellClientAlive} within timeout. If they don't
+     * respond, car watchdog server reports the current state and kills them.
+     *
+     * <p>Before car watchdog server kills the client, it calls onPrepareProcessKill to allow them
+     * to prepare the termination. They will be killed in 1 second.
+     */
+    public abstract class CarWatchdogClientCallback {
+        /**
+         * Car watchdog server pings the client to check if it is alive.
+         *
+         * <p>The callback method is called at the Executor which is specifed in {@link
+         * #registerClient}.
+         *
+         * @param sessionId Unique id to distinguish each health checking.
+         * @param timeout Time duration within which the client should respond.
+         *
+         * @return whether the response is immediately acknowledged. If {@code true}, car watchdog
+         *         server considers that the response is acknowledged already. If {@code false},
+         *         the client should call {@link CarWatchdogManager.tellClientAlive} later to tell
+         *         that it is alive.
+         */
+        public boolean onCheckHealthStatus(int sessionId, @TimeoutLengthEnum int timeout) {
+            return false;
+        }
+
+        /**
+         * Car watchdog server notifies the client that it will be terminated in 1 second.
+         *
+         * <p>The callback method is called at the Executor which is specifed in {@link
+         * #registerClient}.
+         */
+        // TODO(b/150006093): After adding a callback to ICarWatchdogClient, subsequent
+        // implementation should be done in CarWatchdogService and CarWatchdogManager.
+        public void onPrepareProcessTermination() {}
+    }
+
+    /** @hide */
+    public CarWatchdogManager(Car car, IBinder service) {
+        super(car);
+        mService = ICarWatchdogService.Stub.asInterface(service);
+        mClientImpl = new ICarWatchdogClientImpl(this);
+    }
+
+    /**
+     * Registers the car watchdog clients to {@link CarWatchdogManager}.
+     *
+     * <p>It is allowed to register a client from any thread, but only one client can be
+     * registered. If two or more clients are needed, create a new {@link Car} and register a client
+     * to it.
+     *
+     * @param client Watchdog client implementing {@link CarWatchdogClientCallback} interface.
+     * @param timeout The time duration within which the client desires to respond. The actual
+     *        timeout is decided by watchdog server.
+     * @throws IllegalStateException if at least one client is already registered.
+     */
+    @RequiresPermission(Car.PERMISSION_USE_CAR_WATCHDOG)
+    public void registerClient(@NonNull @CallbackExecutor Executor executor,
+            @NonNull CarWatchdogClientCallback client, @TimeoutLengthEnum int timeout) {
+        synchronized (mLock) {
+            if (mRegisteredClient == client) {
+                return;
+            }
+            if (mRegisteredClient != null) {
+                throw new IllegalStateException(
+                        "Cannot register the client. Only one client can be registered.");
+            }
+            mRegisteredClient = client;
+            mCallbackExecutor = executor;
+        }
+        try {
+            mService.registerClient(mClientImpl, timeout);
+        } catch (RemoteException e) {
+            synchronized (mLock) {
+                mRegisteredClient = null;
+            }
+            handleRemoteExceptionFromCarService(e);
+        }
+    }
+
+    /**
+     * Unregisters the car watchdog client from {@link CarWatchdogManager}.
+     *
+     * @param client Watchdog client implementing {@link CarWatchdogClientCallback} interface.
+     */
+    @RequiresPermission(Car.PERMISSION_USE_CAR_WATCHDOG)
+    public void unregisterClient(@NonNull CarWatchdogClientCallback client) {
+        synchronized (mLock) {
+            if (mRegisteredClient != client) {
+                Log.w(TAG, "Cannot unregister the client. It has not been registered.");
+                return;
+            }
+            mRegisteredClient = null;
+            mCallbackExecutor = null;
+        }
+        try {
+            mService.unregisterClient(mClientImpl);
+        } catch (RemoteException e) {
+            handleRemoteExceptionFromCarService(e);
+        }
+    }
+
+    /**
+     * Tells {@link CarWatchdogManager} that the client is alive.
+     *
+     * @param client Watchdog client implementing {@link CarWatchdogClientCallback} interface.
+     * @param sessionId Session id given by {@link CarWatchdogManager}.
+     * @throws IllegalStateException if {@code client} is not registered.
+     */
+    @RequiresPermission(Car.PERMISSION_USE_CAR_WATCHDOG)
+    public void tellClientAlive(@NonNull CarWatchdogClientCallback client, int sessionId) {
+        // TODO(ericjeong): Need to check if main thread is active regardless of how many clients
+        // are registered.
+        synchronized (mLock) {
+            if (mRegisteredClient != client) {
+                throw new IllegalStateException(
+                        "Cannot report client status. The client has not been registered.");
+            }
+        }
+        try {
+            mService.tellClientAlive(mClientImpl, sessionId);
+        } catch (RemoteException e) {
+            handleRemoteExceptionFromCarService(e);
+        }
+    }
+
+    /** @hide */
+    @Override
+    public void onCarDisconnected() {
+        // nothing to do
+    }
+
+    private void checkClientStatus(int sessionId, int timeout) {
+        CarWatchdogClientCallback client;
+        Executor executor;
+        synchronized (mLock) {
+            if (mRegisteredClient == null) {
+                Log.w(TAG, "Cannot check client status. The client has not been registered.");
+                return;
+            }
+            client = mRegisteredClient;
+            executor = mCallbackExecutor;
+        }
+        executor.execute(() -> client.onCheckHealthStatus(sessionId, timeout));
+    }
+
+    /** @hide */
+    private static final class ICarWatchdogClientImpl extends ICarWatchdogClient.Stub {
+        private final WeakReference<CarWatchdogManager> mManager;
+
+        private ICarWatchdogClientImpl(CarWatchdogManager manager) {
+            mManager = new WeakReference<>(manager);
+        }
+
+        @Override
+        public void checkIfAlive(int sessionId, int timeout) {
+            CarWatchdogManager manager = mManager.get();
+            if (manager != null) {
+                manager.checkClientStatus(sessionId, timeout);
+            }
+        }
+    }
+}
diff --git a/car-lib/src/android/car/watchdog/ICarWatchdogService.aidl b/car-lib/src/android/car/watchdog/ICarWatchdogService.aidl
new file mode 100644
index 0000000..06aca25
--- /dev/null
+++ b/car-lib/src/android/car/watchdog/ICarWatchdogService.aidl
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.car.watchdog;
+
+import android.automotive.watchdog.ICarWatchdogClient;
+import android.automotive.watchdog.TimeoutLength;
+
+/** @hide */
+oneway interface ICarWatchdogService {
+    void registerClient(in ICarWatchdogClient client, in TimeoutLength timeout);
+    void unregisterClient(in ICarWatchdogClient client);
+    void tellClientAlive(in ICarWatchdogClient client, in int sessionId);
+}
diff --git a/car-lib/src_feature_future/com/android/car/internal/FeatureConfiguration.java b/car-lib/src_feature_future/com/android/car/internal/FeatureConfiguration.java
deleted file mode 100644
index 66cff60..0000000
--- a/car-lib/src_feature_future/com/android/car/internal/FeatureConfiguration.java
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.car.internal;
-
-/**
- * Class to hold static boolean flag for enabling / disabling features.
- *
- * @hide
- */
-public class FeatureConfiguration {
-    /** Enable future feature by default. */
-    public static final boolean DEFAULT = true;
-    /** product configuration in CarInfoManager */
-    public static final boolean ENABLE_PRODUCT_CONFIGURATION_INFO = DEFAULT;
-    public static final boolean ENABLE_VEHICLE_MAP_SERVICE = DEFAULT;
-}
diff --git a/car-lib/src_stub/android/media/AudioPatch.java b/car-lib/src_stub/android/media/AudioPatch.java
deleted file mode 100644
index f50481b..0000000
--- a/car-lib/src_stub/android/media/AudioPatch.java
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media;
-
-/**
- * This is added to build system-stub library as hidden API, AudioPatch was used by mistake in
- * CarAudioManager for system API. This should not be used by apps using system API as the real
- * API from framework is really hidden API.
- *
- * @hide
- */
-public class AudioPatch {
-}
diff --git a/car-systemtest-lib/Android.bp b/car-systemtest-lib/Android.bp
new file mode 100644
index 0000000..aee61a3
--- /dev/null
+++ b/car-systemtest-lib/Android.bp
@@ -0,0 +1,28 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//
+
+java_library {
+
+    name: "car-systemtest",
+
+    srcs: [
+        "src/**/*.java",
+        "src/**/I*.aidl",
+    ],
+
+    libs: ["android.car"],
+
+}
diff --git a/car-systemtest-lib/Android.mk b/car-systemtest-lib/Android.mk
deleted file mode 100644
index 6e4def5..0000000
--- a/car-systemtest-lib/Android.mk
+++ /dev/null
@@ -1,31 +0,0 @@
-# Copyright (C) 2015 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-#
-
-LOCAL_PATH:= $(call my-dir)
-
-include $(CLEAR_VARS)
-
-LOCAL_MODULE := car-systemtest
-
-LOCAL_MODULE_TAGS := optional
-
-LOCAL_SRC_FILES := $(call all-java-files-under, src) $(call all-Iaidl-files-under, src)
-
-LOCAL_AIDL_INCLUDES += packages/services/Car/libvehiclenetwork/java/src/
-
-LOCAL_JAVA_LIBRARIES += android.car
-
-include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/car-test-lib/Android.bp b/car-test-lib/Android.bp
index 3d913a0..7113348 100644
--- a/car-test-lib/Android.bp
+++ b/car-test-lib/Android.bp
@@ -24,7 +24,13 @@
     },
     static_libs: [
         "android.car",
+        "car-service-test-lib",
+    ],
+    libs: [
         "mockito",
     ],
     installable: false,
+    dist: {
+        targets: ["dist_files"],
+    }
 }
diff --git a/car-test-lib/Android.mk b/car-test-lib/Android.mk
deleted file mode 100644
index 6ece515..0000000
--- a/car-test-lib/Android.mk
+++ /dev/null
@@ -1,27 +0,0 @@
-# Copyright (C) 2019 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-#
-
-#disble build in PDK, missing aidl import breaks build
-ifneq ($(TARGET_BUILD_PDK),true)
-
-include $(CLEAR_VARS)
-
-ifeq ($(BOARD_IS_AUTOMOTIVE), true)
-full_classes_jar := $(call intermediates-dir-for,JAVA_LIBRARIES,android.car.testapi,,COMMON)/classes.jar
-$(call dist-for-goals,dist_files,$(full_classes_jar):android.car.testapi.jar)
-endif
-
-endif #TARGET_BUILD_PDK
diff --git a/car-test-lib/src/android/car/testapi/CarAppFocusController.java b/car-test-lib/src/android/car/testapi/CarAppFocusController.java
new file mode 100644
index 0000000..9aafc65
--- /dev/null
+++ b/car-test-lib/src/android/car/testapi/CarAppFocusController.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.car.testapi;
+
+import android.os.Looper;
+
+/** Used to manipulate properties of the CarAppFocusManager in unit tests. */
+public interface CarAppFocusController {
+    /** Set what the UID of the "foreground" app is */
+    void setForegroundUid(int uid);
+
+    /** Set what the PID of the "foreground" app is */
+    void setForegroundPid(int pid);
+
+    /** Resets the foreground UID such that all UIDs are considered to be foreground */
+    void resetForegroundUid();
+
+    /** Resets the foreground PID such that all PIDs are considered to be foreground */
+    void resetForegroundPid();
+
+    /** Gets the {@link android.os.Looper} for the internal handler for the service */
+    Looper getLooper();
+}
diff --git a/car-test-lib/src/android/car/testapi/CarNavigationStatusController.java b/car-test-lib/src/android/car/testapi/CarNavigationStatusController.java
new file mode 100644
index 0000000..bb8324f
--- /dev/null
+++ b/car-test-lib/src/android/car/testapi/CarNavigationStatusController.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.car.testapi;
+
+import android.os.Bundle;
+
+/**
+ * Controller to manipulate and verify {@link android.car.navigation.CarNavigationStatusManager} in
+ * unit tests.
+ */
+public interface CarNavigationStatusController {
+    /** Returns the last state sent to the manager. */
+    Bundle getCurrentNavState();
+
+    /** Sets the current internal cluster state to use a cluster that can accept custom images. */
+    void setCustomImageClusterInfo(
+            int minIntervalMillis,
+            int imageWidth,
+            int imageHeight,
+            int imageColorDepthBits);
+
+    /** Sets the current internal cluster state to use a cluster that can accept image codes. */
+    void setImageCodeClusterInfo(int minIntervalMillis);
+}
diff --git a/car-test-lib/src/android/car/testapi/CarProjectionController.java b/car-test-lib/src/android/car/testapi/CarProjectionController.java
index 19d8aeb..45cd155 100644
--- a/car-test-lib/src/android/car/testapi/CarProjectionController.java
+++ b/car-test-lib/src/android/car/testapi/CarProjectionController.java
@@ -18,12 +18,12 @@
 
 import android.car.CarProjectionManager;
 import android.car.projection.ProjectionOptions;
-import android.net.wifi.WifiConfiguration;
+import android.net.wifi.SoftApConfiguration;
 
 /** Controller to change behavior of {@link CarProjectionManager} */
 public interface CarProjectionController {
-    /** Set WifiConfiguration for wireless projection or null to simulate failure to start AP */
-    void setWifiConfiguration(WifiConfiguration wifiConfiguration);
+    /** Set SoftApConfiguration for wireless projection or null to simulate failure to start AP */
+    void setSoftApConfiguration(SoftApConfiguration softApConfiguration);
 
     /**
      * Sets {@link ProjectionOptions} object returns by
diff --git a/car-test-lib/src/android/car/testapi/CarUxRestrictionsController.java b/car-test-lib/src/android/car/testapi/CarUxRestrictionsController.java
new file mode 100644
index 0000000..0881f11
--- /dev/null
+++ b/car-test-lib/src/android/car/testapi/CarUxRestrictionsController.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.car.testapi;
+
+import android.os.RemoteException;
+
+/**
+ * Used to set the current set of UX restrictions being enforced in the test.
+ */
+public interface CarUxRestrictionsController {
+    /**
+     * Sets the current set of UX restrictions.
+     *
+     * @param restrictions is a bitmask of UX restrictions as defined in {@link
+     * android.car.drivingstate.CarUxRestrictions}
+     * @throws RemoteException when the underlying service fails to set the given restrictions.
+     */
+    void setUxRestrictions(int restrictions) throws RemoteException;
+
+    /**
+     * Clears all UX restrictions.
+     *
+     * @throws RemoteException when the underlying service fails to clear the restrictions.
+     */
+    void clearUxRestrictions() throws RemoteException;
+
+    /**
+     * Returns {@code true} if a {@link
+     * android.car.drivingstate.CarUxRestrictionsManager.OnUxRestrictionsChangedListener} is
+     * registered with the manager, otherwise returns {@code false}.
+     */
+    boolean isListenerRegistered();
+}
diff --git a/car-test-lib/src/android/car/testapi/FakeAppFocusService.java b/car-test-lib/src/android/car/testapi/FakeAppFocusService.java
new file mode 100644
index 0000000..df97525
--- /dev/null
+++ b/car-test-lib/src/android/car/testapi/FakeAppFocusService.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.car.testapi;
+
+import android.car.CarAppFocusManager;
+import android.car.IAppFocus;
+import android.content.Context;
+import android.os.Looper;
+
+import com.android.car.AppFocusService;
+
+/**
+ * Fake service that is used by {@link FakeCar} to provide an implementation of {@link IAppFocus}
+ * to allow the use of {@link CarAppFocusManager} in unit tests.
+ */
+public class FakeAppFocusService extends AppFocusService implements CarAppFocusController {
+    FakeSystemActivityMonitoringService mSystemActivityMonitoringService;
+
+    private FakeAppFocusService(
+            Context context,
+            FakeSystemActivityMonitoringService fakeSystemActivityMonitoringService) {
+        super(context, fakeSystemActivityMonitoringService);
+        mSystemActivityMonitoringService = fakeSystemActivityMonitoringService;
+    }
+
+    public FakeAppFocusService(Context context) {
+        this(context, new FakeSystemActivityMonitoringService(context));
+        super.init();
+    }
+
+    //************************* CarAppFocusController implementation ******************************/
+
+    @Override
+    public synchronized void setForegroundUid(int uid) {
+        mSystemActivityMonitoringService.setForegroundUid(uid);
+    }
+
+    @Override
+    public synchronized void setForegroundPid(int pid) {
+        mSystemActivityMonitoringService.setForegroundPid(pid);
+    }
+
+    @Override
+    public synchronized void resetForegroundUid() {
+        mSystemActivityMonitoringService.resetForegroundUid();
+    }
+
+    @Override
+    public synchronized void resetForegroundPid() {
+        mSystemActivityMonitoringService.resetForegroundPid();
+    }
+
+    @Override
+    public Looper getLooper() {
+        return super.getLooper();
+    }
+}
diff --git a/car-test-lib/src/android/car/testapi/FakeCar.java b/car-test-lib/src/android/car/testapi/FakeCar.java
index 47757be..b6f22fb 100644
--- a/car-test-lib/src/android/car/testapi/FakeCar.java
+++ b/car-test-lib/src/android/car/testapi/FakeCar.java
@@ -17,20 +17,16 @@
 package android.car.testapi;
 
 import android.car.Car;
-import android.car.IAppFocus;
 import android.car.ICar;
 import android.car.ICarBluetooth;
 import android.car.cluster.IInstrumentClusterManagerService;
-import android.car.cluster.renderer.IInstrumentClusterNavigation;
 import android.car.content.pm.ICarPackageManager;
 import android.car.diagnostic.ICarDiagnostic;
 import android.car.drivingstate.ICarDrivingState;
-import android.car.drivingstate.ICarUxRestrictionsManager;
 import android.car.hardware.power.ICarPower;
 import android.car.media.ICarAudio;
 import android.car.settings.ICarConfigurationManager;
 import android.car.storagemonitoring.ICarStorageMonitoring;
-import android.car.vms.IVmsSubscriberService;
 import android.content.Context;
 import android.os.IBinder;
 import android.os.RemoteException;
@@ -39,6 +35,9 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import java.util.Collections;
+import java.util.List;
+
 /*
     The idea behind this class is that we can fake-out interfaces between Car*Manager and
     Car Service.  Effectively creating a fake version of Car Service that can run under Robolectric
@@ -106,28 +105,54 @@
         return mService.mCarProjection;
     }
 
+    /**
+     * Returns the test controller to change the behavior of the underlying
+     * {@link android.car.CarAppFocusManager}
+     */
+    public CarAppFocusController getAppFocusController() {
+        return mService.mAppFocus;
+    }
+
+    /**
+     * Returns the test controller to change the behavior of as well as query the underlying {@link
+     * android.car.navigation.CarNavigationStatusManager}.
+     */
+    public CarNavigationStatusController getCarNavigationStatusController() {
+        return mService.mInstrumentClusterNavigation;
+    }
+
+    /**
+     * Returns a test controller that can modify and query the underlying service for the {@link
+     * android.car.drivingstate.CarUxRestrictionsManager}.
+     */
+    public CarUxRestrictionsController getCarUxRestrictionController() {
+        return mService.mCarUxRestrictionService;
+    }
+
     private static class FakeCarService extends ICar.Stub {
         @Mock ICarAudio.Stub mCarAudio;
-        @Mock IAppFocus.Stub mAppFocus;
         @Mock ICarPackageManager.Stub mCarPackageManager;
         @Mock ICarDiagnostic.Stub mCarDiagnostic;
         @Mock ICarPower.Stub mCarPower;
-        @Mock IInstrumentClusterNavigation.Stub mClusterNavigation;
         @Mock IInstrumentClusterManagerService.Stub mClusterService;
-        @Mock IVmsSubscriberService.Stub mVmsSubscriberService;
         @Mock ICarBluetooth.Stub mCarBluetooth;
         @Mock ICarStorageMonitoring.Stub mCarStorageMonitoring;
         @Mock ICarDrivingState.Stub mCarDrivingState;
-        @Mock ICarUxRestrictionsManager.Stub mCarUxRestriction;
         @Mock ICarConfigurationManager.Stub mCarConfigurationManager;
 
+        private final FakeAppFocusService mAppFocus;
         private final FakeCarPropertyService mCarProperty;
         private final FakeCarProjectionService mCarProjection;
+        private final FakeInstrumentClusterNavigation mInstrumentClusterNavigation;
+        private final FakeCarUxRestrictionsService mCarUxRestrictionService;
 
         FakeCarService(Context context) {
             MockitoAnnotations.initMocks(this);
+            mAppFocus = new FakeAppFocusService(context);
             mCarProperty = new FakeCarPropertyService();
             mCarProjection = new FakeCarProjectionService(context);
+            mInstrumentClusterNavigation = new FakeInstrumentClusterNavigation();
+            mCarUxRestrictionService = new FakeCarUxRestrictionsService();
         }
 
         @Override
@@ -136,6 +161,17 @@
         }
 
         @Override
+        public void onUserLifecycleEvent(int eventType, long timestampMs, int fromUserId,
+                int toUserId) {
+            // Nothing to do yet.
+        }
+
+        @Override
+        public void onFirstUserUnlocked(int userId, long timestampMs, long duration) {
+            // Nothing to do yet.
+        }
+
+        @Override
         public void setUserLockStatus(int userHandle, int unlocked) throws RemoteException {
             // Nothing to do yet.
         }
@@ -166,13 +202,11 @@
                 case Car.VENDOR_EXTENSION_SERVICE:
                     return mCarProperty;
                 case Car.CAR_NAVIGATION_SERVICE:
-                    return mClusterNavigation;
+                    return mInstrumentClusterNavigation;
                 case Car.CAR_INSTRUMENT_CLUSTER_SERVICE:
                     return mClusterService;
                 case Car.PROJECTION_SERVICE:
                     return mCarProjection;
-                case Car.VMS_SUBSCRIBER_SERVICE:
-                    return mVmsSubscriberService;
                 case Car.BLUETOOTH_SERVICE:
                     return mCarBluetooth;
                 case Car.STORAGE_MONITORING_SERVICE:
@@ -180,7 +214,7 @@
                 case Car.CAR_DRIVING_STATE_SERVICE:
                     return mCarDrivingState;
                 case Car.CAR_UX_RESTRICTION_SERVICE:
-                    return mCarUxRestriction;
+                    return mCarUxRestrictionService;
                 case Car.CAR_CONFIGURATION_SERVICE:
                     return mCarConfigurationManager;
                 default:
@@ -193,6 +227,41 @@
         public int getCarConnectionType() throws RemoteException {
             return Car.CONNECTION_TYPE_EMBEDDED;
         }
+
+        @Override
+        public boolean isFeatureEnabled(String featureName) {
+            return false;
+        }
+
+        @Override
+        public int enableFeature(String featureName) {
+            return Car.FEATURE_REQUEST_SUCCESS;
+        }
+
+        @Override
+        public int disableFeature(String featureName) {
+            return Car.FEATURE_REQUEST_SUCCESS;
+        }
+
+        @Override
+        public List<String> getAllEnabledFeatures() {
+            return Collections.emptyList();
+        }
+
+        @Override
+        public List<String> getAllPendingDisabledFeatures() {
+            return Collections.emptyList();
+        }
+
+        @Override
+        public List<String> getAllPendingEnabledFeatures() {
+            return Collections.emptyList();
+        }
+
+        @Override
+        public String getCarManagerClassForFeature(String featureName) {
+            return null;
+        }
     }
 
 }
diff --git a/car-test-lib/src/android/car/testapi/FakeCarProjectionService.java b/car-test-lib/src/android/car/testapi/FakeCarProjectionService.java
index 0408f5d..275ae1c 100644
--- a/car-test-lib/src/android/car/testapi/FakeCarProjectionService.java
+++ b/car-test-lib/src/android/car/testapi/FakeCarProjectionService.java
@@ -28,7 +28,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.ServiceConnection;
-import android.net.wifi.WifiConfiguration;
+import android.net.wifi.SoftApConfiguration;
 import android.os.Bundle;
 import android.os.IBinder;
 import android.os.Message;
@@ -51,7 +51,7 @@
 
     private final Context mContext;
 
-    private WifiConfiguration mWifiConfiguration;
+    private SoftApConfiguration mSoftApConfiguration;
     private Messenger mApMessenger;
     private IBinder mApBinder;
     private List<ICarProjectionStatusListener> mStatusListeners = new ArrayList<>();
@@ -115,9 +115,9 @@
         mApBinder = binder;
 
         Message message = Message.obtain();
-        if (mWifiConfiguration != null) {
+        if (mSoftApConfiguration != null) {
             message.what = CarProjectionManager.PROJECTION_AP_STARTED;
-            message.obj = mWifiConfiguration;
+            message.obj = mSoftApConfiguration;
         } else {
             message.what = CarProjectionManager.PROJECTION_AP_FAILED;
             message.arg1 = ProjectionAccessPointCallback.ERROR_GENERIC;
@@ -179,8 +179,8 @@
     }
 
     @Override
-    public void setWifiConfiguration(WifiConfiguration wifiConfiguration) {
-        mWifiConfiguration = wifiConfiguration;
+    public void setSoftApConfiguration(SoftApConfiguration softApConfiguration) {
+        mSoftApConfiguration = softApConfiguration;
     }
 
     @Override
diff --git a/car-test-lib/src/android/car/testapi/FakeCarPropertyService.java b/car-test-lib/src/android/car/testapi/FakeCarPropertyService.java
index a1c8bb3..0060302 100644
--- a/car-test-lib/src/android/car/testapi/FakeCarPropertyService.java
+++ b/car-test-lib/src/android/car/testapi/FakeCarPropertyService.java
@@ -92,7 +92,8 @@
     }
 
     @Override
-    public void setProperty(CarPropertyValue prop) throws RemoteException {
+    public void setProperty(CarPropertyValue prop, ICarPropertyEventListener listener)
+            throws RemoteException {
         mValues.put(PropKey.of(prop), prop);
         mValuesSet.add(prop);
         sendEvent(prop);
diff --git a/car-test-lib/src/android/car/testapi/FakeCarUxRestrictionsService.java b/car-test-lib/src/android/car/testapi/FakeCarUxRestrictionsService.java
new file mode 100644
index 0000000..6100e93
--- /dev/null
+++ b/car-test-lib/src/android/car/testapi/FakeCarUxRestrictionsService.java
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.car.testapi;
+
+import static android.car.drivingstate.CarUxRestrictions.UX_RESTRICTIONS_BASELINE;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.car.drivingstate.CarUxRestrictionsConfiguration;
+import android.car.drivingstate.ICarUxRestrictionsChangeListener;
+import android.car.drivingstate.ICarUxRestrictionsManager;
+import android.os.IRemoteCallback;
+import android.os.RemoteException;
+import android.os.SystemClock;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * A fake implementation of {@link ICarUxRestrictionsManager.Stub} to facilitate the use of {@link
+ * android.car.drivingstate.CarUxRestrictionsManager} in external unit tests.
+ *
+ * @hide
+ */
+public class FakeCarUxRestrictionsService extends ICarUxRestrictionsManager.Stub implements
+        CarUxRestrictionsController {
+    private final Object mLock = new Object();
+
+    @GuardedBy("mLock")
+    private CarUxRestrictions mCarUxRestrictions;
+    @GuardedBy("mLock")
+    private ICarUxRestrictionsChangeListener mListener;
+
+    @GuardedBy("mLock")
+    private String mMode = "baseline";
+
+    private static CarUxRestrictions createCarUxRestrictions(int activeRestrictions) {
+        return new CarUxRestrictions.Builder(
+                false, /* requires driving distraction optimization */
+                activeRestrictions,
+                SystemClock.elapsedRealtimeNanos())
+                .build();
+    }
+
+    FakeCarUxRestrictionsService() {
+        synchronized (mLock) {
+            mCarUxRestrictions = createCarUxRestrictions(UX_RESTRICTIONS_BASELINE);
+        }
+    }
+
+    @Override
+    public void registerUxRestrictionsChangeListener(
+            ICarUxRestrictionsChangeListener listener, int displayId) {
+        synchronized (mLock) {
+            this.mListener = listener;
+        }
+    }
+
+    @Override
+    public void unregisterUxRestrictionsChangeListener(ICarUxRestrictionsChangeListener listener) {
+        synchronized (mLock) {
+            this.mListener = null;
+        }
+    }
+
+    @Override
+    public CarUxRestrictions getCurrentUxRestrictions(int displayId) {
+        synchronized (mLock) {
+            return mCarUxRestrictions;
+        }
+    }
+
+    @Override
+    public List<CarUxRestrictionsConfiguration> getStagedConfigs() {
+        return Collections.emptyList();
+    }
+
+    @Override
+    public List<CarUxRestrictionsConfiguration> getConfigs() {
+        return Collections.emptyList();
+    }
+
+    @Override
+    public boolean setRestrictionMode(String mode) throws RemoteException {
+        synchronized (mLock) {
+            mMode = mode;
+        }
+        return true;
+    }
+
+    @Override
+    public String getRestrictionMode() throws RemoteException {
+        synchronized (mLock) {
+            return mMode;
+        }
+    }
+
+    @Override
+    public void reportVirtualDisplayToPhysicalDisplay(IRemoteCallback binder, int virtualDisplayId,
+            int physicalDisplayId) throws RemoteException {
+
+    }
+
+    @Override
+    public int getMappedPhysicalDisplayOfVirtualDisplay(int displayId) throws RemoteException {
+        return 0;
+    }
+
+    @Override
+    public boolean saveUxRestrictionsConfigurationForNextBoot(
+            List<CarUxRestrictionsConfiguration> config) {
+        return true;
+    }
+
+    /**************************** CarUxRestrictionsController impl ********************************/
+
+    @Override
+    public void setUxRestrictions(int restrictions) throws RemoteException {
+        synchronized (mLock) {
+            mCarUxRestrictions = createCarUxRestrictions(restrictions);
+            if (isListenerRegistered()) {
+                mListener.onUxRestrictionsChanged(mCarUxRestrictions);
+            }
+        }
+    }
+
+    @Override
+    public void clearUxRestrictions() throws RemoteException {
+        setUxRestrictions(0);
+    }
+
+    @Override
+    public boolean isListenerRegistered() {
+        synchronized (mLock) {
+            return mListener != null;
+        }
+    }
+
+}
diff --git a/car-test-lib/src/android/car/testapi/FakeInstrumentClusterNavigation.java b/car-test-lib/src/android/car/testapi/FakeInstrumentClusterNavigation.java
new file mode 100644
index 0000000..d427028
--- /dev/null
+++ b/car-test-lib/src/android/car/testapi/FakeInstrumentClusterNavigation.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.car.testapi;
+
+import android.car.cluster.renderer.IInstrumentClusterNavigation;
+import android.car.navigation.CarNavigationInstrumentCluster;
+import android.os.Bundle;
+
+/**
+ * Fake implementation of {@link IInstrumentClusterNavigation} used by FakeCar.
+ *
+ * @hide
+ */
+public class FakeInstrumentClusterNavigation extends IInstrumentClusterNavigation.Stub
+        implements CarNavigationStatusController {
+    private static final int DEFAULT_MIN_UPDATE_INTERVAL_MILLIS = 1000;
+
+    private Bundle mCurrentNavigationState;
+    private CarNavigationInstrumentCluster mCarNavigationInstrumentCluster =
+            CarNavigationInstrumentCluster.createCluster(DEFAULT_MIN_UPDATE_INTERVAL_MILLIS);
+
+    @Override
+    public void onNavigationStateChanged(Bundle bundle) {
+        mCurrentNavigationState = bundle;
+    }
+
+    @Override
+    public CarNavigationInstrumentCluster getInstrumentClusterInfo() {
+        return mCarNavigationInstrumentCluster;
+    }
+
+
+    //********************** CarNavigationStatusController implementation *************************/
+    @Override
+    public Bundle getCurrentNavState() {
+        return mCurrentNavigationState;
+    }
+
+    @Override
+    public void setImageCodeClusterInfo(int minIntervalMillis) {
+        mCarNavigationInstrumentCluster =
+                CarNavigationInstrumentCluster.createCluster(minIntervalMillis);
+    }
+
+    @Override
+    public void setCustomImageClusterInfo(
+            int minIntervalMillis,
+            int imageWidth,
+            int imageHeight,
+            int imageColorDepthBits) {
+        mCarNavigationInstrumentCluster =
+                CarNavigationInstrumentCluster
+                        .createCustomImageCluster(
+                                minIntervalMillis, imageWidth, imageHeight, imageColorDepthBits);
+    }
+}
diff --git a/car-test-lib/src/android/car/testapi/FakeSystemActivityMonitoringService.java b/car-test-lib/src/android/car/testapi/FakeSystemActivityMonitoringService.java
new file mode 100644
index 0000000..045ec4e
--- /dev/null
+++ b/car-test-lib/src/android/car/testapi/FakeSystemActivityMonitoringService.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.car.testapi;
+
+import android.content.Context;
+
+import com.android.car.SystemActivityMonitoringService;
+
+/**
+ * This is fake implementation of the SystemActivityMonitoringService which is used by the
+ * {@link com.android.car.AppFocusService}. A faked version is needed for offline unit tests so that
+ * the foreground app ID can be changed manually.
+ *
+ * @hide
+ */
+public class FakeSystemActivityMonitoringService extends SystemActivityMonitoringService {
+    private static final int DEFAULT_FOREGROUND_ID = -1;
+
+    private int mForegroundPid = DEFAULT_FOREGROUND_ID;
+    private int mForegroundUid = DEFAULT_FOREGROUND_ID;
+
+    FakeSystemActivityMonitoringService(Context context) {
+         super(context);
+    }
+
+    @Override
+    public boolean isInForeground(int pid, int uid) {
+        return (mForegroundPid == DEFAULT_FOREGROUND_ID || mForegroundPid == pid)
+                && (mForegroundUid == DEFAULT_FOREGROUND_ID || mForegroundUid == uid);
+    }
+
+    void setForegroundPid(int pid) {
+        mForegroundPid = pid;
+    }
+
+    void setForegroundUid(int uid) {
+        mForegroundUid = uid;
+    }
+
+    void resetForegroundPid() {
+        mForegroundPid = DEFAULT_FOREGROUND_ID;
+    }
+
+    void resetForegroundUid() {
+        mForegroundUid = DEFAULT_FOREGROUND_ID;
+    }
+}
diff --git a/car_product/build/car.mk b/car_product/build/car.mk
index 7c03328..4a76b5c 100644
--- a/car_product/build/car.mk
+++ b/car_product/build/car.mk
@@ -41,6 +41,8 @@
     VmsSubscriberClientSample \
     DirectRenderingCluster \
     GarageModeTestApp \
+    ExperimentalCarService \
+
 
 # SEPolicy for test apps / services
 BOARD_SEPOLICY_DIRS += packages/services/Car/car_product/sepolicy/test
@@ -59,15 +61,6 @@
 
 # Overlay for Google network and fused location providers
 $(call inherit-product, device/sample/products/location_overlay.mk)
-$(call inherit-product-if-exists, frameworks/base/data/fonts/fonts.mk)
-$(call inherit-product-if-exists, external/google-fonts/dancing-script/fonts.mk)
-$(call inherit-product-if-exists, external/google-fonts/carrois-gothic-sc/fonts.mk)
-$(call inherit-product-if-exists, external/google-fonts/coming-soon/fonts.mk)
-$(call inherit-product-if-exists, external/google-fonts/cutive-mono/fonts.mk)
-$(call inherit-product-if-exists, external/noto-fonts/fonts.mk)
-$(call inherit-product-if-exists, external/roboto-fonts/fonts.mk)
-$(call inherit-product-if-exists, external/hyphenation-patterns/patterns.mk)
-$(call inherit-product-if-exists, frameworks/base/data/keyboards/keyboards.mk)
 $(call inherit-product-if-exists, frameworks/webview/chromium/chromium.mk)
 $(call inherit-product, packages/services/Car/car_product/build/car_base.mk)
 
@@ -108,30 +101,102 @@
     libcar-framework-service-jni \
 
 # System Server components
+# Order is important: if X depends on Y, then Y should precede X on the list.
 PRODUCT_SYSTEM_SERVER_JARS += car-frameworks-service
 
 # Boot animation
 PRODUCT_COPY_FILES += \
     packages/services/Car/car_product/bootanimations/bootanimation-832.zip:system/media/bootanimation.zip
 
-PRODUCT_COPY_FILES += \
-    packages/services/Car/car_product/init/init.car.rc:system/etc/init/init.car.rc
+PRODUCT_LOCALES := \
+    en_US \
+    af_ZA \
+    am_ET \
+    ar_EG ar_XB \
+    as_IN \
+    az_AZ \
+    be_BY \
+    bg_BG \
+    bn_BD \
+    bs_BA \
+    ca_ES \
+    cs_CZ \
+    da_DK \
+    de_DE \
+    el_GR \
+    en_AU en_CA en_GB en_IN en_XA \
+    es_ES es_US \
+    et_EE \
+    eu_ES \
+    fa_IR \
+    fi_FI \
+    fil_PH \
+    fr_CA fr_FR \
+    gl_ES \
+    gu_IN \
+    hi_IN \
+    hr_HR \
+    hu_HU \
+    hy_AM \
+    id_ID \
+    is_IS \
+    it_IT \
+    iw_IL \
+    ja_JP \
+    ka_GE \
+    kk_KZ \
+    km_KH km_MH \
+    kn_IN \
+    ko_KR \
+    ky_KG \
+    lo_LA \
+    lv_LV \
+    lt_LT \
+    mk_MK \
+    ml_IN \
+    mn_MN \
+    mr_IN \
+    ms_MY \
+    my_MM \
+    ne_NP \
+    nl_NL \
+    no_NO \
+    or_IN \
+    pa_IN \
+    pl_PL \
+    pt_BR pt_PT \
+    ro_RO \
+    ru_RU \
+    si_LK \
+    sk_SK \
+    sl_SI \
+    sq_AL \
+    sr_RS \
+    sv_SE \
+    sw_TZ \
+    ta_IN \
+    te_IN \
+    th_TH \
+    tr_TR \
+    uk_UA \
+    ur_PK \
+    uz_UZ \
+    vi_VN \
+    zh_CN zh_HK zh_TW \
+    zu_ZA
 
-PRODUCT_LOCALES := en_US af_ZA am_ET ar_EG bg_BG bn_BD ca_ES cs_CZ da_DK de_DE el_GR en_AU en_GB en_IN es_ES es_US et_EE eu_ES fa_IR fi_FI fr_CA fr_FR gl_ES hi_IN hr_HR hu_HU hy_AM in_ID is_IS it_IT iw_IL ja_JP ka_GE km_KH ko_KR ky_KG lo_LA lt_LT lv_LV km_MH kn_IN mn_MN ml_IN mk_MK mr_IN ms_MY my_MM ne_NP nb_NO nl_NL pl_PL pt_BR pt_PT ro_RO ru_RU si_LK sk_SK sl_SI sr_RS sv_SE sw_TZ ta_IN te_IN th_TH tl_PH tr_TR uk_UA vi_VN zh_CN zh_HK zh_TW zu_ZA en_XA ar_XB
-
-# should add to BOOT_JARS only once
-ifeq (,$(INCLUDED_ANDROID_CAR_TO_PRODUCT_BOOT_JARS))
 PRODUCT_BOOT_JARS += \
     android.car
 
 PRODUCT_HIDDENAPI_STUBS := \
-    android.car-stubs
+    android.car-stubs-dex
 
 PRODUCT_HIDDENAPI_STUBS_SYSTEM := \
-    android.car-system-stubs
+    android.car-system-stubs-dex
 
 PRODUCT_HIDDENAPI_STUBS_TEST := \
-    android.car-test-stubs
+    android.car-test-stubs-dex
 
-INCLUDED_ANDROID_CAR_TO_PRODUCT_BOOT_JARS := yes
-endif
+# Disable Prime Shader Cache in SurfaceFlinger to make it available faster
+PRODUCT_PROPERTY_OVERRIDES += \
+    service.sf.prime_shader_cache=0
diff --git a/car_product/build/car_base.mk b/car_product/build/car_base.mk
index 72feacf..a1b6534 100644
--- a/car_product/build/car_base.mk
+++ b/car_product/build/car_base.mk
@@ -20,6 +20,7 @@
 PRODUCT_PACKAGE_OVERLAYS += packages/services/Car/car_product/overlay
 
 PRODUCT_PACKAGES += \
+    com.android.wifi \
     Home \
     BasicDreams \
     CaptivePortalLogin \
@@ -51,7 +52,6 @@
     libspeexresampler \
     libvariablespeed \
     libwebrtc_audio_preprocessing \
-    wifi-service \
     A2dpSinkService \
     PackageInstaller \
     car-bugreportd \
@@ -69,10 +69,19 @@
 
 # Default permission grant exceptions
 PRODUCT_COPY_FILES += \
-    packages/services/Car/car_product/build/default-car-permissions.xml:system/etc/default-permissions/default-car-permissions.xml
+    packages/services/Car/car_product/build/default-car-permissions.xml:system/etc/default-permissions/default-car-permissions.xml \
+    packages/services/Car/car_product/build/preinstalled-packages-product-car-base.xml:system/etc/sysconfig/preinstalled-packages-product-car-base.xml
 
 $(call inherit-product, $(SRC_TARGET_DIR)/product/core_minimal.mk)
 
 # Default dex optimization configurations
 PRODUCT_PROPERTY_OVERRIDES += \
      pm.dexopt.disable_bg_dexopt=true
+
+# Required init rc files for car
+PRODUCT_COPY_FILES += \
+    packages/services/Car/car_product/init/init.bootstat.rc:system/etc/init/init.bootstat.car.rc \
+    packages/services/Car/car_product/init/init.car.rc:system/etc/init/init.car.rc
+
+# Enable car watchdog
+include packages/services/Car/watchdog/product/carwatchdog.mk
diff --git a/car_product/build/preinstalled-packages-product-car-base.xml b/car_product/build/preinstalled-packages-product-car-base.xml
new file mode 100644
index 0000000..b15007f
--- /dev/null
+++ b/car_product/build/preinstalled-packages-product-car-base.xml
@@ -0,0 +1,326 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+  -->
+<!-- System packages to preinstall on all automotive devices, per user type.
+     OEMs must provide they own as well, listing their specific apps (like launcher, settings, etc...)
+     Documentation at frameworks/base/data/etc/preinstalled-packages-platform.xml
+-->
+<config>
+<!--
+  Here the apps will have SYSTEM only.
+-->
+    <install-in-user-type package="com.android.experimentalcar">
+        <install-in user-type="SYSTEM" />
+    </install-in-user-type>
+
+<!--
+  Apps that need to run on SYSTEM and evaluated by package owner.
+  Here the apps will have FULL and SYSTEM.
+-->
+    <install-in-user-type package="android">
+        <install-in user-type="FULL" />
+        <install-in user-type="SYSTEM" />
+    </install-in-user-type>
+    <install-in-user-type package="android.car.cluster">
+        <install-in user-type="FULL" />
+        <install-in user-type="SYSTEM" />
+    </install-in-user-type>
+    <install-in-user-type package="com.android.car">
+        <install-in user-type="FULL" />
+        <install-in user-type="SYSTEM" />
+    </install-in-user-type>
+    <install-in-user-type package="com.android.car.frameworkpackagestubs">
+        <install-in user-type="FULL" />
+        <install-in user-type="SYSTEM" />
+    </install-in-user-type>
+
+    <!-- Needed for Projected on Embedded so it receives LOCKED_BOOT_COMPLETED immediately,
+      otherwise projection wouldn't launch on startup -->
+    <install-in-user-type package="android.car.usb.handler">
+        <install-in user-type="FULL" />
+        <install-in user-type="SYSTEM" />
+    </install-in-user-type>
+
+    <!-- Need to upload collected bugreports even if full user was deleted or changed -->
+    <install-in-user-type package="com.android.car.bugreport">
+        <install-in user-type="FULL" />
+        <install-in user-type="SYSTEM" />
+    </install-in-user-type>
+
+    <!-- Not sure, leave for security purpose -->
+    <install-in-user-type package="com.android.keychain">
+        <install-in user-type="FULL" />
+        <install-in user-type="SYSTEM" />
+    </install-in-user-type>
+
+    <!-- Mainline Wi-fi stack, it's needed for all users -->
+    <install-in-user-type package="com.android.wifi">
+        <install-in user-type="SYSTEM" />
+        <install-in user-type="FULL" />
+    </install-in-user-type>
+
+    <!-- Provides Settings. Secure for SYSTEM, which are used in places such as SUW -->
+    <install-in-user-type package="com.android.providers.settings">
+        <install-in user-type="FULL" />
+        <install-in user-type="SYSTEM" />
+    </install-in-user-type>
+
+    <!--
+      CompanionDeviceSupport app needs to run on SYSTEM for the Trusted Device feature to work.
+      It needs to run in the foreground user for other companion app features like calendar sync
+      and notifications bridging
+    -->
+    <install-in-user-type package="com.android.car.companiondevicesupport">
+        <install-in user-type="FULL" />
+        <install-in user-type="SYSTEM" />
+    </install-in-user-type>
+
+    <!-- Not sure, leave for security purpose -->
+    <install-in-user-type package="com.android.se">
+        <install-in user-type="FULL" />
+        <install-in user-type="SYSTEM" />
+    </install-in-user-type>
+
+    <!-- Needed by the Location service during system bootup -->
+    <install-in-user-type package="com.android.location.fused">
+        <install-in user-type="FULL" />
+        <install-in user-type="SYSTEM" />
+    </install-in-user-type>
+
+    <!-- Resides on a device's /system partition to verify certain upgrade scenarios -->
+    <install-in-user-type package="com.android.cts.ctsshim">
+        <install-in user-type="FULL" />
+        <install-in user-type="SYSTEM" />
+    </install-in-user-type>
+
+    <!-- Resides on a device's /system partition to verify certain upgrade scenarios -->
+    <install-in-user-type package="com.android.cts.priv.ctsshim">
+        <install-in user-type="FULL" />
+        <install-in user-type="SYSTEM" />
+    </install-in-user-type>
+
+    <!-- Contains exported and single user service -->
+    <install-in-user-type package="com.android.ons">
+        <install-in user-type="FULL" />
+        <install-in user-type="SYSTEM" />
+    </install-in-user-type>
+
+    <!-- Needs this packages during bootup, otherwise system won't boot -->
+    <install-in-user-type package="com.android.wifi.resources">
+        <install-in user-type="FULL" />
+        <install-in user-type="SYSTEM" />
+    </install-in-user-type>
+
+    <!-- Uses system user id -->
+    <install-in-user-type package="com.android.incremental.nativeadb">
+        <install-in user-type="FULL" />
+        <install-in user-type="SYSTEM" />
+    </install-in-user-type>
+
+    <!-- Uses system user id -->
+    <install-in-user-type package="com.android.networkstack.inprocess">
+        <install-in user-type="FULL" />
+        <install-in user-type="SYSTEM" />
+    </install-in-user-type>
+
+    <!-- Required StorageManagerService to bind to the ExternalStorageService -->
+    <install-in-user-type package="com.android.providers.media.module">
+        <install-in user-type="FULL" />
+        <install-in user-type="SYSTEM" />
+    </install-in-user-type>
+
+    <!-- Needs to run on system user otherwise cannot find available device -->
+    <install-in-user-type package="com.android.bluetooth">
+        <install-in user-type="FULL" />
+        <install-in user-type="SYSTEM" />
+    </install-in-user-type>
+
+    <!-- Required to create application com.android.phone.PhoneApp -->
+    <install-in-user-type package="com.android.telephony.resources">
+        <install-in user-type="FULL" />
+        <install-in user-type="SYSTEM" />
+    </install-in-user-type>
+
+    <!-- Required to find provider info for telephony for com.android.phone.PhoneApp -->
+    <install-in-user-type package="com.android.providers.telephony">
+        <install-in user-type="FULL" />
+        <install-in user-type="SYSTEM" />
+    </install-in-user-type>
+
+    <!-- Required to use adb -->
+    <install-in-user-type package="com.android.shell">
+        <install-in user-type="FULL" />
+        <install-in user-type="SYSTEM" />
+    </install-in-user-type>
+
+    <!-- Required to get current location; check map -->
+    <install-in-user-type package="com.android.server.telecom">
+        <install-in user-type="FULL" />
+        <install-in user-type="SYSTEM" />
+    </install-in-user-type>
+
+    <!-- Failed to find provider info for downloads error if not installed for system user -->
+    <install-in-user-type package="com.android.providers.downloads">
+        <install-in user-type="FULL" />
+        <install-in user-type="SYSTEM" />
+    </install-in-user-type>
+
+    <!-- Failed to find provider info for calendar error if not installed for system user -->
+    <install-in-user-type package="com.android.providers.calendar">
+        <install-in user-type="FULL" />
+        <install-in user-type="SYSTEM" />
+    </install-in-user-type>
+
+<!--
+  Apps that do need to run on SYSTEM and evaluated by package owner.
+  Here the apps will have FULL only.
+-->
+    <install-in-user-type package="com.android.htmlviewer">
+        <install-in user-type="FULL" />
+    </install-in-user-type>
+    <install-in-user-type package="com.android.protips">
+        <install-in user-type="FULL" />
+    </install-in-user-type>
+    <install-in-user-type package="com.android.inputdevices">
+        <install-in user-type="FULL" />
+    </install-in-user-type>
+    <install-in-user-type package="com.android.soundpicker">
+        <install-in user-type="FULL" />
+    </install-in-user-type>
+    <install-in-user-type package="com.android.captiveportallogin">
+        <install-in user-type="FULL" />
+    </install-in-user-type>
+    <install-in-user-type package="com.google.android.car.hideapps">
+        <install-in user-type="FULL" />
+    </install-in-user-type>
+    <install-in-user-type package="com.android.stk">
+        <install-in user-type="FULL" />
+    </install-in-user-type>
+    <install-in-user-type package="com.android.dreams.phototable">
+        <install-in user-type="FULL" />
+    </install-in-user-type>
+    <install-in-user-type package="com.android.carrierdefaultapp">
+        <install-in user-type="FULL" />
+    </install-in-user-type>
+    <install-in-user-type package="com.android.soundrecorder">
+        <install-in user-type="FULL" />
+    </install-in-user-type>
+    <install-in-user-type package="com.android.bips">
+        <install-in user-type="FULL" />
+    </install-in-user-type>
+    <install-in-user-type package="com.android.settings.intelligence">
+        <install-in user-type="FULL" />
+    </install-in-user-type>
+    <install-in-user-type package="com.android.egg">
+        <install-in user-type="FULL" />
+    </install-in-user-type>
+    <install-in-user-type package="com.android.simappdialog">
+        <install-in user-type="FULL" />
+    </install-in-user-type>
+    <install-in-user-type package="com.android.dreams.basic">
+        <install-in user-type="FULL" />
+    </install-in-user-type>
+    <install-in-user-type package="com.android.companiondevicemanager">
+        <install-in user-type="FULL" />
+    </install-in-user-type>
+    <install-in-user-type package="com.android.bluetoothmidiservice">
+        <install-in user-type="FULL" />
+    </install-in-user-type>
+    <install-in-user-type package="com.android.smspush">
+        <install-in user-type="FULL" />
+    </install-in-user-type>
+    <install-in-user-type package="com.android.statementservice">
+        <install-in user-type="FULL" />
+    </install-in-user-type>
+    <install-in-user-type package="com.android.backupconfirm">
+        <install-in user-type="FULL" />
+    </install-in-user-type>
+   <install-in-user-type package="com.android.calllogbackup">
+        <install-in user-type="FULL" />
+    </install-in-user-type>
+    <install-in-user-type package="com.android.providers.blockednumber">
+        <install-in user-type="FULL" />
+    </install-in-user-type>
+    <install-in-user-type package="com.android.providers.contacts">
+        <install-in user-type="FULL" />
+    </install-in-user-type>
+    <install-in-user-type package="com.android.providers.downloads.ui">
+        <install-in user-type="FULL" />
+    </install-in-user-type>
+    <install-in-user-type package="com.android.providers.media">
+        <install-in user-type="FULL" />
+    </install-in-user-type>
+    <install-in-user-type package="com.android.providers.userdictionary">
+        <install-in user-type="FULL" />
+    </install-in-user-type>
+    <install-in-user-type package="com.android.voicetrigger">
+        <install-in user-type="FULL" />
+    </install-in-user-type>
+    <install-in-user-type package="com.android.internal.display.cutout.emulation.hole">
+        <install-in user-type="FULL" />
+    </install-in-user-type>
+    <install-in-user-type package="com.android.internal.display.cutout.emulation.waterfall">
+        <install-in user-type="FULL" />
+    </install-in-user-type>
+    <install-in-user-type package="android.auto_generated_rro_product__">
+        <install-in user-type="FULL" />
+    </install-in-user-type>
+    <install-in-user-type package="android.auto_generated_rro_vendor__">
+        <install-in user-type="FULL" />
+    </install-in-user-type>
+    <install-in-user-type package="com.android.car.calendar">
+        <install-in user-type="FULL" />
+    </install-in-user-type>
+    <install-in-user-type package="com.android.car.ui.sharedlibrary">
+        <install-in user-type="FULL" />
+    </install-in-user-type>
+    <install-in-user-type package="com.android.wifi.inprocess.overlay.car">
+        <install-in user-type="FULL" />
+    </install-in-user-type>
+    <install-in-user-type package="com.android.cellbroadcastreceiver">
+        <install-in user-type="FULL" />
+    </install-in-user-type>
+    <install-in-user-type package="com.android.networkstack.permissionconfig">
+        <install-in user-type="FULL" />
+    </install-in-user-type>
+    <install-in-user-type package="com.android.storagemanager">
+        <install-in user-type="FULL" />
+    </install-in-user-type>
+    <install-in-user-type package="android.autoinstalls.config.google.car">
+        <install-in user-type="FULL" />
+    </install-in-user-type>
+    <install-in-user-type package="com.android.carrierconfig">
+        <install-in user-type="FULL" />
+    </install-in-user-type>
+    <install-in-user-type package="com.android.certinstaller">
+        <install-in user-type="FULL" />
+    </install-in-user-type>
+    <install-in-user-type package="com.android.pacprocessor">
+        <install-in user-type="FULL" />
+    </install-in-user-type>
+    <install-in-user-type package="com.android.proxyhandler">
+        <install-in user-type="FULL" />
+    </install-in-user-type>
+    <install-in-user-type package="com.android.vpndialogs">
+        <install-in user-type="FULL" />
+    </install-in-user-type>
+    <install-in-user-type package="com.android.sharedstoragebackup">
+        <install-in user-type="FULL" />
+    </install-in-user-type>
+    <install-in-user-type package="com.android.externalstorage">
+        <install-in user-type="FULL" />
+    </install-in-user-type>
+</config>
diff --git a/car_product/occupant_awareness/OccupantAwareness.mk b/car_product/occupant_awareness/OccupantAwareness.mk
new file mode 100644
index 0000000..43d904f
--- /dev/null
+++ b/car_product/occupant_awareness/OccupantAwareness.mk
@@ -0,0 +1,7 @@
+# Occupant Awareness SELinux policy variable definitions
+LOCAL_PATH:= $(call my-dir)
+
+BOARD_PLAT_PUBLIC_SEPOLICY_DIR += $(LOCAL_PATH)/sepolicy/public
+BOARD_PLAT_PRIVATE_SEPOLICY_DIR += $(LOCAL_PATH)/sepolicy/private
+
+BOARD_SEPOLICY_DIRS += $(LOCAL_PATH)/sepolicy
diff --git a/car_product/occupant_awareness/sepolicy/file_contexts b/car_product/occupant_awareness/sepolicy/file_contexts
new file mode 100644
index 0000000..09a2859
--- /dev/null
+++ b/car_product/occupant_awareness/sepolicy/file_contexts
@@ -0,0 +1 @@
+/vendor/bin/hw/android\.hardware\.automotive\.occupant_awareness@1\.0-service u:object_r:hal_occupant_awareness_default_exec:s0
diff --git a/car_product/occupant_awareness/sepolicy/hal_occupant_awareness_default.te b/car_product/occupant_awareness/sepolicy/hal_occupant_awareness_default.te
new file mode 100644
index 0000000..ca4cf7d
--- /dev/null
+++ b/car_product/occupant_awareness/sepolicy/hal_occupant_awareness_default.te
@@ -0,0 +1,5 @@
+type hal_occupant_awareness_default, domain;
+hal_server_domain(hal_occupant_awareness_default, hal_occupant_awareness)
+
+type hal_occupant_awareness_default_exec, exec_type, vendor_file_type, file_type;
+init_daemon_domain(hal_occupant_awareness_default)
diff --git a/car_product/occupant_awareness/sepolicy/private/service_contexts b/car_product/occupant_awareness/sepolicy/private/service_contexts
new file mode 100644
index 0000000..3796d53
--- /dev/null
+++ b/car_product/occupant_awareness/sepolicy/private/service_contexts
@@ -0,0 +1 @@
+android.hardware.automotive.occupant_awareness.IOccupantAwareness/default u:object_r:hal_occupant_awareness_service:s0
diff --git a/car_product/occupant_awareness/sepolicy/public/attributes b/car_product/occupant_awareness/sepolicy/public/attributes
new file mode 100644
index 0000000..d772d1c
--- /dev/null
+++ b/car_product/occupant_awareness/sepolicy/public/attributes
@@ -0,0 +1 @@
+hal_attribute(occupant_awareness);
diff --git a/car_product/occupant_awareness/sepolicy/public/hal_occupant_awareness.te b/car_product/occupant_awareness/sepolicy/public/hal_occupant_awareness.te
new file mode 100644
index 0000000..bcc522f
--- /dev/null
+++ b/car_product/occupant_awareness/sepolicy/public/hal_occupant_awareness.te
@@ -0,0 +1,8 @@
+# HwBinder IPC from client to server, and callbacks
+binder_call(hal_occupant_awareness_client, hal_occupant_awareness_server)
+binder_call(hal_occupant_awareness_server, hal_occupant_awareness_client)
+add_service(hal_occupant_awareness_server, hal_occupant_awareness_service)
+
+binder_use(hal_occupant_awareness_server)
+
+allow hal_occupant_awareness_client hal_occupant_awareness_service:service_manager find;
diff --git a/car_product/occupant_awareness/sepolicy/public/service.te b/car_product/occupant_awareness/sepolicy/public/service.te
new file mode 100644
index 0000000..a2ff448
--- /dev/null
+++ b/car_product/occupant_awareness/sepolicy/public/service.te
@@ -0,0 +1 @@
+type hal_occupant_awareness_service, vendor_service, service_manager_type;
diff --git a/car_product/overlay/frameworks/base/core/res/res/values/config.xml b/car_product/overlay/frameworks/base/core/res/res/values/config.xml
index 56dbf19..a38a437 100644
--- a/car_product/overlay/frameworks/base/core/res/res/values/config.xml
+++ b/car_product/overlay/frameworks/base/core/res/res/values/config.xml
@@ -91,11 +91,31 @@
 
     <string name="config_dataUsageSummaryComponent">com.android.car.settings/com.android.car.settings.datausage.DataWarningAndLimitActivity</string>
 
+    <!-- Controls whether system buttons use all caps for text -->
+    <bool name="config_buttonTextAllCaps">false</bool>
+
     <bool name="config_automotiveHideNavBarForKeyboard">true</bool>
 
     <!-- Turn off Wallpaper service -->
     <bool name="config_enableWallpaperService">false</bool>
 
-    <!-- Automotive explicitly controls when the system suspends. Do not use autoSuspend. -->
-    <bool name="config_enableAutoSuspend" translatable="false">false</bool>
+    <!-- Whether to only install system packages on a user if they're whitelisted for that user
+         type. Override the default value in framework config file.
+         0  - disable whitelist (install all system packages; no logging)
+         1  - enforce (only install system packages if they are whitelisted)
+         2  - log (log when a non-whitelisted package is run)
+         4  - any package not mentioned in the whitelist file is implicitly whitelisted on all users
+         8  - same as 4, but just for the SYSTEM user
+         16 - ignore OTAs (don't install system packages during OTAs)
+
+         Common scenarios for auto:
+          - to enable feature (fully enforced) for a complete whitelist: 1
+          - to enable feature for an incomplete whitelist (so use implicit whitelist mode): 5 -->
+    <integer name="config_userTypePackageWhitelistMode">5</integer> <!-- 1+4 -->
+
+    <!-- Default user restrictions for system user 0. -->
+    <string-array translatable="false" name="config_defaultFirstUserRestrictions">
+        <item>"no_modify_accounts"</item>
+    </string-array>
+
 </resources>
diff --git a/car_product/overlay/frameworks/base/core/res/res/values/styles_device_default.xml b/car_product/overlay/frameworks/base/core/res/res/values/styles_device_default.xml
index 37139d3..be7e15a 100644
--- a/car_product/overlay/frameworks/base/core/res/res/values/styles_device_default.xml
+++ b/car_product/overlay/frameworks/base/core/res/res/values/styles_device_default.xml
@@ -87,6 +87,10 @@
     </style>
 
     <style name="Widget.DeviceDefault.Button" parent="android:Widget.Material.Button">
+        <item name="android:singleLine">true</item>
+        <item name="android:ellipsize">none</item>
+        <item name="android:requiresFadingEdge">horizontal</item>
+        <item name="android:fadingEdgeLength">@*android:dimen/car_textview_fading_edge_length</item>
         <item name="android:background">@*android:drawable/car_button_background</item>
         <item name="android:layout_height">@*android:dimen/car_button_height</item>
         <item name="android:minWidth">@*android:dimen/car_button_min_width</item>
diff --git a/car_product/overlay/frameworks/base/core/res/res/values/themes_device_defaults.xml b/car_product/overlay/frameworks/base/core/res/res/values/themes_device_defaults.xml
index c36e925..98b52dd 100644
--- a/car_product/overlay/frameworks/base/core/res/res/values/themes_device_defaults.xml
+++ b/car_product/overlay/frameworks/base/core/res/res/values/themes_device_defaults.xml
@@ -53,6 +53,7 @@
         <item name="android:textAppearanceListItem">@*android:style/TextAppearance.DeviceDefault.Large</item>
         <item name="android:textAppearanceListItemSmall">@*android:style/TextAppearance.DeviceDefault.Large</item>
         <item name="android:textAppearanceListItemSecondary">@*android:style/TextAppearance.DeviceDefault.Small</item>
+        <item name="android:textAppearanceButton">@*android:style/Widget.DeviceDefault.Button</item>
         <item name="android:borderlessButtonStyle">@*android:style/Widget.DeviceDefault.Button.Borderless.Colored</item>
         <item name="android:buttonBarButtonStyle">@*android:style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item>
         <item name="android:buttonStyle">@*android:style/Widget.DeviceDefault.Button</item>
@@ -68,6 +69,7 @@
         <item name="android:textColorHint">@*android:color/car_body2</item>
         <item name="android:textColorPrimary">@*android:color/text_color_primary</item>
         <item name="android:textColorSecondary">@*android:color/car_body2</item>
+        <item name="android:windowTitleStyle">?android:attr/textAppearanceLarge</item>
     </style>
 
     <style name="Theme.DeviceDefault.Dialog.NoActionBar" parent="android:Theme.DeviceDefault.Dialog">
@@ -76,6 +78,10 @@
     </style>
 
     <style name="Theme.DeviceDefault.Dialog.Alert" parent="android:Theme.Material.Dialog.Alert">
+        <item name="android:textAppearanceLarge">@*android:style/TextAppearance.DeviceDefault.Large</item>
+        <item name="android:textAppearanceMedium">@*android:style/TextAppearance.DeviceDefault.Medium</item>
+        <item name="android:textAppearanceSmall">@*android:style/TextAppearance.DeviceDefault.Small</item>
+        <item name="android:textAppearanceButton">@*android:style/Widget.DeviceDefault.Button</item>
         <item name="android:borderlessButtonStyle">@*android:style/Widget.DeviceDefault.Button.Borderless.Colored</item>
         <item name="android:buttonBarButtonStyle">@*android:style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item>
         <item name="android:buttonStyle">@*android:style/Widget.DeviceDefault.Button</item>
@@ -94,6 +100,7 @@
         <item name="android:textAppearanceListItem">@*android:style/TextAppearance.DeviceDefault.Large</item>
         <item name="android:textAppearanceListItemSmall">@*android:style/TextAppearance.DeviceDefault.Large</item>
         <item name="android:textAppearanceListItemSecondary">@*android:style/TextAppearance.DeviceDefault.Small</item>
+        <item name="android:windowTitleStyle">?android:attr/textAppearanceLarge</item>
     </style>
 
     <style name="Theme.DeviceDefault.Settings.Dialog" parent="android:Theme.DeviceDefault.Dialog.Alert">
diff --git a/car_product/overlay/frameworks/base/core/res/res/xml/config_user_types.xml b/car_product/overlay/frameworks/base/core/res/res/xml/config_user_types.xml
new file mode 100644
index 0000000..1baae68
--- /dev/null
+++ b/car_product/overlay/frameworks/base/core/res/res/xml/config_user_types.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<user-types>
+    <full-type name="android.os.usertype.full.SECONDARY" >
+        <default-restrictions />
+    </full-type>
+
+    <full-type name="android.os.usertype.full.GUEST" >
+        <default-restrictions no_factory_reset="true" no_remove_user="true"
+                  no_modify_accounts="true" no_install_apps="true" no_install_unknown_sources="true"
+                  no_uninstall_apps="true"/>
+    </full-type>
+</user-types>
diff --git a/car_product/overlay/frameworks/base/packages/CarSystemUI/res/values/config.xml b/car_product/overlay/frameworks/base/packages/CarSystemUI/res/values/config.xml
index 7fd5d38..d8d8516 100644
--- a/car_product/overlay/frameworks/base/packages/CarSystemUI/res/values/config.xml
+++ b/car_product/overlay/frameworks/base/packages/CarSystemUI/res/values/config.xml
@@ -80,41 +80,4 @@
     <!-- Keep the notification background when the container has been expanded. The children will
          expand inline within the container, so it can keep its original background. -->
     <bool name="config_showGroupNotificationBgWhenExpanded">true</bool>
-
-    <!--
-      Service components below were copied verbatim from frameworks/base/packages/SystemUI/res/values/config.xml,
-      then the services that are not needed by automotive were commented out (to improve boot and user switch time).
-    -->
-    <string-array name="config_systemUIServiceComponents" translatable="false">
-        <item>com.android.systemui.util.NotificationChannels</item>
-        <item>com.android.systemui.statusbar.CommandQueue$CommandQueueStart</item>
-        <item>com.android.systemui.keyguard.KeyguardViewMediator</item>
-<!--
-        <item>com.android.systemui.recents.Recents</item>
--->
-<!--
-        <item>com.android.systemui.volume.VolumeUI</item>
--->
-        <item>com.android.systemui.stackdivider.Divider</item>
-        <item>com.android.systemui.SystemBars</item>
-        <item>com.android.systemui.usb.StorageNotification</item>
-        <item>com.android.systemui.power.PowerUI</item>
-        <item>com.android.systemui.media.RingtonePlayer</item>
-        <item>com.android.systemui.keyboard.KeyboardUI</item>
-<!--
-        <item>com.android.systemui.pip.PipUI</item>
--->
-        <item>com.android.systemui.shortcut.ShortcutKeyDispatcher</item>
-        <item>@string/config_systemUIVendorServiceComponent</item>
-        <item>com.android.systemui.util.leak.GarbageMonitor$Service</item>
-        <item>com.android.systemui.LatencyTester</item>
-        <item>com.android.systemui.globalactions.GlobalActionsComponent</item>
-        <item>com.android.systemui.ScreenDecorations</item>
-        <item>com.android.systemui.biometrics.BiometricDialogImpl</item>
-        <item>com.android.systemui.SliceBroadcastRelayHandler</item>
-        <item>com.android.systemui.SizeCompatModeActivityController</item>
-        <item>com.android.systemui.statusbar.notification.InstantAppNotifier</item>
-        <item>com.android.systemui.theme.ThemeOverlayController</item>
-    </string-array>
-
 </resources>
diff --git a/car_product/overlay/frameworks/base/packages/CarSystemUI/res/values/dimens.xml b/car_product/overlay/frameworks/base/packages/CarSystemUI/res/values/dimens.xml
deleted file mode 100644
index b57fcb9..0000000
--- a/car_product/overlay/frameworks/base/packages/CarSystemUI/res/values/dimens.xml
+++ /dev/null
@@ -1,94 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-** Copyright 2016, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-<resources>
-    <!-- The alpha for the scrim behind the notification shade. This value is 1 so that the
-         scrim has no transparency. -->
-    <item name="scrim_behind_alpha" format="float" type="dimen">1.0</item>
-
-    <!-- Largest size an avatar might need to be drawn in the user picker, status bar, or
-         quick settings header -->
-    <dimen name="max_avatar_size">128dp</dimen>
-
-    <!-- The width of panel holding the notification card. -->
-    <dimen name="notification_panel_width">522dp</dimen>
-
-    <!-- The width of the quick settings panel. -1 for match_parent. -->
-    <dimen name="qs_panel_width">-1px</dimen>
-
-    <!-- Height of a small notification in the status bar-->
-    <dimen name="notification_min_height">192dp</dimen>
-
-    <!-- Height of a small notification in the status bar which was used before android N -->
-    <dimen name="notification_min_height_legacy">192dp</dimen>
-
-    <!-- Height of a large notification in the status bar -->
-    <dimen name="notification_max_height">400dp</dimen>
-
-    <!-- Height of a heads up notification in the status bar for legacy custom views -->
-    <dimen name="notification_max_heads_up_height_legacy">400dp</dimen>
-
-    <!-- Height of a heads up notification in the status bar -->
-    <dimen name="notification_max_heads_up_height">400dp</dimen>
-
-    <!-- Height of the status bar header bar -->
-    <dimen name="status_bar_header_height">54dp</dimen>
-
-    <!-- The height of the divider between the individual notifications. -->
-    <dimen name="notification_divider_height">16dp</dimen>
-
-    <!-- The height of the divider between the individual notifications when the notification
-         wants it to be increased. This value is the same as notification_divider_height so that
-         the spacing between all notifications will always be the same. -->
-    <dimen name="notification_divider_height_increased">@dimen/notification_divider_height</dimen>
-
-    <!-- The alpha of the dividing line between child notifications of a notification group. -->
-    <item name="notification_divider_alpha" format="float" type="dimen">1.0</item>
-
-    <!-- The width of each individual notification card. -->
-    <dimen name="notification_child_width">522dp</dimen>
-
-    <!-- The top margin of the notification panel. -->
-    <dimen name="notification_panel_margin_top">32dp</dimen>
-
-    <!-- The bottom margin of the panel that holds the list of notifications. -->
-    <dimen name="notification_panel_margin_bottom">@dimen/notification_divider_height</dimen>
-
-    <!-- The corner radius of the shadow behind the notification. -->
-    <dimen name="notification_shadow_radius">16dp</dimen>
-
-    <!-- The amount of space below the notification list. This value is 0 so the list scrolls
-         all the way to the bottom. -->
-    <dimen name="close_handle_underlap">0dp</dimen>
-
-    <!-- The height of the divider between the individual notifications in a notification group. -->
-    <dimen name="notification_children_container_divider_height">1dp</dimen>
-
-    <!-- The height of the header for a container containing child notifications. -->
-    <dimen name="notification_children_container_header_height">76dp</dimen>
-
-    <!-- The top margin for the notification children container in its non-expanded form. This
-         value is smaller than notification_children_container_header_height to bring the first
-         child closer so there is less wasted space. -->
-    <dimen name="notification_children_container_margin_top">68dp</dimen>
-
-    <!-- The height of the quick settings footer that holds the user switcher, settings icon,
-         etc. in the car setting.-->
-    <dimen name="qs_footer_height">74dp</dimen>
-
-</resources>
diff --git a/car_product/overlay/frameworks/base/packages/SettingsProvider/res/values/default.xml b/car_product/overlay/frameworks/base/packages/SettingsProvider/res/values/default.xml
deleted file mode 100644
index 6009626..0000000
--- a/car_product/overlay/frameworks/base/packages/SettingsProvider/res/values/default.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/**
- * Copyright (c) 2016, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
--->
-<resources>
-    <!-- There is no frx in the emulator so default to being fully set up. -->
-    <bool name="def_device_provisioned">true</bool>
-    <bool name="def_user_setup_complete">true</bool>
-    <bool name="def_bluetooth_on">true</bool>
-    <bool name="def_wifi_on">false</bool>
-    <!-- Disable system heads-up notifications -->
-    <integer name="def_heads_up_enabled">0</integer>
-</resources>
diff --git a/car_product/overlay/frameworks/base/packages/SettingsProvider/res/values/defaults.xml b/car_product/overlay/frameworks/base/packages/SettingsProvider/res/values/defaults.xml
index cb44830..81a9a3d 100644
--- a/car_product/overlay/frameworks/base/packages/SettingsProvider/res/values/defaults.xml
+++ b/car_product/overlay/frameworks/base/packages/SettingsProvider/res/values/defaults.xml
@@ -20,4 +20,10 @@
     <bool name="def_device_provisioned">false</bool>
     <!--  default setting for Settings.System.END_BUTTON_BEHAVIOR: 0 for car -->
     <integer name="def_end_button_behavior">0</integer>
+
+    <bool name="def_bluetooth_on">true</bool>
+    <bool name="def_wifi_on">false</bool>
+
+    <!-- Disable system heads-up notifications -->
+    <integer name="def_heads_up_enabled">0</integer>
 </resources>
diff --git a/car_product/overlay/packages/apps/Bluetooth/res/values/config.xml b/car_product/overlay/packages/apps/Bluetooth/res/values/config.xml
index 810549a..199e0ae 100644
--- a/car_product/overlay/packages/apps/Bluetooth/res/values/config.xml
+++ b/car_product/overlay/packages/apps/Bluetooth/res/values/config.xml
@@ -44,6 +44,7 @@
     <bool name="profile_supported_hfpclient">true</bool>
     <bool name="hfp_client_connection_service_enabled">true</bool>
     <bool name="profile_supported_avrcp_controller">true</bool>
+    <bool name="avrcp_controller_enable_cover_art">true</bool>
     <bool name="profile_supported_a2dp_sink">true</bool>
     <bool name="profile_supported_pbapclient">true</bool>
     <bool name="profile_supported_pan">true</bool>
diff --git a/car_product/sepolicy/OWNERS b/car_product/sepolicy/OWNERS
new file mode 100644
index 0000000..307b9d5
--- /dev/null
+++ b/car_product/sepolicy/OWNERS
@@ -0,0 +1,5 @@
+# Project owners
+danharms@google.com
+keunyoung@google.com
+sgurun@google.com
+gurunagarajan@google.com
diff --git a/car_product/sepolicy/private/carservice_app.te b/car_product/sepolicy/private/carservice_app.te
index 05c7b3f..e49d677 100644
--- a/car_product/sepolicy/private/carservice_app.te
+++ b/car_product/sepolicy/private/carservice_app.te
@@ -12,9 +12,6 @@
 # Allow Car Service to register/access itself with ServiceManager
 add_service(carservice_app, carservice_service)
 
-# Allow Car Service to register its stats service with ServiceManager
-add_service(carservice_app, carstats_service)
-
 # Allow Car Service to access certain system services.
 # Keep alphabetically sorted.
 allow carservice_app {
@@ -38,8 +35,10 @@
     power_service
     procfsinspector_service
     sensorservice_service
+    statsmanager_service
     surfaceflinger_service
     telecom_service
+    thermal_service
     uimode_service
     voiceinteraction_service
     wifi_service
@@ -62,8 +61,14 @@
 
 allow carservice_app procfsinspector:binder call;
 
+# Allow binder calls with statsd
+allow carservice_app statsd:binder call;
+
 # To access /sys/fs/<type>/<partition>/lifetime_write_kbytes
 allow carservice_app sysfs_fs_lifetime_write:file { getattr open read };
 
 set_prop(carservice_app, ctl_start_prop)
 unix_socket_connect(carservice_app, dumpstate, dumpstate)
+
+# Allow reading vehicle-specific configuration
+get_prop(carservice_app, vehicle_hal_prop)
diff --git a/car_product/sepolicy/private/service_contexts b/car_product/sepolicy/private/service_contexts
index 38d994c..7ac544c 100644
--- a/car_product/sepolicy/private/service_contexts
+++ b/car_product/sepolicy/private/service_contexts
@@ -1,3 +1,2 @@
 car_service  u:object_r:carservice_service:s0
-car_stats u:object_r:carstats_service:s0
 com.android.car.procfsinspector u:object_r:procfsinspector_service:s0
diff --git a/car_product/sepolicy/private/statsd.te b/car_product/sepolicy/private/statsd.te
deleted file mode 100644
index 1a17418..0000000
--- a/car_product/sepolicy/private/statsd.te
+++ /dev/null
@@ -1,2 +0,0 @@
-# Allow statsd to pull atoms from car_stats service
-allow statsd carstats_service:service_manager find;
diff --git a/car_product/sepolicy/public/service.te b/car_product/sepolicy/public/service.te
index c6a2e30..87426f4 100644
--- a/car_product/sepolicy/public/service.te
+++ b/car_product/sepolicy/public/service.te
@@ -1,3 +1,2 @@
 type carservice_service, app_api_service, service_manager_type;
-type carstats_service, service_manager_type;
 type procfsinspector_service, service_manager_type;
diff --git a/car_product/sepolicy/test/experimentalcarservice_app.te b/car_product/sepolicy/test/experimentalcarservice_app.te
new file mode 100644
index 0000000..082f326
--- /dev/null
+++ b/car_product/sepolicy/test/experimentalcarservice_app.te
@@ -0,0 +1,46 @@
+# Domain to run ExperimentalCarService (com.android.experimentalcar)
+type experimentalcarservice_app, domain, coredomain;
+app_domain(experimentalcarservice_app);
+
+allow experimentalcarservice_app wifi_service:service_manager find;
+
+# Allow access certain to system services.
+# Keep alphabetically sorted.
+allow experimentalcarservice_app {
+    accessibility_service
+    activity_service
+    activity_task_service
+    audio_service
+    audioserver_service
+    autofill_service
+    bluetooth_manager_service
+    carservice_service
+    connectivity_service
+    content_service
+    deviceidle_service
+    display_service
+    graphicsstats_service
+    input_method_service
+    input_service
+    location_service
+    media_session_service
+    network_management_service
+    power_service
+    procfsinspector_service
+    sensorservice_service
+    surfaceflinger_service
+    telecom_service
+    uimode_service
+    voiceinteraction_service
+}:service_manager find;
+
+# Read and write /data/data subdirectory.
+allow experimentalcarservice_app system_app_data_file:dir create_dir_perms;
+allow experimentalcarservice_app system_app_data_file:{ file lnk_file } create_file_perms;
+# R/W /data/system/car
+allow experimentalcarservice_app system_car_data_file:dir create_dir_perms;
+allow experimentalcarservice_app system_car_data_file:{ file lnk_file } create_file_perms;
+
+net_domain(experimentalcarservice_app)
+
+allow experimentalcarservice_app cgroup:file rw_file_perms;
diff --git a/car_product/sepolicy/test/kitchensink_app.te b/car_product/sepolicy/test/kitchensink_app.te
deleted file mode 100644
index fc0d7e6..0000000
--- a/car_product/sepolicy/test/kitchensink_app.te
+++ /dev/null
@@ -1,36 +0,0 @@
-# Domain to run EmbeddedKitchenSink app (for test-purpose)
-type kitchensink_app, domain;
-app_domain(kitchensink_app);
-
-# Allow Car Service to be the client of Vehicle HAL
-hal_client_domain(kitchensink_app, hal_vehicle)
-
-# Keep alphabetically sorted.
-allow kitchensink_app {
-    accessibility_service
-    activity_service
-    activity_task_service
-    audio_service
-    audioserver_service
-    autofill_service
-    carservice_service
-    connectivity_service
-    content_service
-    deviceidle_service
-    display_service
-    graphicsstats_service
-    input_method_service
-    input_service
-    location_service
-    mediaserver_service
-    network_management_service
-    power_service
-    sensorservice_service
-    surfaceflinger_service
-    uimode_service
-    wifi_service
-}:service_manager find;
-
-# Read and write /data/data subdirectory.
-allow kitchensink_app system_app_data_file:dir { create_dir_perms getattr };
-allow kitchensink_app system_app_data_file:{ file lnk_file } create_file_perms;
diff --git a/car_product/sepolicy/test/seapp_contexts b/car_product/sepolicy/test/seapp_contexts
index 530e60e..a818d73 100644
--- a/car_product/sepolicy/test/seapp_contexts
+++ b/car_product/sepolicy/test/seapp_contexts
@@ -1 +1 @@
-user=system seinfo=platform name=com.google.android.car.kitchensink domain=kitchensink_app type=system_app_data_file
+user=system seinfo=platform name=com.android.experimentalcar domain=experimentalcarservice_app type=system_app_data_file
diff --git a/computepipe/Android.mk b/computepipe/Android.mk
new file mode 100644
index 0000000..319852c
--- /dev/null
+++ b/computepipe/Android.mk
@@ -0,0 +1,18 @@
+# Copyright (C) 2018 The Android Open Source Project
+
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH := $(call my-dir)
+include $(call all-makefiles-under,$(LOCAL_PATH))
+
+
diff --git a/computepipe/OWNERS b/computepipe/OWNERS
new file mode 100644
index 0000000..323a0bc
--- /dev/null
+++ b/computepipe/OWNERS
@@ -0,0 +1,2 @@
+hanumantsingh@google.com
+ankitarora@google.com
diff --git a/computepipe/aidl/Android.bp b/computepipe/aidl/Android.bp
new file mode 100644
index 0000000..31378b4
--- /dev/null
+++ b/computepipe/aidl/Android.bp
@@ -0,0 +1,39 @@
+aidl_interface {
+    name: "android.automotive.computepipe.runner",
+    vendor_available: true,
+    srcs: [
+        "android/automotive/computepipe/runner/*.aidl",
+        "android/automotive/computepipe/*.aidl",
+    ],
+    imports: [
+        "android.hardware.graphics.common",
+    ],
+    stability: "vintf",
+    backend: {
+        java: {
+            enabled: false,
+        },
+        cpp: {
+            enabled: false,
+        },
+    },
+}
+
+aidl_interface {
+    name: "android.automotive.computepipe.registry",
+    vendor_available: true,
+    imports: ["android.automotive.computepipe.runner"],
+    srcs: [
+        "android/automotive/computepipe/registry/*.aidl",
+        "android/automotive/computepipe/*.aidl",
+    ],
+    stability: "vintf",
+    backend: {
+        java: {
+            enabled: false,
+        },
+        cpp: {
+            enabled: false,
+        },
+    },
+}
diff --git a/computepipe/aidl/android/automotive/computepipe/registry/IClientInfo.aidl b/computepipe/aidl/android/automotive/computepipe/registry/IClientInfo.aidl
new file mode 100644
index 0000000..1dd2010
--- /dev/null
+++ b/computepipe/aidl/android/automotive/computepipe/registry/IClientInfo.aidl
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.automotive.computepipe.registry;
+
+@VintfStability
+interface IClientInfo {
+    /**
+     * Retrieve the name of the client.
+     */
+     @utf8InCpp String getClientName();
+}
diff --git a/computepipe/aidl/android/automotive/computepipe/registry/IPipeQuery.aidl b/computepipe/aidl/android/automotive/computepipe/registry/IPipeQuery.aidl
new file mode 100644
index 0000000..0602971
--- /dev/null
+++ b/computepipe/aidl/android/automotive/computepipe/registry/IPipeQuery.aidl
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.automotive.computepipe.registry;
+
+import android.automotive.computepipe.registry.IClientInfo;
+import android.automotive.computepipe.runner.IPipeRunner;
+
+/**
+ * Provides mechanism for pipe/graph runner discovery
+ */
+@VintfStability
+interface IPipeQuery {
+    /**
+     * A client will lookup the registered graphs using this method
+     * The registry implementation will return all the graphs registered.
+     * The registration is a one time event.
+     *
+     * @param out get supported graphs by name
+     */
+    @utf8InCpp String[] getGraphList();
+
+    /**
+     * Returns the graph runner for a specific graph
+     *
+     * Once the client has found the graph it is interested in using
+     * getGraphList(), it will use this to retrieve the runner for that graph.
+     * It is possible that between the GraphList retrieval and the invocation of
+     * this method the runner for this graph has gone down. In which case appropriate binder
+     * status will be used to report such an event.
+     *
+     * @param: graphId graph name for which corresponding runner is sought.
+     * @param: info retrieve client information for match purposes when needed
+     * @return handle to interact with specific graph
+     */
+    IPipeRunner getPipeRunner(in @utf8InCpp String graphName, in IClientInfo info);
+}
diff --git a/computepipe/aidl/android/automotive/computepipe/registry/IPipeRegistration.aidl b/computepipe/aidl/android/automotive/computepipe/registry/IPipeRegistration.aidl
new file mode 100644
index 0000000..23039da
--- /dev/null
+++ b/computepipe/aidl/android/automotive/computepipe/registry/IPipeRegistration.aidl
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.automotive.computepipe.registry;
+
+import android.automotive.computepipe.runner.IPipeRunner;
+
+/**
+ * Provides mechanism for graph/pipe runner to register with router.
+ */
+@VintfStability
+interface IPipeRegistration {
+    /**
+     * Returns a successful registration
+     * A runner will register itself as supporting a graph only once.
+     *
+     * @param graphName: Graph id for which runner and debugger are registered
+     * @param runner: Graph runner for associated graph.
+     * @return: returns ok if successful
+     */
+    void registerPipeRunner(in @utf8InCpp String graphName, IPipeRunner graphRunner);
+}
diff --git a/computepipe/aidl/android/automotive/computepipe/runner/IPipeDebugger.aidl b/computepipe/aidl/android/automotive/computepipe/runner/IPipeDebugger.aidl
new file mode 100644
index 0000000..58e69e5
--- /dev/null
+++ b/computepipe/aidl/android/automotive/computepipe/runner/IPipeDebugger.aidl
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.automotive.computepipe.runner;
+
+import android.automotive.computepipe.runner.PipeProfilingType;
+import android.automotive.computepipe.runner.ProfilingData;
+
+/**
+ * interface to debug and profile a graph
+ */
+@VintfStability
+interface IPipeDebugger {
+    /**
+     * Set the debug options for a pipe. The profiling options can be an
+     * externsion of the options mentioned here.
+     * https://mediapipe.readthedocs.io/en/latest/measure_performance.html
+     * This should be done prior to the applyPipeConfigs() call.
+     *
+     * @param type: The type of profiling a client wants to enable
+     */
+    void setPipeProfileOptions(in PipeProfilingType type);
+
+    /**
+     * Start the profiling for the mediapipe graph.
+     * This should be issued after the pipe has transitioned to the PipeState::RUNNING
+     *
+     * @param out if starting profiling was successful it returns binder::Status::OK
+     */
+    void startPipeProfiling();
+
+    /**
+     * Stop the profiling for the mediapipe graph.
+     * This can be done at any point of after a pipe is in RUNNING state.
+     *
+     * @param out if stoping profiling was successful, it returns binder::Status::OK
+     */
+    void stopPipeProfiling();
+
+    /**
+     * Retrieve the profiling information
+     * This can be done after a stopPipeProfiling() call or if a PipeState::DONE
+     * notification has been received.
+     *
+     * This is a polling api, If the pipe crashes, any calls to this api will fail.
+     * It blocks until profiling information is available.
+     * It returns the profiling data associated with the profiling options
+     * chosen by setPipe*().
+     * The profiling data is not retained after a call to resetPipeConfigs().
+     */
+    ProfilingData getPipeProfilingInfo();
+
+    /**
+     * Clear up all client specific resources.
+     *
+     * This clears out any gathered profiling data.
+     * This also resets the profiling configuration chosen by the client.
+     * After this method is invoked, client will be responsible for
+     * reconfiguring the profiling steps.
+     *
+     * @param out OK if release was configured successfully.
+     */
+    void releaseDebugger();
+}
diff --git a/computepipe/aidl/android/automotive/computepipe/runner/IPipeRunner.aidl b/computepipe/aidl/android/automotive/computepipe/runner/IPipeRunner.aidl
new file mode 100644
index 0000000..7e80dbc
--- /dev/null
+++ b/computepipe/aidl/android/automotive/computepipe/runner/IPipeRunner.aidl
@@ -0,0 +1,188 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.automotive.computepipe.runner;
+
+import android.automotive.computepipe.runner.PipeDescriptor;
+import android.automotive.computepipe.runner.IPipeStateCallback;
+import android.automotive.computepipe.runner.IPipeStream;
+import android.automotive.computepipe.runner.IPipeDebugger;
+
+@VintfStability
+interface IPipeRunner {
+    /**
+     * Init the runner
+     *
+     * @param statecb state handler to notify client of different state
+     * transitions in the runner. The runner deletes this callback after release
+     * is invoked. This is the first call that should be invoked by the client
+     */
+    void init(in IPipeStateCallback statecb);
+
+    /**
+     * Returns the descriptor for the associated mediapipe
+     *
+     * @param out A descriptor that describes the input options, offload options
+     * and the outputstreams of a computepipe instance.
+     */
+    PipeDescriptor getPipeDescriptor();
+
+    /**
+     * Set the input source for the computepipe graph.
+     * This should be done prior to invoking startPipe.
+     *
+     * @param configId id selected from the available input options.
+     * @param out if selection of input source was supported returns OK
+     */
+    void setPipeInputSource(in int configId);
+
+    /**
+     * Set the offload options for a graph.
+     * This should be a subset of the supported offload options present in the
+     * descriptor. This should be done prior to invoking startPipe
+     *
+     * @param configID offload option id from the advertised offload options.
+     * @param out if offload option was set then returns OK.
+     */
+    void setPipeOffloadOptions(in int configId);
+
+    /**
+     * Set the termination options for a graph.
+     * This should be a subset of the supported termination options present in the
+     * descriptor. This should be done prior to invoking startPipe.
+     *
+     * @param terminationId id of the supported termination option as advertized
+     * in the pipe descriptor
+     * @param out if termination criteria was supported then returns OK.
+     */
+    void setPipeTermination(in int configId);
+
+    /**
+     * Enable a output stream and install call back for packets from that
+     * stream. This should be invoked prior to calling startPipe.
+     * Call this for each output stream that a client wants to enable
+     *
+     * @param configId: describes the output stream configuration the client
+     * wants to enable
+     * @param maxInFlightCount: The maximum number of inflight packets the
+     * client can handle.
+     * @param handler: the handler for the output packets to be invoked once
+     * packet is received
+     * @param out OK void if setting callback succeeded
+     */
+    void setPipeOutputConfig(in int configId, in int maxInFlightCount, in IPipeStream handler);
+
+    /**
+     * Apply all configs.
+     * The client has finsihed specifying all the config options.
+     * Now the configs should be applied. Once the configs are applied the
+     * client will get a notification saying PipeState::CONFIG_DONE.
+     * The configuration applied with this step, will be retained for all future runs
+     * unless explicitly reset by calling resetPipeConfigs().
+     * In case of client death as well, the applied configurations are reset.
+     * In case the runner reports a ERR_HALT state, at any time after applyPipeConfigs(),
+     * all configurations are retained, and expected to be reset by the client
+     * explicitly, prior to attempting a new run.
+     * This call is only allowed when pipe is not running.
+     *
+     * @param out void::OK if the runner was notified to apply config.
+     */
+    void applyPipeConfigs();
+
+    /**
+     * Reset all configs.
+     * The runner stores the configuration even after pipe execution has
+     * completed. This call erases the previous configuration, so that client
+     * can modify and set the configuration again. This call is only allowed
+     * when pipe is not running.
+     *
+     * @param out void::OK if the runner was notified to apply config.
+     */
+    void resetPipeConfigs();
+
+    /**
+     * Start pipe execution on the runner. Prior to this step
+     * each of the configuration steps should be completed. Once the
+     * configurations have been applied, the state handler will be invoked with
+     * the PipeState::CONFIG_DONE notification. Wait for this notification before starting the pipe.
+     * Once the Pipe starts execution the client will receive the state
+     * notification PipeState::RUNNING through the state handler.
+     *
+     * @param out OK void if start succeeded.
+     */
+    void startPipe();
+
+    /**
+     * Stop pipe execution on the runner.
+     *
+     * This can invoked only when the pipe is in run state ie PipeState::RUNNING.
+     * If a client has already chosen a termination option, then this
+     * call overrides that termination criteria.
+     *
+     * Client will be notified once the pipe has stopped using PipeState::DONE
+     * notification. Until then, outstanding packets may continue to be received.
+     * These packets must still be returned with doneWithPacket(). (This does not
+     * apply to SEMANTIC_DATA, as they are copied in the stream callback).
+     *
+     * Once the Pipe stops execution (no new packets generated),
+     * the client will receive the state
+     * notification, PipeState::DONE.
+     *
+     * Once the pipe has completely quiesced, it will transition back to
+     * PipeState::CONFIG_DONE and at this point a new startPipe() can be issued or
+     * previously applied configs can be reset using the resetPipeConfigs() call.
+     *
+     * @param out OK void if stop succeeded
+     */
+    void stopPipe();
+
+    /**
+     * Signal completion of a packet having been consumed by the client.
+     * With this signal from client the runner should release buffer corresponding to the packet.
+     *
+     * @param bufferId Buffer id of the packet
+     * @param streamId Stream id of the packet
+     * @param out OK void if successful
+     */
+    void doneWithPacket(in int bufferId, in int streamId);
+
+    /**
+     * Returns the debugger associated with the runner for this graph
+     *
+     * @param out Debugger handle to interact with specific graph
+     */
+    IPipeDebugger getPipeDebugger();
+
+    /**
+     * Immediately frees up all config resources associated with the client.
+     * Client will not receive state notifications after this call is complete.
+     *
+     * This will also free up any in flight packet.
+     * The client may still get in flight IPipeStream::deliverPacket() callbacks.
+     * However the underlying buffer has been freed up from the packets.
+     *
+     * This also resets any configuration that a client may have performed,
+     * ie pipe transitions back to PipeState::RESET state.
+     * So client will have to start next session with
+     * the configuration steps, ie invoke setPipe*() methods.
+     *
+     * If the client had chosen to enable profiling through IPipeDebugger,
+     * the client should first invoke IPipeDebugger::Release() prior to
+     * this method.
+     *
+     * @return status OK if all resources were freed up.
+     */
+    void releaseRunner();
+}
diff --git a/computepipe/aidl/android/automotive/computepipe/runner/IPipeStateCallback.aidl b/computepipe/aidl/android/automotive/computepipe/runner/IPipeStateCallback.aidl
new file mode 100644
index 0000000..b9289f6
--- /dev/null
+++ b/computepipe/aidl/android/automotive/computepipe/runner/IPipeStateCallback.aidl
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.automotive.computepipe.runner;
+
+import android.automotive.computepipe.runner.PipeState;
+
+@VintfStability
+interface IPipeStateCallback {
+    /**
+     * Callback that notifies a client about the state of a pipe
+     *
+     * Client installs IPipeStateHandler with the runner by invoking
+     * setPipeStateNotifier(). The runner invokes the method below to notify the
+     * client of any state changes.
+     *
+     * @param state is the state of the pipe.
+     */
+    oneway void handleState(in PipeState state);
+}
diff --git a/computepipe/aidl/android/automotive/computepipe/runner/IPipeStream.aidl b/computepipe/aidl/android/automotive/computepipe/runner/IPipeStream.aidl
new file mode 100644
index 0000000..8ea6481
--- /dev/null
+++ b/computepipe/aidl/android/automotive/computepipe/runner/IPipeStream.aidl
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.automotive.computepipe.runner;
+
+import android.automotive.computepipe.runner.PacketDescriptor;
+
+@VintfStability
+interface IPipeStream {
+    /**
+     * Receives calls from the AIDL implementation each time a new packet is available.
+     * Semantic data is contaied in the packet descriptor.
+     * Only Zero copy data packets received by this method must be returned via calls to
+     * IPipeRunner::doneWithPacket(), using the bufId field in the descriptor.
+     * After the pipe execution has stopped this callback may continue to happen for sometime.
+     * Those packets must still be returned. Last frame will be indicated with
+     * a null packet. After that there will not be any further packets.
+     *
+     * @param: packet is a descriptor for the packet.
+     */
+    oneway void deliverPacket(in PacketDescriptor packet);
+}
diff --git a/computepipe/aidl/android/automotive/computepipe/runner/PacketDescriptor.aidl b/computepipe/aidl/android/automotive/computepipe/runner/PacketDescriptor.aidl
new file mode 100644
index 0000000..02fc82f
--- /dev/null
+++ b/computepipe/aidl/android/automotive/computepipe/runner/PacketDescriptor.aidl
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.automotive.computepipe.runner;
+
+import android.automotive.computepipe.runner.PacketDescriptorPacketType;
+import android.hardware.graphics.common.HardwareBuffer;
+
+/**
+ * Structure that describes the output packet for a specific outputstream
+ * that gets returned to the client.
+ */
+@VintfStability
+parcelable PacketDescriptor {
+    /**
+     * packet id, used in case of zero copy data.
+     * Used to notify the runner of consumption.
+     */
+    int bufId;
+    /**
+     * type of the buffer
+     */
+    PacketDescriptorPacketType type;
+    /**
+     * size of the memory region
+     */
+    int size;
+    /**
+     * Handle to pixel data. This handle can be mapped and data retrieved.
+     * The description field will contain the
+     * graphics buffer description. Must be freed with call to doneWithPacket()
+     * This is populated only if type is PIXEL
+     */
+    HardwareBuffer handle;
+    /**
+     * Zero copy semantic data handle.
+     * Must be freed with call to doneWithPacket().
+     * This is populated only if type is SEMANTIC
+     */
+    ParcelFileDescriptor[] dataFds;
+    /**
+     * Timestamp of event at source. Timestamp value is milliseconds since epoch.
+     */
+    long sourceTimeStampMillis;
+    /**
+     * semantic data. Requires no doneWithPacket() acknowledgement.
+     */
+    byte[] data;
+}
diff --git a/computepipe/aidl/android/automotive/computepipe/runner/PacketDescriptorPacketType.aidl b/computepipe/aidl/android/automotive/computepipe/runner/PacketDescriptorPacketType.aidl
new file mode 100644
index 0000000..fd1f5ce
--- /dev/null
+++ b/computepipe/aidl/android/automotive/computepipe/runner/PacketDescriptorPacketType.aidl
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.automotive.computepipe.runner;
+
+/**
+ * types of packet
+ */
+@VintfStability
+@Backing(type="int")
+enum PacketDescriptorPacketType {
+    /**
+     * General semantic data derived from input stream
+     */
+    SEMANTIC_DATA = 0,
+    /**
+     * Pixel data generated, for eg annotated frames
+     */
+    PIXEL_DATA,
+    /**
+     * Semantic data with zero copy requirements.
+     */
+    SEMANTIC_ZERO_COPY_DATA,
+}
diff --git a/computepipe/aidl/android/automotive/computepipe/runner/PipeDescriptor.aidl b/computepipe/aidl/android/automotive/computepipe/runner/PipeDescriptor.aidl
new file mode 100644
index 0000000..128e803
--- /dev/null
+++ b/computepipe/aidl/android/automotive/computepipe/runner/PipeDescriptor.aidl
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.automotive.computepipe.runner;
+
+import android.automotive.computepipe.runner.PipeInputConfig;
+import android.automotive.computepipe.runner.PipeOffloadConfig;
+import android.automotive.computepipe.runner.PipeTerminationConfig;
+import android.automotive.computepipe.runner.PipeOutputConfig;
+
+/**
+ * Pipe Descriptor
+ *
+ * This is the descriptor for use case that describes all the supported
+ * a) input options
+ * b) termination options
+ * c) offload options
+ * d) output streams
+ *
+ * This is returned by the HIDL implementation to the client to describe a given graph.
+ * The client selects the config for a given graph run from the available
+ * choices advertised. Note the output stream that a client wants to subscribes
+ * to, require the client to subscribe to each stream individually.
+ *
+ * This descriptor is returned by the HAL to the client.
+ */
+@VintfStability
+parcelable PipeDescriptor {
+    /**
+     * input configurations supported by the graph.
+     */
+    PipeInputConfig[] inputConfig;
+    /**
+     * Offload options supported by the graph.
+     */
+    PipeOffloadConfig[] offloadConfig;
+    /**
+     * Termination options supported by the graph.
+     */
+    PipeTerminationConfig[] terminationConfig;
+    /**
+     * Output streams supported by the graph.
+     */
+    PipeOutputConfig[] outputConfig;
+}
+
diff --git a/computepipe/aidl/android/automotive/computepipe/runner/PipeInputConfig.aidl b/computepipe/aidl/android/automotive/computepipe/runner/PipeInputConfig.aidl
new file mode 100644
index 0000000..e49aba3
--- /dev/null
+++ b/computepipe/aidl/android/automotive/computepipe/runner/PipeInputConfig.aidl
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.automotive.computepipe.runner;
+
+import android.automotive.computepipe.runner.PipeInputConfigInputSourceDesc;
+
+/**
+ * Transaction data types
+ *
+ *
+ * Input config descriptor
+ *
+ * Structure that describes the input sources
+ *
+ * This is provided by the AIDL implementation to the client
+ */
+@VintfStability
+parcelable PipeInputConfig {
+    /**
+     * input option.
+     */
+    PipeInputConfigInputSourceDesc[] inputSources;
+    /**
+     * ID for the option.
+     */
+    int configId;
+}
+
diff --git a/computepipe/aidl/android/automotive/computepipe/runner/PipeInputConfigCameraDesc.aidl b/computepipe/aidl/android/automotive/computepipe/runner/PipeInputConfigCameraDesc.aidl
new file mode 100644
index 0000000..9788042
--- /dev/null
+++ b/computepipe/aidl/android/automotive/computepipe/runner/PipeInputConfigCameraDesc.aidl
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.automotive.computepipe.runner;
+
+import android.automotive.computepipe.runner.PipeInputConfigCameraType;
+
+/**
+ * Camera config descriptor
+ */
+@VintfStability
+parcelable PipeInputConfigCameraDesc {
+    /**
+     * input stream type
+     */
+    PipeInputConfigCameraType type;
+    /**
+     * camera identifier to disambiguate multiple instances
+     * of InputType. If only one of a certain type is present
+     * this should be 0. For VIDEO_FILE this should be 0.
+     */
+    @utf8InCpp String camId;
+}
diff --git a/computepipe/aidl/android/automotive/computepipe/runner/PipeInputConfigCameraType.aidl b/computepipe/aidl/android/automotive/computepipe/runner/PipeInputConfigCameraType.aidl
new file mode 100644
index 0000000..6750eb3
--- /dev/null
+++ b/computepipe/aidl/android/automotive/computepipe/runner/PipeInputConfigCameraType.aidl
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.automotive.computepipe.runner;
+
+/**
+ * State of the remote graph
+ */
+@VintfStability
+@Backing(type="int")
+enum PipeInputConfigCameraType {
+    /**
+     * Driver focused Camera stream
+     */
+    DRIVER_VIEW_CAMERA = 0,
+    /**
+     * Camera with wider field of view that can capture
+     * occupants in the car.
+     */
+    OCCUPANT_VIEW_CAMERA,
+    /**
+     * External Camera
+     */
+    EXTERNAL_CAMERA,
+    /**
+     * Surround view
+     */
+    SURROUND_VIEW_CAMERA,
+}
diff --git a/computepipe/aidl/android/automotive/computepipe/runner/PipeInputConfigFormatType.aidl b/computepipe/aidl/android/automotive/computepipe/runner/PipeInputConfigFormatType.aidl
new file mode 100644
index 0000000..b25e8b2
--- /dev/null
+++ b/computepipe/aidl/android/automotive/computepipe/runner/PipeInputConfigFormatType.aidl
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.automotive.computepipe.runner;
+
+@VintfStability
+@Backing(type="int")
+enum PipeInputConfigFormatType {
+    /**
+     * RGB input
+     */
+    RGB,
+    /**
+     * NIR input
+     */
+    NIR,
+    /**
+     * NIR + Depth Frame
+     */
+    NIR_DEPTH,
+}
diff --git a/computepipe/aidl/android/automotive/computepipe/runner/PipeInputConfigImageFileDesc.aidl b/computepipe/aidl/android/automotive/computepipe/runner/PipeInputConfigImageFileDesc.aidl
new file mode 100644
index 0000000..07ea7f8
--- /dev/null
+++ b/computepipe/aidl/android/automotive/computepipe/runner/PipeInputConfigImageFileDesc.aidl
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.automotive.computepipe.runner;
+
+import android.automotive.computepipe.runner.PipeInputConfigImageFileType;
+
+/**
+ * Image file descriptor
+ */
+@VintfStability
+parcelable PipeInputConfigImageFileDesc {
+    /**
+     * File type
+     */
+    PipeInputConfigImageFileType fileType;
+
+    /**
+     * File path
+     */
+     @utf8InCpp String filePath;
+}
diff --git a/computepipe/aidl/android/automotive/computepipe/runner/PipeInputConfigImageFileType.aidl b/computepipe/aidl/android/automotive/computepipe/runner/PipeInputConfigImageFileType.aidl
new file mode 100644
index 0000000..34e224e
--- /dev/null
+++ b/computepipe/aidl/android/automotive/computepipe/runner/PipeInputConfigImageFileType.aidl
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.automotive.computepipe.runner;
+
+/**
+ * Image file type
+ */
+@VintfStability
+@Backing(type="int")
+enum PipeInputConfigImageFileType {
+    /**
+     * JPEG
+     */
+    JPEG = 0,
+    /**
+     * Configuration completed
+     */
+    PNG,
+}
diff --git a/computepipe/aidl/android/automotive/computepipe/runner/PipeInputConfigInputSourceDesc.aidl b/computepipe/aidl/android/automotive/computepipe/runner/PipeInputConfigInputSourceDesc.aidl
new file mode 100644
index 0000000..b4b93a7
--- /dev/null
+++ b/computepipe/aidl/android/automotive/computepipe/runner/PipeInputConfigInputSourceDesc.aidl
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.automotive.computepipe.runner;
+
+import android.automotive.computepipe.runner.PipeInputConfigCameraDesc;
+import android.automotive.computepipe.runner.PipeInputConfigFormatType;
+import android.automotive.computepipe.runner.PipeInputConfigImageFileDesc;
+import android.automotive.computepipe.runner.PipeInputConfigInputType;
+import android.automotive.computepipe.runner.PipeInputConfigVideoFileDesc;
+
+/**
+ * Input source descriptor
+ */
+@VintfStability
+parcelable PipeInputConfigInputSourceDesc {
+    /**
+     * input stream type
+     */
+    PipeInputConfigInputType type;
+    /**
+     * format of the input stream
+     */
+    PipeInputConfigFormatType format;
+    /**
+     * width resolution of the input stream
+     */
+    int width;
+    /**
+     * height resolution of the input stream
+     */
+    int height;
+    /**
+     * stride for the frame
+     */
+    int stride;
+    /**
+     * Camera config. This should be populated if the type is a camera.
+     */
+    PipeInputConfigCameraDesc camDesc;
+    /**
+     * Video file config. This should be populated if the type set is video file.
+     */
+    PipeInputConfigVideoFileDesc videoDesc;
+    /**
+     * Camera config. This should be populated if the type is a video file
+     */
+    PipeInputConfigImageFileDesc imageDesc;
+}
diff --git a/computepipe/aidl/android/automotive/computepipe/runner/PipeInputConfigInputType.aidl b/computepipe/aidl/android/automotive/computepipe/runner/PipeInputConfigInputType.aidl
new file mode 100644
index 0000000..bf2e739
--- /dev/null
+++ b/computepipe/aidl/android/automotive/computepipe/runner/PipeInputConfigInputType.aidl
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.automotive.computepipe.runner;
+
+/**
+ * Types of input streams supported by runner
+ */
+@VintfStability
+@Backing(type="int")
+enum PipeInputConfigInputType {
+    /**
+     * Camera type is used
+     */
+    CAMERA = 0,
+    /**
+     * Video file
+     */
+    VIDEO_FILE,
+    /**
+     * Image files
+     */
+    IMAGE_FILES,
+}
diff --git a/computepipe/aidl/android/automotive/computepipe/runner/PipeInputConfigVideoFileDesc.aidl b/computepipe/aidl/android/automotive/computepipe/runner/PipeInputConfigVideoFileDesc.aidl
new file mode 100644
index 0000000..35a59d0
--- /dev/null
+++ b/computepipe/aidl/android/automotive/computepipe/runner/PipeInputConfigVideoFileDesc.aidl
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.automotive.computepipe.runner;
+
+import android.automotive.computepipe.runner.PipeInputConfigVideoFileType;
+
+/**
+ * Video file descriptor
+ */
+@VintfStability
+parcelable PipeInputConfigVideoFileDesc {
+    /**
+     * File type
+     */
+    PipeInputConfigVideoFileType fileType;
+
+    /**
+     * File path
+     */
+     @utf8InCpp String filePath;
+}
diff --git a/computepipe/aidl/android/automotive/computepipe/runner/PipeInputConfigVideoFileType.aidl b/computepipe/aidl/android/automotive/computepipe/runner/PipeInputConfigVideoFileType.aidl
new file mode 100644
index 0000000..0dba428
--- /dev/null
+++ b/computepipe/aidl/android/automotive/computepipe/runner/PipeInputConfigVideoFileType.aidl
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.automotive.computepipe.runner;
+
+/**
+ *Type of encoding for the video file
+ */
+@VintfStability
+@Backing(type="int")
+enum PipeInputConfigVideoFileType {
+    /**
+     * MPEG
+     */
+    MPEG = 0,
+}
diff --git a/computepipe/aidl/android/automotive/computepipe/runner/PipeOffloadConfig.aidl b/computepipe/aidl/android/automotive/computepipe/runner/PipeOffloadConfig.aidl
new file mode 100644
index 0000000..2682095
--- /dev/null
+++ b/computepipe/aidl/android/automotive/computepipe/runner/PipeOffloadConfig.aidl
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.automotive.computepipe.runner;
+
+import android.automotive.computepipe.runner.PipeOffloadConfigOffloadType;
+import android.automotive.computepipe.runner.PipeOffloadConfigOffloadDesc;
+
+/**
+ * Offload configs
+ *
+ * Structure that describes the offload options that a graph can use.
+ * This is determined at graph creation time by the developer.
+ * A graph can advertise different combinations of offload options that it
+ * can use. A client can choose amongst the combinations of offload options, for
+ * any given iteration of the graph execution.
+ *
+ * This is provided by the HIDL implementation to the client
+ */
+@VintfStability
+parcelable PipeOffloadConfig {
+    /**
+     * Offload descriptor that the graph can support.
+     */
+    PipeOffloadConfigOffloadDesc desc;
+    /**
+     * identifier for the option.
+     */
+    String configId;
+}
+
diff --git a/computepipe/aidl/android/automotive/computepipe/runner/PipeOffloadConfigOffloadDesc.aidl b/computepipe/aidl/android/automotive/computepipe/runner/PipeOffloadConfigOffloadDesc.aidl
new file mode 100644
index 0000000..825bb3e
--- /dev/null
+++ b/computepipe/aidl/android/automotive/computepipe/runner/PipeOffloadConfigOffloadDesc.aidl
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.automotive.computepipe.runner;
+
+import android.automotive.computepipe.runner.PipeOffloadConfigOffloadType;
+
+/**
+ * structure that describes the combination of offload options.
+ * This is a per graph specific combination.
+ */
+@VintfStability
+parcelable PipeOffloadConfigOffloadDesc {
+    /**
+     * combination of different offload engines
+     */
+    PipeOffloadConfigOffloadType[] type;
+    /**
+     * 1:1 correspondence for each type above.
+     * Every offload engine has a flag describing if its virtual device
+     */
+    boolean[] isVirtual;
+}
+
diff --git a/computepipe/aidl/android/automotive/computepipe/runner/PipeOffloadConfigOffloadType.aidl b/computepipe/aidl/android/automotive/computepipe/runner/PipeOffloadConfigOffloadType.aidl
new file mode 100644
index 0000000..3502b1c
--- /dev/null
+++ b/computepipe/aidl/android/automotive/computepipe/runner/PipeOffloadConfigOffloadType.aidl
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.automotive.computepipe.runner;
+
+/**
+ * Types of offload options for graph computations available to the runner
+ * All of the offload options maybe virtualized for different execution
+ * environments.
+ */
+@VintfStability
+@Backing(type="int")
+enum PipeOffloadConfigOffloadType {
+    /**
+     * Default cpu only execution
+     */
+    CPU = 0,
+    /**
+     * GPU, Open GLES based acceleration
+     */
+    GPU,
+    /**
+     * Dedicated neural engine provided by SOC vendor
+     */
+    NEURAL_ENGINE,
+    /**
+     * Computer Vision engine provided by SOC vendor
+     */
+    CV_ENGINE,
+}
diff --git a/computepipe/aidl/android/automotive/computepipe/runner/PipeOutputConfig.aidl b/computepipe/aidl/android/automotive/computepipe/runner/PipeOutputConfig.aidl
new file mode 100644
index 0000000..e7b0b89
--- /dev/null
+++ b/computepipe/aidl/android/automotive/computepipe/runner/PipeOutputConfig.aidl
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.automotive.computepipe.runner;
+
+import android.automotive.computepipe.runner.PipeOutputConfigOutputDesc;
+
+/**
+ * Output configs
+ *
+ * Structure that describes the output stream packets of a graph
+ *
+ * Provided by AIDL implementation to the client as part of GraphDescriptor
+ */
+@VintfStability
+parcelable PipeOutputConfig {
+    /**
+     * output stream.
+     */
+    PipeOutputConfigOutputDesc output;
+    /**
+     * id for the stream.
+     */
+    int outputId;
+}
diff --git a/computepipe/aidl/android/automotive/computepipe/runner/PipeOutputConfigOutputDesc.aidl b/computepipe/aidl/android/automotive/computepipe/runner/PipeOutputConfigOutputDesc.aidl
new file mode 100644
index 0000000..7c512df
--- /dev/null
+++ b/computepipe/aidl/android/automotive/computepipe/runner/PipeOutputConfigOutputDesc.aidl
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.automotive.computepipe.runner;
+
+import android.automotive.computepipe.runner.PipeOutputConfigPacketType;
+
+/**
+ * Output descriptor
+ */
+@VintfStability
+parcelable PipeOutputConfigOutputDesc {
+    /**
+     * name of the output stream
+     */
+    String name;
+    /**
+     * type of packets produced
+     */
+    PipeOutputConfigPacketType type;
+}
diff --git a/computepipe/aidl/android/automotive/computepipe/runner/PipeOutputConfigPacketType.aidl b/computepipe/aidl/android/automotive/computepipe/runner/PipeOutputConfigPacketType.aidl
new file mode 100644
index 0000000..44fffb6
--- /dev/null
+++ b/computepipe/aidl/android/automotive/computepipe/runner/PipeOutputConfigPacketType.aidl
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.automotive.computepipe.runner;
+
+/**
+ * Packet type
+ */
+@VintfStability
+@Backing(type="int")
+enum PipeOutputConfigPacketType {
+    /**
+     * semantic data that can be copied.
+     */
+    SEMANTIC_DATA = 0,
+    /**
+     * pixel data with copy semantics.
+     */
+    PIXEL_DATA,
+    /**
+     * pixel data with zero copy requirement.
+     */
+    PIXEL_ZERO_COPY_DATA,
+}
diff --git a/computepipe/aidl/android/automotive/computepipe/runner/PipeProfilingType.aidl b/computepipe/aidl/android/automotive/computepipe/runner/PipeProfilingType.aidl
new file mode 100644
index 0000000..1c8a632
--- /dev/null
+++ b/computepipe/aidl/android/automotive/computepipe/runner/PipeProfilingType.aidl
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.automotive.computepipe.runner;
+
+/**
+ * Profiling types
+ */
+@VintfStability
+@Backing(type="int")
+enum PipeProfilingType {
+    /**
+     * Latency profiling
+     */
+    LATENCY = 0,
+    /**
+     * Trace events
+     */
+    TRACE_EVENTS = 1,
+}
diff --git a/computepipe/aidl/android/automotive/computepipe/runner/PipeState.aidl b/computepipe/aidl/android/automotive/computepipe/runner/PipeState.aidl
new file mode 100644
index 0000000..5e1768f
--- /dev/null
+++ b/computepipe/aidl/android/automotive/computepipe/runner/PipeState.aidl
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.automotive.computepipe.runner;
+
+/**
+ * State of the remote graph
+ */
+@VintfStability
+@Backing(type="int")
+enum PipeState {
+    /**
+     * Reset
+     */
+    RESET = 0,
+    /**
+     * Configuration completed
+     */
+    CONFIG_DONE,
+    /**
+     * Running
+     */
+    RUNNING,
+    /**
+     * Finished
+     */
+    DONE,
+    /**
+     * In halt due to error
+     */
+    ERR_HALT,
+}
diff --git a/computepipe/aidl/android/automotive/computepipe/runner/PipeTerminationConfig.aidl b/computepipe/aidl/android/automotive/computepipe/runner/PipeTerminationConfig.aidl
new file mode 100644
index 0000000..b462507
--- /dev/null
+++ b/computepipe/aidl/android/automotive/computepipe/runner/PipeTerminationConfig.aidl
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.automotive.computepipe.runner;
+
+import android.automotive.computepipe.runner.PipeTerminationConfigTerminationDesc;
+import android.automotive.computepipe.runner.PipeTerminationConfigTerminationType;
+
+/**
+ * Termination configs
+ *
+ * Structure that describes the termination options a graph can advertise
+ *
+ * Provided by HIDL implementation to the client as part of GraphDescriptor
+ *
+ * The client has the option of choosing one of the provided options supported
+ * by the graph or calling calling stopPipe() explicitly.
+ */
+@VintfStability
+parcelable PipeTerminationConfig {
+    /**
+     * termination option supported by graph.
+     */
+    PipeTerminationConfigTerminationDesc desc;
+    /**
+     * identifiers for the option.
+     */
+    int configId;
+}
+
diff --git a/computepipe/aidl/android/automotive/computepipe/runner/PipeTerminationConfigTerminationDesc.aidl b/computepipe/aidl/android/automotive/computepipe/runner/PipeTerminationConfigTerminationDesc.aidl
new file mode 100644
index 0000000..cf1434e
--- /dev/null
+++ b/computepipe/aidl/android/automotive/computepipe/runner/PipeTerminationConfigTerminationDesc.aidl
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.automotive.computepipe.runner;
+
+import android.automotive.computepipe.runner.PipeOutputConfig;
+import android.automotive.computepipe.runner.PipeTerminationConfigTerminationType;
+
+/**
+ * structure that describes the different termination options supported
+ * by the graph
+ */
+@VintfStability
+parcelable PipeTerminationConfigTerminationDesc {
+    /**
+     * type of termination criteria
+     */
+    PipeTerminationConfigTerminationType type;
+    /**
+     * type based qualifier, could be run time, packet count, or usecase
+     * specific event identifier.
+     */
+    int qualifier;
+    /**
+     * Output stream config. This will only be used when type is MAX_PACKET_COUNT
+     */
+     PipeOutputConfig streamConfig;
+}
+
diff --git a/computepipe/aidl/android/automotive/computepipe/runner/PipeTerminationConfigTerminationType.aidl b/computepipe/aidl/android/automotive/computepipe/runner/PipeTerminationConfigTerminationType.aidl
new file mode 100644
index 0000000..e6ecd9c
--- /dev/null
+++ b/computepipe/aidl/android/automotive/computepipe/runner/PipeTerminationConfigTerminationType.aidl
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */package android.automotive.computepipe.runner;
+
+/**
+ * Types of termination options
+ */
+@VintfStability
+@Backing(type="int")
+enum PipeTerminationConfigTerminationType {
+    /**
+     * run indefinetely until client stop.
+     */
+    CLIENT_STOP = 0,
+    /**
+     * run for minimum number of valid output packets
+     */
+    MIN_PACKET_COUNT,
+    /**
+     * run for fixed maximum duration, graph may still produce some packets
+     * post run time, because of delayed signal.
+     */
+    MAX_RUN_TIME,
+    /**
+     * run until specified event.
+     */
+    EVENT,
+}
diff --git a/computepipe/aidl/android/automotive/computepipe/runner/ProfilingData.aidl b/computepipe/aidl/android/automotive/computepipe/runner/ProfilingData.aidl
new file mode 100644
index 0000000..a6973ba
--- /dev/null
+++ b/computepipe/aidl/android/automotive/computepipe/runner/ProfilingData.aidl
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.automotive.computepipe.runner;
+
+import android.automotive.computepipe.runner.PipeProfilingType;
+
+/**
+ * Structure that describes the profiling information output from
+ * an executing pipe.
+ */
+@VintfStability
+parcelable ProfilingData {
+    /**
+     * Type of profiling information
+     */
+    PipeProfilingType type;
+    /**
+     * size of the memory region
+     */
+    int size;
+    /**
+     * handle to memory region containing zero copy or semantic data
+     * as described in https://mediapipe.readthedocs.io/en/latest/measure_performance.html
+     */
+    ParcelFileDescriptor[] dataFds;
+}
+
diff --git a/computepipe/example/Android.bp b/computepipe/example/Android.bp
new file mode 100644
index 0000000..cfd979d
--- /dev/null
+++ b/computepipe/example/Android.bp
@@ -0,0 +1,151 @@
+// Copyright 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+cc_binary {
+    name: "computepipe_face_runner",
+    srcs: [
+        "Runner.cpp",
+    ],
+    static_libs: [
+        "libcomputepipeprotos",
+        "computepipe_prebuilt_graph",
+        "computepipe_client_interface",
+    ],
+    shared_libs: [
+        "computepipe_runner_engine",
+        "libprotobuf-cpp-lite",
+        "libbase",
+        "libbinder_ndk",
+        "android.automotive.computepipe.runner-ndk_platform",
+        "android.automotive.computepipe.registry-ndk_platform",
+        "liblog",
+        "libutils",
+        "libdl",
+        "libfacegraph",
+	"libnativewindow",
+    ],
+    cflags: ["-DLOG_TAG=\"ComputepipeRunner\""] + [
+        "-D_LIBCPP_ENABLE_THREAD_SAFETY_ANNOTATIONS",
+        "-Wall",
+        "-Werror",
+        "-Wunused",
+        "-Wunreachable-code",
+        "-Wthread-safety",
+    ],
+    header_libs: [
+        "computepipe_runner_includes",
+    ],
+    include_dirs: [
+        "packages/services/Car/computepipe",
+    ],
+}
+
+cc_prebuilt_library_shared {
+    name: "libfacegraph",
+    strip: {
+        keep_symbols: true,
+    },
+    target: {
+        android_arm64: {
+            srcs: ["lib_arm64/libfacegraph.so"],
+        },
+        android_arm: {
+            srcs: ["lib_arm/libfacegraph.so"],
+        },
+        android_x86_64: {
+            srcs: ["lib_x86_64/libfacegraph.so"],
+        },
+        android_x86: {
+            srcs: ["lib_x86/libfacegraph.so"],
+        },
+    },
+    shared_libs: [
+        "libc",
+        "libdl",
+        "libEGL",
+        "libGLESv2",
+        "liblog",
+        "libm",
+        "libz",
+    ],
+}
+
+cc_binary {
+    name: "computepipe_face_tracker",
+    srcs: [
+        "FaceTracker.cpp",
+	"ClientSvc.cpp",
+    ],
+    vendor: true,
+    static_libs: [
+        "libcomputepipefaceproto",
+    ],
+    shared_libs: [
+        "liblog",
+        "libbase",
+        "libbinder_ndk",
+        "libutils",
+        "android.hardware.automotive.occupant_awareness-ndk_platform",
+        "libprotobuf-cpp-lite",
+        "android.automotive.computepipe.runner-ndk_platform",
+        "android.automotive.computepipe.registry-ndk_platform",
+    ],
+    cflags: ["-DLOG_TAG=\"FaceTrackerClient\""] + [
+        "-D_LIBCPP_ENABLE_THREAD_SAFETY_ANNOTATIONS",
+        "-Wall",
+        "-Werror",
+        "-Wunused",
+        "-Wunreachable-code",
+        "-Wthread-safety",
+    ],
+}
+
+cc_defaults {
+    name: "libcomputepipeface-defaults",
+
+    proto: {
+        export_proto_headers: true,
+        include_dirs: ["external/protobuf/src"],
+    },
+
+    cflags: [
+        "-Wall",
+        "-Werror",
+        "-Wno-unused-parameter",
+    ],
+
+    srcs: [
+        "*.proto",
+    ],
+}
+
+cc_library {
+    name: "libcomputepipefaceproto",
+    defaults: ["libcomputepipeface-defaults"],
+    host_supported: false,
+    vendor_available: true,
+    target: {
+        android: {
+            proto: {
+                type: "lite",
+            },
+            static_libs: [
+                "libprotobuf-cpp-lite",
+            ],
+            shared: {
+                enabled: false,
+            },
+        },
+    },
+}
diff --git a/computepipe/example/ClientSvc.cpp b/computepipe/example/ClientSvc.cpp
new file mode 100644
index 0000000..59af7df
--- /dev/null
+++ b/computepipe/example/ClientSvc.cpp
@@ -0,0 +1,47 @@
+// Copyright 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <android-base/logging.h>
+#include <android/binder_process.h>
+#include <utils/Log.h>
+
+#include "FaceTracker.h"
+
+using android::automotive::computepipe::FaceTracker;
+
+namespace {
+
+void terminationCallback(bool error, std::string errorMsg) {
+    if (error) {
+        LOG(ERROR) << errorMsg;
+        exit(2);
+    }
+    LOG(ERROR) << "Test completed";
+    exit(0);
+}
+}  // namespace
+int main(int /* argc */, char** /* argv */) {
+    std::function<void(bool, std::string)> cb = terminationCallback;
+    FaceTracker client;
+
+    ABinderProcess_startThreadPool();
+    ndk::ScopedAStatus status = client.init(std::move(cb));
+    if (!status.isOk()) {
+        LOG(ERROR) << "Unable to init client connection";
+        return -1;
+    }
+    ABinderProcess_joinThreadPool();
+
+    return 0;
+}
diff --git a/computepipe/example/FaceOutput.proto b/computepipe/example/FaceOutput.proto
new file mode 100644
index 0000000..59487b1
--- /dev/null
+++ b/computepipe/example/FaceOutput.proto
@@ -0,0 +1,21 @@
+syntax = "proto2";
+
+package android.automotive.computepipe.example;
+
+message BoundingBox {
+  optional int32 top_x = 1;
+  optional int32 top_y = 2;
+  optional int32 width = 3;
+  optional int32 height = 4;
+};
+
+message Pose {
+  optional float pan  = 1;
+  optional float tilt = 2;
+}
+
+message FaceOutput {
+  optional BoundingBox box = 1;
+
+  optional Pose pose = 2;
+};
diff --git a/computepipe/example/FaceTracker.cpp b/computepipe/example/FaceTracker.cpp
new file mode 100644
index 0000000..47e237b
--- /dev/null
+++ b/computepipe/example/FaceTracker.cpp
@@ -0,0 +1,168 @@
+// Copyright 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "FaceTracker.h"
+
+#include <android-base/logging.h>
+#include <android/binder_manager.h>
+#include <utils/Log.h>
+
+#include <memory>
+#include <mutex>
+#include <thread>
+
+namespace android {
+namespace automotive {
+namespace computepipe {
+
+using ::android::automotive::computepipe::example::FaceOutput;
+namespace {
+constexpr char kReigstryInterface[] = "router";
+constexpr char kGraphName[] = "Face Tracker Graph";
+}  // namespace
+
+/**
+ * RemoteState monitor
+ */
+PipeState RemoteState::GetCurrentState() {
+    std::unique_lock<std::mutex> lock(mStateLock);
+    mWait.wait(lock, [this]() { return hasChanged; });
+    hasChanged = false;
+    return mState;
+}
+
+void RemoteState::UpdateCurrentState(const PipeState& state) {
+    std::lock_guard<std::mutex> lock(mStateLock);
+    mState = state;
+    if (mState == PipeState::ERR_HALT) {
+        mTerminationCb(true, "Received error from runner");
+    } else if (mState == PipeState::DONE) {
+        mTerminationCb(false, "");
+    } else {
+        hasChanged = true;
+        mWait.notify_all();
+    }
+}
+
+RemoteState::RemoteState(std::function<void(bool, std::string)>& cb) : mTerminationCb(cb) {
+}
+
+/**
+ * StateCallback methods
+ */
+StateCallback::StateCallback(std::shared_ptr<RemoteState> s) : mStateTracker(s) {
+}
+
+ndk::ScopedAStatus StateCallback::handleState(PipeState state) {
+    mStateTracker->UpdateCurrentState(state);
+    return ndk::ScopedAStatus::ok();
+}
+
+/**
+ * FaceTracker methods
+ */
+ndk::ScopedAStatus FaceTracker::init(std::function<void(bool, std::string)>&& cb) {
+    auto termination = cb;
+    mRemoteState = std::make_shared<RemoteState>(termination);
+    std::string instanceName = std::string() + IPipeQuery::descriptor + "/" + kReigstryInterface;
+
+    ndk::SpAIBinder binder(AServiceManager_getService(instanceName.c_str()));
+    CHECK(binder.get());
+
+    std::shared_ptr<IPipeQuery> queryService = IPipeQuery::fromBinder(binder);
+    mClientInfo = ndk::SharedRefBase::make<ClientInfo>();
+    ndk::ScopedAStatus status = queryService->getPipeRunner(kGraphName, mClientInfo, &mPipeRunner);
+    if (!status.isOk()) {
+        LOG(ERROR) << "Failed to get handle to runner";
+        return status;
+    }
+    mStreamCallback = ndk::SharedRefBase::make<StreamCallback>();
+    mStateCallback = ndk::SharedRefBase::make<StateCallback>(mRemoteState);
+    return setupConfig();
+}
+
+ndk::ScopedAStatus FaceTracker::setupConfig() {
+    ndk::ScopedAStatus status = mPipeRunner->init(mStateCallback);
+    if (!status.isOk()) {
+        LOG(ERROR) << "Failed to init runner";
+        return status;
+    }
+    status = mPipeRunner->setPipeInputSource(0);
+    if (!status.isOk()) {
+        LOG(ERROR) << "Failed to set pipe input config";
+        return status;
+    }
+    status = mPipeRunner->setPipeOutputConfig(0, 10, mStreamCallback);
+    if (!status.isOk()) {
+        LOG(ERROR) << "Failed to set pipe output config";
+        return status;
+    }
+    status = mPipeRunner->applyPipeConfigs();
+    if (!status.isOk()) {
+        LOG(ERROR) << "Failed to set apply configs";
+        return status;
+    }
+    std::thread t(&FaceTracker::start, this);
+    t.detach();
+    return ndk::ScopedAStatus::ok();
+}
+
+void FaceTracker::start() {
+    PipeState state = mRemoteState->GetCurrentState();
+    CHECK(state == PipeState::CONFIG_DONE);
+    ndk::ScopedAStatus status = mPipeRunner->startPipe();
+    CHECK(status.isOk());
+    state = mRemoteState->GetCurrentState();
+    CHECK(state == PipeState::RUNNING);
+}
+
+void FaceTracker::stop() {
+    ndk::ScopedAStatus status = mPipeRunner->startPipe();
+    CHECK(status.isOk());
+}
+
+/**
+ * Stream Callback implementation
+ */
+
+ndk::ScopedAStatus StreamCallback::deliverPacket(const PacketDescriptor& in_packet) {
+    std::string output(in_packet.data.begin(), in_packet.data.end());
+
+    FaceOutput faceData;
+    faceData.ParseFromString(output);
+
+    BoundingBox currentBox = faceData.box();
+
+    if (!faceData.has_box()) {
+        mLastBox = BoundingBox();
+        return ndk::ScopedAStatus::ok();
+    }
+
+    if (!mLastBox.has_top_x()) {
+        mLastBox = currentBox;
+        return ndk::ScopedAStatus::ok();
+    }
+
+    if (currentBox.top_x() > mLastBox.top_x() + 1) {
+        LOG(ERROR) << "Face moving left";
+    } else if (currentBox.top_x() + 1 < mLastBox.top_x()) {
+        LOG(ERROR) << "Face moving right";
+    }
+    mLastBox = currentBox;
+    return ndk::ScopedAStatus::ok();
+}
+
+}  // namespace computepipe
+}  // namespace automotive
+}  // namespace android
diff --git a/computepipe/example/FaceTracker.h b/computepipe/example/FaceTracker.h
new file mode 100644
index 0000000..60fd473
--- /dev/null
+++ b/computepipe/example/FaceTracker.h
@@ -0,0 +1,106 @@
+// Copyright 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+#ifndef COMPUTEPIPE_EXAMPLE_FACE_TRACKER_H
+#define COMPUTEPIPE_EXAMPLE_FACE_TRACKER_H
+
+#include <aidl/android/automotive/computepipe/registry/BnClientInfo.h>
+#include <aidl/android/automotive/computepipe/registry/IPipeQuery.h>
+#include <aidl/android/automotive/computepipe/registry/IPipeRegistration.h>
+#include <aidl/android/automotive/computepipe/runner/BnPipeStateCallback.h>
+#include <aidl/android/automotive/computepipe/runner/BnPipeStream.h>
+#include <aidl/android/automotive/computepipe/runner/PipeState.h>
+
+#include <condition_variable>
+#include <iostream>
+#include <memory>
+
+#include "FaceOutput.pb.h"
+
+namespace android {
+namespace automotive {
+namespace computepipe {
+
+using ::aidl::android::automotive::computepipe::registry::BnClientInfo;
+using ::aidl::android::automotive::computepipe::registry::IPipeQuery;
+using ::aidl::android::automotive::computepipe::runner::BnPipeStateCallback;
+using ::aidl::android::automotive::computepipe::runner::BnPipeStream;
+using ::aidl::android::automotive::computepipe::runner::IPipeRunner;
+using ::aidl::android::automotive::computepipe::runner::IPipeStream;
+using ::aidl::android::automotive::computepipe::runner::PacketDescriptor;
+using ::aidl::android::automotive::computepipe::runner::PipeState;
+using ::android::automotive::computepipe::example::BoundingBox;
+
+class RemoteState {
+  public:
+    explicit RemoteState(std::function<void(bool, std::string)>& cb);
+    PipeState GetCurrentState();
+    void UpdateCurrentState(const PipeState& state);
+
+  private:
+    bool hasChanged = false;
+    PipeState mState = PipeState::RESET;
+    std::mutex mStateLock;
+    std::condition_variable mWait;
+    std::function<void(bool, std::string)> mTerminationCb;
+};
+
+class ClientInfo : public BnClientInfo {
+  public:
+    ndk::ScopedAStatus getClientName(std::string* _aidl_return) override {
+        if (_aidl_return) {
+            *_aidl_return = "FaceTrackerClient";
+            return ndk::ScopedAStatus::ok();
+        }
+        return ndk::ScopedAStatus::fromExceptionCode(EX_TRANSACTION_FAILED);
+    }
+};
+
+class StreamCallback : public BnPipeStream {
+  public:
+    StreamCallback() = default;
+    ndk::ScopedAStatus deliverPacket(const PacketDescriptor& in_packet) override;
+
+  private:
+    BoundingBox mLastBox;
+};
+
+class StateCallback : public BnPipeStateCallback {
+  public:
+    explicit StateCallback(std::shared_ptr<RemoteState> s);
+    ndk::ScopedAStatus handleState(PipeState state) override;
+
+  private:
+    std::shared_ptr<RemoteState> mStateTracker = nullptr;
+};
+
+class FaceTracker {
+  public:
+    FaceTracker() = default;
+    ndk::ScopedAStatus init(std::function<void(bool, std::string)>&& termination);
+    void start();
+    void stop();
+
+  private:
+    ndk::ScopedAStatus setupConfig();
+    std::shared_ptr<IPipeRunner> mPipeRunner = nullptr;
+    std::shared_ptr<ClientInfo> mClientInfo = nullptr;
+    std::shared_ptr<StreamCallback> mStreamCallback = nullptr;
+    std::shared_ptr<StateCallback> mStateCallback = nullptr;
+    std::shared_ptr<RemoteState> mRemoteState = nullptr;
+};
+
+}  // namespace computepipe
+}  // namespace automotive
+}  // namespace android
+#endif
diff --git a/computepipe/example/Runner.cpp b/computepipe/example/Runner.cpp
new file mode 100644
index 0000000..b46345e
--- /dev/null
+++ b/computepipe/example/Runner.cpp
@@ -0,0 +1,75 @@
+// Copyright 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+#include <android-base/logging.h>
+#include <android/binder_process.h>
+#include <utils/Errors.h>
+#include <utils/Log.h>
+
+#include <iostream>
+#include <memory>
+#include <thread>
+
+#include "ClientConfig.pb.h"
+#include "ClientInterface.h"
+#include "MemHandle.h"
+#include "Options.pb.h"
+#include "PrebuiltGraph.h"
+#include "RunnerEngine.h"
+#include "types/Status.h"
+
+using android::automotive::computepipe::Status;
+using android::automotive::computepipe::graph::PrebuiltGraph;
+using android::automotive::computepipe::proto::ClientConfig;
+using android::automotive::computepipe::proto::Options;
+using android::automotive::computepipe::runner::client_interface::ClientInterface;
+using android::automotive::computepipe::runner::client_interface::ClientInterfaceFactory;
+using android::automotive::computepipe::runner::engine::RunnerEngine;
+using android::automotive::computepipe::runner::engine::RunnerEngineFactory;
+
+namespace {
+RunnerEngineFactory sEngineFactory;
+ClientInterfaceFactory sClientFactory;
+}  // namespace
+void terminate(bool isError, std::string msg) {
+    if (isError) {
+        LOG(ERROR) << "Error msg " << msg;
+        exit(2);
+    }
+    LOG(ERROR) << "Test complete";
+    exit(0);
+}
+
+int main(int /* argc */, char** /* argv */) {
+    std::shared_ptr<RunnerEngine> engine =
+        sEngineFactory.createRunnerEngine(RunnerEngineFactory::kDefault, "");
+
+    std::unique_ptr<PrebuiltGraph> graph;
+    graph.reset(PrebuiltGraph::GetPrebuiltGraphFromLibrary("libfacegraph.so", engine));
+
+    Options options = graph->GetSupportedGraphConfigs();
+    engine->setPrebuiltGraph(std::move(graph));
+
+    std::function<void(bool, std::string)> cb = terminate;
+    std::unique_ptr<ClientInterface> client =
+        sClientFactory.createClientInterface("aidl", options, engine);
+    if (!client) {
+        std::cerr << "Unable to allocate client";
+        return -1;
+    }
+    engine->setClientInterface(std::move(client));
+    ABinderProcess_startThreadPool();
+    engine->activate();
+    ABinderProcess_joinThreadPool();
+    return 0;
+}
diff --git a/computepipe/example/lib_arm/libfacegraph.so b/computepipe/example/lib_arm/libfacegraph.so
new file mode 100644
index 0000000..3988416
--- /dev/null
+++ b/computepipe/example/lib_arm/libfacegraph.so
Binary files differ
diff --git a/computepipe/example/lib_arm64/libfacegraph.so b/computepipe/example/lib_arm64/libfacegraph.so
new file mode 100644
index 0000000..c890bde
--- /dev/null
+++ b/computepipe/example/lib_arm64/libfacegraph.so
Binary files differ
diff --git a/computepipe/example/lib_x86/libfacegraph.so b/computepipe/example/lib_x86/libfacegraph.so
new file mode 100644
index 0000000..f77fe74
--- /dev/null
+++ b/computepipe/example/lib_x86/libfacegraph.so
Binary files differ
diff --git a/computepipe/example/lib_x86_64/libfacegraph.so b/computepipe/example/lib_x86_64/libfacegraph.so
new file mode 100644
index 0000000..cc52bc2
--- /dev/null
+++ b/computepipe/example/lib_x86_64/libfacegraph.so
Binary files differ
diff --git a/computepipe/products/computepipe.mk b/computepipe/products/computepipe.mk
new file mode 100644
index 0000000..d5fe7f7
--- /dev/null
+++ b/computepipe/products/computepipe.mk
@@ -0,0 +1,35 @@
+# Copyright (C) 2019 The Android Open Source Project
+
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Enable computepipe interface
+PRODUCT_PACKAGES += android.automotive.computepipe.router@1.0-impl
+
+# Enable computepipe router
+PRODUCT_PACKAGES += android.automotive.computepipe.router@1.0
+
+# Enable computepipe runner engine library
+PRODUCT_PACKAGES += computepipe_runner_engine
+
+# Enable computepipe runner engine client interface library
+PRODUCT_PACKAGES += computepipe_client_interface
+
+# Enable computepipe runner engine prebuilt graph library
+PRODUCT_PACKAGES += computepipe_prebuilt_graph
+
+
+# Selinux public policies for computepipe services
+BOARD_PLAT_PUBLIC_SEPOLICY_DIR += packages/services/Car/computepipe/sepolicy/public
+
+# Selinux private policies for computepipe services
+BOARD_PLAT_PRIVATE_SEPOLICY_DIR += packages/services/Car/computepipe/sepolicy/private
diff --git a/computepipe/proto/Android.bp b/computepipe/proto/Android.bp
new file mode 100644
index 0000000..68eab52
--- /dev/null
+++ b/computepipe/proto/Android.bp
@@ -0,0 +1,52 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+cc_defaults {
+    name: "libcomputepipeprotos-defaults",
+
+    proto: {
+        export_proto_headers: true,
+        include_dirs: ["external/protobuf/src"],
+    },
+
+    cflags: [
+        "-Wall",
+        "-Werror",
+        "-Wno-unused-parameter",
+    ],
+
+    srcs: [
+        "*.proto",
+    ],
+}
+
+cc_library {
+    name: "libcomputepipeprotos",
+    defaults: ["libcomputepipeprotos-defaults"],
+    host_supported: false,
+    vendor_available: true,
+    target: {
+        android: {
+            proto: {
+                type: "lite",
+            },
+            static_libs: [
+                "libprotobuf-cpp-lite",
+            ],
+            shared: {
+                enabled: false,
+            },
+        },
+    },
+}
diff --git a/computepipe/proto/ClientConfig.proto b/computepipe/proto/ClientConfig.proto
new file mode 100644
index 0000000..38b6cde
--- /dev/null
+++ b/computepipe/proto/ClientConfig.proto
@@ -0,0 +1,10 @@
+syntax = "proto2";
+
+package android.automotive.computepipe.proto;
+
+message ClientConfig {
+  optional int32 input_config_id = 1;
+  optional int32 offload_id = 2;
+  map<int32, int32> output_options = 3;
+  optional int32 termination_id = 4;
+}
diff --git a/computepipe/proto/ConfigurationCommand.proto b/computepipe/proto/ConfigurationCommand.proto
new file mode 100644
index 0000000..88dc038
--- /dev/null
+++ b/computepipe/proto/ConfigurationCommand.proto
@@ -0,0 +1,27 @@
+syntax = "proto2";
+
+package android.automotive.computepipe.proto;
+
+message SetInputSource {
+  optional int32 source_id = 1;
+}
+
+message SetOffloadOptions {
+  optional int32 offload_option_id = 1;
+}
+
+message SetTerminationOptions {
+  optional int32 termination_option_id = 1;
+}
+
+message SetOutputStream {
+  optional int32 stream_id  = 1;
+  optional int32 max_inflight_packets_count = 2;
+}
+
+message ConfigurationCommand {
+  optional SetInputSource set_input_source = 1;
+  optional SetOffloadOptions set_offload_offload = 2;
+  optional SetTerminationOptions set_termination_option = 3;
+  optional SetOutputStream set_output_stream = 4;
+}
diff --git a/computepipe/proto/ControlCommand.proto b/computepipe/proto/ControlCommand.proto
new file mode 100644
index 0000000..d6ae34a
--- /dev/null
+++ b/computepipe/proto/ControlCommand.proto
@@ -0,0 +1,31 @@
+syntax = "proto2";
+
+package android.automotive.computepipe.proto;
+
+message StartGraph {
+  // No member fields yet, as StartGraph() does not take any arguments.
+}
+
+message StopGraph {
+  // No member fields yet, as StopGraph() does not take any arguments.
+}
+
+message ApplyConfigs {
+  // No member fields yet, as ApplyConfigs() does not take any arguments.
+}
+
+message ResetConfigs {
+  // No member fields yet, as ResetConfigs() does not take any arguments.
+}
+
+message DeathNotification {
+  // No member fields yet.
+}
+
+message ControlCommand {
+  optional StartGraph start_graph = 1;
+  optional StopGraph stop_graph = 2;
+  optional ApplyConfigs apply_configs = 3;
+  optional DeathNotification death_notification = 4;
+  optional ResetConfigs reset_configs = 5;
+}
diff --git a/computepipe/proto/InputConfig.proto b/computepipe/proto/InputConfig.proto
new file mode 100644
index 0000000..0381e1a
--- /dev/null
+++ b/computepipe/proto/InputConfig.proto
@@ -0,0 +1,81 @@
+syntax = "proto2";
+
+package android.automotive.computepipe.proto;
+
+message ImageFileConfig {
+  enum ImageFileType {
+    JPEG = 0;
+    PNG = 1;
+  }
+
+  optional ImageFileType file_type = 1;
+
+  optional string image_dir = 2;
+}
+
+message VideoFileConfig {
+  enum VideoFileType {
+    MPEG = 0;
+  }
+
+  optional VideoFileType file_type = 1;
+
+  optional string file_path = 2;
+}
+
+message CameraConfig {
+  enum CameraType {
+    DRIVER_VIEW_CAMERA = 0;
+    OCCUPANT_VIEW_CAMERA = 1;
+    EXTERNAL_CAMERA = 2;
+    SURROUND_VIEW_CAMERA = 3;
+  };
+
+  optional CameraType camera_type = 1;
+
+  optional string cam_id = 2;
+}
+
+message InputStreamConfig {
+  enum InputType {
+    CAMERA = 1;
+    IMAGE_FILES = 2;
+    VIDEO_FILE = 3;
+  }
+
+  optional InputType type = 1;
+
+  enum FormatType {
+    RGB = 0;
+    NIR = 1;
+    NIR_DEPTH = 2;
+  }
+
+  optional FormatType format = 2;
+
+  optional int32 width = 3;
+
+  optional int32 height = 4;
+
+  optional int32 stride = 5;
+
+  // The camera id string essential to start the camera.
+  optional CameraConfig cam_config = 6;
+
+  // for image file input type the following attributes apply
+  optional ImageFileConfig image_config = 7;
+
+  // Must be present when InputType is ImageFile
+  optional VideoFileConfig video_config = 8;
+
+  optional int32 stream_id = 9;
+}
+
+// A graph could require streams from multiple cameras simultaneously, so each possible input
+// config could specify multiple camera configs.
+message InputConfig {
+    repeated InputStreamConfig input_stream = 1;
+
+    // config id which would be used to set the config rather than passing the entire config proto
+    optional int32 config_id = 2;
+}
diff --git a/computepipe/proto/OffloadConfig.proto b/computepipe/proto/OffloadConfig.proto
new file mode 100644
index 0000000..68ec09e
--- /dev/null
+++ b/computepipe/proto/OffloadConfig.proto
@@ -0,0 +1,26 @@
+syntax = "proto2";
+
+package android.automotive.computepipe.proto;
+
+message OffloadOption {
+  enum OffloadType {
+    CPU = 0;
+    GPU = 1;
+    NEURAL_ENGINE = 2;
+    CV_ENGINE = 3;
+  }
+  repeated OffloadType offload_types = 1;
+
+  /**
+     * 1:1 correspondence for each type above.
+     * Every offload engine has a flag describing if its virtual device
+     */
+  repeated bool is_virtual = 2;
+}
+
+message OffloadConfig {
+
+    optional OffloadOption options = 1;
+
+    optional string config_id = 2;
+}
diff --git a/computepipe/proto/Options.proto b/computepipe/proto/Options.proto
new file mode 100644
index 0000000..14d130d
--- /dev/null
+++ b/computepipe/proto/Options.proto
@@ -0,0 +1,33 @@
+syntax = "proto2";
+
+package android.automotive.computepipe.proto;
+
+import "packages/services/Car/computepipe/proto/InputConfig.proto";
+import "packages/services/Car/computepipe/proto/OffloadConfig.proto";
+import "packages/services/Car/computepipe/proto/OutputConfig.proto";
+import "packages/services/Car/computepipe/proto/TerminationConfig.proto";
+
+message Options {
+  /**
+   * input configurations supported by the graph
+   */
+  repeated InputConfig input_configs = 1;
+
+  /**
+   * Offload options supported by the graph
+   */
+  repeated OffloadConfig offload_configs = 2;
+  /**
+   * Termination options supported by the graph
+   */
+  repeated TerminationConfig termination_configs = 3;
+  /**
+   * Output streams supported by the graph.
+   */
+  repeated OutputConfig output_configs = 4;
+
+  /**
+   * Name of the computer vision graph.
+   */
+  optional string graph_name = 5;
+}
diff --git a/computepipe/proto/OutputConfig.proto b/computepipe/proto/OutputConfig.proto
new file mode 100644
index 0000000..f392b4b
--- /dev/null
+++ b/computepipe/proto/OutputConfig.proto
@@ -0,0 +1,17 @@
+syntax = "proto2";
+
+package android.automotive.computepipe.proto;
+
+enum PacketType {
+  SEMANTIC_DATA = 0;
+  PIXEL_DATA = 1;
+  PIXEL_ZERO_COPY_DATA = 2;
+}
+
+message OutputConfig {
+  optional string stream_name = 1;
+
+  optional PacketType type = 2;
+
+  optional int32 stream_id = 3;
+}
diff --git a/computepipe/proto/PacketDescriptor.proto b/computepipe/proto/PacketDescriptor.proto
new file mode 100644
index 0000000..8014141
--- /dev/null
+++ b/computepipe/proto/PacketDescriptor.proto
@@ -0,0 +1,23 @@
+syntax = "proto2";
+
+package android.automotive.computepipe.proto;
+import "packages/services/Car/computepipe/proto/OutputConfig.proto";
+
+message NativeHandles {
+  // File descriptors
+  repeated int32 fds = 1;
+
+  // Corresponding indexes.
+  repeated int32 ix = 2;
+}
+
+message PacketDescriptor {
+  optional int32 buffer_id = 1;
+
+  optional NativeHandles native_handles = 2;
+
+  //  Timestamp value is milliseconds since epoch.
+  optional int64 timestamp_ms = 3;
+
+  optional PacketType packet_type = 4;
+}
diff --git a/computepipe/proto/TerminationConfig.proto b/computepipe/proto/TerminationConfig.proto
new file mode 100644
index 0000000..aea1c4f
--- /dev/null
+++ b/computepipe/proto/TerminationConfig.proto
@@ -0,0 +1,27 @@
+syntax = "proto2";
+
+package android.automotive.computepipe.proto;
+
+import "packages/services/Car/computepipe/proto/OutputConfig.proto";
+
+message TerminationOption {
+  enum TerminationType {
+    CLIENT_STOP = 0;
+    MIN_PACKET_COUNT = 1;
+    MAX_RUN_TIME = 2;
+    EVENT = 3;
+  }
+
+  optional TerminationType type = 1;
+  /**
+    * type based qualifier, could be run time, packet count, or usecase
+    * specific event identifier.
+    */
+  optional int32 qualifier = 2;
+}
+
+message TerminationConfig {
+  optional TerminationOption options = 1;
+  optional int32 config_id = 2;
+  optional OutputConfig output_config = 3;
+}
diff --git a/computepipe/router/1.0/Android.bp b/computepipe/router/1.0/Android.bp
new file mode 100644
index 0000000..dd4b6c0
--- /dev/null
+++ b/computepipe/router/1.0/Android.bp
@@ -0,0 +1,72 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+cc_library {
+    name: "android.automotive.computepipe.router@1.0-impl",
+    vendor_available: true,
+    srcs: [
+        "PipeClient.cpp",
+        "PipeRunner.cpp",
+        "PipeRegistration.cpp",
+        "PipeQuery.cpp",
+        "RemoteState.cpp",
+    ],
+    cflags: ["-DLOG_TAG=\"ComputepipeRouter\""] +
+        [
+            "-Wall",
+            "-Werror",
+            "-Wextra",
+            "-Wno-unused-parameter",
+        ],
+    header_libs: [
+        "computepipe_router_headers",
+    ],
+    export_include_dirs: ["include"],
+    shared_libs: [
+        "liblog",
+        "libutils",
+        "libbinder_ndk",
+        "android.automotive.computepipe.registry-ndk_platform",
+        "android.automotive.computepipe.runner-ndk_platform",
+    ],
+}
+
+cc_binary {
+    name: "android.automotive.computepipe.router@1.0",
+    srcs: [
+        "service.cpp",
+        "RouterSvc.cpp",
+    ],
+    shared_libs: [
+        "libcutils",
+        "liblog",
+        "libutils",
+        "libbinder_ndk",
+        "android.automotive.computepipe.router@1.0-impl",
+        "android.automotive.computepipe.registry-ndk_platform",
+    ],
+    header_libs: [
+        "computepipe_router_headers",
+    ],
+    init_rc: ["android.automotive.computepipe.router@1.0.rc"],
+    cflags: ["-DLOG_TAG=\"ComputepipeRouter\""] + [
+        "-DGL_GLEXT_PROTOTYPES",
+        "-DEGL_EGLEXT_PROTOTYPES",
+    ] + [
+        "-Wall",
+        "-Werror",
+        "-Wunused",
+        "-Wunreachable-code",
+    ],
+}
diff --git a/computepipe/router/1.0/PipeClient.cpp b/computepipe/router/1.0/PipeClient.cpp
new file mode 100644
index 0000000..b7e299d
--- /dev/null
+++ b/computepipe/router/1.0/PipeClient.cpp
@@ -0,0 +1,64 @@
+/**
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "PipeClient.h"
+
+#include <android/binder_ibinder.h>
+
+namespace android {
+namespace automotive {
+namespace computepipe {
+namespace router {
+namespace V1_0 {
+namespace implementation {
+
+using namespace aidl::android::automotive::computepipe::registry;
+using namespace ndk;
+
+PipeClient::PipeClient(const std::shared_ptr<IClientInfo>& info)
+    : mDeathMonitor(AIBinder_DeathRecipient_new(RemoteMonitor::BinderDiedCallback)),
+      mClientInfo(info) {
+}
+
+std::string PipeClient::getClientName() {
+    if (mClientInfo == nullptr) {
+        return 0;
+    }
+    std::string name;
+    auto status = mClientInfo->getClientName(&name);
+    return (status.isOk()) ? name : "";
+}
+
+bool PipeClient::startClientMonitor() {
+    mState = std::make_shared<RemoteState>();
+    auto monitor = new RemoteMonitor(mState);
+    auto status = ScopedAStatus::fromStatus(
+        AIBinder_linkToDeath(mClientInfo->asBinder().get(), mDeathMonitor.get(), monitor));
+    return status.isOk();
+}
+
+bool PipeClient::isAlive() {
+    return mState->isAlive();
+}
+
+PipeClient::~PipeClient() {
+}
+}  // namespace implementation
+}  // namespace V1_0
+}  // namespace router
+}  // namespace computepipe
+}  // namespace automotive
+}  // namespace android
diff --git a/computepipe/router/1.0/PipeQuery.cpp b/computepipe/router/1.0/PipeQuery.cpp
new file mode 100644
index 0000000..42e4f0b
--- /dev/null
+++ b/computepipe/router/1.0/PipeQuery.cpp
@@ -0,0 +1,66 @@
+/**
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "PipeQuery.h"
+
+#include "PipeClient.h"
+
+namespace android {
+namespace automotive {
+namespace computepipe {
+namespace router {
+namespace V1_0 {
+namespace implementation {
+
+using namespace android::automotive::computepipe::router;
+using namespace aidl::android::automotive::computepipe::registry;
+using namespace aidl::android::automotive::computepipe::runner;
+using namespace ndk;
+
+ScopedAStatus PipeQuery::getGraphList(std::vector<std::string>* outNames) {
+    if (!mRegistry || !outNames) {
+        return ScopedAStatus(AStatus_fromExceptionCode(EX_ILLEGAL_STATE));
+    }
+    auto names = mRegistry->getPipeList();
+    std::copy(names.begin(), names.end(), std::back_inserter(*outNames));
+    return ScopedAStatus::ok();
+}
+
+ScopedAStatus PipeQuery::getPipeRunner(const std::string& graphName,
+                                       const std::shared_ptr<IClientInfo>& info,
+                                       std::shared_ptr<IPipeRunner>* outRunner) {
+    *outRunner = nullptr;
+    if (!mRegistry) {
+        return ScopedAStatus(AStatus_fromExceptionCode(EX_ILLEGAL_STATE));
+    }
+    std::unique_ptr<ClientHandle> clientHandle = std::make_unique<PipeClient>(info);
+    auto pipeHandle = mRegistry->getClientPipeHandle(graphName, std::move(clientHandle));
+    if (!pipeHandle) {
+        return ScopedAStatus(AStatus_fromExceptionCode(EX_ILLEGAL_STATE));
+    }
+    auto pipeRunner = pipeHandle->getInterface();
+    *outRunner = pipeRunner->runner;
+    return ScopedAStatus::ok();
+}
+
+const char* PipeQuery::getIfaceName() {
+    return this->descriptor;
+}
+}  // namespace implementation
+}  // namespace V1_0
+}  // namespace router
+}  // namespace computepipe
+}  // namespace automotive
+}  // namespace android
diff --git a/computepipe/router/1.0/PipeRegistration.cpp b/computepipe/router/1.0/PipeRegistration.cpp
new file mode 100644
index 0000000..cff413c
--- /dev/null
+++ b/computepipe/router/1.0/PipeRegistration.cpp
@@ -0,0 +1,56 @@
+/**
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "PipeRegistration.h"
+
+namespace android {
+namespace automotive {
+namespace computepipe {
+namespace router {
+namespace V1_0 {
+namespace implementation {
+
+using namespace android::automotive::computepipe;
+using namespace aidl::android::automotive::computepipe::runner;
+using namespace ndk;
+// Methods from ::android::automotive::computepipe::registry::V1_0::IPipeRegistration follow.
+ScopedAStatus PipeRegistration::registerPipeRunner(const std::string& graphName,
+                                                   const std::shared_ptr<IPipeRunner>& graphRunner) {
+    if (!mRegistry) {
+        return ScopedAStatus(AStatus_fromExceptionCode(EX_ILLEGAL_STATE));
+    }
+    std::unique_ptr<PipeHandle<PipeRunner>> handle = std::make_unique<RunnerHandle>(graphRunner);
+    auto err = mRegistry->RegisterPipe(std::move(handle), graphName);
+    return convertToBinderStatus(err);
+}
+
+ScopedAStatus PipeRegistration::convertToBinderStatus(Error err) {
+    switch (err) {
+        case OK:
+            return ScopedAStatus::ok();
+        default:
+            return ScopedAStatus(AStatus_fromExceptionCode(EX_ILLEGAL_STATE));
+    }
+}
+
+const char* PipeRegistration::getIfaceName() {
+    return this->descriptor;
+}
+}  // namespace implementation
+}  // namespace V1_0
+}  // namespace router
+}  // namespace computepipe
+}  // namespace automotive
+}  // namespace android
diff --git a/computepipe/router/1.0/PipeRunner.cpp b/computepipe/router/1.0/PipeRunner.cpp
new file mode 100644
index 0000000..cdf1cb1
--- /dev/null
+++ b/computepipe/router/1.0/PipeRunner.cpp
@@ -0,0 +1,65 @@
+/**
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "PipeRunner.h"
+
+#include <android/binder_ibinder.h>
+
+#include <mutex>
+
+namespace android {
+namespace automotive {
+namespace computepipe {
+namespace router {
+namespace V1_0 {
+namespace implementation {
+
+using namespace aidl::android::automotive::computepipe::runner;
+using namespace ndk;
+
+PipeRunner::PipeRunner(const std::shared_ptr<IPipeRunner>& graphRunner) : runner(graphRunner) {
+}
+
+RunnerHandle::RunnerHandle(const std::shared_ptr<IPipeRunner>& r)
+    : PipeHandle(std::make_unique<PipeRunner>(r)),
+      mDeathMonitor(AIBinder_DeathRecipient_new(RemoteMonitor::BinderDiedCallback)) {
+}
+
+bool RunnerHandle::startPipeMonitor() {
+    mState = std::make_shared<RemoteState>();
+    auto monitor = new RemoteMonitor(mState);
+    auto status = ScopedAStatus::fromStatus(
+        AIBinder_linkToDeath(mInterface->runner->asBinder().get(), mDeathMonitor.get(), monitor));
+    return status.isOk();
+}
+
+bool RunnerHandle::isAlive() {
+    return mState->isAlive();
+}
+
+PipeHandle<PipeRunner>* RunnerHandle::clone() const {
+    return new RunnerHandle(mInterface->runner);
+}
+
+RunnerHandle::~RunnerHandle() {
+}
+
+}  // namespace implementation
+}  // namespace V1_0
+}  // namespace router
+}  // namespace computepipe
+}  // namespace automotive
+}  // namespace android
diff --git a/computepipe/router/1.0/RemoteState.cpp b/computepipe/router/1.0/RemoteState.cpp
new file mode 100644
index 0000000..9d81fb6
--- /dev/null
+++ b/computepipe/router/1.0/RemoteState.cpp
@@ -0,0 +1,52 @@
+/**
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "RemoteState.h"
+
+namespace android {
+namespace automotive {
+namespace computepipe {
+namespace router {
+namespace V1_0 {
+namespace implementation {
+
+void RemoteState::markDead() {
+    std::lock_guard<std::mutex> lock(mStateLock);
+    mAlive = false;
+}
+
+bool RemoteState::isAlive() {
+    std::lock_guard<std::mutex> lock(mStateLock);
+    return mAlive;
+}
+
+void RemoteMonitor::binderDied() {
+    auto state = mState.lock();
+    if (state != nullptr) {
+        state->markDead();
+    }
+}
+
+void RemoteMonitor::BinderDiedCallback(void* cookie) {
+    auto monitor = static_cast<RemoteMonitor*>(cookie);
+    monitor->binderDied();
+}
+}  // namespace implementation
+}  // namespace V1_0
+}  // namespace router
+}  // namespace computepipe
+}  // namespace automotive
+}  // namespace android
diff --git a/computepipe/router/1.0/RouterSvc.cpp b/computepipe/router/1.0/RouterSvc.cpp
new file mode 100644
index 0000000..0b296e6
--- /dev/null
+++ b/computepipe/router/1.0/RouterSvc.cpp
@@ -0,0 +1,88 @@
+/**
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "RouterSvc.h"
+
+#include <android/binder_interface_utils.h>
+#include <android/binder_manager.h>
+#include <binder/IServiceManager.h>
+
+#include "PipeQuery.h"
+#include "PipeRegistration.h"
+#include "Registry.h"
+
+namespace android {
+namespace automotive {
+namespace computepipe {
+namespace router {
+namespace V1_0 {
+namespace implementation {
+
+const static char kRouterName[] = "router";
+
+Error RouterSvc::parseArgs(int argc, char** argv) {
+    (void)argc;
+    (void)argv;
+    return OK;
+}
+
+Error RouterSvc::initSvc() {
+    mRegistry = std::make_shared<RouterRegistry>();
+    auto ret = initRegistrationEngine();
+    if (ret != OK) {
+        return ret;
+    }
+    ret = initQueryEngine();
+    if (ret != OK) {
+        return ret;
+    }
+    return OK;
+}
+
+Error RouterSvc::initRegistrationEngine() {
+    mRegisterEngine = ndk::SharedRefBase::make<PipeRegistration>(mRegistry);
+    if (!mRegisterEngine) {
+        ALOGE("unable to allocate registration engine");
+        return NOMEM;
+    }
+    std::string name = std::string() + mRegisterEngine->getIfaceName() + "/" + kRouterName;
+    auto status = AServiceManager_addService(mRegisterEngine->asBinder().get(), name.c_str());
+    if (status != STATUS_OK) {
+        ALOGE("unable to add registration service %s", name.c_str());
+        return INTERNAL_ERR;
+    }
+    return OK;
+}
+
+Error RouterSvc::initQueryEngine() {
+    mQueryEngine = ndk::SharedRefBase::make<PipeQuery>(mRegistry);
+    if (!mQueryEngine) {
+        ALOGE("unable to allocate query service");
+        return NOMEM;
+    }
+    std::string name = std::string() + mQueryEngine->getIfaceName() + "/" + kRouterName;
+    auto status = AServiceManager_addService(mQueryEngine->asBinder().get(), name.c_str());
+    if (status != STATUS_OK) {
+        ALOGE("unable to add query service %s", name.c_str());
+        return INTERNAL_ERR;
+    }
+    return OK;
+}
+}  // namespace implementation
+}  // namespace V1_0
+}  // namespace router
+}  // namespace computepipe
+}  // namespace automotive
+}  // namespace android
diff --git a/computepipe/router/1.0/RouterSvc.h b/computepipe/router/1.0/RouterSvc.h
new file mode 100644
index 0000000..08ea59c
--- /dev/null
+++ b/computepipe/router/1.0/RouterSvc.h
@@ -0,0 +1,63 @@
+/**
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#ifndef ANDROID_AUTOMOTIVE_COMPUTEPIPE_ROUTER_V1_0_ROUTERSVC
+#define ANDROID_AUTOMOTIVE_COMPUTEPIPE_ROUTER_V1_0_ROUTERSVC
+
+#include "PipeQuery.h"
+#include "PipeRegistration.h"
+
+namespace android {
+namespace automotive {
+namespace computepipe {
+namespace router {
+namespace V1_0 {
+namespace implementation {
+
+namespace router = android::automotive::computepipe::router;
+
+class RouterRegistry : public router::PipeRegistry<PipeRunner> {
+    std ::unique_ptr<PipeHandle<PipeRunner>> getDebuggerPipeHandle(const std::string& name) {
+        return getPipeHandle(name, nullptr);
+    }
+    Error RemoveEntry(const std::string& name) {
+        return DeletePipeHandle(name);
+    }
+};
+
+class RouterSvc {
+  public:
+    router::Error initSvc();
+    router::Error parseArgs(int argc, char** argv);
+    std::string getSvcName() {
+        return mSvcName;
+    }
+
+  private:
+    router::Error initQueryEngine();
+    router::Error initRegistrationEngine();
+
+    std::string mSvcName = "ComputePipeRouter";
+    std::shared_ptr<PipeQuery> mQueryEngine;
+    std::shared_ptr<PipeRegistration> mRegisterEngine;
+    std::shared_ptr<RouterRegistry> mRegistry;
+};
+}  // namespace implementation
+}  // namespace V1_0
+}  // namespace router
+}  // namespace computepipe
+}  // namespace automotive
+}  // namespace android
+#endif
diff --git a/computepipe/router/1.0/android.automotive.computepipe.router@1.0.rc b/computepipe/router/1.0/android.automotive.computepipe.router@1.0.rc
new file mode 100644
index 0000000..7cf49b3
--- /dev/null
+++ b/computepipe/router/1.0/android.automotive.computepipe.router@1.0.rc
@@ -0,0 +1,5 @@
+service computepipe_router_service /system/bin/android.automotive.computepipe.router@1.0
+    class main
+    priority -20
+    user system
+    group system
diff --git a/computepipe/router/1.0/include/PipeClient.h b/computepipe/router/1.0/include/PipeClient.h
new file mode 100644
index 0000000..32730f4
--- /dev/null
+++ b/computepipe/router/1.0/include/PipeClient.h
@@ -0,0 +1,63 @@
+/**
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_AUTOMOTIVE_COMPUTEPIPE_ROUTER_V1_0_PIPECLIENT
+#define ANDROID_AUTOMOTIVE_COMPUTEPIPE_ROUTER_V1_0_PIPECLIENT
+
+#include <aidl/android/automotive/computepipe/registry/IClientInfo.h>
+#include <android/binder_auto_utils.h>
+
+#include <functional>
+#include <memory>
+#include <mutex>
+
+#include "ClientHandle.h"
+#include "RemoteState.h"
+
+namespace android {
+namespace automotive {
+namespace computepipe {
+namespace router {
+namespace V1_0 {
+namespace implementation {
+
+/**
+ * PipeClient: Encapsulated the IPC interface to the client.
+ *
+ * Allows for querrying the client state
+ */
+class PipeClient : public ClientHandle {
+  public:
+    explicit PipeClient(
+        const std::shared_ptr<aidl::android::automotive::computepipe::registry::IClientInfo>& info);
+    bool startClientMonitor() override;
+    std::string getClientName() override;
+    bool isAlive() override;
+    ~PipeClient();
+
+  private:
+    ::ndk::ScopedAIBinder_DeathRecipient mDeathMonitor;
+    std::shared_ptr<RemoteState> mState;
+    const std::shared_ptr<aidl::android::automotive::computepipe::registry::IClientInfo> mClientInfo;
+};
+
+}  // namespace implementation
+}  // namespace V1_0
+}  // namespace router
+}  // namespace computepipe
+}  // namespace automotive
+}  // namespace android
+#endif
diff --git a/computepipe/router/1.0/include/PipeQuery.h b/computepipe/router/1.0/include/PipeQuery.h
new file mode 100644
index 0000000..4cf80ac
--- /dev/null
+++ b/computepipe/router/1.0/include/PipeQuery.h
@@ -0,0 +1,56 @@
+/**
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_AUTOMOTIVE_COMPUTEPIPE_ROUTER_V1_0_PIPEQUERY
+#define ANDROID_AUTOMOTIVE_COMPUTEPIPE_ROUTER_V1_0_PIPEQUERY
+
+#include <aidl/android/automotive/computepipe/registry/BnPipeQuery.h>
+
+#include <memory>
+
+#include "PipeRunner.h"
+#include "Registry.h"
+
+namespace android {
+namespace automotive {
+namespace computepipe {
+namespace router {
+namespace V1_0 {
+namespace implementation {
+
+class PipeQuery : public aidl::android::automotive::computepipe::registry::BnPipeQuery {
+  public:
+    explicit PipeQuery(std::shared_ptr<PipeRegistry<PipeRunner>> r) : mRegistry(r) {
+    }
+    ndk::ScopedAStatus getGraphList(::std::vector<std::string>* outNames) override;
+    ndk::ScopedAStatus getPipeRunner(
+        const std::string& graphName,
+        const std::shared_ptr<aidl::android::automotive::computepipe::registry::IClientInfo>& info,
+        std::shared_ptr<aidl::android::automotive::computepipe::runner::IPipeRunner>* outRunner)
+        override;
+    const char* getIfaceName();
+
+  private:
+    std::shared_ptr<PipeRegistry<PipeRunner>> mRegistry;
+};
+
+}  // namespace implementation
+}  // namespace V1_0
+}  // namespace router
+}  // namespace computepipe
+}  // namespace automotive
+}  // namespace android
+#endif
diff --git a/computepipe/router/1.0/include/PipeRegistration.h b/computepipe/router/1.0/include/PipeRegistration.h
new file mode 100644
index 0000000..abd5a99
--- /dev/null
+++ b/computepipe/router/1.0/include/PipeRegistration.h
@@ -0,0 +1,59 @@
+/**
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_AUTOMOTIVE_COMPUTEPIPE_ROUTER_V1_0_PIPEREGISTRATION
+#define ANDROID_AUTOMOTIVE_COMPUTEPIPE_ROUTER_V1_0_PIPEREGISTRATION
+#include <aidl/android/automotive/computepipe/registry/BnPipeRegistration.h>
+
+#include <memory>
+#include <utility>
+
+#include "PipeRunner.h"
+#include "Registry.h"
+
+namespace android {
+namespace automotive {
+namespace computepipe {
+namespace router {
+namespace V1_0 {
+namespace implementation {
+
+class PipeRegistration
+    : public aidl::android::automotive::computepipe::registry::BnPipeRegistration {
+  public:
+    // Method from ::android::automotive::computepipe::registry::V1_0::IPipeRegistration
+    ndk::ScopedAStatus registerPipeRunner(
+        const std::string& graphName,
+        const std::shared_ptr<aidl::android::automotive::computepipe::runner::IPipeRunner>&
+            graphRunner) override;
+
+    explicit PipeRegistration(std::shared_ptr<PipeRegistry<PipeRunner>> r) : mRegistry(r) {
+    }
+    const char* getIfaceName();
+
+  private:
+    // Convert internal registry error codes to PipeStatus
+    ndk::ScopedAStatus convertToBinderStatus(Error err);
+    std::shared_ptr<PipeRegistry<PipeRunner>> mRegistry;
+};
+
+}  // namespace implementation
+}  // namespace V1_0
+}  // namespace router
+}  // namespace computepipe
+}  // namespace automotive
+}  // namespace android
+#endif
diff --git a/computepipe/router/1.0/include/PipeRunner.h b/computepipe/router/1.0/include/PipeRunner.h
new file mode 100644
index 0000000..d3d0453
--- /dev/null
+++ b/computepipe/router/1.0/include/PipeRunner.h
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_AUTOMOTIVE_COMPUTEPIPE_ROUTER_V1_0_PIPERUNNER
+#define ANDROID_AUTOMOTIVE_COMPUTEPIPE_ROUTER_V1_0_PIPERUNNER
+#include <aidl/android/automotive/computepipe/runner/IPipeRunner.h>
+
+#include <functional>
+#include <memory>
+#include <mutex>
+
+#include "PipeHandle.h"
+#include "RemoteState.h"
+
+namespace android {
+namespace automotive {
+namespace computepipe {
+namespace router {
+namespace V1_0 {
+namespace implementation {
+
+/**
+ * Wrapper for IPC handle
+ */
+struct PipeRunner {
+    explicit PipeRunner(
+        const std::shared_ptr<aidl::android::automotive::computepipe::runner::IPipeRunner>&
+            graphRunner);
+    std::shared_ptr<aidl::android::automotive::computepipe::runner::IPipeRunner> runner;
+};
+
+/**
+ * Runner Handle to be stored with registry.
+ *
+ * This is used to represent a runner at the time of registration as well as for
+ * query purposes
+ */
+class RunnerHandle : public android::automotive::computepipe::router::PipeHandle<PipeRunner> {
+  public:
+    explicit RunnerHandle(
+        const std::shared_ptr<aidl::android::automotive::computepipe::runner::IPipeRunner>& r);
+    /**
+     * override registry pipehandle methods
+     */
+    bool isAlive() override;
+    bool startPipeMonitor() override;
+    PipeHandle<PipeRunner>* clone() const override;
+    ~RunnerHandle();
+
+  private:
+    std::shared_ptr<RemoteState> mState;
+    ndk::ScopedAIBinder_DeathRecipient mDeathMonitor;
+};
+
+}  // namespace implementation
+}  // namespace V1_0
+}  // namespace router
+}  // namespace computepipe
+}  // namespace automotive
+}  // namespace android
+#endif
diff --git a/computepipe/router/1.0/include/RemoteState.h b/computepipe/router/1.0/include/RemoteState.h
new file mode 100644
index 0000000..6fa34df
--- /dev/null
+++ b/computepipe/router/1.0/include/RemoteState.h
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_AUTOMOTIVE_COMPUTEPIPE_ROUTER_V1_0_REMOTESTATE
+#define ANDROID_AUTOMOTIVE_COMPUTEPIPE_ROUTER_V1_0_REMOTESTATE
+
+#include <utils/RefBase.h>
+
+#include <memory>
+#include <mutex>
+
+namespace android {
+namespace automotive {
+namespace computepipe {
+namespace router {
+namespace V1_0 {
+namespace implementation {
+
+/**
+ * Wrapper for the Runner State machine
+ */
+class RemoteState {
+  public:
+    RemoteState() = default;
+    void markDead();
+    bool isAlive();
+
+  private:
+    std::mutex mStateLock;
+    bool mAlive = true;
+};
+
+/**
+ * Monitor for tracking remote state
+ */
+class RemoteMonitor : public RefBase {
+  public:
+    explicit RemoteMonitor(const std::weak_ptr<RemoteState>& s) : mState(s) {
+    }
+    static void BinderDiedCallback(void* cookie);
+    void binderDied();
+
+  private:
+    std::weak_ptr<RemoteState> mState;
+};
+
+}  // namespace implementation
+}  // namespace V1_0
+}  // namespace router
+}  // namespace computepipe
+}  // namespace automotive
+}  // namespace android
+#endif
diff --git a/computepipe/router/1.0/service.cpp b/computepipe/router/1.0/service.cpp
new file mode 100644
index 0000000..6135836
--- /dev/null
+++ b/computepipe/router/1.0/service.cpp
@@ -0,0 +1,36 @@
+#include <android/binder_process.h>
+#include <utils/Errors.h>
+#include <utils/Log.h>
+
+#include <iostream>
+#include <thread>
+
+#include "RouterSvc.h"
+
+using namespace android::automotive::computepipe::router::V1_0::implementation;
+
+static RouterSvc gSvcInstance;
+
+#define SVC_NAME() gSvcInstance.getSvcName().c_str()
+
+static void startService() {
+    auto status = gSvcInstance.initSvc();
+    if (status != router::Error::OK) {
+        ALOGE("Could not register service %s", SVC_NAME());
+        exit(2);
+    }
+    ALOGE("Registration Complete");
+}
+
+int main(int argc, char** argv) {
+    auto status = gSvcInstance.parseArgs(argc - 1, &argv[1]);
+    if (status != router::Error::OK) {
+        ALOGE("Bad Args");
+        exit(2);
+    }
+    ABinderProcess_startThreadPool();
+    std::thread registrationThread(startService);
+    ABinderProcess_joinThreadPool();
+    ALOGE("Router thread joined IPC pool");
+    return 1;
+}
diff --git a/computepipe/router/Android.bp b/computepipe/router/Android.bp
new file mode 100644
index 0000000..901e211
--- /dev/null
+++ b/computepipe/router/Android.bp
@@ -0,0 +1,20 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+
+cc_library_headers {
+    name: "computepipe_router_headers",
+    vendor_available : true,
+    export_include_dirs: ["include"],
+}
diff --git a/computepipe/router/include/ClientHandle.h b/computepipe/router/include/ClientHandle.h
new file mode 100644
index 0000000..4e2796a
--- /dev/null
+++ b/computepipe/router/include/ClientHandle.h
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_AUTOMOTIVE_COMPUTEPIPE_ROUTER_CLIENT_HANDLE
+#define ANDROID_AUTOMOTIVE_COMPUTEPIPE_ROUTER_CLIENT_HANDLE
+
+#include <cstdint>
+#include <string>
+
+namespace android {
+namespace automotive {
+namespace computepipe {
+namespace router {
+
+/**
+ * ClientHandle: Represents client state.
+ *
+ * Allows for querrying client state to determine assignment
+ * of pipe handles. This should be instantiated with the client IPC
+ * capabilities to query clients.
+ */
+class ClientHandle {
+  public:
+    /* Get client identifier as defined by the graph */
+    virtual std::string getClientName() = 0;
+    /* Is client alive */
+    virtual bool isAlive() = 0;
+    /* start client monitor */
+    virtual bool startClientMonitor() = 0;
+    virtual ~ClientHandle() {
+    }
+};
+
+}  // namespace router
+}  // namespace computepipe
+}  // namespace automotive
+}  // namespace android
+
+#endif
diff --git a/computepipe/router/include/PipeContext.h b/computepipe/router/include/PipeContext.h
new file mode 100644
index 0000000..2b442d5
--- /dev/null
+++ b/computepipe/router/include/PipeContext.h
@@ -0,0 +1,101 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#ifndef ANDROID_AUTOMOTIVE_COMPUTEPIPE_ROUTER_PIPE_CONTEXT
+#define ANDROID_AUTOMOTIVE_COMPUTEPIPE_ROUTER_PIPE_CONTEXT
+
+#include <memory>
+#include <string>
+#include <utility>
+
+#include "ClientHandle.h"
+#include "PipeHandle.h"
+
+namespace android {
+namespace automotive {
+namespace computepipe {
+namespace router {
+
+/**
+ * This is the context of a registered pipe.
+ * It tracks assignments to clients and availability.
+ * It also owns the handle to the runner interface.
+ * This is utilized by the registry to track every registered pipe
+ */
+template <typename T>
+class PipeContext {
+  public:
+    // Check if associated runner is alive
+    bool isAlive() const {
+        return mPipeHandle->isAlive();
+    }
+    // Retrieve the graph name
+    std::string getGraphName() const {
+        return mGraphName;
+    }
+    /**
+     * Check if its available for clients
+     *
+     * If no client is assigned mClientHandle is null.
+     * If a client is assigned use it to determine if its still alive,
+     * If its not then the runner is available
+     */
+    bool isAvailable() {
+        if (!mClientHandle) {
+            return true;
+        }
+        if (!mClientHandle->isAlive()) {
+            mClientHandle = nullptr;
+            return true;
+        }
+        return false;
+    }
+    // Mark availability. True if available
+    void setClient(std::unique_ptr<ClientHandle> clientHandle) {
+        mClientHandle.reset(clientHandle.release());
+    }
+    // Set the name of the graph
+    void setGraphName(std::string name) {
+        mGraphName = name;
+    }
+    // Duplicate the pipehandle for retrieval by clients.
+    std::unique_ptr<PipeHandle<T>> dupPipeHandle() {
+        return std::unique_ptr<PipeHandle<T>>(mPipeHandle->clone());
+    }
+    // Setup pipecontext
+    PipeContext(std::unique_ptr<PipeHandle<T>> h, std::string name)
+        : mGraphName(name), mPipeHandle(std::move(h)) {
+    }
+    ~PipeContext() {
+        if (mPipeHandle) {
+            mPipeHandle = nullptr;
+        }
+        if (mClientHandle) {
+            mClientHandle = nullptr;
+        }
+    }
+
+  private:
+    std::string mGraphName;
+    std::unique_ptr<PipeHandle<T>> mPipeHandle;
+    std::unique_ptr<ClientHandle> mClientHandle;
+    bool hasClient;
+};
+
+}  // namespace router
+}  // namespace computepipe
+}  // namespace automotive
+}  // namespace android
+#endif
diff --git a/computepipe/router/include/PipeHandle.h b/computepipe/router/include/PipeHandle.h
new file mode 100644
index 0000000..f40b5ba
--- /dev/null
+++ b/computepipe/router/include/PipeHandle.h
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#ifndef ANDROID_AUTOMOTIVE_COMPUTEPIPE_ROUTER_PIPE_HANDLE
+#define ANDROID_AUTOMOTIVE_COMPUTEPIPE_ROUTER_PIPE_HANDLE
+
+#include <memory>
+#include <string>
+#include <utility>
+
+namespace android {
+namespace automotive {
+namespace computepipe {
+namespace router {
+
+/**
+ * This abstracts the runner interface object and hides its
+ * details from the inner routing logic.
+ */
+template <typename T>
+class PipeHandle {
+  public:
+    explicit PipeHandle(std::unique_ptr<T> intf) : mInterface(std::move(intf)) {
+    }
+    // Check if runner process is still alive
+    virtual bool isAlive() = 0;
+    // Start the monitor for the pipe
+    virtual bool startPipeMonitor() = 0;
+    // Any successful client lookup, clones this handle
+    // The implementation must handle refcounting of remote objects
+    // accordingly.
+    virtual PipeHandle<T>* clone() const = 0;
+    // Retrieve the underlying remote IPC object
+    std::shared_ptr<T> getInterface() {
+        return mInterface;
+    }
+    virtual ~PipeHandle() = default;
+
+  protected:
+    explicit PipeHandle(std::shared_ptr<T> intf) : mInterface(intf){};
+    // Interface object
+    std::shared_ptr<T> mInterface;
+};
+
+}  // namespace router
+}  // namespace computepipe
+}  // namespace automotive
+}  // namespace android
+
+#endif
diff --git a/computepipe/router/include/Registry.h b/computepipe/router/include/Registry.h
new file mode 100644
index 0000000..a668051
--- /dev/null
+++ b/computepipe/router/include/Registry.h
@@ -0,0 +1,172 @@
+/**
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#ifndef ANDROID_AUTOMOTIVE_COMPUTEPIPE_ROUTER_REGISTRY
+#define ANDROID_AUTOMOTIVE_COMPUTEPIPE_ROUTER_REGISTRY
+
+#include <list>
+#include <memory>
+#include <mutex>
+#include <string>
+#include <unordered_map>
+#include <utility>
+
+#include "PipeContext.h"
+
+namespace android {
+namespace automotive {
+namespace computepipe {
+namespace router {
+
+enum Error {
+    // Operation successful
+    OK = 0,
+    // Unable to find pipe
+    PIPE_NOT_FOUND = -1,
+    // Duplicate pipe
+    DUPLICATE_PIPE = -2,
+    // Runner unavailable
+    RUNNER_BUSY = -3,
+    // Runner dead
+    RUNNER_DEAD = -4,
+    // Permission error
+    BAD_PERMISSION = -5,
+    // Bad args
+    BAD_ARGUMENTS = -6,
+    // no memory
+    NOMEM = -7,
+    // Internal error
+    INTERNAL_ERR = -8,
+};
+
+/**
+ * PipeRegistry
+ *
+ * Class that represents the current database of graphs and their associated
+ * runners.
+ */
+template <typename T>
+class PipeRegistry {
+  public:
+    /**
+     * Returns the runner for a particular graph
+     * If a runner dies, the discovery is made lazily at the point of
+     * attempted retrieval by a client, and the correct result is returned.
+     */
+    std::unique_ptr<PipeHandle<T>> getClientPipeHandle(const std::string& name,
+                                                       std::unique_ptr<ClientHandle> clientHandle) {
+        if (!clientHandle || !clientHandle->startClientMonitor()) {
+            return nullptr;
+        }
+        return getPipeHandle(name, std::move(clientHandle));
+    }
+    /**
+     * Returns list of registered graphs.
+     */
+    std::list<std::string> getPipeList();
+    /**
+     * Registers a graph and the associated runner
+     * if a restarted runner attempts to reregister, the existing entry is checked
+     * and updated if the old entry is found to be invalid.
+     */
+    Error RegisterPipe(std::unique_ptr<PipeHandle<T>> h, const std::string& name) {
+        std::lock_guard<std::mutex> lock(mPipeDbLock);
+        if (mPipeRunnerDb.find(name) == mPipeRunnerDb.end()) {
+            if (!h->startPipeMonitor()) {
+                return RUNNER_DEAD;
+            }
+            mPipeRunnerDb.emplace(
+                name, std::unique_ptr<PipeContext<T>>(new PipeContext<T>(std::move(h), name)));
+            return OK;
+        }
+        if (!mPipeRunnerDb[name]->isAlive()) {
+            mPipeRunnerDb.erase(name);
+            if (!h->startPipeMonitor()) {
+                return RUNNER_DEAD;
+            }
+            mPipeRunnerDb.emplace(
+                name, std::unique_ptr<PipeContext<T>>(new PipeContext<T>(std::move(h), name)));
+            return OK;
+        }
+        return DUPLICATE_PIPE;
+    }
+
+    PipeRegistry() = default;
+
+    ~PipeRegistry() {
+        mPipeRunnerDb.clear();
+    }
+
+  protected:
+    /**
+     * The retrieval of the pipe handle for debug purposes is controlled by the
+     * instantiator of the pipe registry. This is not exposed to the users of
+     * the pipe registry.
+     */
+    std::unique_ptr<PipeHandle<T>> getPipeHandle(const std::string& name,
+                                                 std::unique_ptr<ClientHandle> clientHandle) {
+        std::lock_guard<std::mutex> lock(mPipeDbLock);
+        if (mPipeRunnerDb.find(name) == mPipeRunnerDb.end()) {
+            return nullptr;
+        }
+
+        if (!clientHandle) {
+            return mPipeRunnerDb[name]->isAlive() ? mPipeRunnerDb[name]->dupPipeHandle() : nullptr;
+        }
+
+        if (mPipeRunnerDb[name]->isAvailable()) {
+            if (mPipeRunnerDb[name]->isAlive()) {
+                mPipeRunnerDb[name]->setClient(std::move(clientHandle));
+                return mPipeRunnerDb[name]->dupPipeHandle();
+            } else {
+                mPipeRunnerDb.erase(name);
+                return nullptr;
+            }
+        }
+        return nullptr;
+    }
+    /**
+     * The deletion of specific entries is protected and can be performed by
+     * only the instantiator
+     */
+    Error DeletePipeHandle(const std::string& name) {
+        std::lock_guard<std::mutex> lock(mPipeDbLock);
+        if (mPipeRunnerDb.find(name) == mPipeRunnerDb.end()) {
+            return PIPE_NOT_FOUND;
+        }
+        mPipeRunnerDb.erase(name);
+        return OK;
+    }
+
+  private:
+    std::mutex mPipeDbLock;
+    std::unordered_map<std::string, std::unique_ptr<PipeContext<T>>> mPipeRunnerDb;
+};  // namespace router
+
+template <typename T>
+std::list<std::string> PipeRegistry<T>::getPipeList() {
+    std::list<std::string> pNames;
+
+    std::lock_guard<std::mutex> lock(mPipeDbLock);
+    for (auto const& kv : mPipeRunnerDb) {
+        pNames.push_back(kv.first);
+    }
+    return pNames;
+}
+}  // namespace router
+}  // namespace computepipe
+}  // namespace automotive
+}  // namespace android
+#endif
diff --git a/computepipe/runner/Android.bp b/computepipe/runner/Android.bp
new file mode 100644
index 0000000..94e5928
--- /dev/null
+++ b/computepipe/runner/Android.bp
@@ -0,0 +1,41 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+cc_library_headers {
+    name: "computepipe_runner_includes",
+    export_include_dirs: ["include"],
+    static_libs: [
+        "libcomputepipeprotos",
+    ],
+}
+
+cc_library {
+    name: "computepipe_runner_component",
+    srcs: [
+        "RunnerComponent.cpp",
+        "EventGenerator.cpp",
+    ],
+    header_libs: [
+        "computepipe_runner_includes",
+    ],
+    static_libs: [
+        "libcomputepipeprotos",
+    ],
+    shared_libs: [
+        "libprotobuf-cpp-lite",
+    ],
+    include_dirs: [
+        "packages/services/Car/computepipe",
+    ],
+}
diff --git a/computepipe/runner/EventGenerator.cpp b/computepipe/runner/EventGenerator.cpp
new file mode 100644
index 0000000..c8e4370
--- /dev/null
+++ b/computepipe/runner/EventGenerator.cpp
@@ -0,0 +1,63 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "EventGenerator.h"
+
+#include "RunnerComponent.h"
+
+namespace android {
+namespace automotive {
+namespace computepipe {
+namespace runner {
+namespace generator {
+
+bool DefaultEvent::isPhaseEntry() const {
+    return mType == static_cast<int>(PhaseState::ENTRY);
+}
+bool DefaultEvent::isAborted() const {
+    return mType == static_cast<int>(PhaseState::ABORTED);
+}
+bool DefaultEvent::isTransitionComplete() const {
+    return mType == static_cast<int>(PhaseState::TRANSITION_COMPLETE);
+}
+
+Status DefaultEvent::dispatchToComponent(const std::shared_ptr<RunnerComponentInterface>& iface) {
+    switch (mPhase) {
+        case RESET:
+            return iface->handleResetPhase(*this);
+        case RUN:
+            return iface->handleExecutionPhase(*this);
+        case STOP_WITH_FLUSH:
+            return iface->handleStopWithFlushPhase(*this);
+        case STOP_IMMEDIATE:
+            return iface->handleStopImmediatePhase(*this);
+    }
+    return Status::ILLEGAL_STATE;
+}
+
+DefaultEvent DefaultEvent::generateEntryEvent(Phase p) {
+    return DefaultEvent(PhaseState::ENTRY, p);
+}
+DefaultEvent DefaultEvent::generateAbortEvent(Phase p) {
+    return DefaultEvent(PhaseState::ABORTED, p);
+}
+DefaultEvent DefaultEvent::generateTransitionCompleteEvent(Phase p) {
+    return DefaultEvent(PhaseState::TRANSITION_COMPLETE, p);
+}
+
+}  // namespace generator
+}  // namespace runner
+}  // namespace computepipe
+}  // namespace automotive
+}  // namespace android
diff --git a/computepipe/runner/RunnerComponent.cpp b/computepipe/runner/RunnerComponent.cpp
new file mode 100644
index 0000000..755379a
--- /dev/null
+++ b/computepipe/runner/RunnerComponent.cpp
@@ -0,0 +1,126 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "RunnerComponent.h"
+
+#include "ClientConfig.pb.h"
+#include "types/Status.h"
+
+namespace android {
+namespace automotive {
+namespace computepipe {
+namespace runner {
+
+/* Is this a notification to enter the phase */
+bool RunnerEvent::isPhaseEntry() const {
+    return false;
+}
+/* Is this a notification that all components have transitioned to the phase */
+bool RunnerEvent::isTransitionComplete() const {
+    return false;
+}
+
+bool RunnerEvent::isAborted() const {
+    return false;
+}
+
+/**
+ * ClientConfig methods
+ */
+Status ClientConfig::dispatchToComponent(const std::shared_ptr<RunnerComponentInterface>& iface) {
+    return iface->handleConfigPhase(*this);
+}
+
+std::string ClientConfig::getSerializedClientConfig() const {
+    proto::ClientConfig config;
+    std::string output;
+
+    config.set_input_config_id(mInputConfigId);
+    config.set_termination_id(mTerminationId);
+    config.set_offload_id(mOffloadId);
+    for (auto it : mOutputConfigs) {
+        (*config.mutable_output_options())[it.first] = it.second;
+    }
+    if (!config.SerializeToString(&output)) {
+        return "";
+    }
+    return output;
+}
+
+Status ClientConfig::getInputConfigId(int* outId) const {
+    if (mInputConfigId == kInvalidId) {
+        return Status::ILLEGAL_STATE;
+    }
+    *outId = mInputConfigId;
+    return Status::SUCCESS;
+}
+
+Status ClientConfig::getOffloadId(int* outId) const {
+    if (mOffloadId == kInvalidId) {
+        return Status::ILLEGAL_STATE;
+    }
+    *outId = mOffloadId;
+    return Status::SUCCESS;
+}
+
+Status ClientConfig::getTerminationId(int* outId) const {
+    if (mTerminationId == kInvalidId) {
+        return Status::ILLEGAL_STATE;
+    }
+    *outId = mTerminationId;
+    return Status::SUCCESS;
+}
+
+Status ClientConfig::getOutputStreamConfigs(std::map<int, int>& outputConfig) const {
+    if (mOutputConfigs.empty()) {
+        return Status::ILLEGAL_STATE;
+    }
+    outputConfig = mOutputConfigs;
+    return Status::SUCCESS;
+}
+
+Status ClientConfig::getOptionalConfigs(std::string& outOptional) const {
+    outOptional = mOptionalConfigs;
+    return Status::SUCCESS;
+}
+
+/**
+ * Methods for ComponentInterface
+ */
+
+/* handle a ConfigPhase related event notification from Runner Engine */
+Status RunnerComponentInterface::handleConfigPhase(const ClientConfig& /* e*/) {
+    return Status::SUCCESS;
+}
+/* handle execution phase notification from Runner Engine */
+Status RunnerComponentInterface::handleExecutionPhase(const RunnerEvent& /* e*/) {
+    return SUCCESS;
+}
+/* handle a stop with flushing semantics phase notification from the engine */
+Status RunnerComponentInterface::handleStopWithFlushPhase(const RunnerEvent& /* e*/) {
+    return SUCCESS;
+}
+/* handle an immediate stop phase notification from the engine */
+Status RunnerComponentInterface::handleStopImmediatePhase(const RunnerEvent& /* e*/) {
+    return SUCCESS;
+}
+/* handle an engine notification to return to reset state */
+Status RunnerComponentInterface::handleResetPhase(const RunnerEvent& /* e*/) {
+    return SUCCESS;
+}
+
+}  // namespace runner
+}  // namespace computepipe
+}  // namespace automotive
+}  // namespace android
diff --git a/computepipe/runner/client_interface/AidlClient.cpp b/computepipe/runner/client_interface/AidlClient.cpp
new file mode 100644
index 0000000..d8bfa61
--- /dev/null
+++ b/computepipe/runner/client_interface/AidlClient.cpp
@@ -0,0 +1,176 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#define LOG_TAG "RunnerIpcInterface"
+
+#include <string>
+
+#include "AidlClient.h"
+
+#include <aidl/android/automotive/computepipe/registry/IPipeRegistration.h>
+#include <android/binder_manager.h>
+#include <android-base/logging.h>
+
+#include <thread>
+
+#include "types/GraphState.h"
+
+namespace android {
+namespace automotive {
+namespace computepipe {
+namespace runner {
+namespace client_interface {
+namespace aidl_client {
+
+using ::aidl::android::automotive::computepipe::registry::IPipeRegistration;
+using ::ndk::ScopedAStatus;
+
+namespace {
+constexpr char kRegistryInterfaceName[] = "router";
+constexpr int kMaxRouterConnectionAttempts = 10;
+constexpr int kRouterConnectionAttemptIntervalSeconds = 2;
+
+void deathNotifier(void* cookie) {
+    CHECK(cookie);
+    AidlClient* runnerIface = static_cast<AidlClient*>(cookie);
+    runnerIface->routerDied();
+}
+
+}  // namespace
+
+Status AidlClient::dispatchPacketToClient(int32_t streamId,
+                                          const std::shared_ptr<MemHandle> packet) {
+    if (!mPipeRunner) {
+        return Status::ILLEGAL_STATE;
+    }
+    return mPipeRunner->dispatchPacketToClient(streamId, packet);
+}
+
+Status AidlClient::activate() {
+    if (mPipeRunner) {
+        return Status::ILLEGAL_STATE;
+    }
+
+    mPipeRunner = ndk::SharedRefBase::make<AidlClientImpl>(
+        mGraphOptions, mRunnerEngine);
+    std::thread t(&AidlClient::tryRegisterPipeRunner, this);
+    t.detach();
+    return Status::SUCCESS;
+}
+
+void AidlClient::routerDied() {
+    std::thread t(&AidlClient::tryRegisterPipeRunner, this);
+    t.detach();
+}
+
+void AidlClient::tryRegisterPipeRunner() {
+    if (!mPipeRunner) {
+        LOG(ERROR) << "Init must be called before attempting to connect to router.";
+        return;
+    }
+
+    const std::string instanceName =
+        std::string() + IPipeRegistration::descriptor + "/" + kRegistryInterfaceName;
+
+    for (int i =0; i < kMaxRouterConnectionAttempts; i++) {
+        if (i != 0) {
+            sleep(kRouterConnectionAttemptIntervalSeconds);
+        }
+
+        ndk::SpAIBinder binder(AServiceManager_getService(instanceName.c_str()));
+        if (binder.get() == nullptr) {
+            LOG(ERROR) << "Failed to connect to router service";
+            continue;
+        }
+
+        // Connected to router registry, register the runner and dealth callback.
+        std::shared_ptr<IPipeRegistration> registryService = IPipeRegistration::fromBinder(binder);
+        ndk::ScopedAStatus status =
+            registryService->registerPipeRunner(mGraphOptions.graph_name().c_str(), mPipeRunner);
+
+        if (!status.isOk()) {
+            LOG(ERROR) << "Failed to register runner instance at router registy.";
+            continue;
+        }
+
+        AIBinder_DeathRecipient* recipient = AIBinder_DeathRecipient_new(&deathNotifier);
+        AIBinder_linkToDeath(registryService->asBinder().get(), recipient, this);
+        LOG(ERROR) << "Runner was registered at router registry.";
+        return;
+    }
+
+    LOG(ERROR) << "Max connection attempts reached, router connection attempts failed.";
+    return;
+}
+
+Status AidlClient::handleResetPhase(const RunnerEvent& e) {
+    if (!mPipeRunner) {
+        return Status::ILLEGAL_STATE;
+    }
+    if (e.isTransitionComplete()) {
+        mPipeRunner->stateUpdateNotification(GraphState::RESET);
+    }
+    return SUCCESS;
+}
+
+Status AidlClient::handleConfigPhase(const ClientConfig& e) {
+    if (!mPipeRunner) {
+        return Status::ILLEGAL_STATE;
+    }
+    if (e.isTransitionComplete()) {
+        mPipeRunner->stateUpdateNotification(GraphState::CONFIG_DONE);
+    } else if (e.isAborted()) {
+        mPipeRunner->stateUpdateNotification(GraphState::ERR_HALT);
+    }
+    return SUCCESS;
+}
+
+Status AidlClient::handleExecutionPhase(const RunnerEvent& e) {
+    if (!mPipeRunner) {
+        return Status::ILLEGAL_STATE;
+    }
+    if (e.isTransitionComplete()) {
+        mPipeRunner->stateUpdateNotification(GraphState::RUNNING);
+    } else if (e.isAborted()) {
+        mPipeRunner->stateUpdateNotification(GraphState::ERR_HALT);
+    }
+    return SUCCESS;
+}
+
+Status AidlClient::handleStopWithFlushPhase(const RunnerEvent& e) {
+    if (!mPipeRunner) {
+        return Status::ILLEGAL_STATE;
+    }
+    if (e.isTransitionComplete()) {
+        mPipeRunner->stateUpdateNotification(GraphState::DONE);
+    }
+    return SUCCESS;
+}
+
+Status AidlClient::handleStopImmediatePhase(const RunnerEvent& e) {
+    if (!mPipeRunner) {
+        return Status::ILLEGAL_STATE;
+    }
+    if (e.isTransitionComplete()) {
+        mPipeRunner->stateUpdateNotification(GraphState::ERR_HALT);
+    }
+    return SUCCESS;
+}
+
+}  // namespace aidl_client
+}  // namespace client_interface
+}  // namespace runner
+}  // namespace computepipe
+}  // namespace automotive
+}  // namespace android
diff --git a/computepipe/runner/client_interface/AidlClient.h b/computepipe/runner/client_interface/AidlClient.h
new file mode 100644
index 0000000..8dd6ad6
--- /dev/null
+++ b/computepipe/runner/client_interface/AidlClient.h
@@ -0,0 +1,71 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef COMPUTEPIPE_RUNNER_CLIENT_INTERFACE_AIDL_CLIENT_H
+#define COMPUTEPIPE_RUNNER_CLIENT_INTERFACE_AIDL_CLIENT_H
+
+#include <memory>
+
+#include "ClientInterface.h"
+#include "AidlClientImpl.h"
+
+namespace android {
+namespace automotive {
+namespace computepipe {
+namespace runner {
+namespace client_interface {
+namespace aidl_client {
+
+class AidlClient : public ClientInterface {
+  public:
+    explicit AidlClient(const proto::Options graphOptions,
+                        const std::shared_ptr<ClientEngineInterface>& engine)
+            : mGraphOptions(graphOptions), mRunnerEngine(engine) {}
+    /**
+     * Override ClientInterface Functions
+     */
+    Status dispatchPacketToClient(int32_t streamId,
+                                  const std::shared_ptr<MemHandle> packet) override;
+    Status activate() override;
+    /**
+     * Override RunnerComponentInterface function
+     */
+    Status handleResetPhase(const RunnerEvent& e) override;
+    Status handleConfigPhase(const ClientConfig& e) override;
+    Status handleExecutionPhase(const RunnerEvent& e) override;
+    Status handleStopWithFlushPhase(const RunnerEvent& e) override;
+    Status handleStopImmediatePhase(const RunnerEvent& e) override;
+
+    void routerDied();
+
+    virtual ~AidlClient() = default;
+
+  private:
+
+    // Attempt to register pipe runner with router. Returns true on success.
+    // This is a blocking API, calling thread will be blocked until router connection is
+    // established or max attempts are made without success.
+    void tryRegisterPipeRunner();
+    const proto::Options mGraphOptions;
+    std::shared_ptr<AidlClientImpl> mPipeRunner = nullptr;
+    std::shared_ptr<ClientEngineInterface> mRunnerEngine;
+};
+
+}  // namespace aidl_client
+}  // namespace client_interface
+}  // namespace runner
+}  // namespace computepipe
+}  // namespace automotive
+}  // namespace android
+#endif
diff --git a/computepipe/runner/client_interface/AidlClientImpl.cpp b/computepipe/runner/client_interface/AidlClientImpl.cpp
new file mode 100644
index 0000000..14ebe03
--- /dev/null
+++ b/computepipe/runner/client_interface/AidlClientImpl.cpp
@@ -0,0 +1,373 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.c
+
+#define LOG_TAG "RunnerIpcInterface"
+
+#include "AidlClientImpl.h"
+
+#include <vector>
+
+#include "OutputConfig.pb.h"
+#include "PacketDescriptor.pb.h"
+#include "PipeOptionsConverter.h"
+
+#include <aidl/android/automotive/computepipe/runner/PacketDescriptor.h>
+#include <aidl/android/automotive/computepipe/runner/PacketDescriptorPacketType.h>
+#include <android-base/logging.h>
+#include <android/binder_auto_utils.h>
+
+namespace android {
+namespace automotive {
+namespace computepipe {
+namespace runner {
+namespace client_interface {
+namespace aidl_client {
+namespace {
+
+using ::aidl::android::automotive::computepipe::runner::IPipeStateCallback;
+using ::aidl::android::automotive::computepipe::runner::IPipeStream;
+using ::aidl::android::automotive::computepipe::runner::PacketDescriptor;
+using ::aidl::android::automotive::computepipe::runner::PacketDescriptorPacketType;
+using ::aidl::android::automotive::computepipe::runner::PipeDescriptor;
+using ::aidl::android::automotive::computepipe::runner::PipeState;
+using ::ndk::ScopedAStatus;
+
+ScopedAStatus ToNdkStatus(Status status) {
+    switch (status) {
+        case SUCCESS:
+            return ScopedAStatus::ok();
+        case INTERNAL_ERROR:
+            return ScopedAStatus::fromExceptionCode(EX_TRANSACTION_FAILED);
+        case INVALID_ARGUMENT:
+            return ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+        case FATAL_ERROR:
+        default:
+            return ScopedAStatus::fromExceptionCode(EX_TRANSACTION_FAILED);
+    }
+}
+
+PipeState ToAidlState(GraphState state) {
+    switch (state) {
+        case RESET:
+            return PipeState::RESET;
+        case CONFIG_DONE:
+            return PipeState::CONFIG_DONE;
+        case RUNNING:
+            return PipeState::RUNNING;
+        case DONE:
+            return PipeState::DONE;
+        case ERR_HALT:
+        default:
+            return PipeState::ERR_HALT;
+    }
+}
+
+void deathNotifier(void* cookie) {
+    CHECK(cookie);
+    AidlClientImpl* iface = static_cast<AidlClientImpl*>(cookie);
+    iface->clientDied();
+}
+
+Status ToAidlPacketType(proto::PacketType type, PacketDescriptorPacketType* outType) {
+    if (outType == nullptr) {
+        return Status::INTERNAL_ERROR;
+    }
+    switch (type) {
+        case proto::SEMANTIC_DATA:
+            *outType = PacketDescriptorPacketType::SEMANTIC_DATA;
+            return Status::SUCCESS;
+        case proto::PIXEL_DATA:
+            *outType = PacketDescriptorPacketType::PIXEL_DATA;
+            return Status::SUCCESS;
+        default:
+            LOG(ERROR) << "unknown packet type " << type;
+            return Status::INVALID_ARGUMENT;
+    }
+}
+
+}  // namespace
+
+Status AidlClientImpl::DispatchSemanticData(int32_t streamId,
+                                           const std::shared_ptr<MemHandle>& packetHandle) {
+    PacketDescriptor desc;
+
+    if (mPacketHandlers.find(streamId) == mPacketHandlers.end()) {
+        LOG(ERROR) << "Bad streamId";
+        return Status::INVALID_ARGUMENT;
+    }
+    Status status = ToAidlPacketType(packetHandle->getType(), &desc.type);
+    if (status != SUCCESS) {
+        return status;
+    }
+    desc.data = std::vector(reinterpret_cast<const signed char*>(packetHandle->getData()),
+        reinterpret_cast<const signed char*>(packetHandle->getData() + packetHandle->getSize()));
+    desc.size = packetHandle->getSize();
+    if (static_cast<int32_t>(desc.data.size()) != desc.size) {
+        LOG(ERROR) << "mismatch in char data size and reported size";
+        return Status::INVALID_ARGUMENT;
+    }
+    desc.sourceTimeStampMillis = packetHandle->getTimeStamp();
+    desc.bufId = 0;
+    ScopedAStatus ret = mPacketHandlers[streamId]->deliverPacket(desc);
+    if (!ret.isOk()) {
+        LOG(ERROR) << "Dropping Semantic packet due to error ";
+    }
+    return Status::SUCCESS;
+}
+
+Status AidlClientImpl::DispatchPixelData(int32_t streamId,
+                                         const std::shared_ptr<MemHandle>& packetHandle) {
+    PacketDescriptor desc;
+
+    if (mPacketHandlers.find(streamId) == mPacketHandlers.end()) {
+        LOG(ERROR) << "Bad stream id";
+        return Status::INVALID_ARGUMENT;
+    }
+    Status status = ToAidlPacketType(packetHandle->getType(), &desc.type);
+    if (status != Status::SUCCESS) {
+        LOG(ERROR) << "Invalid packet type";
+        return status;
+    }
+
+    // Copies the native handle to the aidl interface.
+    const native_handle_t* nativeHandle =
+        AHardwareBuffer_getNativeHandle(packetHandle->getHardwareBuffer());
+    for (int i = 0; i < nativeHandle->numFds; i++) {
+        desc.handle.handle.fds.push_back(ndk::ScopedFileDescriptor(nativeHandle->data[i]));
+    }
+    for (int i = 0; i < nativeHandle->numInts; i++) {
+        desc.handle.handle.ints.push_back(nativeHandle->data[i + nativeHandle->numFds]);
+    }
+
+    // Copies buffer descriptor to the aidl interface.
+    AHardwareBuffer_Desc bufferDesc;
+    AHardwareBuffer_describe(packetHandle->getHardwareBuffer(), &bufferDesc);
+    desc.handle.description.width = bufferDesc.width;
+    desc.handle.description.height = bufferDesc.height;
+    desc.handle.description.stride = bufferDesc.stride;
+    desc.handle.description.layers = bufferDesc.layers;
+    desc.handle.description.format =
+        static_cast<::aidl::android::hardware::graphics::common::PixelFormat>(bufferDesc.format);
+    desc.handle.description.usage =
+        static_cast<::aidl::android::hardware::graphics::common::BufferUsage>(bufferDesc.usage);
+
+    desc.bufId = packetHandle->getBufferId();
+    desc.sourceTimeStampMillis = packetHandle->getTimeStamp();
+
+    ScopedAStatus ret = mPacketHandlers[streamId]->deliverPacket(desc);
+    if (!ret.isOk()) {
+        LOG(ERROR) << "Unable to deliver packet. Dropping it and returning an error";
+        return Status::INTERNAL_ERROR;
+    }
+    return Status::SUCCESS;
+}
+
+// Thread-safe function to deliver new packets to client.
+Status AidlClientImpl::dispatchPacketToClient(int32_t streamId,
+                                              const std::shared_ptr<MemHandle>& packetHandle) {
+    // TODO(146464279) implement.
+    if (!packetHandle) {
+        LOG(ERROR) << "invalid packetHandle";
+        return Status::INVALID_ARGUMENT;
+    }
+    proto::PacketType packetType = packetHandle->getType();
+    switch (packetType) {
+        case proto::SEMANTIC_DATA:
+            return DispatchSemanticData(streamId, packetHandle);
+        case proto::PIXEL_DATA:
+            return DispatchPixelData(streamId, packetHandle);
+        default:
+            LOG(ERROR) << "Unsupported packet type " << packetHandle->getType();
+            return Status::INVALID_ARGUMENT;
+    }
+    return Status::SUCCESS;
+}
+
+Status AidlClientImpl::stateUpdateNotification(const GraphState newState) {
+    if (mClientStateChangeCallback) {
+        (void)mClientStateChangeCallback->handleState(ToAidlState(newState));
+    }
+    return Status::SUCCESS;
+}
+
+ScopedAStatus AidlClientImpl::getPipeDescriptor(PipeDescriptor* _aidl_return) {
+    if (_aidl_return == nullptr) {
+        return ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+    }
+    *_aidl_return = OptionsToPipeDescriptor(mGraphOptions);
+    return ScopedAStatus::ok();
+}
+
+ScopedAStatus AidlClientImpl::setPipeInputSource(int32_t configId) {
+    if (!isClientInitDone()) {
+        return ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
+    }
+
+    proto::ConfigurationCommand configurationCommand;
+    configurationCommand.mutable_set_input_source()->set_source_id(configId);
+
+    Status status = mEngine->processClientConfigUpdate(configurationCommand);
+    return ToNdkStatus(status);
+}
+
+ScopedAStatus AidlClientImpl::setPipeOffloadOptions(int32_t configId) {
+    if (!isClientInitDone()) {
+        return ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
+    }
+
+    proto::ConfigurationCommand configurationCommand;
+    configurationCommand.mutable_set_offload_offload()->set_offload_option_id(configId);
+
+    Status status = mEngine->processClientConfigUpdate(configurationCommand);
+    return ToNdkStatus(status);
+}
+
+ScopedAStatus AidlClientImpl::setPipeTermination(int32_t configId) {
+    if (!isClientInitDone()) {
+        return ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
+    }
+
+    proto::ConfigurationCommand configurationCommand;
+    configurationCommand.mutable_set_termination_option()->set_termination_option_id(configId);
+
+    Status status = mEngine->processClientConfigUpdate(configurationCommand);
+    return ToNdkStatus(status);
+}
+
+ScopedAStatus AidlClientImpl::init(const std::shared_ptr<IPipeStateCallback>& stateCb) {
+    if (isClientInitDone()) {
+        return ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
+    }
+
+    AIBinder_DeathRecipient* recipient = AIBinder_DeathRecipient_new(&deathNotifier);
+    AIBinder_linkToDeath(stateCb->asBinder().get(), recipient, this);
+
+    mClientStateChangeCallback = stateCb;
+    return ScopedAStatus::ok();
+}
+
+bool AidlClientImpl::isClientInitDone() {
+    if (mClientStateChangeCallback == nullptr) {
+        return false;
+    }
+    return true;
+}
+
+void AidlClientImpl::clientDied() {
+    LOG(INFO) << "Client has died";
+    releaseRunner();
+}
+
+ScopedAStatus AidlClientImpl::setPipeOutputConfig(int32_t streamId, int32_t maxInFlightCount,
+                                                  const std::shared_ptr<IPipeStream>& handler) {
+    if (!isClientInitDone()) {
+        return ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
+    }
+
+    if (mPacketHandlers.find(streamId) != mPacketHandlers.end()) {
+        LOG(INFO) << "Handler for stream id " << streamId
+                  << " has already"
+                     " been registered.";
+        return ToNdkStatus(INVALID_ARGUMENT);
+    }
+
+    mPacketHandlers.insert(std::pair<int, std::shared_ptr<IPipeStream>>(streamId, handler));
+
+    proto::ConfigurationCommand configurationCommand;
+    configurationCommand.mutable_set_output_stream()->set_stream_id(streamId);
+    configurationCommand.mutable_set_output_stream()->set_max_inflight_packets_count(
+        maxInFlightCount);
+    Status status = mEngine->processClientConfigUpdate(configurationCommand);
+
+    if (status != SUCCESS) {
+        LOG(INFO) << "Failed to register handler for stream id " << streamId;
+        mPacketHandlers.erase(streamId);
+    }
+    return ToNdkStatus(status);
+}
+
+ScopedAStatus AidlClientImpl::applyPipeConfigs() {
+    if (!isClientInitDone()) {
+        return ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
+    }
+
+    proto::ControlCommand controlCommand;
+    *controlCommand.mutable_apply_configs() = proto::ApplyConfigs();
+
+    Status status = mEngine->processClientCommand(controlCommand);
+    return ToNdkStatus(status);
+}
+
+ScopedAStatus AidlClientImpl::resetPipeConfigs() {
+    if (!isClientInitDone()) {
+        return ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
+    }
+
+    proto::ControlCommand controlCommand;
+    *controlCommand.mutable_reset_configs() = proto::ResetConfigs();
+
+    Status status = mEngine->processClientCommand(controlCommand);
+    return ToNdkStatus(status);
+}
+
+ScopedAStatus AidlClientImpl::startPipe() {
+    proto::ControlCommand controlCommand;
+    *controlCommand.mutable_start_graph() = proto::StartGraph();
+
+    Status status = mEngine->processClientCommand(controlCommand);
+    return ToNdkStatus(status);
+}
+
+ScopedAStatus AidlClientImpl::stopPipe() {
+    proto::ControlCommand controlCommand;
+    *controlCommand.mutable_stop_graph() = proto::StopGraph();
+
+    Status status = mEngine->processClientCommand(controlCommand);
+    return ToNdkStatus(status);
+}
+
+ScopedAStatus AidlClientImpl::doneWithPacket(int32_t bufferId, int32_t streamId) {
+    auto it = mPacketHandlers.find(streamId);
+    if (it == mPacketHandlers.end()) {
+        LOG(ERROR) << "Bad stream id provided for doneWithPacket call";
+        return ToNdkStatus(Status::INVALID_ARGUMENT);
+    }
+
+    return ToNdkStatus(mEngine->freePacket(bufferId, streamId));
+}
+
+ndk::ScopedAStatus AidlClientImpl::getPipeDebugger(
+    std::shared_ptr<aidl::android::automotive::computepipe::runner::IPipeDebugger>*
+    /* _aidl_return */ ) {
+    // TODO(146464281) implement.
+    return ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus AidlClientImpl::releaseRunner() {
+    proto::ControlCommand controlCommand;
+    *controlCommand.mutable_death_notification() = proto::DeathNotification();
+
+    Status status = mEngine->processClientCommand(controlCommand);
+
+    mClientStateChangeCallback = nullptr;
+    mPacketHandlers.clear();
+    return ToNdkStatus(status);
+}
+
+}  // namespace aidl_client
+}  // namespace client_interface
+}  // namespace runner
+}  // namespace computepipe
+}  // namespace automotive
+}  // namespace android
diff --git a/computepipe/runner/client_interface/AidlClientImpl.h b/computepipe/runner/client_interface/AidlClientImpl.h
new file mode 100644
index 0000000..f239bd6
--- /dev/null
+++ b/computepipe/runner/client_interface/AidlClientImpl.h
@@ -0,0 +1,109 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef COMPUTEPIPE_RUNNER_CLIENTINTERFACE_AIDLCLIENTIMPL_H
+#define COMPUTEPIPE_RUNNER_CLIENTINTERFACE_AIDLCLIENTIMPL_H
+#include <aidl/android/automotive/computepipe/runner/BnPipeRunner.h>
+
+#include <map>
+#include <memory>
+#include <string>
+
+#include "ClientEngineInterface.h"
+#include "MemHandle.h"
+#include "Options.pb.h"
+#include "types/GraphState.h"
+#include "types/Status.h"
+
+namespace android {
+namespace automotive {
+namespace computepipe {
+namespace runner {
+namespace client_interface {
+namespace aidl_client {
+
+// RunnerInterface registers an IPipeRunner interface with computepipe router.
+// RunnerInterface handles binder IPC calls and invokes appropriate callbacks.
+class AidlClientImpl : public aidl::android::automotive::computepipe::runner::BnPipeRunner {
+  public:
+    explicit AidlClientImpl(const proto::Options graphOptions,
+                            const std::shared_ptr<ClientEngineInterface>& engine)
+        : mGraphOptions(graphOptions), mEngine(engine) {
+    }
+
+    ~AidlClientImpl() {
+    }
+
+    Status dispatchPacketToClient(int32_t streamId, const std::shared_ptr<MemHandle>& packetHandle);
+
+    Status stateUpdateNotification(const GraphState newState);
+
+    // Methods from android::automotive::computepipe::runner::BnPipeRunner
+    ndk::ScopedAStatus init(
+        const std::shared_ptr<aidl::android::automotive::computepipe::runner::IPipeStateCallback>&
+            stateCb) override;
+    ndk::ScopedAStatus getPipeDescriptor(
+        aidl::android::automotive::computepipe::runner::PipeDescriptor* _aidl_return) override;
+    ndk::ScopedAStatus setPipeInputSource(int32_t configId) override;
+    ndk::ScopedAStatus setPipeOffloadOptions(int32_t configId) override;
+    ndk::ScopedAStatus setPipeTermination(int32_t configId) override;
+    ndk::ScopedAStatus setPipeOutputConfig(
+        int32_t streamId, int32_t maxInFlightCount,
+        const std::shared_ptr<aidl::android::automotive::computepipe::runner::IPipeStream>& handler)
+        override;
+    ndk::ScopedAStatus applyPipeConfigs() override;
+    ndk::ScopedAStatus resetPipeConfigs() override;
+    ndk::ScopedAStatus startPipe() override;
+    ndk::ScopedAStatus stopPipe() override;
+    ndk::ScopedAStatus doneWithPacket(int32_t bufferId, int32_t streamId) override;
+
+    ndk::ScopedAStatus getPipeDebugger(
+        std::shared_ptr<aidl::android::automotive::computepipe::runner::IPipeDebugger>* _aidl_return)
+        override;
+
+    ndk::ScopedAStatus releaseRunner() override;
+
+    void clientDied();
+
+  private:
+    // Dispatch semantic data to client. Has copy semantics and does not expect
+    // client to invoke doneWithPacket.
+    Status DispatchSemanticData(int32_t streamId, const std::shared_ptr<MemHandle>& packetHandle);
+
+    // Dispatch pixel data to client. Expects the client to invoke done with
+    // packet.
+    Status DispatchPixelData(int32_t streamId, const std::shared_ptr<MemHandle>& packetHandle);
+
+    bool isClientInitDone();
+
+    const proto::Options mGraphOptions;
+    std::shared_ptr<ClientEngineInterface> mEngine;
+
+    // If value of mClientStateChangeCallback is null pointer, client has not
+    // invoked init.
+    std::shared_ptr<aidl::android::automotive::computepipe::runner::IPipeStateCallback>
+        mClientStateChangeCallback = nullptr;
+
+    std::map<int, std::shared_ptr<aidl::android::automotive::computepipe::runner::IPipeStream>>
+        mPacketHandlers;
+};
+
+}  // namespace aidl_client
+}  // namespace client_interface
+}  // namespace runner
+}  // namespace computepipe
+}  // namespace automotive
+}  // namespace android
+
+#endif  // COMPUTEPIPE_RUNNER_CLIENTINTERFACE_AIDLCLIENTIMPL_H
diff --git a/computepipe/runner/client_interface/Android.bp b/computepipe/runner/client_interface/Android.bp
new file mode 100644
index 0000000..02da2d2
--- /dev/null
+++ b/computepipe/runner/client_interface/Android.bp
@@ -0,0 +1,44 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+cc_library {
+    name: "computepipe_client_interface",
+    srcs: [
+        "AidlClient.cpp",
+        "AidlClientImpl.cpp",
+        "Factory.cpp",
+        "PipeOptionsConverter.cpp",
+    ],
+    export_include_dirs: ["include"],
+    header_libs: [
+        "computepipe_runner_includes",
+    ],
+    static_libs: [
+        "libcomputepipeprotos",
+    ],
+    shared_libs: [
+        "computepipe_runner_component",
+        "libbase",
+        "libbinder_ndk",
+        "liblog",
+	"libnativewindow",
+        "libutils",
+        "android.automotive.computepipe.runner-ndk_platform",
+        "android.automotive.computepipe.registry-ndk_platform",
+        "libprotobuf-cpp-lite",
+    ],
+    include_dirs: [
+        "packages/services/Car/computepipe",
+    ],
+}
diff --git a/computepipe/runner/client_interface/Factory.cpp b/computepipe/runner/client_interface/Factory.cpp
new file mode 100644
index 0000000..36dd834
--- /dev/null
+++ b/computepipe/runner/client_interface/Factory.cpp
@@ -0,0 +1,48 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "AidlClient.h"
+#include "ClientInterface.h"
+
+namespace android {
+namespace automotive {
+namespace computepipe {
+namespace runner {
+namespace client_interface {
+
+using aidl_client::AidlClient;
+
+namespace {
+
+std::unique_ptr<AidlClient> buildAidlClient(const proto::Options& options,
+                                            const std::shared_ptr<ClientEngineInterface>& engine) {
+    return std::make_unique<AidlClient>(options, engine);
+}
+
+}  // namespace
+
+std::unique_ptr<ClientInterface> ClientInterfaceFactory::createClientInterface(
+    std::string iface, const proto::Options graphOptions,
+    const std::shared_ptr<ClientEngineInterface>& engine) {
+    if (iface == "aidl") {
+        return buildAidlClient(graphOptions, engine);
+    }
+    return nullptr;
+}
+
+}  // namespace client_interface
+}  // namespace runner
+}  // namespace computepipe
+}  // namespace automotive
+}  // namespace android
diff --git a/computepipe/runner/client_interface/PipeOptionsConverter.cpp b/computepipe/runner/client_interface/PipeOptionsConverter.cpp
new file mode 100644
index 0000000..c53da46
--- /dev/null
+++ b/computepipe/runner/client_interface/PipeOptionsConverter.cpp
@@ -0,0 +1,221 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "PipeOptionsConverter.h"
+
+#include "aidl/android/automotive/computepipe/runner/PipeInputConfigInputType.h"
+
+namespace android {
+namespace automotive {
+namespace computepipe {
+namespace runner {
+namespace client_interface {
+namespace aidl_client {
+
+using ::aidl::android::automotive::computepipe::runner::PipeDescriptor;
+using ::aidl::android::automotive::computepipe::runner::PipeInputConfig;
+using ::aidl::android::automotive::computepipe::runner::PipeInputConfigCameraDesc;
+using ::aidl::android::automotive::computepipe::runner::PipeInputConfigCameraType;
+using ::aidl::android::automotive::computepipe::runner::PipeInputConfigFormatType;
+using ::aidl::android::automotive::computepipe::runner::PipeInputConfigImageFileType;
+using ::aidl::android::automotive::computepipe::runner::PipeInputConfigInputSourceDesc;
+using ::aidl::android::automotive::computepipe::runner::PipeInputConfigInputType;
+using ::aidl::android::automotive::computepipe::runner::PipeInputConfigVideoFileType;
+using ::aidl::android::automotive::computepipe::runner::PipeOffloadConfig;
+using ::aidl::android::automotive::computepipe::runner::PipeOffloadConfigOffloadType;
+using ::aidl::android::automotive::computepipe::runner::PipeOutputConfig;
+using ::aidl::android::automotive::computepipe::runner::PipeOutputConfigPacketType;
+using ::aidl::android::automotive::computepipe::runner::PipeTerminationConfig;
+using ::aidl::android::automotive::computepipe::runner::PipeTerminationConfigTerminationType;
+
+namespace {
+
+PipeInputConfigInputType ConvertInputType(proto::InputStreamConfig_InputType type) {
+    switch (type) {
+        case proto::InputStreamConfig_InputType_CAMERA:
+            return PipeInputConfigInputType::CAMERA;
+        case proto::InputStreamConfig_InputType_VIDEO_FILE:
+            return PipeInputConfigInputType::VIDEO_FILE;
+        case proto::InputStreamConfig_InputType_IMAGE_FILES:
+            return PipeInputConfigInputType::IMAGE_FILES;
+    }
+}
+
+PipeInputConfigCameraType ConvertCameraType(proto::CameraConfig_CameraType type) {
+    switch (type) {
+        case proto::CameraConfig_CameraType_DRIVER_VIEW_CAMERA:
+            return PipeInputConfigCameraType::DRIVER_VIEW_CAMERA;
+        case proto::CameraConfig_CameraType_OCCUPANT_VIEW_CAMERA:
+            return PipeInputConfigCameraType::OCCUPANT_VIEW_CAMERA;
+        case proto::CameraConfig_CameraType_EXTERNAL_CAMERA:
+            return PipeInputConfigCameraType::EXTERNAL_CAMERA;
+        case proto::CameraConfig_CameraType_SURROUND_VIEW_CAMERA:
+            return PipeInputConfigCameraType::SURROUND_VIEW_CAMERA;
+    }
+}
+
+PipeInputConfigImageFileType ConvertImageFileType(proto::ImageFileConfig_ImageFileType type) {
+    switch (type) {
+        case proto::ImageFileConfig_ImageFileType_JPEG:
+            return PipeInputConfigImageFileType::JPEG;
+        case proto::ImageFileConfig_ImageFileType_PNG:
+            return PipeInputConfigImageFileType::PNG;
+    }
+}
+
+PipeInputConfigVideoFileType ConvertVideoFileType(proto::VideoFileConfig_VideoFileType type) {
+    switch (type) {
+        case proto::VideoFileConfig_VideoFileType_MPEG:
+            return PipeInputConfigVideoFileType::MPEG;
+    }
+}
+
+PipeInputConfigFormatType ConvertInputFormat(proto::InputStreamConfig_FormatType type) {
+    switch (type) {
+        case proto::InputStreamConfig_FormatType_RGB:
+            return PipeInputConfigFormatType::RGB;
+        case proto::InputStreamConfig_FormatType_NIR:
+            return PipeInputConfigFormatType::NIR;
+        case proto::InputStreamConfig_FormatType_NIR_DEPTH:
+            return PipeInputConfigFormatType::NIR_DEPTH;
+    }
+}
+
+PipeOffloadConfigOffloadType ConvertOffloadType(proto::OffloadOption_OffloadType type) {
+    switch (type) {
+        case proto::OffloadOption_OffloadType_CPU:
+            return PipeOffloadConfigOffloadType::CPU;
+        case proto::OffloadOption_OffloadType_GPU:
+            return PipeOffloadConfigOffloadType::GPU;
+        case proto::OffloadOption_OffloadType_NEURAL_ENGINE:
+            return PipeOffloadConfigOffloadType::NEURAL_ENGINE;
+        case proto::OffloadOption_OffloadType_CV_ENGINE:
+            return PipeOffloadConfigOffloadType::CV_ENGINE;
+    }
+}
+
+PipeOutputConfigPacketType ConvertOutputType(proto::PacketType type) {
+    switch (type) {
+        case proto::PacketType::SEMANTIC_DATA:
+            return PipeOutputConfigPacketType::SEMANTIC_DATA;
+        case proto::PacketType::PIXEL_DATA:
+            return PipeOutputConfigPacketType::PIXEL_DATA;
+        case proto::PacketType::PIXEL_ZERO_COPY_DATA:
+            return PipeOutputConfigPacketType::PIXEL_ZERO_COPY_DATA;
+    }
+}
+
+PipeTerminationConfigTerminationType ConvertTerminationType(
+    proto::TerminationOption_TerminationType type) {
+    switch (type) {
+        case proto::TerminationOption_TerminationType_CLIENT_STOP:
+            return PipeTerminationConfigTerminationType::CLIENT_STOP;
+        case proto::TerminationOption_TerminationType_MIN_PACKET_COUNT:
+            return PipeTerminationConfigTerminationType::MIN_PACKET_COUNT;
+        case proto::TerminationOption_TerminationType_MAX_RUN_TIME:
+            return PipeTerminationConfigTerminationType::MAX_RUN_TIME;
+        case proto::TerminationOption_TerminationType_EVENT:
+            return PipeTerminationConfigTerminationType::EVENT;
+    }
+}
+
+PipeInputConfig ConvertInputConfigProto(const proto::InputConfig& proto) {
+    PipeInputConfig aidlConfig;
+
+    for (const auto& inputStreamConfig : proto.input_stream()) {
+        PipeInputConfigInputSourceDesc aidlInputDesc;
+        aidlInputDesc.type = ConvertInputType(inputStreamConfig.type());
+        aidlInputDesc.format = ConvertInputFormat(inputStreamConfig.format());
+        aidlInputDesc.width = inputStreamConfig.width();
+        aidlInputDesc.height = inputStreamConfig.height();
+        aidlInputDesc.stride = inputStreamConfig.stride();
+        aidlInputDesc.camDesc.camId = inputStreamConfig.cam_config().cam_id();
+        aidlInputDesc.camDesc.type = ConvertCameraType(inputStreamConfig.cam_config().camera_type());
+        aidlInputDesc.imageDesc.fileType =
+            ConvertImageFileType(inputStreamConfig.image_config().file_type());
+        aidlInputDesc.imageDesc.filePath = inputStreamConfig.image_config().image_dir();
+        aidlInputDesc.videoDesc.fileType =
+            ConvertVideoFileType(inputStreamConfig.video_config().file_type());
+        aidlInputDesc.videoDesc.filePath = inputStreamConfig.video_config().file_path();
+        aidlConfig.inputSources.emplace_back(aidlInputDesc);
+    }
+    aidlConfig.configId = proto.config_id();
+
+    return aidlConfig;
+}
+
+PipeOffloadConfig ConvertOffloadConfigProto(const proto::OffloadConfig& proto) {
+    PipeOffloadConfig aidlConfig;
+
+    for (int i = 0; i < proto.options().offload_types_size(); i++) {
+        auto offloadType =
+            static_cast<proto::OffloadOption_OffloadType>(proto.options().offload_types()[i]);
+        PipeOffloadConfigOffloadType aidlType = ConvertOffloadType(offloadType);
+        aidlConfig.desc.type.emplace_back(aidlType);
+        aidlConfig.desc.isVirtual.emplace_back(proto.options().is_virtual()[i]);
+    }
+
+    aidlConfig.configId = proto.config_id();
+    return aidlConfig;
+}
+
+PipeOutputConfig ConvertOutputConfigProto(const proto::OutputConfig& proto) {
+    PipeOutputConfig aidlConfig;
+    aidlConfig.output.name = proto.stream_name();
+    aidlConfig.output.type = ConvertOutputType(proto.type());
+    aidlConfig.outputId = proto.stream_id();
+    return aidlConfig;
+}
+
+PipeTerminationConfig ConvertTerminationConfigProto(const proto::TerminationConfig& proto) {
+    PipeTerminationConfig aidlConfig;
+    aidlConfig.desc.type = ConvertTerminationType(proto.options().type());
+    aidlConfig.desc.qualifier = proto.options().qualifier();
+    aidlConfig.configId = proto.config_id();
+    return aidlConfig;
+}
+
+}  // namespace
+
+PipeDescriptor OptionsToPipeDescriptor(const proto::Options& options) {
+    PipeDescriptor desc;
+    for (int i = 0; i < options.input_configs_size(); i++) {
+        PipeInputConfig inputConfig = ConvertInputConfigProto(options.input_configs()[i]);
+        desc.inputConfig.emplace_back(inputConfig);
+    }
+
+    for (int i = 0; i < options.offload_configs_size(); i++) {
+        PipeOffloadConfig offloadConfig = ConvertOffloadConfigProto(options.offload_configs()[i]);
+        desc.offloadConfig.emplace_back(offloadConfig);
+    }
+
+    for (int i = 0; i < options.termination_configs_size(); i++) {
+        PipeTerminationConfig terminationConfig =
+            ConvertTerminationConfigProto(options.termination_configs()[i]);
+        desc.terminationConfig.emplace_back(terminationConfig);
+    }
+
+    for (int i = 0; i < options.output_configs_size(); i++) {
+        PipeOutputConfig outputConfig = ConvertOutputConfigProto(options.output_configs()[i]);
+        desc.outputConfig.emplace_back(outputConfig);
+    }
+    return desc;
+}
+
+}  // namespace aidl_client
+}  // namespace client_interface
+}  // namespace runner
+}  // namespace computepipe
+}  // namespace automotive
+}  // namespace android
diff --git a/computepipe/runner/client_interface/PipeOptionsConverter.h b/computepipe/runner/client_interface/PipeOptionsConverter.h
new file mode 100644
index 0000000..d5d3d75
--- /dev/null
+++ b/computepipe/runner/client_interface/PipeOptionsConverter.h
@@ -0,0 +1,39 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef COMPUTEPIPE_RUNNER_UTILS_PIPEOPTIONSCONVERTER_H_
+#define COMPUTEPIPE_RUNNER_UTILS_PIPEOPTIONSCONVERTER_H_
+
+#include <aidl/android/automotive/computepipe/runner/BnPipeRunner.h>
+
+#include "Options.pb.h"
+
+namespace android {
+namespace automotive {
+namespace computepipe {
+namespace runner {
+namespace client_interface {
+namespace aidl_client {
+
+aidl::android::automotive::computepipe::runner::PipeDescriptor OptionsToPipeDescriptor(
+    const proto::Options& options);
+
+}  // namespace aidl_client
+}  // namespace client_interface
+}  // namespace runner
+}  // namespace computepipe
+}  // namespace automotive
+}  // namespace android
+
+#endif  // COMPUTEPIPE_RUNNER_UTILS_PIPEOPTIONSCONVERTER_H_
diff --git a/computepipe/runner/client_interface/include/ClientEngineInterface.h b/computepipe/runner/client_interface/include/ClientEngineInterface.h
new file mode 100644
index 0000000..d75a1f9
--- /dev/null
+++ b/computepipe/runner/client_interface/include/ClientEngineInterface.h
@@ -0,0 +1,58 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef COMPUTEPIPE_RUNNER_CLIENT_ENGINE_INTERFACE_H
+#define COMPUTEPIPE_RUNNER_CLIENT_ENGINE_INTERFACE_H
+
+#include <memory>
+
+#include "ConfigurationCommand.pb.h"
+#include "ControlCommand.pb.h"
+#include "MemHandle.h"
+#include "types/Status.h"
+
+namespace android {
+namespace automotive {
+namespace computepipe {
+namespace runner {
+namespace client_interface {
+
+/**
+ * Communications from client component to runner engine
+ */
+class ClientEngineInterface {
+  public:
+    /**
+     * Used by client to provide the engine with incremental client configuration
+     * choices
+     */
+    virtual Status processClientConfigUpdate(const proto::ConfigurationCommand& command) = 0;
+    /**
+     * Used by client to provide the engine, the latest client command
+     */
+    virtual Status processClientCommand(const proto::ControlCommand& command) = 0;
+    /**
+     * Used by client to notify the engine of a consumed packet
+     */
+    virtual Status freePacket(int bufferId, int streamId) = 0;
+    virtual ~ClientEngineInterface() = default;
+};
+
+}  // namespace client_interface
+}  // namespace runner
+}  // namespace computepipe
+}  // namespace automotive
+}  // namespace android
+
+#endif  // COMPUTEPIPE_RUNNER_CLIENT_ENGINE_INTERFACE_H
diff --git a/computepipe/runner/client_interface/include/ClientInterface.h b/computepipe/runner/client_interface/include/ClientInterface.h
new file mode 100644
index 0000000..b84e264
--- /dev/null
+++ b/computepipe/runner/client_interface/include/ClientInterface.h
@@ -0,0 +1,67 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef COMPUTEPIPE_RUNNER_CLIENT_INTERFACE_H
+#define COMPUTEPIPE_RUNNER_CLIENT_INTERFACE_H
+
+#include <memory>
+
+#include "ClientEngineInterface.h"
+#include "MemHandle.h"
+#include "Options.pb.h"
+#include "RunnerComponent.h"
+
+namespace android {
+namespace automotive {
+namespace computepipe {
+namespace runner {
+namespace client_interface {
+
+/**
+ * ClientInterface: Is the runner component that represents the client of the
+ * runner. This Component exposes communications from engine to the client.
+ */
+
+class ClientInterface : public RunnerComponentInterface {
+  public:
+    /**
+     * Used by the runner engine to dispatch Graph output packes to the clients
+     */
+    virtual Status dispatchPacketToClient(int32_t streamId,
+                                          const std::shared_ptr<MemHandle> packet) = 0;
+    /**
+     * Used by the runner engine to activate the client interface and open it to
+     * external clients
+     */
+    virtual Status activate() = 0;
+    virtual ~ClientInterface() = default;
+};
+
+class ClientInterfaceFactory {
+  public:
+    std::unique_ptr<ClientInterface> createClientInterface(
+        std::string ifaceName, const proto::Options graphOptions,
+        const std::shared_ptr<ClientEngineInterface>& engine);
+    ClientInterfaceFactory(const ClientInterfaceFactory&) = delete;
+    ClientInterfaceFactory& operator=(const ClientInterfaceFactory&) = delete;
+    ClientInterfaceFactory() = default;
+};
+
+}  // namespace client_interface
+}  // namespace runner
+}  // namespace computepipe
+}  // namespace automotive
+}  // namespace android
+
+#endif
diff --git a/computepipe/runner/engine/Android.bp b/computepipe/runner/engine/Android.bp
new file mode 100644
index 0000000..9593ed0
--- /dev/null
+++ b/computepipe/runner/engine/Android.bp
@@ -0,0 +1,63 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+cc_library {
+    name: "computepipe_runner_engine",
+    srcs: [
+        "ConfigBuilder.cpp",
+	"DefaultEngine.cpp",
+	"Factory.cpp",
+    ],
+    export_include_dirs: ["include"],
+    header_libs: [
+        "computepipe_runner_includes",
+    ],
+    static_libs: [
+        "libcomputepipeprotos",
+        "computepipe_runner_component",
+        "computepipe_input_manager",
+        "computepipe_stream_manager",
+    ],
+    shared_libs: [
+        "computepipe_client_interface",
+        "computepipe_prebuilt_graph",
+        "libprotobuf-cpp-lite",
+        "android.hardware.automotive.evs@1.0",
+        "libbase",
+        "libcutils",
+        "libdl",
+        "libevssupport",
+        "libhardware",
+        "libhidlbase",
+        "liblog",
+	"libnativewindow",
+        "libpng",
+        "libprotobuf-cpp-lite",
+        "libui",
+        "libutils",
+        "libEGL",
+        "libGLESv2",
+    ],
+    cflags: [
+        "-D_LIBCPP_ENABLE_THREAD_SAFETY_ANNOTATIONS",
+        "-Wall",
+        "-Werror",
+        "-Wunused",
+        "-Wunreachable-code",
+        "-Wthread-safety",
+    ],
+    include_dirs: [
+        "packages/services/Car/computepipe",
+    ],
+}
diff --git a/computepipe/runner/engine/ConfigBuilder.cpp b/computepipe/runner/engine/ConfigBuilder.cpp
new file mode 100644
index 0000000..a47b588
--- /dev/null
+++ b/computepipe/runner/engine/ConfigBuilder.cpp
@@ -0,0 +1,80 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "ConfigBuilder.h"
+
+namespace android {
+namespace automotive {
+namespace computepipe {
+namespace runner {
+namespace engine {
+
+void ConfigBuilder::setDebugDisplayStream(int id) {
+    mDisplayStream = id;
+    mOutputConfig.emplace(id, 1);
+}
+
+bool ConfigBuilder::clientConfigEnablesDisplayStream() {
+    return mConfigHasDisplayStream;
+}
+
+ConfigBuilder& ConfigBuilder::updateInputConfigOption(int id) {
+    mInputConfigId = id;
+    return *this;
+}
+
+ConfigBuilder& ConfigBuilder::updateOutputStreamOption(int id, int maxInFlightPackets) {
+    if (id == mDisplayStream) {
+        mConfigHasDisplayStream = true;
+    }
+    mOutputConfig.emplace(id, maxInFlightPackets);
+    return *this;
+}
+
+ConfigBuilder& ConfigBuilder::updateTerminationOption(int id) {
+    mTerminationId = id;
+    return *this;
+}
+
+ConfigBuilder& ConfigBuilder::updateOffloadOption(int id) {
+    mOffloadId = id;
+    return *this;
+}
+
+ConfigBuilder& ConfigBuilder::updateOptionalConfig(std::string options) {
+    mOptionalConfig = options;
+    return *this;
+}
+
+ClientConfig ConfigBuilder::emitClientOptions() {
+    return ClientConfig(mInputConfigId, mOffloadId, mTerminationId, mOutputConfig, mOptionalConfig);
+}
+
+ConfigBuilder& ConfigBuilder::reset() {
+    mInputConfigId = ClientConfig::kInvalidId;
+    mTerminationId = ClientConfig::kInvalidId;
+    mOffloadId = ClientConfig::kInvalidId;
+    mOutputConfig.clear();
+    if (mDisplayStream != ClientConfig::kInvalidId) {
+        mOutputConfig.emplace(mDisplayStream, 1);
+    }
+    mConfigHasDisplayStream = false;
+    return *this;
+}
+
+}  // namespace engine
+}  // namespace runner
+}  // namespace computepipe
+}  // namespace automotive
+}  // namespace android
diff --git a/computepipe/runner/engine/ConfigBuilder.h b/computepipe/runner/engine/ConfigBuilder.h
new file mode 100644
index 0000000..35442ad
--- /dev/null
+++ b/computepipe/runner/engine/ConfigBuilder.h
@@ -0,0 +1,82 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef COMPUTEPIPE_RUNNER_CONFIG_BUILDER_H
+#define COMPUTEPIPE_RUNNER_CONFIG_BUILDER_H
+
+#include "RunnerComponent.h"
+#include "types/Status.h"
+
+namespace android {
+namespace automotive {
+namespace computepipe {
+namespace runner {
+namespace engine {
+
+class ConfigBuilder {
+  public:
+    /**
+     * Set debug display stream in the final client config
+     */
+    void setDebugDisplayStream(int id);
+    /**
+     * Does client explicitly enable display stream
+     */
+    bool clientConfigEnablesDisplayStream();
+    /**
+     * Update current input option
+     */
+    ConfigBuilder& updateInputConfigOption(int id);
+    /**
+     * Update current output options
+     */
+    ConfigBuilder& updateOutputStreamOption(int id, int maxInFlightPackets);
+    /**
+     * Update current termination options
+     */
+    ConfigBuilder& updateTerminationOption(int id);
+    /**
+     * Update current offload options
+     */
+    ConfigBuilder& updateOffloadOption(int id);
+    /**
+     * Update optional Config
+     */
+    ConfigBuilder& updateOptionalConfig(std::string options);
+    /**
+     * Emit Options
+     */
+    ClientConfig emitClientOptions();
+    /**
+     * Clear current options.
+     */
+    ConfigBuilder& reset();
+
+  private:
+    int mDisplayStream = ClientConfig::kInvalidId;
+    int mInputConfigId = ClientConfig::kInvalidId;
+    int mOffloadId = ClientConfig::kInvalidId;
+    int mTerminationId = ClientConfig::kInvalidId;
+    bool mConfigHasDisplayStream = false;
+    std::map<int, int> mOutputConfig;
+    std::string mOptionalConfig;
+};
+
+}  // namespace engine
+}  // namespace runner
+}  // namespace computepipe
+}  // namespace automotive
+}  // namespace android
+
+#endif
diff --git a/computepipe/runner/engine/DefaultEngine.cpp b/computepipe/runner/engine/DefaultEngine.cpp
new file mode 100644
index 0000000..d93de2e
--- /dev/null
+++ b/computepipe/runner/engine/DefaultEngine.cpp
@@ -0,0 +1,635 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "DefaultEngine.h"
+
+#include <android-base/logging.h>
+
+#include <algorithm>
+#include <cassert>
+#include <memory>
+#include <mutex>
+#include <thread>
+#include <vector>
+
+#include "ClientInterface.h"
+#include "EventGenerator.h"
+#include "InputFrame.h"
+#include "PrebuiltGraph.h"
+
+namespace android {
+namespace automotive {
+namespace computepipe {
+namespace runner {
+namespace engine {
+
+using android::automotive::computepipe::graph::PrebuiltGraph;
+using android::automotive::computepipe::runner::client_interface::ClientInterface;
+using android::automotive::computepipe::runner::generator::DefaultEvent;
+using android::automotive::computepipe::runner::input_manager::InputEngineInterface;
+using android::automotive::computepipe::runner::stream_manager::StreamEngineInterface;
+using android::automotive::computepipe::runner::stream_manager::StreamManager;
+
+namespace {
+
+int getStreamIdFromSource(std::string source) {
+    auto pos = source.find(":");
+    return std::stoi(source.substr(pos + 1));
+}
+}  // namespace
+
+void DefaultEngine::setClientInterface(std::unique_ptr<ClientInterface>&& client) {
+    mClient = std::move(client);
+}
+
+void DefaultEngine::setPrebuiltGraph(std::unique_ptr<PrebuiltGraph>&& graph) {
+    mGraph = std::move(graph);
+    mGraphDescriptor = mGraph->GetSupportedGraphConfigs();
+}
+
+Status DefaultEngine::setArgs(std::string engine_args) {
+    auto pos = engine_args.find(kNoInputManager);
+    if (pos != std::string::npos) {
+        mIgnoreInputManager = true;
+    }
+    pos = engine_args.find(kDisplayStreamId);
+    if (pos == std::string::npos) {
+        return Status::SUCCESS;
+    }
+    mDisplayStream = std::stoi(engine_args.substr(pos + strlen(kDisplayStreamId)));
+    mConfigBuilder.setDebugDisplayStream(mDisplayStream);
+    return Status::SUCCESS;
+}
+
+Status DefaultEngine::activate() {
+    mConfigBuilder.reset();
+    mEngineThread = std::make_unique<std::thread>(&DefaultEngine::processCommands, this);
+    return mClient->activate();
+}
+
+Status DefaultEngine::processClientConfigUpdate(const proto::ConfigurationCommand& command) {
+    // TODO check current phase
+    std::lock_guard<std::mutex> lock(mEngineLock);
+    if (mCurrentPhase != kResetPhase) {
+        return Status::ILLEGAL_STATE;
+    }
+    if (command.has_set_input_source()) {
+        mConfigBuilder =
+            mConfigBuilder.updateInputConfigOption(command.set_input_source().source_id());
+    } else if (command.has_set_termination_option()) {
+        mConfigBuilder = mConfigBuilder.updateTerminationOption(
+            command.set_termination_option().termination_option_id());
+    } else if (command.has_set_output_stream()) {
+        mConfigBuilder = mConfigBuilder.updateOutputStreamOption(
+            command.set_output_stream().stream_id(),
+            command.set_output_stream().max_inflight_packets_count());
+    } else if (command.has_set_offload_offload()) {
+        mConfigBuilder =
+            mConfigBuilder.updateOffloadOption(command.set_offload_offload().offload_option_id());
+    } else {
+        return SUCCESS;
+    }
+    return Status::SUCCESS;
+}
+
+Status DefaultEngine::processClientCommand(const proto::ControlCommand& command) {
+    // TODO check current phase
+    std::lock_guard<std::mutex> lock(mEngineLock);
+
+    if (command.has_apply_configs()) {
+        if (mCurrentPhase != kResetPhase) {
+            return Status::ILLEGAL_STATE;
+        }
+        queueCommand("ClientInterface", EngineCommand::Type::BROADCAST_CONFIG);
+        return Status::SUCCESS;
+    }
+    if (command.has_start_graph()) {
+        if (mCurrentPhase != kConfigPhase) {
+            return Status::ILLEGAL_STATE;
+        }
+        queueCommand("ClientInterface", EngineCommand::Type::BROADCAST_START_RUN);
+        return Status::SUCCESS;
+    }
+    if (command.has_stop_graph()) {
+        if (mCurrentPhase != kRunPhase) {
+            return Status::ILLEGAL_STATE;
+        }
+        mStopFromClient = true;
+        queueCommand("ClientInterface", EngineCommand::Type::BROADCAST_INITIATE_STOP);
+        return Status::SUCCESS;
+    }
+    if (command.has_death_notification()) {
+        mCurrentPhaseError = std::make_unique<ComponentError>(
+                "ClientInterface", "Client death", mCurrentPhase, false);
+        mWakeLooper.notify_all();
+        return Status::SUCCESS;
+    }
+    return Status::SUCCESS;
+}
+
+Status DefaultEngine::freePacket(int bufferId, int streamId) {
+    if (mStreamManagers.find(streamId) == mStreamManagers.end()) {
+        LOG(ERROR)
+            << "Unable to find the stream manager corresponding to the id for freeing the packet.";
+        return Status::INVALID_ARGUMENT;
+    }
+    return mStreamManagers[streamId]->freePacket(bufferId);
+}
+
+/**
+ * Methods from PrebuiltEngineInterface
+ */
+void DefaultEngine::DispatchPixelData(int streamId, int64_t timestamp, const InputFrame& frame) {
+    LOG(INFO) << "Engine::Received data for pixel stream  " << streamId << " with timestamp "
+              << timestamp;
+    if (mStreamManagers.find(streamId) == mStreamManagers.end()) {
+        LOG(ERROR) << "Engine::Received bad stream id from prebuilt graph";
+        return;
+    }
+    mStreamManagers[streamId]->queuePacket(frame, timestamp);
+}
+
+void DefaultEngine::DispatchSerializedData(int streamId, int64_t timestamp, std::string&& output) {
+    LOG(INFO) << "Engine::Received data for stream  " << streamId << " with timestamp " << timestamp;
+    if (mStreamManagers.find(streamId) == mStreamManagers.end()) {
+        LOG(ERROR) << "Engine::Received bad stream id from prebuilt graph";
+        return;
+    }
+    std::string data(output);
+    mStreamManagers[streamId]->queuePacket(data.c_str(), data.size(), timestamp);
+}
+
+void DefaultEngine::DispatchGraphTerminationMessage(Status s, std::string&& msg) {
+    std::lock_guard<std::mutex> lock(mEngineLock);
+    if (s == SUCCESS) {
+        if (mCurrentPhase == kRunPhase) {
+            queueCommand("PrebuiltGraph", EngineCommand::Type::BROADCAST_INITIATE_STOP);
+        } else {
+            LOG(WARNING) << "Graph termination when not in run phase";
+        }
+    } else {
+        std::string error = msg;
+        queueError("PrebuiltGraph", error, false);
+    }
+}
+
+Status DefaultEngine::broadcastClientConfig() {
+    ClientConfig config = mConfigBuilder.emitClientOptions();
+
+    LOG(INFO) << "Engine::create stream manager";
+    Status ret = populateStreamManagers(config);
+    if (ret != Status::SUCCESS) {
+        return ret;
+    }
+
+    if (mGraph) {
+        ret = populateInputManagers(config);
+        if (ret != Status::SUCCESS) {
+            abortClientConfig(config);
+            return ret;
+        }
+
+        LOG(INFO) << "Engine::send client config entry to graph";
+        config.setPhaseState(PhaseState::ENTRY);
+        ret = mGraph->handleConfigPhase(config);
+        if (ret != Status::SUCCESS) {
+            abortClientConfig(config);
+            return ret;
+        }
+        LOG(INFO) << "Engine::send client config transition complete to graph";
+        config.setPhaseState(PhaseState::TRANSITION_COMPLETE);
+        ret = mGraph->handleConfigPhase(config);
+        if (ret != Status::SUCCESS) {
+            abortClientConfig(config);
+            return ret;
+        }
+    }
+    LOG(INFO) << "Engine::Graph configured";
+    // TODO add handling for remote graph
+    ret = mClient->handleConfigPhase(config);
+    if (ret != Status::SUCCESS) {
+        config.setPhaseState(PhaseState::ABORTED);
+        abortClientConfig(config, true);
+        return ret;
+    }
+    mCurrentPhase = kConfigPhase;
+    return Status::SUCCESS;
+}
+
+void DefaultEngine::abortClientConfig(const ClientConfig& config, bool resetGraph) {
+    mStreamManagers.clear();
+    mInputManagers.clear();
+    if (resetGraph && mGraph) {
+        (void)mGraph->handleConfigPhase(config);
+    }
+    (void)mClient->handleConfigPhase(config);
+    // TODO add handling for remote graph
+}
+
+Status DefaultEngine::broadcastStartRun() {
+    DefaultEvent runEvent = DefaultEvent::generateEntryEvent(DefaultEvent::RUN);
+
+    std::vector<int> successfulStreams;
+    std::vector<int> successfulInputs;
+    for (auto& it : mStreamManagers) {
+        if (it.second->handleExecutionPhase(runEvent) != Status::SUCCESS) {
+            LOG(ERROR) << "Engine::failure to enter run phase for stream " << it.first;
+            broadcastAbortRun(successfulStreams, successfulInputs);
+            return Status::INTERNAL_ERROR;
+        }
+        successfulStreams.push_back(it.first);
+    }
+    // TODO: send to remote
+    Status ret;
+    if (mGraph) {
+        LOG(INFO) << "Engine::sending start run to prebuilt";
+        ret = mGraph->handleExecutionPhase(runEvent);
+        if (ret != Status::SUCCESS) {
+            broadcastAbortRun(successfulStreams, successfulInputs);
+        }
+        for (auto& it : mInputManagers) {
+            if (it.second->handleExecutionPhase(runEvent) != Status::SUCCESS) {
+                LOG(ERROR) << "Engine::failure to enter run phase for input manager " << it.first;
+                broadcastAbortRun(successfulStreams, successfulInputs, true);
+                return Status::INTERNAL_ERROR;
+            }
+            successfulInputs.push_back(it.first);
+        }
+    }
+    runEvent = DefaultEvent::generateTransitionCompleteEvent(DefaultEvent::RUN);
+    LOG(INFO) << "Engine::sending run transition complete to client";
+    ret = mClient->handleExecutionPhase(runEvent);
+    if (ret != Status::SUCCESS) {
+        LOG(ERROR) << "Engine::client failure to acknowledge transition to run complete ";
+        broadcastAbortRun(successfulStreams, successfulInputs, true);
+        return ret;
+    }
+    for (auto& it : mStreamManagers) {
+        (void)it.second->handleExecutionPhase(runEvent);
+    }
+    // TODO: send to remote
+    if (mGraph) {
+        LOG(INFO) << "Engine::sending run transition complete to prebuilt";
+        (void)mGraph->handleExecutionPhase(runEvent);
+        for (auto& it : mInputManagers) {
+            (void)it.second->handleExecutionPhase(runEvent);
+        }
+    }
+    LOG(INFO) << "Engine::Running";
+    mCurrentPhase = kRunPhase;
+    return Status::SUCCESS;
+}
+
+void DefaultEngine::broadcastAbortRun(const std::vector<int>& streamIds,
+                                      const std::vector<int>& inputIds, bool abortGraph) {
+    DefaultEvent runEvent = DefaultEvent::generateAbortEvent(DefaultEvent::RUN);
+    std::for_each(streamIds.begin(), streamIds.end(), [this, runEvent](int id) {
+        (void)this->mStreamManagers[id]->handleExecutionPhase(runEvent);
+    });
+    std::for_each(inputIds.begin(), inputIds.end(), [this, runEvent](int id) {
+        (void)this->mInputManagers[id]->handleExecutionPhase(runEvent);
+    });
+    if (abortGraph) {
+        if (mGraph) {
+            (void)mGraph->handleExecutionPhase(runEvent);
+        }
+    }
+    (void)mClient->handleExecutionPhase(runEvent);
+}
+
+Status DefaultEngine::broadcastStopWithFlush() {
+    DefaultEvent runEvent = DefaultEvent::generateEntryEvent(DefaultEvent::STOP_WITH_FLUSH);
+
+    if (mGraph) {
+        for (auto& it : mInputManagers) {
+            (void)it.second->handleStopWithFlushPhase(runEvent);
+        }
+        if (mStopFromClient) {
+            (void)mGraph->handleStopWithFlushPhase(runEvent);
+        }
+    }
+    // TODO: send to remote.
+    for (auto& it : mStreamManagers) {
+        (void)it.second->handleStopWithFlushPhase(runEvent);
+    }
+    if (!mStopFromClient) {
+        (void)mClient->handleStopWithFlushPhase(runEvent);
+    }
+    mCurrentPhase = kStopPhase;
+    return Status::SUCCESS;
+}
+
+Status DefaultEngine::broadcastStopComplete() {
+    DefaultEvent runEvent =
+        DefaultEvent::generateTransitionCompleteEvent(DefaultEvent::STOP_WITH_FLUSH);
+    if (mGraph) {
+        for (auto& it : mInputManagers) {
+            (void)it.second->handleStopWithFlushPhase(runEvent);
+        }
+        (void)mGraph->handleStopWithFlushPhase(runEvent);
+    }
+    // TODO: send to remote.
+    for (auto& it : mStreamManagers) {
+        (void)it.second->handleStopWithFlushPhase(runEvent);
+    }
+    (void)mClient->handleStopWithFlushPhase(runEvent);
+    mCurrentPhase = kConfigPhase;
+    return Status::SUCCESS;
+}
+
+void DefaultEngine::broadcastHalt() {
+    DefaultEvent stopEvent = DefaultEvent::generateEntryEvent(DefaultEvent::STOP_IMMEDIATE);
+
+    if (mGraph) {
+        for (auto& it : mInputManagers) {
+            (void)it.second->handleStopImmediatePhase(stopEvent);
+        }
+
+        if ((mCurrentPhaseError->source.find("PrebuiltGraph") == std::string::npos)) {
+            (void)mGraph->handleStopImmediatePhase(stopEvent);
+        }
+    }
+    // TODO: send to remote if client was source.
+    for (auto& it : mStreamManagers) {
+        (void)it.second->handleStopImmediatePhase(stopEvent);
+    }
+    if (mCurrentPhaseError->source.find("ClientInterface") == std::string::npos) {
+        (void)mClient->handleStopImmediatePhase(stopEvent);
+    }
+
+    stopEvent = DefaultEvent::generateTransitionCompleteEvent(DefaultEvent::STOP_IMMEDIATE);
+    if (mGraph) {
+        for (auto& it : mInputManagers) {
+            (void)it.second->handleStopImmediatePhase(stopEvent);
+        }
+        // TODO: send to graph or remote if client was source.
+
+        if ((mCurrentPhaseError->source.find("PrebuiltGraph") == std::string::npos) && mGraph) {
+            (void)mGraph->handleStopImmediatePhase(stopEvent);
+        }
+    }
+    for (auto& it : mStreamManagers) {
+        (void)it.second->handleStopImmediatePhase(stopEvent);
+    }
+    if (mCurrentPhaseError->source.find("ClientInterface") == std::string::npos) {
+        (void)mClient->handleStopImmediatePhase(stopEvent);
+    }
+    mCurrentPhase = kConfigPhase;
+}
+
+void DefaultEngine::broadcastReset() {
+    mStreamManagers.clear();
+    mInputManagers.clear();
+    DefaultEvent resetEvent = DefaultEvent::generateEntryEvent(DefaultEvent::RESET);
+    (void)mClient->handleResetPhase(resetEvent);
+    if (mGraph) {
+        (void)mGraph->handleResetPhase(resetEvent);
+    }
+    resetEvent = DefaultEvent::generateTransitionCompleteEvent(DefaultEvent::RESET);
+    (void)mClient->handleResetPhase(resetEvent);
+    if (mGraph) {
+        (void)mGraph->handleResetPhase(resetEvent);
+    }
+    // TODO: send to remote runner
+    mConfigBuilder.reset();
+    mCurrentPhase = kResetPhase;
+    mStopFromClient = false;
+}
+
+Status DefaultEngine::populateStreamManagers(const ClientConfig& config) {
+    std::map<int, int> outputConfigs;
+    if (config.getOutputStreamConfigs(outputConfigs) != Status::SUCCESS) {
+        return Status::ILLEGAL_STATE;
+    }
+    for (auto& configIt : outputConfigs) {
+        int streamId = configIt.first;
+        int maxInFlightPackets = configIt.second;
+        proto::OutputConfig outputDescriptor;
+        // find the output descriptor for requested stream id
+        bool foundDesc = false;
+        for (auto& optionIt : mGraphDescriptor.output_configs()) {
+            if (optionIt.stream_id() == streamId) {
+                outputDescriptor = optionIt;
+                foundDesc = true;
+                break;
+            }
+        }
+        if (!foundDesc) {
+            LOG(ERROR) << "no matching output config for requested id " << streamId;
+            return Status::INVALID_ARGUMENT;
+        }
+        std::function<Status(std::shared_ptr<MemHandle>)> packetCb =
+            [this, streamId](std::shared_ptr<MemHandle> handle) -> Status {
+            return this->forwardOutputDataToClient(streamId, handle);
+        };
+
+        std::function<void(std::string)> errorCb = [this, streamId](std::string m) {
+            std::string source = "StreamManager:" + std::to_string(streamId) + " : " + m;
+            this->queueError(source, m, false);
+        };
+
+        std::function<void()> eos = [this, streamId]() {
+            std::string source = "StreamManager:" + std::to_string(streamId);
+            std::lock_guard<std::mutex> lock(this->mEngineLock);
+            this->queueCommand(source, EngineCommand::Type::POLL_COMPLETE);
+        };
+
+        std::shared_ptr<StreamEngineInterface> engine = std::make_shared<StreamCallback>(
+            std::move(eos), std::move(errorCb), std::move(packetCb));
+        mStreamManagers.emplace(configIt.first, mStreamFactory.getStreamManager(
+                                                    outputDescriptor, engine, maxInFlightPackets));
+        if (mStreamManagers[streamId] == nullptr) {
+            LOG(ERROR) << "unable to create stream manager for stream " << streamId;
+            return Status::INTERNAL_ERROR;
+        }
+    }
+    return Status::SUCCESS;
+}
+
+Status DefaultEngine::forwardOutputDataToClient(int streamId,
+                                                std::shared_ptr<MemHandle>& dataHandle) {
+    if (streamId == mDisplayStream) {
+        // TODO: dispatch to display
+        if (mConfigBuilder.clientConfigEnablesDisplayStream()) {
+            return mClient->dispatchPacketToClient(streamId, dataHandle);
+        } else {
+            return Status::SUCCESS;
+        }
+    }
+    return mClient->dispatchPacketToClient(streamId, dataHandle);
+}
+
+Status DefaultEngine::populateInputManagers(const ClientConfig& config) {
+    if (mIgnoreInputManager) {
+        return Status::SUCCESS;
+    }
+    proto::InputConfig inputDescriptor;
+    int selectedId;
+
+    if (config.getInputConfigId(&selectedId) != Status::SUCCESS) {
+        return Status::INVALID_ARGUMENT;
+    }
+
+    for (auto& inputIt : mGraphDescriptor.input_configs()) {
+        if (selectedId == inputIt.config_id()) {
+            inputDescriptor = inputIt;
+            std::shared_ptr<InputCallback> cb = std::make_shared<InputCallback>(
+                selectedId,
+                [this](int id) {
+                    std::string source = "InputManager:" + std::to_string(id);
+                    this->queueError(source, "", false);
+                },
+                [this](int streamId, int64_t timestamp, const InputFrame& frame) {
+                    return this->mGraph->SetInputStreamPixelData(streamId, timestamp, frame);
+                });
+            mInputManagers.emplace(selectedId,
+                                   mInputFactory.createInputManager(inputDescriptor, cb));
+            if (mInputManagers[selectedId] == nullptr) {
+                LOG(ERROR) << "unable to create input manager for stream " << selectedId;
+                // TODO: Add print
+                return Status::INTERNAL_ERROR;
+            }
+            return Status::SUCCESS;
+        }
+    }
+    return Status::INVALID_ARGUMENT;
+}
+
+/**
+ * Engine Command Queue and Error Queue handling
+ */
+void DefaultEngine::processCommands() {
+    std::unique_lock<std::mutex> lock(mEngineLock);
+    while (1) {
+        LOG(INFO) << "Engine::Waiting on commands ";
+        mWakeLooper.wait(lock, [this] {
+            if (this->mCommandQueue.empty() && !mCurrentPhaseError) {
+                return false;
+            } else {
+                return true;
+            }
+        });
+        if (mCurrentPhaseError) {
+            mErrorQueue.push(*mCurrentPhaseError);
+
+            processComponentError(mCurrentPhaseError->source);
+            mCurrentPhaseError = nullptr;
+            std::queue<EngineCommand> empty;
+            std::swap(mCommandQueue, empty);
+            continue;
+        }
+        EngineCommand ec = mCommandQueue.front();
+        mCommandQueue.pop();
+        switch (ec.cmdType) {
+            case EngineCommand::Type::BROADCAST_CONFIG:
+                LOG(INFO) << "Engine::Received broacast config request";
+                (void)broadcastClientConfig();
+                break;
+            case EngineCommand::Type::BROADCAST_START_RUN:
+                LOG(INFO) << "Engine::Received broacast run request";
+                (void)broadcastStartRun();
+                break;
+            case EngineCommand::Type::BROADCAST_INITIATE_STOP:
+                if (ec.source.find("ClientInterface") != std::string::npos) {
+                    mStopFromClient = true;
+                }
+                LOG(INFO) << "Engine::Received broacast stop with flush request";
+                broadcastStopWithFlush();
+                break;
+            case EngineCommand::Type::POLL_COMPLETE:
+                LOG(INFO) << "Engine::Received Poll stream managers for completion request";
+                int id = getStreamIdFromSource(ec.source);
+                bool all_done = true;
+                for (auto& it : mStreamManagers) {
+                    if (it.first == id) {
+                        continue;
+                    }
+                    if (it.second->getState() != StreamManager::State::STOPPED) {
+                        all_done = false;
+                    }
+                }
+                if (all_done) {
+                    broadcastStopComplete();
+                }
+                break;
+        }
+    }
+}
+
+void DefaultEngine::processComponentError(std::string source) {
+    if (mCurrentPhase == kRunPhase || mCurrentPhase == kStopPhase) {
+        (void)broadcastHalt();
+    }
+    if (source.find("ClientInterface") != std::string::npos) {
+        (void)broadcastReset();
+    }
+}
+
+void DefaultEngine::queueCommand(std::string source, EngineCommand::Type type) {
+    mCommandQueue.push(EngineCommand(source, type));
+    mWakeLooper.notify_all();
+}
+
+void DefaultEngine::queueError(std::string source, std::string msg, bool fatal) {
+    std::lock_guard<std::mutex> lock(mEngineLock);
+    // current phase already has an error report
+    if (!mCurrentPhaseError) {
+        mCurrentPhaseError = std::make_unique<ComponentError>(source, msg, mCurrentPhase, fatal);
+        mWakeLooper.notify_all();
+    }
+}
+
+/**
+ * InputCallback implementation
+ */
+InputCallback::InputCallback(
+    int id, const std::function<void(int)>&& cb,
+    const std::function<Status(int, int64_t timestamp, const InputFrame&)>&& packetCb)
+    : mErrorCallback(cb), mPacketHandler(packetCb), mInputId(id) {
+}
+
+Status InputCallback::dispatchInputFrame(int streamId, int64_t timestamp, const InputFrame& frame) {
+    return mPacketHandler(streamId, timestamp, frame);
+}
+
+void InputCallback::notifyInputError() {
+    mErrorCallback(mInputId);
+}
+
+/**
+ * StreamCallback implementation
+ */
+StreamCallback::StreamCallback(
+    const std::function<void()>&& eos, const std::function<void(std::string)>&& errorCb,
+    const std::function<Status(const std::shared_ptr<MemHandle>&)>&& packetHandler)
+    : mErrorHandler(errorCb), mEndOfStreamHandler(eos), mPacketHandler(packetHandler) {
+}
+
+void StreamCallback::notifyError(std::string msg) {
+    mErrorHandler(msg);
+}
+
+void StreamCallback::notifyEndOfStream() {
+    mEndOfStreamHandler();
+}
+
+Status StreamCallback::dispatchPacket(const std::shared_ptr<MemHandle>& packet) {
+    return mPacketHandler(packet);
+}
+
+}  // namespace engine
+}  // namespace runner
+}  // namespace computepipe
+}  // namespace automotive
+}  // namespace android
diff --git a/computepipe/runner/engine/DefaultEngine.h b/computepipe/runner/engine/DefaultEngine.h
new file mode 100644
index 0000000..2578915
--- /dev/null
+++ b/computepipe/runner/engine/DefaultEngine.h
@@ -0,0 +1,326 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef COMPUTEPIPE_RUNNER_ENGINE_DEFAULT_H
+#define COMPUTEPIPE_RUNNER_ENGINE_DEFAULT_H
+
+#include <condition_variable>
+#include <functional>
+#include <mutex>
+#include <queue>
+#include <thread>
+#include <vector>
+
+#include "ConfigBuilder.h"
+#include "InputManager.h"
+#include "Options.pb.h"
+#include "RunnerEngine.h"
+#include "StreamManager.h"
+
+namespace android {
+namespace automotive {
+namespace computepipe {
+namespace runner {
+namespace engine {
+
+class InputCallback;
+
+/**
+ * EngineCommand represents client requests or error events.
+ * Each command is queued, and processed by the engine thread.
+ */
+struct EngineCommand {
+  public:
+    enum Type {
+        BROADCAST_CONFIG = 0,
+        BROADCAST_START_RUN,
+        BROADCAST_INITIATE_STOP,
+        POLL_COMPLETE,
+    };
+    std::string source;
+    Type cmdType;
+    explicit EngineCommand(std::string s, Type t) : source(s), cmdType(t) {
+    }
+};
+
+/**
+ * Component Error represents the type of error reported by a component.
+ */
+struct ComponentError {
+  public:
+    bool isFatal;
+    std::string source;
+    std::string message;
+    std::string currentPhase;
+    explicit ComponentError(std::string s, std::string m, std::string p, bool fatal = false)
+        : isFatal(fatal), source(s), message(m), currentPhase(p) {
+    }
+};
+
+/**
+ * Default Engine implementation.
+ * Takes ownership of externally instantiated graph & client interface
+ * instances. Brings the runner online. Manages components.
+ * Responds to client events.
+ */
+class DefaultEngine : public RunnerEngine {
+  public:
+    static constexpr char kDisplayStreamId[] = "display_stream:";
+    static constexpr char kNoInputManager[] = "no_input_manager";
+    static constexpr char kResetPhase[] = "Reset";
+    static constexpr char kConfigPhase[] = "Config";
+    static constexpr char kRunPhase[] = "Running";
+    static constexpr char kStopPhase[] = "Stopping";
+    /**
+     * Methods from Runner Engine to override
+     */
+    Status setArgs(std::string engine_args) override;
+    void setClientInterface(std::unique_ptr<client_interface::ClientInterface>&& client) override;
+    void setPrebuiltGraph(std::unique_ptr<graph::PrebuiltGraph>&& graph) override;
+    Status activate() override;
+    /**
+     * Methods from ClientEngineInterface to override
+     */
+    Status processClientConfigUpdate(const proto::ConfigurationCommand& command) override;
+    Status processClientCommand(const proto::ControlCommand& command) override;
+    Status freePacket(int bufferId, int streamId) override;
+    /**
+     * Methods from PrebuiltEngineInterface to override
+     */
+    void DispatchPixelData(int streamId, int64_t timestamp, const InputFrame& frame) override;
+
+    void DispatchSerializedData(int streamId, int64_t timestamp, std::string&& output) override;
+
+    void DispatchGraphTerminationMessage(Status s, std::string&& msg) override;
+
+  private:
+    // TODO: b/147704051 Add thread analyzer annotations
+    /**
+     * BroadCast Client config to all components. If all components handle the
+     * notification correctly, then broadcast transition complete.
+     * Successful return from this function implies runner has transitioned to
+     * configuration done.
+     * @Lock held mEngineLock
+     */
+    Status broadcastClientConfig();
+    /**
+     * Abort an ongoing attempt to apply client configs.
+     * @Lock held mEngineLock
+     */
+    void abortClientConfig(const ClientConfig& config, bool resetGraph = false);
+    /**
+     * BroadCast start to all components. The order of entry into run phase
+     * notification delivery is downstream components to upstream components.
+     * If all components handle the entry notification correctly then broadcast
+     * transition complete notification again from down stream to upstream.
+     * Successful return from this function implies runner has transitioned to
+     * running.
+     * @Lock held mEngineLock
+     */
+    Status broadcastStartRun();
+    /**
+     * BroadCast abort of started run to given components. This gets called if during
+     * broadcastStartRun(), one of the components failed to set itself up for the run. In that case
+     * the components that had successfully acknowledged broacastStartRun(),
+     * need to be told to abort. We transition back to config phase at the end
+     * of this call.
+     * @Lock held mEngineLock
+     */
+    void broadcastAbortRun(const std::vector<int>& streamIds, const std::vector<int>& inputIds,
+                           bool graph = false);
+
+    /**
+     * Broadcast stop with flush to all components. The stop with flush phase
+     * entry notification is sent from in the order of upstream to downstream.
+     * A successful return can leave the runner in stopping phase.
+     * We transition to stop completely, once all inflight traffic has been drained at a later
+     * point, identified by stream managers.
+     * @Lock held mEngineLock
+     */
+    Status broadcastStopWithFlush();
+    /**
+     * Broadcast transtion to stop complete. This is a confirmation to all
+     * components that stop has finished. At the end of this we transition back
+     * to config phase.
+     * @Lock held mEngineLock
+     */
+    Status broadcastStopComplete();
+    /**
+     * Broadcast halt to all components. All inflight traffic is dropped.
+     * Successful return from this function implies all components have
+     * exited run phase and are back in config phase.
+     * @Lock held mEngineLock
+     */
+    void broadcastHalt();
+    /**
+     * Broadcast reset to all components. All components drop client
+     * specific configuration and transition to reset state. For RAII
+     * components, they are freed at this point. ALso resets the mConfigBuilder
+     * to its original state. Successful return puts the runner in reset phase.
+     * @Lock held mEngineLock
+     */
+    void broadcastReset();
+    /**
+     * Populate stream managers for a given client config. For each client
+     * selected output config, we generate stream managers. During reset phase
+     * we clear out any previously constructed stream managers. This should be
+     * invoked only in response to applyConfigs() issued by client.
+     * @Lock held mEngineLock
+     */
+    Status populateStreamManagers(const ClientConfig& config);
+    /**
+     * Populate input managers for a given client config. For each client
+     * selected output config, we generate input managers. During reset phase
+     * we clear out any previously constructed input managers. This should be
+     * invoked only in response to applyConfigs() issued by client.
+     * @Lock held mEngineLock
+     */
+    Status populateInputManagers(const ClientConfig& config);
+    /**
+     * Helper method to forward packet to client interface for transmission
+     */
+    Status forwardOutputDataToClient(int streamId, std::shared_ptr<MemHandle>& handle);
+    /**
+     * Helper to handle error notification from components, in the errorQueue.
+     * In case the source of the error is client interface, it will
+     * broadcastReset().
+     * This called in the mEngineThread when processing an entry from the
+     * errorQueue,
+     * @Lock acquires mEngineLock.
+     */
+    void processComponentError(std::string source);
+    /**
+     * Method run by the engine thread to process commands.
+     * Uses condition variable. Acquires lock mEngineLock to access
+     * command queues.
+     */
+    void processCommands();
+    /**
+     * Method run by external components to queue commands to the engine.
+     * Must be called with mEngineLock held. Wakes up the looper.
+     */
+    void queueCommand(std::string source, EngineCommand::Type type);
+    /**
+     * Method called by component reporting error.
+     * This will acquire mEngineLock and queue the error.
+     */
+    void queueError(std::string source, std::string msg, bool fatal);
+    /**
+     * client interface handle
+     */
+    std::unique_ptr<client_interface::ClientInterface> mClient = nullptr;
+    /**
+     * builder to build up client config incrementally.
+     */
+    ConfigBuilder mConfigBuilder;
+    /**
+     * Stream management members
+     */
+    std::map<int, std::unique_ptr<stream_manager::StreamManager>> mStreamManagers;
+    stream_manager::StreamManagerFactory mStreamFactory;
+    /**
+     * Input manager members
+     */
+    std::map<int, std::unique_ptr<input_manager::InputManager>> mInputManagers;
+    input_manager::InputManagerFactory mInputFactory;
+    /**
+     * stream to dump to display for debug purposes
+     */
+    int32_t mDisplayStream = ClientConfig::kInvalidId;
+    /**
+     * graph descriptor
+     */
+    proto::Options mGraphDescriptor;
+    std::unique_ptr<graph::PrebuiltGraph> mGraph;
+    /**
+     * stop signal source
+     */
+    bool mStopFromClient = true;
+    /**
+     * Phase management members
+     */
+    std::string mCurrentPhase = kResetPhase;
+    std::mutex mEngineLock;
+    /**
+     * Used to track the first error occurrence for a given phase.
+     */
+    std::unique_ptr<ComponentError> mCurrentPhaseError = nullptr;
+
+    /**
+     * Queue for client commands
+     */
+    std::queue<EngineCommand> mCommandQueue;
+    /**
+     * Queue for error notifications
+     */
+    std::queue<ComponentError> mErrorQueue;
+    /**
+     * Engine looper
+     */
+    std::unique_ptr<std::thread> mEngineThread;
+    /**
+     * Condition variable for looper
+     */
+    std::condition_variable mWakeLooper;
+    /**
+     * ignore input manager allocation
+     */
+    bool mIgnoreInputManager = false;
+};
+
+/**
+ * Handles callbacks from individual stream managers as specified in the
+ * StreamEngineInterface.
+ */
+class StreamCallback : public stream_manager::StreamEngineInterface {
+  public:
+    explicit StreamCallback(
+        const std::function<void()>&& eos, const std::function<void(std::string)>&& errorCb,
+        const std::function<Status(const std::shared_ptr<MemHandle>&)>&& packetHandler);
+    void notifyEndOfStream() override;
+    void notifyError(std::string msg) override;
+    Status dispatchPacket(const std::shared_ptr<MemHandle>& outData) override;
+    ~StreamCallback() = default;
+
+  private:
+    std::function<void(std::string)> mErrorHandler;
+    std::function<void()> mEndOfStreamHandler;
+    std::function<Status(const std::shared_ptr<MemHandle>&)> mPacketHandler;
+};
+
+/**
+ * Handles callbacks from input managers. Forwards frames to the graph.
+ * Only used if graph implementation is local
+ */
+class InputCallback : public input_manager::InputEngineInterface {
+  public:
+    explicit InputCallback(int id, const std::function<void(int)>&& cb,
+                           const std::function<Status(int, int64_t, const InputFrame&)>&& packetCb);
+    Status dispatchInputFrame(int streamId, int64_t timestamp, const InputFrame& frame) override;
+    void notifyInputError() override;
+    ~InputCallback() = default;
+
+  private:
+    std::function<void(int)> mErrorCallback;
+    std::function<Status(int, int64_t, const InputFrame&)> mPacketHandler;
+    int mInputId;
+};
+
+}  // namespace engine
+}  // namespace runner
+}  // namespace computepipe
+}  // namespace automotive
+}  // namespace android
+
+#endif
diff --git a/computepipe/runner/engine/Factory.cpp b/computepipe/runner/engine/Factory.cpp
new file mode 100644
index 0000000..67b3cd3
--- /dev/null
+++ b/computepipe/runner/engine/Factory.cpp
@@ -0,0 +1,46 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "DefaultEngine.h"
+#include "RunnerEngine.h"
+
+namespace android {
+namespace automotive {
+namespace computepipe {
+namespace runner {
+namespace engine {
+
+namespace {
+std::unique_ptr<DefaultEngine> createDefaultEngine(std::string engine_args) {
+    std::unique_ptr<DefaultEngine> engine = std::make_unique<DefaultEngine>();
+    if (engine->setArgs(engine_args) != Status::SUCCESS) {
+        return nullptr;
+    }
+    return engine;
+}
+}  // namespace
+
+std::unique_ptr<RunnerEngine> RunnerEngineFactory::createRunnerEngine(std::string engine,
+                                                                      std::string engine_args) {
+    if (engine == kDefault) {
+        return createDefaultEngine(engine_args);
+    }
+    return nullptr;
+}
+
+}  // namespace engine
+}  // namespace runner
+}  // namespace computepipe
+}  // namespace automotive
+}  // namespace android
diff --git a/computepipe/runner/engine/include/RunnerEngine.h b/computepipe/runner/engine/include/RunnerEngine.h
new file mode 100644
index 0000000..778099d
--- /dev/null
+++ b/computepipe/runner/engine/include/RunnerEngine.h
@@ -0,0 +1,71 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef COMPUTEPIPE_RUNNER_ENGINE_H
+#define COMPUTEPIPE_RUNNER_ENGINE_H
+
+#include <memory>
+#include <string>
+
+#include "ClientInterface.h"
+#include "PrebuiltGraph.h"
+#include "types/Status.h"
+
+namespace android {
+namespace automotive {
+namespace computepipe {
+namespace runner {
+namespace engine {
+
+/**
+ * Class that offers an interface into an engine. It derives from the client,
+ * prebuilt -> engine interfaces to enforce that any instantiation of an engine
+ * needs to provide an implementation for those interfaces.
+ */
+class RunnerEngine : public client_interface::ClientEngineInterface,
+                     public graph::PrebuiltEngineInterface {
+  public:
+    /**
+     * Any args that a given engine instance needs in order to configure itself.
+     */
+    virtual Status setArgs(std::string engine_args) = 0;
+    /**
+     * Set the client and the prebuilt graph instances
+     */
+    virtual void setClientInterface(std::unique_ptr<client_interface::ClientInterface>&& client) = 0;
+
+    virtual void setPrebuiltGraph(std::unique_ptr<graph::PrebuiltGraph>&& graph) = 0;
+    /**
+     * Activates the client interface and advertises to the rest of the world
+     * that the runner is online
+     */
+    virtual Status activate() = 0;
+};
+
+class RunnerEngineFactory {
+  public:
+    static constexpr char kDefault[] = "default_engine";
+    std::unique_ptr<RunnerEngine> createRunnerEngine(std::string engine, std::string engine_args);
+    RunnerEngineFactory(const RunnerEngineFactory&) = delete;
+    RunnerEngineFactory& operator=(const RunnerEngineFactory&) = delete;
+    RunnerEngineFactory() = default;
+};
+
+}  // namespace engine
+}  // namespace runner
+}  // namespace computepipe
+}  // namespace automotive
+}  // namespace android
+
+#endif
diff --git a/computepipe/runner/graph/Android.bp b/computepipe/runner/graph/Android.bp
new file mode 100644
index 0000000..589c00f
--- /dev/null
+++ b/computepipe/runner/graph/Android.bp
@@ -0,0 +1,48 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+cc_library {
+    name: "computepipe_prebuilt_graph",
+
+    cflags: [
+        "-Wall",
+        "-Werror",
+        "-Wextra",
+        "-Wno-unused-parameter",
+    ],
+
+    export_include_dirs: ["include"],
+    static_libs: [
+        "computepipe_runner_component",
+        "libcomputepipeprotos",
+    ],
+
+    header_libs: ["computepipe_runner_includes"],
+    include_dirs: [
+          "packages/services/Car/computepipe",
+          "packages/services/Car/computepipe/runner/graph",
+    ],
+
+    shared_libs: [
+        "libbase",
+        "libdl",
+        "liblog",
+        "libutils",
+        "libprotobuf-cpp-lite",
+    ],
+
+    srcs: [
+        "PrebuiltGraph.cpp",
+    ],
+}
diff --git a/computepipe/runner/graph/PrebuiltGraph.cpp b/computepipe/runner/graph/PrebuiltGraph.cpp
new file mode 100644
index 0000000..343e6ad
--- /dev/null
+++ b/computepipe/runner/graph/PrebuiltGraph.cpp
@@ -0,0 +1,380 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "PrebuiltGraph.h"
+
+#include <android-base/logging.h>
+#include <dlfcn.h>
+
+#include <functional>
+#include <iostream>
+#include <mutex>
+#include <shared_mutex>
+#include <string>
+#include <vector>
+
+#include "ClientConfig.pb.h"
+#include "InputFrame.h"
+#include "RunnerComponent.h"
+#include "prebuilt_interface.h"
+#include "types/Status.h"
+
+namespace android {
+namespace automotive {
+namespace computepipe {
+namespace graph {
+
+#define LOAD_FUNCTION(name)                                                        \
+    {                                                                              \
+        std::string func_name = std::string("PrebuiltComputepipeRunner_") + #name; \
+        mPrebuiltGraphInstance->mFn##name =                                        \
+            dlsym(mPrebuiltGraphInstance->mHandle, func_name.c_str());             \
+        if (mPrebuiltGraphInstance->mFn##name == nullptr) {                        \
+            initialized = false;                                                   \
+            LOG(ERROR) << std::string(dlerror()) << std::endl;                     \
+        }                                                                          \
+    }
+
+std::mutex PrebuiltGraph::mCreationMutex;
+PrebuiltGraph* PrebuiltGraph::mPrebuiltGraphInstance = nullptr;
+
+// Function to confirm that there would be no further changes to the graph configuration. This
+// needs to be called before starting the graph.
+Status PrebuiltGraph::handleConfigPhase(const runner::ClientConfig& e) {
+    if (mGraphState.load() == PrebuiltGraphState::UNINITIALIZED) {
+        return Status::ILLEGAL_STATE;
+    }
+
+    // handleConfigPhase is a blocking call, so abort call is pointless for this RunnerEvent.
+    if (e.isAborted()) {
+        return Status::INVALID_ARGUMENT;
+    } else if (e.isTransitionComplete()) {
+        return Status::SUCCESS;
+    }
+
+    std::string config = e.getSerializedClientConfig();
+    auto mappedFn =
+        (PrebuiltComputepipeRunner_ErrorCode(*)(const unsigned char*, size_t))mFnUpdateGraphConfig;
+    PrebuiltComputepipeRunner_ErrorCode errorCode =
+        mappedFn(reinterpret_cast<const unsigned char*>(config.c_str()), config.length());
+    if (errorCode != PrebuiltComputepipeRunner_ErrorCode::SUCCESS) {
+        return static_cast<Status>(static_cast<int>(errorCode));
+    }
+
+    // Set the pixel stream callback function. The same function will be called for all requested
+    // pixel output streams.
+    if (mEngineInterface) {
+        auto pixelCallbackFn = (PrebuiltComputepipeRunner_ErrorCode(*)(
+            void (*)(void* cookie, int, int64_t, const uint8_t* pixels, int width, int height,
+                     int step, int format)))mFnSetOutputPixelStreamCallback;
+        PrebuiltComputepipeRunner_ErrorCode errorCode =
+            pixelCallbackFn(PrebuiltGraph::OutputPixelStreamCallbackFunction);
+        if (errorCode != PrebuiltComputepipeRunner_ErrorCode::SUCCESS) {
+            return static_cast<Status>(static_cast<int>(errorCode));
+        }
+
+        // Set the serialized stream callback function. The same callback function will be invoked
+        // for all requested serialized output streams.
+        auto streamCallbackFn = (PrebuiltComputepipeRunner_ErrorCode(*)(void (*)(
+            void* cookie, int, int64_t, const unsigned char*, size_t)))mFnSetOutputStreamCallback;
+        errorCode = streamCallbackFn(PrebuiltGraph::OutputStreamCallbackFunction);
+        if (errorCode != PrebuiltComputepipeRunner_ErrorCode::SUCCESS) {
+            return static_cast<Status>(static_cast<int>(errorCode));
+        }
+
+        // Set the callback function for when the graph terminates.
+        auto terminationCallback = (PrebuiltComputepipeRunner_ErrorCode(*)(
+            void (*)(void* cookie, const unsigned char*, size_t)))mFnSetGraphTerminationCallback;
+        errorCode = terminationCallback(PrebuiltGraph::GraphTerminationCallbackFunction);
+        if (errorCode != PrebuiltComputepipeRunner_ErrorCode::SUCCESS) {
+            return static_cast<Status>(static_cast<int>(errorCode));
+        }
+    }
+
+    return Status::SUCCESS;
+}
+
+// Starts the graph.
+Status PrebuiltGraph::handleExecutionPhase(const runner::RunnerEvent& e) {
+    if (mGraphState.load() != PrebuiltGraphState::STOPPED) {
+        return Status::ILLEGAL_STATE;
+    }
+
+    if (e.isAborted()) {
+        // Starting the graph is a blocking call and cannot be aborted in between.
+        return Status::INVALID_ARGUMENT;
+    } else if (e.isTransitionComplete()) {
+        return Status::SUCCESS;
+    }
+
+    auto mappedFn = (PrebuiltComputepipeRunner_ErrorCode(*)(void*, bool))mFnStartGraphExecution;
+    PrebuiltComputepipeRunner_ErrorCode errorCode =
+        mappedFn(reinterpret_cast<void*>(this), /* debuggingEnabled =*/false);
+    if (errorCode == PrebuiltComputepipeRunner_ErrorCode::SUCCESS) {
+        mGraphState.store(PrebuiltGraphState::RUNNING);
+    }
+    return static_cast<Status>(static_cast<int>(errorCode));
+}
+
+// Stops the graph while letting the graph flush output packets in flight.
+Status PrebuiltGraph::handleStopWithFlushPhase(const runner::RunnerEvent& e) {
+    if (mGraphState.load() != PrebuiltGraphState::RUNNING) {
+        return Status::ILLEGAL_STATE;
+    }
+
+    if (e.isAborted()) {
+        return Status::INVALID_ARGUMENT;
+    } else if (e.isTransitionComplete()) {
+        return Status::SUCCESS;
+    }
+
+    return StopGraphExecution(/* flushOutputFrames = */ true);
+}
+
+// Stops the graph and cancels all the output packets.
+Status PrebuiltGraph::handleStopImmediatePhase(const runner::RunnerEvent& e) {
+    if (mGraphState.load() != PrebuiltGraphState::RUNNING) {
+        return Status::ILLEGAL_STATE;
+    }
+
+    if (e.isAborted()) {
+        return Status::INVALID_ARGUMENT;
+    } else if (e.isTransitionComplete()) {
+        return Status::SUCCESS;
+    }
+
+    return StopGraphExecution(/* flushOutputFrames = */ false);
+}
+
+Status PrebuiltGraph::handleResetPhase(const runner::RunnerEvent& e) {
+    if (mGraphState.load() != PrebuiltGraphState::STOPPED) {
+        return Status::ILLEGAL_STATE;
+    }
+
+    if (e.isAborted()) {
+        return Status::INVALID_ARGUMENT;
+    } else if (e.isTransitionComplete()) {
+        return Status::SUCCESS;
+    }
+
+    auto mappedFn = (PrebuiltComputepipeRunner_ErrorCode(*)())mFnResetGraph;
+    mappedFn();
+    return Status::SUCCESS;
+}
+
+PrebuiltGraph* PrebuiltGraph::GetPrebuiltGraphFromLibrary(
+    const std::string& prebuilt_library, std::shared_ptr<PrebuiltEngineInterface> engineInterface) {
+    std::unique_lock<std::mutex> lock(PrebuiltGraph::mCreationMutex);
+    if (mPrebuiltGraphInstance != nullptr) {
+        mPrebuiltGraphInstance = new PrebuiltGraph();
+    }
+    if (mPrebuiltGraphInstance->mGraphState.load() != PrebuiltGraphState::UNINITIALIZED) {
+        return mPrebuiltGraphInstance;
+    }
+    mPrebuiltGraphInstance->mHandle = dlopen(prebuilt_library.c_str(), RTLD_NOW);
+
+    if (mPrebuiltGraphInstance->mHandle) {
+        bool initialized = true;
+
+        // Load config and version number first.
+        const unsigned char* (*getVersionFn)() = (const unsigned char* (*)())dlsym(
+            mPrebuiltGraphInstance->mHandle, "PrebuiltComputepipeRunner_GetVersion");
+        if (getVersionFn != nullptr) {
+            mPrebuiltGraphInstance->mGraphVersion =
+                std::string(reinterpret_cast<const char*>(getVersionFn()));
+        } else {
+            LOG(ERROR) << std::string(dlerror());
+            initialized = false;
+        }
+
+        void (*getSupportedGraphConfigsFn)(const void**, size_t*) = (void (*)(
+            const void**, size_t*))dlsym(mPrebuiltGraphInstance->mHandle,
+                                         "PrebuiltComputepipeRunner_GetSupportedGraphConfigs");
+        if (getSupportedGraphConfigsFn != nullptr) {
+            size_t graphConfigSize;
+            const void* graphConfig;
+
+            getSupportedGraphConfigsFn(&graphConfig, &graphConfigSize);
+
+            if (graphConfigSize > 0) {
+                initialized &= mPrebuiltGraphInstance->mGraphConfig.ParseFromString(
+                    std::string(reinterpret_cast<const char*>(graphConfig), graphConfigSize));
+            }
+        } else {
+            LOG(ERROR) << std::string(dlerror());
+            initialized = false;
+        }
+
+        // Null callback interface is not acceptable.
+        if (initialized && engineInterface == nullptr) {
+            initialized = false;
+        }
+
+        LOAD_FUNCTION(GetErrorCode);
+        LOAD_FUNCTION(GetErrorMessage);
+        LOAD_FUNCTION(ResetGraph);
+        LOAD_FUNCTION(UpdateGraphConfig);
+        LOAD_FUNCTION(SetInputStreamData);
+        LOAD_FUNCTION(SetInputStreamPixelData);
+        LOAD_FUNCTION(SetOutputStreamCallback);
+        LOAD_FUNCTION(SetOutputPixelStreamCallback);
+        LOAD_FUNCTION(SetGraphTerminationCallback);
+        LOAD_FUNCTION(StartGraphExecution);
+        LOAD_FUNCTION(StopGraphExecution);
+        LOAD_FUNCTION(GetDebugInfo);
+
+        // This is the only way to create this object and there is already a
+        // lock around object creation, so no need to hold the graphState lock
+        // here.
+        if (initialized) {
+            mPrebuiltGraphInstance->mGraphState.store(PrebuiltGraphState::STOPPED);
+            mPrebuiltGraphInstance->mEngineInterface = engineInterface;
+        }
+    }
+
+    return mPrebuiltGraphInstance;
+}
+
+PrebuiltGraph::~PrebuiltGraph() {
+    if (mHandle) {
+        dlclose(mHandle);
+    }
+}
+
+Status PrebuiltGraph::GetStatus() const {
+    if (mGraphState.load() == PrebuiltGraphState::UNINITIALIZED) {
+        return Status::ILLEGAL_STATE;
+    }
+
+    auto mappedFn = (PrebuiltComputepipeRunner_ErrorCode(*)())mFnGetErrorCode;
+    PrebuiltComputepipeRunner_ErrorCode errorCode = mappedFn();
+    return static_cast<Status>(static_cast<int>(errorCode));
+}
+
+std::string PrebuiltGraph::GetErrorMessage() const {
+    if (mGraphState.load() == PrebuiltGraphState::UNINITIALIZED) {
+        return "Graph has not been initialized";
+    }
+    auto mappedFn =
+        (PrebuiltComputepipeRunner_ErrorCode(*)(unsigned char*, size_t, size_t*))mFnGetErrorMessage;
+    size_t errorMessageSize = 0;
+
+    PrebuiltComputepipeRunner_ErrorCode errorCode = mappedFn(nullptr, 0, &errorMessageSize);
+    std::vector<unsigned char> errorMessage(errorMessageSize);
+
+    errorCode = mappedFn(&errorMessage[0], errorMessage.size(), &errorMessageSize);
+    if (errorCode != PrebuiltComputepipeRunner_ErrorCode::SUCCESS) {
+        return "Unable to get error message from the graph.";
+    }
+
+    return std::string(reinterpret_cast<char*>(&errorMessage[0]),
+                       reinterpret_cast<char*>(&errorMessage[0]) + errorMessage.size());
+}
+
+Status PrebuiltGraph::SetInputStreamData(int streamIndex, int64_t timestamp,
+                                         const std::string& streamData) {
+    if (mGraphState.load() == PrebuiltGraphState::UNINITIALIZED) {
+        return Status::ILLEGAL_STATE;
+    }
+    auto mappedFn = (PrebuiltComputepipeRunner_ErrorCode(*)(int, int64_t, const unsigned char*,
+                                                            size_t))mFnSetInputStreamData;
+    PrebuiltComputepipeRunner_ErrorCode errorCode =
+        mappedFn(streamIndex, timestamp, reinterpret_cast<const unsigned char*>(streamData.c_str()),
+                 streamData.length());
+    return static_cast<Status>(static_cast<int>(errorCode));
+}
+
+Status PrebuiltGraph::SetInputStreamPixelData(int streamIndex, int64_t timestamp,
+                                              const runner::InputFrame& inputFrame) {
+    if (mGraphState.load() == PrebuiltGraphState::UNINITIALIZED) {
+        return Status::ILLEGAL_STATE;
+    }
+
+    auto mappedFn = (PrebuiltComputepipeRunner_ErrorCode(*)(
+        int, int64_t, const uint8_t*, int, int, int,
+        PrebuiltComputepipeRunner_PixelDataFormat))mFnSetInputStreamPixelData;
+    PrebuiltComputepipeRunner_ErrorCode errorCode =
+        mappedFn(streamIndex, timestamp, inputFrame.getFramePtr(), inputFrame.getFrameInfo().width,
+                 inputFrame.getFrameInfo().height, inputFrame.getFrameInfo().stride,
+                 static_cast<PrebuiltComputepipeRunner_PixelDataFormat>(
+                     static_cast<int>(inputFrame.getFrameInfo().format)));
+    return static_cast<Status>(static_cast<int>(errorCode));
+}
+
+Status PrebuiltGraph::StopGraphExecution(bool flushOutputFrames) {
+    auto mappedFn = (PrebuiltComputepipeRunner_ErrorCode(*)(bool))mFnStopGraphExecution;
+    PrebuiltComputepipeRunner_ErrorCode errorCode = mappedFn(flushOutputFrames);
+    if (errorCode == PrebuiltComputepipeRunner_ErrorCode::SUCCESS) {
+        mGraphState.store(flushOutputFrames ? PrebuiltGraphState::FLUSHING
+                                            : PrebuiltGraphState::STOPPED);
+    }
+    return static_cast<Status>(static_cast<int>(errorCode));
+}
+
+std::string PrebuiltGraph::GetDebugInfo() {
+    if (mGraphState.load() == PrebuiltGraphState::UNINITIALIZED) {
+        return "";
+    }
+    auto mappedFn =
+        (PrebuiltComputepipeRunner_ErrorCode(*)(unsigned char*, size_t, size_t*))mFnGetDebugInfo;
+
+    size_t debugInfoSize = 0;
+    PrebuiltComputepipeRunner_ErrorCode errorCode = mappedFn(nullptr, 0, &debugInfoSize);
+    std::vector<unsigned char> debugInfo(debugInfoSize);
+
+    errorCode = mappedFn(&debugInfo[0], debugInfo.size(), &debugInfoSize);
+    if (errorCode != PrebuiltComputepipeRunner_ErrorCode::SUCCESS) {
+        return "";
+    }
+
+    return std::string(reinterpret_cast<char*>(&debugInfo[0]),
+                       reinterpret_cast<char*>(&debugInfo[0]) + debugInfo.size());
+}
+
+void PrebuiltGraph::OutputStreamCallbackFunction(void* cookie, int streamIndex, int64_t timestamp,
+                                                 const unsigned char* data, size_t data_size) {
+    PrebuiltGraph* graph = reinterpret_cast<PrebuiltGraph*>(cookie);
+    CHECK(graph);
+    graph->mEngineInterface->DispatchSerializedData(streamIndex, timestamp,
+                                                    std::string(data, data + data_size));
+}
+
+void PrebuiltGraph::OutputPixelStreamCallbackFunction(void* cookie, int streamIndex,
+                                                      int64_t timestamp, const uint8_t* pixels,
+                                                      int width, int height, int step, int format) {
+    PrebuiltGraph* graph = reinterpret_cast<PrebuiltGraph*>(cookie);
+    CHECK(graph);
+    runner::InputFrame frame(height, width, static_cast<PixelFormat>(format), step, pixels);
+
+    graph->mEngineInterface->DispatchPixelData(streamIndex, timestamp, frame);
+}
+
+void PrebuiltGraph::GraphTerminationCallbackFunction(void* cookie,
+                                                     const unsigned char* termination_message,
+                                                     size_t termination_message_size) {
+    PrebuiltGraph* graph = reinterpret_cast<PrebuiltGraph*>(cookie);
+    CHECK(graph);
+    std::string errorMessage = "";
+    if (termination_message != nullptr && termination_message_size > 0) {
+        std::string(termination_message, termination_message + termination_message_size);
+    }
+    graph->mGraphState.store(PrebuiltGraphState::STOPPED);
+    graph->mEngineInterface->DispatchGraphTerminationMessage(graph->GetStatus(),
+                                                             std::move(errorMessage));
+}
+
+}  // namespace graph
+}  // namespace computepipe
+}  // namespace automotive
+}  // namespace android
diff --git a/computepipe/runner/graph/include/PrebuiltEngineInterface.h b/computepipe/runner/graph/include/PrebuiltEngineInterface.h
new file mode 100644
index 0000000..4339bb1
--- /dev/null
+++ b/computepipe/runner/graph/include/PrebuiltEngineInterface.h
@@ -0,0 +1,44 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+#ifndef COMPUTEPIPE_RUNNER_GRAPH_INCLUDE_PREBUILTENGINEINTERFACE_H_
+#define COMPUTEPIPE_RUNNER_GRAPH_INCLUDE_PREBUILTENGINEINTERFACE_H_
+
+#include <functional>
+
+#include "InputFrame.h"
+#include "types/Status.h"
+
+namespace android {
+namespace automotive {
+namespace computepipe {
+namespace graph {
+
+class PrebuiltEngineInterface {
+  public:
+    virtual ~PrebuiltEngineInterface() = default;
+
+    virtual void DispatchPixelData(int streamId, int64_t timestamp,
+                                   const runner::InputFrame& frame) = 0;
+
+    virtual void DispatchSerializedData(int streamId, int64_t timestamp, std::string&&) = 0;
+
+    virtual void DispatchGraphTerminationMessage(Status, std::string&&) = 0;
+};
+
+}  // namespace graph
+}  // namespace computepipe
+}  // namespace automotive
+}  // namespace android
+
+#endif  // COMPUTEPIPE_RUNNER_GRAPH_INCLUDE_PREBUILTENGINEINTERFACE_H_
diff --git a/computepipe/runner/graph/include/PrebuiltGraph.h b/computepipe/runner/graph/include/PrebuiltGraph.h
new file mode 100644
index 0000000..b777f14
--- /dev/null
+++ b/computepipe/runner/graph/include/PrebuiltGraph.h
@@ -0,0 +1,151 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef COMPUTEPIPE_RUNNER_GRAPH_INCLUDE_PREBUILTGRAPH_H_
+#define COMPUTEPIPE_RUNNER_GRAPH_INCLUDE_PREBUILTGRAPH_H_
+
+#include <functional>
+#include <shared_mutex>
+#include <string>
+
+#include "ClientConfig.pb.h"
+#include "InputFrame.h"
+#include "Options.pb.h"
+#include "PrebuiltEngineInterface.h"
+#include "RunnerComponent.h"
+#include "types/Status.h"
+
+namespace android {
+namespace automotive {
+namespace computepipe {
+namespace graph {
+
+enum PrebuiltGraphState {
+    RUNNING = 0,
+    UNINITIALIZED,
+    FLUSHING,
+    STOPPED,
+};
+
+// PrebuiltGraph is a singleton class. This is because the underlying functions that it implements
+// are C functions that carry around state.
+class PrebuiltGraph : public runner::RunnerComponentInterface {
+  private:
+    // Private constructor
+    PrebuiltGraph() {
+    }
+
+  public:
+    ~PrebuiltGraph();
+
+    // No copy or move constructors or operators are available.
+    PrebuiltGraph(const PrebuiltGraph&) = delete;
+    PrebuiltGraph& operator=(const PrebuiltGraph&) = delete;
+
+    // Override RunnerComponent interface functions for applying configs,
+    // starting the graph and stopping the graph.
+    Status handleConfigPhase(const runner::ClientConfig& e) override;
+    Status handleExecutionPhase(const runner::RunnerEvent& e) override;
+    Status handleStopWithFlushPhase(const runner::RunnerEvent& e) override;
+    Status handleStopImmediatePhase(const runner::RunnerEvent& e) override;
+    Status handleResetPhase(const runner::RunnerEvent& e) override;
+
+    static PrebuiltGraph* GetPrebuiltGraphFromLibrary(
+        const std::string& prebuiltLib, std::shared_ptr<PrebuiltEngineInterface> engineInterface);
+
+    PrebuiltGraphState GetGraphState() const {
+        return mGraphState;
+    }
+
+    Status GetStatus() const;
+
+    std::string GetErrorMessage() const;
+
+    // Gets the supported graph config options.
+    const proto::Options& GetSupportedGraphConfigs() const {
+        return mGraphConfig;
+    }
+
+    // Sets input stream data. The string is expected to be a serialized proto
+    // the definition of which is known to the graph.
+    Status SetInputStreamData(int streamIndex, int64_t timestamp, const std::string& streamData);
+
+    // Sets pixel data to the specified input stream index.
+    Status SetInputStreamPixelData(int streamIndex, int64_t timestamp,
+                                   const runner::InputFrame& inputFrame);
+
+    // Collects debugging and profiling information for the graph. The graph
+    // needs to be started with debugging enabled in order to get valid info.
+    std::string GetDebugInfo();
+
+  private:
+    // Starts the graph execution.
+    Status StartGraphExecution(bool debuggingEnabled);
+
+    // Stops the graph execution.
+    Status StopGraphExecution(bool flushOutputFrames);
+
+    // Callback functions. The class has a C++ function callback interface while it deals with pure
+    // C functions underneath that do not have object context. We need to have these static
+    // functions that need to be passed to the C interface.
+    static void OutputPixelStreamCallbackFunction(void* cookie, int streamIndex, int64_t timestamp,
+                                                  const uint8_t* pixels, int width, int height,
+                                                  int step, int format);
+    static void OutputStreamCallbackFunction(void* cookie, int streamIndex, int64_t timestamp,
+                                             const unsigned char* data, size_t dataSize);
+    static void GraphTerminationCallbackFunction(void* cookie,
+                                                 const unsigned char* terminationMessage,
+                                                 size_t terminationMessageSize);
+
+    // Cached callback interface that is passed in from the runner.
+    std::shared_ptr<PrebuiltEngineInterface> mEngineInterface;
+
+    static std::mutex mCreationMutex;
+    static PrebuiltGraph* mPrebuiltGraphInstance;
+
+    // Even though mutexes are generally preferred over atomics, the only varialble in this class
+    // that changes after initialization is graph state and that is the only vairable that needs
+    // to be guarded. The prebuilt is internally assumed to be thread safe, so that concurrent
+    // calls into the library will automatically be handled in a thread safe manner by the it.
+    std::atomic<PrebuiltGraphState> mGraphState = PrebuiltGraphState::UNINITIALIZED;
+
+    // Dynamic library handle
+    void* mHandle;
+
+    // Repeated function calls need not be made to get the graph version and the config is this is
+    // constant through the operation of the graph. These values are just cached as strings.
+    std::string mGraphVersion;
+    proto::Options mGraphConfig;
+
+    // Cached functions from the dynamic library.
+    void* mFnGetErrorCode;
+    void* mFnGetErrorMessage;
+    void* mFnUpdateGraphConfig;
+    void* mFnResetGraph;
+    void* mFnSetInputStreamData;
+    void* mFnSetInputStreamPixelData;
+    void* mFnSetOutputStreamCallback;
+    void* mFnSetOutputPixelStreamCallback;
+    void* mFnSetGraphTerminationCallback;
+    void* mFnStartGraphExecution;
+    void* mFnStopGraphExecution;
+    void* mFnGetDebugInfo;
+};
+
+}  // namespace graph
+}  // namespace computepipe
+}  // namespace automotive
+}  // namespace android
+
+#endif  // COMPUTEPIPE_RUNNER_GRAPH_INCLUDE_PREBUILTGRAPH_H_
diff --git a/computepipe/runner/include/EventGenerator.h b/computepipe/runner/include/EventGenerator.h
new file mode 100644
index 0000000..3912e99
--- /dev/null
+++ b/computepipe/runner/include/EventGenerator.h
@@ -0,0 +1,63 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef COMPUTEPIPE_RUNNER_EVENT_GENERATOR_H
+#define COMPUTEPIPE_RUNNER_EVENT_GENERATOR_H
+
+#include "RunnerComponent.h"
+
+namespace android {
+namespace automotive {
+namespace computepipe {
+namespace runner {
+namespace generator {
+
+/**
+ * Generate default events for different phases
+ */
+class DefaultEvent : public RunnerEvent {
+  public:
+    enum Phase {
+        RESET = 0,
+        RUN,
+        STOP_WITH_FLUSH,
+        STOP_IMMEDIATE,
+    };
+    /**
+     * Override runner event methods
+     */
+    bool isPhaseEntry() const override;
+    bool isAborted() const override;
+    bool isTransitionComplete() const override;
+    Status dispatchToComponent(const std::shared_ptr<RunnerComponentInterface>& iface) override;
+
+    /**
+     * generator methods
+     */
+    static DefaultEvent generateEntryEvent(Phase p);
+    static DefaultEvent generateAbortEvent(Phase p);
+    static DefaultEvent generateTransitionCompleteEvent(Phase p);
+
+  private:
+    explicit DefaultEvent(int type, Phase p) : mType(type), mPhase(p){};
+    int mType;
+    Phase mPhase;
+};
+
+}  // namespace generator
+}  // namespace runner
+}  // namespace computepipe
+}  // namespace automotive
+}  // namespace android
+#endif
diff --git a/computepipe/runner/include/InputFrame.h b/computepipe/runner/include/InputFrame.h
new file mode 100644
index 0000000..d71b110
--- /dev/null
+++ b/computepipe/runner/include/InputFrame.h
@@ -0,0 +1,86 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef COMPUTEPIPE_RUNNER_INPUT_FRAME
+#define COMPUTEPIPE_RUNNER_INPUT_FRAME
+
+#include <cstdint>
+#include <functional>
+
+#include "types/Status.h"
+namespace android {
+namespace automotive {
+namespace computepipe {
+namespace runner {
+
+typedef std::function<void(uint8_t[])> FrameDeleter;
+/**
+ * Information about the input frame
+ */
+struct FrameInfo {
+    uint32_t height;  // In pixels
+    uint32_t width;   // In pixels
+    PixelFormat format;
+    uint32_t stride;  // In bytes
+    int cameraId;
+};
+
+/**
+ * Wrapper around the pixel data of the input frame
+ */
+struct InputFrame {
+  public:
+    /**
+     * Take info about frame data. InputFrame does not take ownership of the data.
+     */
+    explicit InputFrame(uint32_t height, uint32_t width, PixelFormat format, uint32_t stride,
+                        const uint8_t* ptr) {
+        mInfo.height = height;
+        mInfo.width = width;
+        mInfo.format = format;
+        mInfo.stride = stride;
+        mDataPtr = ptr;
+    }
+
+    /**
+     * This is an unsafe method, that a consumer should use to copy the
+     * underlying frame data
+     */
+    const uint8_t* getFramePtr() const {
+        return mDataPtr;
+    }
+    FrameInfo getFrameInfo() const {
+        return mInfo;
+    }
+    /**
+     * Delete evil constructors
+     */
+    InputFrame() = delete;
+    InputFrame(const InputFrame&) = delete;
+    InputFrame& operator=(const InputFrame& f) = delete;
+    InputFrame(InputFrame&& f) = delete;
+    InputFrame& operator=(InputFrame&& f) = delete;
+
+  private:
+    FrameInfo mInfo;
+    FrameDeleter mDeleter;
+    const uint8_t* mDataPtr;
+};
+
+}  // namespace runner
+}  // namespace computepipe
+}  // namespace automotive
+}  // namespace android
+
+#endif
diff --git a/computepipe/runner/include/MemHandle.h b/computepipe/runner/include/MemHandle.h
new file mode 100644
index 0000000..b687db1
--- /dev/null
+++ b/computepipe/runner/include/MemHandle.h
@@ -0,0 +1,50 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef COMPUTEPIPE_RUNNER_STREAM_MANAGER_MEMHANDLE_H
+#define COMPUTEPIPE_RUNNER_STREAM_MANAGER_MEMHANDLE_H
+
+#include <OutputConfig.pb.h>
+#include <vndk/hardware_buffer.h>
+
+namespace android {
+namespace automotive {
+namespace computepipe {
+
+class MemHandle {
+  public:
+    /* Retrieve stream Id */
+    virtual int getStreamId() const = 0;
+    /* Retrieves the buffer id */
+    virtual int getBufferId() const = 0;
+    /* Retrieve packet type */
+    virtual proto::PacketType getType() const = 0;
+    /* Retrieve packet time stamp */
+    virtual uint64_t getTimeStamp() const = 0;
+    /* Get size */
+    virtual uint32_t getSize() const = 0;
+    /* Get data, raw pointer. Only implemented for copy semantics */
+    virtual const char* getData() const = 0;
+    /* Get native handle. data with zero copy semantics */
+    virtual AHardwareBuffer* getHardwareBuffer() const = 0;
+
+    virtual ~MemHandle() {
+    }
+};
+
+}  // namespace computepipe
+}  // namespace automotive
+}  // namespace android
+
+#endif  // COMPUTEPIPE_RUNNER_STREAM_MANAGER_MEMHANDLE_H
diff --git a/computepipe/runner/include/RunnerComponent.h b/computepipe/runner/include/RunnerComponent.h
new file mode 100644
index 0000000..38b29d6
--- /dev/null
+++ b/computepipe/runner/include/RunnerComponent.h
@@ -0,0 +1,170 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef COMPUTEPIPE_RUNNER_COMPONENT_H
+#define COMPUTEPIPE_RUNNER_COMPONENT_H
+#include <map>
+#include <memory>
+#include <string>
+
+#include "types/Status.h"
+
+namespace android {
+namespace automotive {
+namespace computepipe {
+namespace runner {
+
+class RunnerComponentInterface;
+
+/**
+ * Represents the state of the config phase a particular client config is in
+ */
+enum PhaseState {
+    ENTRY = 0,
+    TRANSITION_COMPLETE,
+    ABORTED,
+};
+
+/**
+ * RunnerEvent represents an event corresponding to a runner phase
+ * Along with start, abort or transition complete query methods.
+ */
+class RunnerEvent {
+  public:
+    /* Is this a notification to enter the phase */
+    virtual bool isPhaseEntry() const;
+    /* Is this a notification that all components have transitioned to the phase */
+    virtual bool isTransitionComplete() const;
+    /* Is this a notification to abort the transition to the started phase */
+    virtual bool isAborted() const;
+    /* Dispatch event to component */
+    virtual Status dispatchToComponent(const std::shared_ptr<RunnerComponentInterface>& iface) = 0;
+    /* Destructor */
+    virtual ~RunnerEvent() = default;
+};
+
+/**
+ * Configuration that gets emitted once client has completely specified config
+ * options
+ */
+class ClientConfig : public RunnerEvent {
+  public:
+    static const int kInvalidId = -1;
+
+    /**
+     * Override relevant methods from RunnerEvent
+     */
+    bool isPhaseEntry() const override {
+        return mState == ENTRY;
+    }
+    bool isTransitionComplete() const override {
+        return mState == TRANSITION_COMPLETE;
+    }
+    bool isAborted() const override {
+        return mState == ABORTED;
+    }
+
+    Status dispatchToComponent(const std::shared_ptr<RunnerComponentInterface>& iface) override;
+    /**
+     * Accessor methods
+     */
+    Status getInputConfigId(int* outId) const;
+    Status getOutputConfigId(int* outId) const;
+    Status getOffloadId(int* outId) const;
+    Status getTerminationId(int* outId) const;
+    Status getOptionalConfigs(std::string& outOptional) const;
+    Status getOutputStreamConfigs(std::map<int, int>& outputConfig) const;
+    std::string getSerializedClientConfig() const;
+    /**
+     * Constructors
+     */
+    ClientConfig& operator=(ClientConfig&& r) {
+        mInputConfigId = r.mInputConfigId;
+        mTerminationId = r.mTerminationId;
+        mOffloadId = r.mOffloadId;
+        mOptionalConfigs = std::move(r.mOptionalConfigs);
+        mOutputConfigs = std::move(r.mOutputConfigs);
+        return *this;
+    }
+    ClientConfig(ClientConfig&& c) {
+        *this = std::move(c);
+    }
+    ClientConfig(int inputConfigId, int offload, int termination, std::map<int, int>& outputConfigs,
+                 std::string opt = "")
+        : mInputConfigId(inputConfigId),
+          mOutputConfigs(outputConfigs),
+          mTerminationId(termination),
+          mOffloadId(offload),
+          mOptionalConfigs(opt) {
+    }
+
+    void setPhaseState(PhaseState state) {
+        mState = state;
+    }
+
+  private:
+    /**
+     * input streamd id from the graph descriptor options
+     */
+    int mInputConfigId = kInvalidId;
+    /**
+     * Options for different output streams
+     */
+    std::map<int, int> mOutputConfigs;
+    /**
+     * Termination Option
+     */
+    int mTerminationId = kInvalidId;
+    /**
+     * offload option
+     */
+    int mOffloadId = kInvalidId;
+    /**
+     * serialized optional config
+     */
+    std::string mOptionalConfigs = "";
+    /**
+     * The state of the client config corresponding
+     * to entry, transition complete or aborted
+     */
+    PhaseState mState = ENTRY;
+};
+
+/**
+ * A component of the Runner Engine implements this interface to receive
+ * RunnerEvents.
+ * A SUCCESS return value indicates the component has handled the particular
+ * event. A failure return value will result in a subsequent abort call
+ * that should be ignored by the component that reported failure.
+ */
+class RunnerComponentInterface {
+  public:
+    /* handle a ConfigPhase related event notification from Runner Engine */
+    virtual Status handleConfigPhase(const ClientConfig& e);
+    /* handle execution phase notification from Runner Engine */
+    virtual Status handleExecutionPhase(const RunnerEvent& e);
+    /* handle a stop with flushing semantics phase notification from the engine */
+    virtual Status handleStopWithFlushPhase(const RunnerEvent& e);
+    /* handle an immediate stop phase notification from the engine */
+    virtual Status handleStopImmediatePhase(const RunnerEvent& e);
+    /* handle an engine notification to return to reset state */
+    virtual Status handleResetPhase(const RunnerEvent& e);
+    virtual ~RunnerComponentInterface() = default;
+};
+
+}  // namespace runner
+}  // namespace computepipe
+}  // namespace automotive
+}  // namespace android
+#endif
diff --git a/computepipe/runner/include/prebuilt_interface.h b/computepipe/runner/include/prebuilt_interface.h
new file mode 100644
index 0000000..3985c53
--- /dev/null
+++ b/computepipe/runner/include/prebuilt_interface.h
@@ -0,0 +1,126 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef COMPUTEPIPE_RUNNER_INCLUDE_PREBUILT_INTERFACE_H_
+#define COMPUTEPIPE_RUNNER_INCLUDE_PREBUILT_INTERFACE_H_
+
+#include <cstddef>
+#include <cstdint>
+
+#define COMPUTEPIPE_RUNNER(a) PrebuiltComputepipeRunner_##a
+
+extern "C" {
+
+// Enum value to report the error code for function calls.
+enum PrebuiltComputepipeRunner_ErrorCode {
+    SUCCESS = 0,
+    INTERNAL_ERROR,
+    INVALID_ARGUMENT,
+    ILLEGAL_STATE,
+    NO_MEMORY,
+    FATAL_ERROR,
+    ERROR_CODE_MAX,
+};
+
+enum PrebuiltComputepipeRunner_PixelDataFormat {
+    RGB = 0,
+    RGBA = 1,
+    GRAY = 2,
+    PIXEL_DATA_FORMAT_MAX = 3,
+};
+
+// Gets the version of the library. The runner should check if the version of
+// the prebuilt matches the version of android runner for which it was built
+// and fail out if needed.
+const unsigned char* COMPUTEPIPE_RUNNER(GetVersion)();
+
+// Gets the error code. This API is necessary because the graph execution is
+// asynchronous and even if the function calls to execute the graph succeed, it
+// could fail at a later point in the execution of the graph.
+PrebuiltComputepipeRunner_ErrorCode COMPUTEPIPE_RUNNER(GetErrorCode)();
+
+// Gets the graph error message from the graph. The return value is not the
+// graph error code but the error code returned if the call to the function
+// fails.
+PrebuiltComputepipeRunner_ErrorCode COMPUTEPIPE_RUNNER(GetErrorMessage)(
+    unsigned char* error_msg_buffer, size_t error_msg_buffer_size, size_t* error_msg_size);
+
+// Gets the supported graph config options. This is ideally generated once and
+// cached for subsequent calls.
+PrebuiltComputepipeRunner_ErrorCode COMPUTEPIPE_RUNNER(GetSupportedGraphConfigs)(
+    const void** config, size_t* config_size);
+
+// Sets the graph configuration or updates it if an incomplete config is passed.
+PrebuiltComputepipeRunner_ErrorCode COMPUTEPIPE_RUNNER(UpdateGraphConfig)(
+    const unsigned char* graph_config, size_t graph_config_size);
+
+// Sets the stream contents. This can only be used after the graph has started
+// running successfully. The contents of this stream are typically a serialized
+// proto and would be deserialized and fed into the graph.
+PrebuiltComputepipeRunner_ErrorCode COMPUTEPIPE_RUNNER(SetInputStreamData)(
+    int stream_index, int64_t timestamp, const unsigned char* stream_data, size_t stream_data_size);
+
+// Sets the pixel data as stream contents. This can be set only after the graph
+// has started running successfully. Pixel data should be copied within this
+// function as there are no guarantess on the lifetime of the pixel data beyond
+// the return of this function call.
+PrebuiltComputepipeRunner_ErrorCode COMPUTEPIPE_RUNNER(SetInputStreamPixelData)(
+    int stream_index, int64_t timestamp, const uint8_t* pixels, int width, int height, int step,
+    int format);
+
+// Sets a callback function for when a packet is generated. Note that a c-style
+// function needs to be passed as no object context is being passed around here.
+// The runner would be responsible for using the buffer provided in the callback
+// immediately or copying it as there are no guarantees on its lifetime beyond
+// the return of the callback.
+PrebuiltComputepipeRunner_ErrorCode COMPUTEPIPE_RUNNER(SetOutputStreamCallback)(
+    void (*streamCallback)(void* cookie, int stream_index, int64_t timestamp,
+                           const unsigned char* data, size_t data_size));
+
+// Sets a callback function for when new pixel data is generated. C-style
+// function pointers need to passed as no object context is being passed around.
+// The runner would be responsible for immediately copying out the data. The
+// prebuilt is expected to pass contiguous data.
+PrebuiltComputepipeRunner_ErrorCode COMPUTEPIPE_RUNNER(SetOutputPixelStreamCallback)(
+    void (*streamCallback)(void* cookie, int stream_index, int64_t timestamp, const uint8_t* pixels,
+                           int width, int height, int step, int format));
+
+// Sets a callback function for when the graph terminates.
+PrebuiltComputepipeRunner_ErrorCode COMPUTEPIPE_RUNNER(SetGraphTerminationCallback)(
+    void (*terminationCallback)(void* cookie, const unsigned char* termination_message,
+                                size_t termination_message_size));
+
+// Starts the graph execution. Debugging can be enabled which will enable
+// profiling. The profiling info can be obtained by calling getDebugInfo once
+// the graph execution has stopped.
+PrebuiltComputepipeRunner_ErrorCode COMPUTEPIPE_RUNNER(StartGraphExecution)(void* cookie,
+                                                                            bool debugging_enabled);
+
+// Stops the graph execution.
+PrebuiltComputepipeRunner_ErrorCode COMPUTEPIPE_RUNNER(StopGraphExecution)(bool flushOutputFrames);
+
+// Resets the graph completely. Should be called only after graph execution has been stopped.
+void COMPUTEPIPE_RUNNER(ResetGraph)();
+
+// Get debugging/profiling information. The function outputs the size of
+// profiling information string and if the buffer size is larger than or equal
+// to the size, then it copies it over to the buffer. Debugging info will be
+// empty if the graph is started without debugging support.
+PrebuiltComputepipeRunner_ErrorCode COMPUTEPIPE_RUNNER(GetDebugInfo)(unsigned char* debug_info,
+                                                                     size_t debug_info_buffer_size,
+                                                                     size_t* debug_info_size);
+}
+#endif  // COMPUTEPIPE_RUNNER_INCLUDE_PREBUILT_INTERFACE_H_
diff --git a/computepipe/runner/input_manager/Android.bp b/computepipe/runner/input_manager/Android.bp
new file mode 100644
index 0000000..fd17e45
--- /dev/null
+++ b/computepipe/runner/input_manager/Android.bp
@@ -0,0 +1,49 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+cc_library {
+    name: "computepipe_input_manager",
+    srcs: [
+        "Factory.cpp",
+        "EvsInputManager.cpp",
+    ],
+    export_include_dirs: ["include"],
+    header_libs: [
+        "computepipe_runner_includes",
+    ],
+    static_libs: [
+        "libcomputepipeprotos",
+    ],
+    shared_libs: [
+        "android.hardware.automotive.evs@1.0",
+        "computepipe_runner_component",
+        "libbase",
+        "libcutils",
+        "libdl",
+        "libevssupport",
+        "libhardware",
+        "libhidlbase",
+        "liblog",
+        "libpng",
+        "libprotobuf-cpp-lite",
+        "libui",
+        "libutils",
+        "libEGL",
+        "libGLESv2",
+    ],
+    include_dirs: [
+        "packages/services/Car/computepipe",
+        "packages/services/Car/evs/support_library",
+    ],
+}
diff --git a/computepipe/runner/input_manager/EvsInputManager.cpp b/computepipe/runner/input_manager/EvsInputManager.cpp
new file mode 100644
index 0000000..92cdf49
--- /dev/null
+++ b/computepipe/runner/input_manager/EvsInputManager.cpp
@@ -0,0 +1,195 @@
+// Copyright 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+#include "EvsInputManager.h"
+
+#include <chrono>
+#include <map>
+#include <shared_mutex>
+#include <string>
+#include <thread>
+
+#include "AnalyzeUseCase.h"
+#include "BaseAnalyzeCallback.h"
+#include "InputConfig.pb.h"
+#include "InputEngineInterface.h"
+#include "Options.pb.h"
+
+using ::android::automotive::evs::support::AnalyzeUseCase;
+using ::android::automotive::evs::support::BaseAnalyzeCallback;
+
+namespace android {
+namespace automotive {
+namespace computepipe {
+namespace runner {
+namespace input_manager {
+
+void AnalyzeCallback::analyze(const ::android::automotive::evs::support::Frame& frame) {
+    std::shared_lock lock(mEngineInterfaceLock);
+    if (mInputEngineInterface != nullptr) {
+        auto time_point = std::chrono::system_clock::now();
+        int64_t timestamp = std::chrono::time_point_cast<std::chrono::microseconds>(time_point)
+                                .time_since_epoch()
+                                .count();
+        // Stride for hardware buffers is specified in pixels whereas for
+        // InputFrame, it is specified in bytes. We therefore need to multiply
+        // the stride by 4 for an RGBA frame.
+        InputFrame inputFrame(frame.height, frame.width, PixelFormat::RGBA, frame.stride * 4,
+                              frame.data);
+        mInputEngineInterface->dispatchInputFrame(mInputStreamId, timestamp, inputFrame);
+    }
+}
+
+void AnalyzeCallback::setEngineInterface(std::shared_ptr<InputEngineInterface> inputEngineInterface) {
+    std::lock_guard lock(mEngineInterfaceLock);
+    mInputEngineInterface = inputEngineInterface;
+}
+
+EvsInputManager::EvsInputManager(const proto::InputConfig& inputConfig,
+                                 std::shared_ptr<InputEngineInterface> inputEngineInterface)
+    : mInputEngineInterface(inputEngineInterface), mInputConfig(inputConfig) {
+}
+
+std::unique_ptr<EvsInputManager> EvsInputManager::createEvsInputManager(
+    const proto::InputConfig& inputConfig,
+    std::shared_ptr<InputEngineInterface> inputEngineInterface) {
+    auto evsManager = std::make_unique<EvsInputManager>(inputConfig, inputEngineInterface);
+    if (evsManager->initializeCameras() == Status::SUCCESS) {
+        return evsManager;
+    }
+
+    return nullptr;
+}
+
+Status EvsInputManager::initializeCameras() {
+    for (int i = 0; i < mInputConfig.input_stream_size(); i++) {
+        // Verify that the stream type specified is a camera stream which is necessary for evs
+        // manager.
+        if (mInputConfig.input_stream(i).type() != proto::InputStreamConfig_InputType_CAMERA) {
+            ALOGE("Evs stream manager expects the input stream type to be camera.");
+            return Status::INVALID_ARGUMENT;
+        }
+        const std::string& cameraId = mInputConfig.input_stream(i).cam_config().cam_id();
+        std::unique_ptr<AnalyzeCallback> analyzeCallback =
+            std::make_unique<AnalyzeCallback>(mInputConfig.input_stream(i).stream_id());
+        AnalyzeUseCase analyzeUseCase =
+            AnalyzeUseCase::createDefaultUseCase(cameraId, analyzeCallback.get());
+        mAnalyzeCallbacks.push_back(std::move(analyzeCallback));
+
+        int streamId = mInputConfig.input_stream(i).stream_id();
+        auto [it, result] = mEvsUseCases.try_emplace(std::move(streamId),
+                                                     std::move(analyzeUseCase));
+        if (!result) {
+            // Multiple camera streams found to have the same camera id.
+            ALOGE("Multiple camera streams have the same stream id.");
+            return Status::INVALID_ARGUMENT;
+        }
+    }
+
+    return Status::SUCCESS;
+}
+
+Status EvsInputManager::handleExecutionPhase(const RunnerEvent& e) {
+    // Starting execution cannot be stopped in between. handleStopImmediate needs to be called.
+    if (e.isAborted()) {
+        return Status::INVALID_ARGUMENT;
+    } else if (e.isTransitionComplete()) {
+        return Status::SUCCESS;
+    }
+
+    if (mEvsUseCases.empty()) {
+        ALOGE("No evs use cases configured. Verify that handleConfigPhase has been called");
+        return Status::ILLEGAL_STATE;
+    }
+
+    // Start all the video streams.
+    bool successfullyStartedAllCameras = true;
+    for (auto& [streamId, evsUseCase] : mEvsUseCases) {
+        if (!evsUseCase.startVideoStream()) {
+            successfullyStartedAllCameras = false;
+            ALOGE("Unable to successfully start all cameras");
+            break;
+        }
+    }
+
+    // If not all video streams have started successfully, stop the streams.
+    if (!successfullyStartedAllCameras) {
+        for (auto& [streamId, evsUseCase] : mEvsUseCases) {
+            evsUseCase.stopVideoStream();
+        }
+        return Status::INTERNAL_ERROR;
+    }
+
+    // Set the input to engine interface for callbacks only when all the streams have successfully
+    // started. This prevents any callback from going out unless all of the streams have started.
+    for (auto& analyzeCallback : mAnalyzeCallbacks) {
+        analyzeCallback->setEngineInterface(mInputEngineInterface);
+    }
+
+    return Status::SUCCESS;
+}
+
+Status EvsInputManager::handleStopImmediatePhase(const RunnerEvent& e) {
+    if (e.isAborted()) {
+        ALOGE(
+            "Unable to abort immediate stopping of EVS cameras. Please start the video streams "
+            "again if "
+            "needed.");
+    } else if (e.isTransitionComplete()) {
+        return Status::SUCCESS;
+    }
+
+    // Reset all input engine interfaces so that callbacks stop going out even if there are evs
+    // frames in flux.
+    for (auto& analyzeCallback : mAnalyzeCallbacks) {
+        analyzeCallback->setEngineInterface(nullptr);
+    }
+
+    for (auto& [streamId, evsUseCase] : mEvsUseCases) {
+        evsUseCase.stopVideoStream();
+    }
+
+    return Status::SUCCESS;
+}
+
+Status EvsInputManager::handleStopWithFlushPhase(const RunnerEvent& e) {
+    if (e.isAborted()) {
+        ALOGE(
+            "Unable to abort stopping and flushing of EVS cameras. Please start the video streams "
+            "again if "
+            "needed.");
+    } else if (e.isTransitionComplete()) {
+        return Status::SUCCESS;
+    }
+
+    for (auto& [streamId, evsUseCase] : mEvsUseCases) {
+        evsUseCase.stopVideoStream();
+    }
+    return Status::SUCCESS;
+}
+
+Status EvsInputManager::handleResetPhase(const RunnerEvent& e) {
+    if (e.isAborted()) {
+        ALOGE("Unable to abort reset.");
+        return Status::INVALID_ARGUMENT;
+    }
+    mEvsUseCases.clear();
+    mAnalyzeCallbacks.clear();
+    return Status::SUCCESS;
+}
+
+}  // namespace input_manager
+}  // namespace runner
+}  // namespace computepipe
+}  // namespace automotive
+}  // namespace android
diff --git a/computepipe/runner/input_manager/Factory.cpp b/computepipe/runner/input_manager/Factory.cpp
new file mode 100644
index 0000000..fe62e60
--- /dev/null
+++ b/computepipe/runner/input_manager/Factory.cpp
@@ -0,0 +1,55 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "EvsInputManager.h"
+#include "InputManager.h"
+
+namespace android {
+namespace automotive {
+namespace computepipe {
+namespace runner {
+namespace input_manager {
+namespace {
+
+enum InputManagerType {
+    EVS = 0,
+    IMAGES,
+    VIDEO,
+};
+
+// Helper function to determine the type of input manager to be created from the
+// input config.
+// TODO(b/147803315): Implement the actual algorithm to determine the input manager to be
+// used. Right now, only EVS manager is enabled, so that is used.
+InputManagerType getInputManagerType(const proto::InputConfig& /* inputConfig */) {
+    return InputManagerType::EVS;
+}
+
+}  // namespace
+std::unique_ptr<InputManager> InputManagerFactory::createInputManager(
+    const proto::InputConfig& config, std::shared_ptr<InputEngineInterface> inputEngineInterface) {
+    InputManagerType inputManagerType = getInputManagerType(config);
+    switch (inputManagerType) {
+        case InputManagerType::EVS:
+            return EvsInputManager::createEvsInputManager(config, inputEngineInterface);
+        default:
+            return nullptr;
+    }
+}
+
+}  // namespace input_manager
+}  // namespace runner
+}  // namespace computepipe
+}  // namespace automotive
+}  // namespace android
diff --git a/computepipe/runner/input_manager/include/EvsInputManager.h b/computepipe/runner/input_manager/include/EvsInputManager.h
new file mode 100644
index 0000000..899593a
--- /dev/null
+++ b/computepipe/runner/input_manager/include/EvsInputManager.h
@@ -0,0 +1,86 @@
+// Copyright 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+#ifndef COMPUTEPIPE_RUNNER_INPUT_MANAGER_INCLUDE_EVSINPUTMANAGER_H_
+#define COMPUTEPIPE_RUNNER_INPUT_MANAGER_INCLUDE_EVSINPUTMANAGER_H_
+
+#include <memory>
+#include <shared_mutex>
+#include <unordered_map>
+#include <vector>
+
+#include "AnalyzeUseCase.h"
+#include "BaseAnalyzeCallback.h"
+#include "InputConfig.pb.h"
+#include "InputEngineInterface.h"
+#include "InputManager.h"
+#include "RunnerComponent.h"
+#include "types/Status.h"
+
+namespace android {
+namespace automotive {
+namespace computepipe {
+namespace runner {
+namespace input_manager {
+
+// Class that is used as a callback for EVS camera streams.
+class AnalyzeCallback : public ::android::automotive::evs::support::BaseAnalyzeCallback {
+  public:
+    AnalyzeCallback(int inputStreamId) : mInputStreamId(inputStreamId) {
+    }
+
+    void analyze(const ::android::automotive::evs::support::Frame&) override;
+
+    void setEngineInterface(std::shared_ptr<InputEngineInterface> inputEngineInterface);
+
+    virtual ~AnalyzeCallback(){};
+
+  private:
+    std::shared_ptr<InputEngineInterface> mInputEngineInterface;
+    std::shared_mutex mEngineInterfaceLock;
+    const int mInputStreamId;
+};
+
+class EvsInputManager : public InputManager {
+  public:
+    explicit EvsInputManager(const proto::InputConfig& inputConfig,
+                             std::shared_ptr<InputEngineInterface> inputEngineInterface);
+
+    static std::unique_ptr<EvsInputManager> createEvsInputManager(
+        const proto::InputConfig& inputConfig,
+        std::shared_ptr<InputEngineInterface> inputEngineInterface);
+
+    Status initializeCameras();
+
+    Status handleExecutionPhase(const RunnerEvent& e) override;
+
+    Status handleStopImmediatePhase(const RunnerEvent& e) override;
+
+    Status handleStopWithFlushPhase(const RunnerEvent& e) override;
+
+    Status handleResetPhase(const RunnerEvent& e) override;
+
+  private:
+    std::unordered_map<int, ::android::automotive::evs::support::AnalyzeUseCase> mEvsUseCases;
+    std::vector<std::unique_ptr<AnalyzeCallback>> mAnalyzeCallbacks;
+    std::shared_ptr<InputEngineInterface> mInputEngineInterface;
+    const proto::InputConfig mInputConfig;
+};
+
+}  // namespace input_manager
+}  // namespace runner
+}  // namespace computepipe
+}  // namespace automotive
+}  // namespace android
+
+#endif  // COMPUTEPIPE_RUNNER_INPUT_MANAGER_INCLUDE_EVSINPUTMANAGER_H_
diff --git a/computepipe/runner/input_manager/include/InputEngineInterface.h b/computepipe/runner/input_manager/include/InputEngineInterface.h
new file mode 100644
index 0000000..154bc74
--- /dev/null
+++ b/computepipe/runner/input_manager/include/InputEngineInterface.h
@@ -0,0 +1,50 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef COMPUTEPIPE_RUNNER_INPUT_ENGINE_INTERFACE_H
+#define COMPUTEPIPE_RUNNER_INPUT_ENGINE_INTERFACE_H
+
+#include "InputFrame.h"
+#include "types/Status.h"
+
+namespace android {
+namespace automotive {
+namespace computepipe {
+namespace runner {
+namespace input_manager {
+
+class InputEngineInterface {
+  public:
+    /**
+     * Dispatch input frame to engine for consumption by the graph
+     */
+    virtual Status dispatchInputFrame(int streamId, int64_t timestamp, const InputFrame& frame) = 0;
+    /**
+     * Report Error Halt to Engine. Engine should report error to other
+     * components.
+     */
+    virtual void notifyInputError() = 0;
+    /**
+     * Destructor
+     */
+    virtual ~InputEngineInterface() = default;
+};
+
+}  // namespace input_manager
+}  // namespace runner
+}  // namespace computepipe
+}  // namespace automotive
+}  // namespace android
+
+#endif
diff --git a/computepipe/runner/input_manager/include/InputManager.h b/computepipe/runner/input_manager/include/InputManager.h
new file mode 100644
index 0000000..9b12414
--- /dev/null
+++ b/computepipe/runner/input_manager/include/InputManager.h
@@ -0,0 +1,57 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef COMPUTEPIPE_RUNNER_INPUT_MANAGER_H
+#define COMPUTEPIPE_RUNNER_INPUT_MANAGER_H
+
+#include <memory>
+
+#include "InputConfig.pb.h"
+#include "InputEngineInterface.h"
+#include "InputFrame.h"
+#include "RunnerComponent.h"
+
+namespace android {
+namespace automotive {
+namespace computepipe {
+namespace runner {
+namespace input_manager {
+
+/**
+ * InputManager: Is the runner component repsonsible for managing the input
+ * source for the graph. The Component exposes communications from the engine to
+ * the input source.
+ */
+class InputManager : public RunnerComponentInterface {};
+
+/**
+ * Factory that instantiates the input manager, for a given input option.
+ */
+class InputManagerFactory {
+  public:
+    std::unique_ptr<InputManager> createInputManager(const proto::InputConfig& config,
+                                                     std::shared_ptr<InputEngineInterface> engine);
+    InputManagerFactory() = default;
+    InputManagerFactory(const InputManagerFactory&) = delete;
+    InputManagerFactory& operator=(const InputManagerFactory&) = delete;
+    InputManagerFactory(const InputManagerFactory&&) = delete;
+    InputManagerFactory& operator=(const InputManagerFactory&&) = delete;
+};
+
+}  // namespace input_manager
+}  // namespace runner
+}  // namespace computepipe
+}  // namespace automotive
+}  // namespace android
+#endif
diff --git a/computepipe/runner/stream_manager/Android.bp b/computepipe/runner/stream_manager/Android.bp
new file mode 100644
index 0000000..7a5cd5a
--- /dev/null
+++ b/computepipe/runner/stream_manager/Android.bp
@@ -0,0 +1,70 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+cc_library {
+    name: "computepipe_stream_manager",
+    srcs: [
+        "Factory.cpp",
+        "PixelStreamManager.cpp",
+        "SemanticManager.cpp",
+    ],
+    export_include_dirs: ["include"],
+    visibility: [
+        "//packages/services/Car/computepipe/runner:__subpackages__",
+        "//packages/services/Car/computepipe/tests:__subpackages__",
+    ],
+    header_libs: [
+        "computepipe_runner_includes",
+    ],
+    static_libs: [
+        "libcomputepipeprotos",
+    ],
+    shared_libs: [
+        "libbase",
+        "computepipe_runner_component",
+        "libbase",
+        "libnativewindow",
+    ],
+    include_dirs: [
+        "packages/services/Car/computepipe",
+    ],
+}
+
+cc_library {
+    name: "mock_stream_engine_interface",
+    srcs: [
+        "MockEngine.cpp",
+    ],
+    visibility: [
+        "//packages/services/Car/computepipe/runner:__subpackages__",
+        "//packages/services/Car/computepipe/tests:__subpackages__",
+    ],
+    header_libs: [
+        "computepipe_runner_includes",
+    ],
+    static_libs: [
+        "libbase",
+        "libgtest",
+        "libgmock",
+        "libcomputepipeprotos",
+    ],
+    shared_libs: [
+        "computepipe_runner_component",
+        "computepipe_stream_manager",
+        "libnativewindow",
+    ],
+    include_dirs: [
+        "packages/services/Car/computepipe",
+    ],
+}
diff --git a/computepipe/runner/stream_manager/Factory.cpp b/computepipe/runner/stream_manager/Factory.cpp
new file mode 100644
index 0000000..aee2d1a
--- /dev/null
+++ b/computepipe/runner/stream_manager/Factory.cpp
@@ -0,0 +1,79 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "OutputConfig.pb.h"
+#include "PixelStreamManager.h"
+#include "SemanticManager.h"
+#include "StreamEngineInterface.h"
+#include "StreamManager.h"
+
+namespace android {
+namespace automotive {
+namespace computepipe {
+namespace runner {
+namespace stream_manager {
+
+namespace {
+
+/**
+ * Build an instance of the Semantic Manager and initialize it
+ */
+std::unique_ptr<SemanticManager> buildSemanticManager(const proto::OutputConfig& config,
+                                                      std::shared_ptr<StreamEngineInterface> engine,
+                                                      uint32_t maxPackets) {
+    std::unique_ptr<SemanticManager> semanticManager =
+        std::make_unique<SemanticManager>(config.stream_name(), config.stream_id(), config.type());
+    semanticManager->setEngineInterface(engine);
+    if (semanticManager->setMaxInFlightPackets(maxPackets) != SUCCESS) {
+        return nullptr;
+    }
+    return semanticManager;
+}
+
+std::unique_ptr<PixelStreamManager> buildPixelStreamManager(
+    const proto::OutputConfig& config, std::shared_ptr<StreamEngineInterface> engine,
+    uint32_t maxPackets) {
+    std::unique_ptr<PixelStreamManager> pixelStreamManager =
+        std::make_unique<PixelStreamManager>(config.stream_name(), config.stream_id());
+    pixelStreamManager->setEngineInterface(engine);
+    if (pixelStreamManager->setMaxInFlightPackets(maxPackets) != Status::SUCCESS) {
+        return nullptr;
+    }
+    return pixelStreamManager;
+}
+
+}  // namespace
+
+std::unique_ptr<StreamManager> StreamManagerFactory::getStreamManager(
+    const proto::OutputConfig& config, std::shared_ptr<StreamEngineInterface> engine,
+    uint32_t maxPackets) {
+    if (!config.has_type()) {
+        return nullptr;
+    }
+    switch (config.type()) {
+        case proto::PacketType::SEMANTIC_DATA:
+            return buildSemanticManager(config, engine, maxPackets);
+        case proto::PacketType::PIXEL_DATA:
+            return buildPixelStreamManager(config, engine, maxPackets);
+        default:
+            return nullptr;
+    }
+    return nullptr;
+}
+
+}  // namespace stream_manager
+}  // namespace runner
+}  // namespace computepipe
+}  // namespace automotive
+}  // namespace android
diff --git a/computepipe/runner/stream_manager/MockEngine.cpp b/computepipe/runner/stream_manager/MockEngine.cpp
new file mode 100644
index 0000000..ac41a98
--- /dev/null
+++ b/computepipe/runner/stream_manager/MockEngine.cpp
@@ -0,0 +1,38 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "MockEngine.h"
+
+namespace android {
+namespace automotive {
+namespace computepipe {
+namespace runner {
+namespace stream_manager {
+
+using ::testing::_;
+
+void MockEngine::delegateToFake(const std::shared_ptr<StreamEngineInterface>& engine) {
+    mFake = engine;
+    ON_CALL(*this, dispatchPacket).WillByDefault([this](const std::shared_ptr<MemHandle>& handle) {
+        return mFake->dispatchPacket(handle);
+    });
+    ON_CALL(*this, notifyError).WillByDefault([this](std::string msg) { mFake->notifyError(msg); });
+    ON_CALL(*this, notifyEndOfStream).WillByDefault([this]() { mFake->notifyEndOfStream(); });
+}
+
+}  // namespace stream_manager
+}  // namespace runner
+}  // namespace computepipe
+}  // namespace automotive
+}  // namespace android
diff --git a/computepipe/runner/stream_manager/MockEngine.h b/computepipe/runner/stream_manager/MockEngine.h
new file mode 100644
index 0000000..32d318b
--- /dev/null
+++ b/computepipe/runner/stream_manager/MockEngine.h
@@ -0,0 +1,47 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef COMPUTEPIPE_RUNNER_STREAM_MOCK_ENGINE_H
+#define COMPUTEPIPE_RUNNER_STREAM_MOCK_ENGINE_H
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include "StreamEngineInterface.h"
+
+namespace android {
+namespace automotive {
+namespace computepipe {
+namespace runner {
+namespace stream_manager {
+
+class MockEngine : public StreamEngineInterface {
+  public:
+    MockEngine() = default;
+    MOCK_METHOD(Status, dispatchPacket, (const std::shared_ptr<MemHandle>& data), (override));
+    MOCK_METHOD(void, notifyEndOfStream, (), (override));
+    MOCK_METHOD(void, notifyError, (std::string msg), (override));
+    void delegateToFake(const std::shared_ptr<StreamEngineInterface>& fake);
+
+  private:
+    std::shared_ptr<StreamEngineInterface> mFake;
+};
+
+}  // namespace stream_manager
+}  // namespace runner
+}  // namespace computepipe
+}  // namespace automotive
+}  // namespace android
+
+#endif
diff --git a/computepipe/runner/stream_manager/PixelStreamManager.cpp b/computepipe/runner/stream_manager/PixelStreamManager.cpp
new file mode 100644
index 0000000..3b2be96
--- /dev/null
+++ b/computepipe/runner/stream_manager/PixelStreamManager.cpp
@@ -0,0 +1,334 @@
+// Copyright 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+#include "PixelStreamManager.h"
+
+#include <android-base/logging.h>
+#include <vndk/hardware_buffer.h>
+
+#include <algorithm>
+#include <mutex>
+#include <thread>
+
+namespace android {
+namespace automotive {
+namespace computepipe {
+namespace runner {
+namespace stream_manager {
+
+AHardwareBuffer_Format PixelFormatToHardwareBufferFormat(PixelFormat pixelFormat) {
+    switch (pixelFormat) {
+        case PixelFormat::RGBA:
+            return AHardwareBuffer_Format::AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM;
+        case PixelFormat::RGB:
+            return AHardwareBuffer_Format::AHARDWAREBUFFER_FORMAT_R8G8B8_UNORM;
+        case PixelFormat::GRAY:
+            return AHardwareBuffer_Format::AHARDWAREBUFFER_FORMAT_S8_UINT;  // TODO: Check if this works
+        default:
+            CHECK(false) << "Unrecognized pixel format seen";
+    }
+    return AHardwareBuffer_Format::AHARDWAREBUFFER_FORMAT_BLOB;
+}
+
+int numBytesPerPixel(PixelFormat pixelFormat) {
+    switch (pixelFormat) {
+        case PixelFormat::RGBA:
+            return 4;
+        case PixelFormat::RGB:
+            return 3;
+        case PixelFormat::GRAY:
+            return 1;
+        default:
+            CHECK(false) << "Unrecognized pixel format seen";
+    }
+    return 1;
+}
+
+PixelMemHandle::PixelMemHandle(int bufferId, int streamId, int additionalUsageFlags)
+    : mBufferId(bufferId),
+      mStreamId(streamId),
+      mBuffer(nullptr),
+      mUsage(AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN | additionalUsageFlags) {
+}
+
+PixelMemHandle::~PixelMemHandle() {
+    if (mBuffer) {
+        AHardwareBuffer_release(mBuffer);
+    }
+}
+
+int PixelMemHandle::getStreamId() const {
+    return mStreamId;
+}
+
+proto::PacketType PixelMemHandle::getType() const {
+    return proto::PacketType::PIXEL_DATA;
+}
+uint64_t PixelMemHandle::getTimeStamp() const {
+    return mTimestamp;
+}
+
+uint32_t PixelMemHandle::getSize() const {
+    return 0;
+}
+
+const char* PixelMemHandle::getData() const {
+    return nullptr;
+}
+
+AHardwareBuffer* PixelMemHandle::getHardwareBuffer() const {
+    return mBuffer;
+}
+
+/* Sets frame info */
+Status PixelMemHandle::setFrameData(uint64_t timestamp, const InputFrame& inputFrame) {
+    // Allocate a new buffer if it is currently null.
+    FrameInfo frameInfo = inputFrame.getFrameInfo();
+    if (mBuffer == nullptr) {
+        mDesc.format = PixelFormatToHardwareBufferFormat(frameInfo.format);
+        mDesc.height = frameInfo.height;
+        mDesc.width = frameInfo.width;
+        mDesc.layers = 1;
+        mDesc.rfu0 = 0;
+        mDesc.rfu1 = 0;
+        mDesc.stride = frameInfo.stride;
+        mDesc.usage = mUsage;
+        int err = AHardwareBuffer_allocate(&mDesc, &mBuffer);
+
+        if (err != 0 || mBuffer == nullptr) {
+            LOG(ERROR) << "Failed to allocate hardware buffer with error " << err;
+            return Status::NO_MEMORY;
+        }
+
+        // Update mDesc with the actual descriptor with which the buffer was created. The actual
+        // stride could be different from the specified stride.
+        AHardwareBuffer_describe(mBuffer, &mDesc);
+    }
+
+    // Verifies that the input frame data has the same type as the allocated buffer.
+    if (frameInfo.width != mDesc.width || frameInfo.height != mDesc.height ||
+        PixelFormatToHardwareBufferFormat(frameInfo.format) != mDesc.format) {
+        LOG(ERROR) << "Variable image sizes from the same stream id is not supported.";
+        return Status::INVALID_ARGUMENT;
+    }
+
+    // Locks the frame for copying the input frame data.
+    void* mappedBuffer = nullptr;
+    int err = AHardwareBuffer_lock(mBuffer, AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN, -1, nullptr,
+                                   &mappedBuffer);
+    if (err != 0 || mappedBuffer == nullptr) {
+        LOG(ERROR) << "Unable to lock a realased hardware buffer.";
+        return Status::INTERNAL_ERROR;
+    }
+
+    // Copies the input frame data.
+    int bytesPerPixel = numBytesPerPixel(frameInfo.format);
+    // The stride for hardware buffer is specified in pixels while the stride
+    // for InputFrame data structure is specified in bytes.
+    if (mDesc.stride * bytesPerPixel == frameInfo.stride) {
+        memcpy(mappedBuffer, inputFrame.getFramePtr(), mDesc.stride * mDesc.height * bytesPerPixel);
+    } else {
+        for (int y = 0; y < frameInfo.height; y++) {
+            memcpy((uint8_t*)mappedBuffer + mDesc.stride * y * bytesPerPixel,
+                   inputFrame.getFramePtr() + y * frameInfo.stride,
+                   std::min(frameInfo.stride, mDesc.stride * bytesPerPixel));
+        }
+    }
+
+    AHardwareBuffer_unlock(mBuffer, nullptr);
+    mTimestamp = timestamp;
+
+    return Status::SUCCESS;
+}
+
+int PixelMemHandle::getBufferId() const {
+    return mBufferId;
+}
+
+void PixelStreamManager::setEngineInterface(std::shared_ptr<StreamEngineInterface> engine) {
+    std::lock_guard lock(mLock);
+    mEngine = engine;
+}
+
+Status PixelStreamManager::setMaxInFlightPackets(uint32_t maxPackets) {
+    std::lock_guard lock(mLock);
+    if (mBuffersInUse.size() > maxPackets) {
+        LOG(ERROR) << "Cannot set max in flight packets after graph has already started.";
+        return Status::ILLEGAL_STATE;
+    }
+
+    mMaxInFlightPackets = maxPackets;
+    std::lock_guard stateLock(mStateLock);
+    mState = CONFIG_DONE;
+    return Status::SUCCESS;
+}
+
+Status PixelStreamManager::freePacket(int bufferId) {
+    std::lock_guard lock(mLock);
+
+    auto it = mBuffersInUse.find(bufferId);
+
+    if (it == mBuffersInUse.end()) {
+        std::lock_guard stateLock(mStateLock);
+        // If the graph has already been stopped, we free the buffers
+        // asynchronously, so return SUCCESS if freePacket is called later.
+        if (mState == STOPPED) {
+            return Status::SUCCESS;
+        }
+
+        LOG(ERROR) << "Unable to find the mem handle. Duplicate release may possible have been "
+                      "called";
+        return Status::INVALID_ARGUMENT;
+    }
+
+    mBuffersReady.push_back(it->second);
+    mBuffersInUse.erase(it);
+
+    return Status::SUCCESS;
+}
+
+void PixelStreamManager::freeAllPackets() {
+    std::lock_guard lock(mLock);
+
+    for (auto [bufferId, memHandle] : mBuffersInUse) {
+        mBuffersReady.push_back(memHandle);
+    }
+    mBuffersInUse.clear();
+}
+
+Status PixelStreamManager::queuePacket(const char* /*data*/, const uint32_t /*size*/,
+                                       uint64_t /*timestamp*/) {
+    LOG(ERROR) << "Trying to queue a semantic packet to a pixel stream manager";
+    return Status::ILLEGAL_STATE;
+}
+
+Status PixelStreamManager::queuePacket(const InputFrame& frame, uint64_t timestamp) {
+    std::lock_guard lock(mLock);
+
+    // State has to be running for the callback to go back.
+    {
+        std::lock_guard stateLock(mStateLock);
+        if (mState != RUNNING) {
+            LOG(ERROR) << "Packet cannot be queued when state is not RUNNING. Current state is"
+                       << mState;
+            return Status::ILLEGAL_STATE;
+        }
+    }
+
+    if (mEngine == nullptr) {
+        LOG(ERROR) << "Stream to engine interface is not set";
+        return Status::ILLEGAL_STATE;
+    }
+
+    if (mBuffersInUse.size() >= mMaxInFlightPackets) {
+        LOG(INFO) << "Too many frames in flight. Skipping frame at timestamp " << timestamp;
+        return Status::SUCCESS;
+    }
+
+    // A unique id per buffer is maintained by incrementing the unique id from the previously
+    // created buffer. The unique id is therefore the number of buffers already created.
+    if (mBuffersReady.empty()) {
+        mBuffersReady.push_back(std::make_shared<PixelMemHandle>(mBuffersInUse.size(), mStreamId));
+    }
+
+    // The previously used buffer is pushed to the back of the vector. Picking the last used buffer
+    // may be more cache efficient if accessing through CPU, so we use that strategy here.
+    std::shared_ptr<PixelMemHandle> memHandle = mBuffersReady[mBuffersReady.size() - 1];
+    mBuffersReady.resize(mBuffersReady.size() - 1);
+    mBuffersInUse.emplace(memHandle->getBufferId(), memHandle);
+
+    Status status = memHandle->setFrameData(timestamp, frame);
+    if (status != Status::SUCCESS) {
+        LOG(ERROR) << "Setting frame data failed with error code " << status;
+        return status;
+    }
+
+    // Dispatch packet to the engine asynchronously in order to avoid circularly
+    // waiting for each others' locks.
+    std::thread t([this, memHandle]() {
+        Status status = mEngine->dispatchPacket(memHandle);
+        if (status != Status::SUCCESS) {
+            mEngine->notifyError(std::string(__func__) + ":" + std::to_string(__LINE__) +
+                                 " Failed to dispatch packet");
+        }
+    });
+    t.detach();
+    return Status::SUCCESS;
+}
+
+Status PixelStreamManager::handleExecutionPhase(const RunnerEvent& e) {
+    std::lock_guard<std::mutex> lock(mStateLock);
+    if (mState == CONFIG_DONE && e.isPhaseEntry()) {
+        mState = RUNNING;
+        return Status::SUCCESS;
+    }
+    if (mState == RESET) {
+        // Cannot get to running phase from reset state without config phase
+        return Status::ILLEGAL_STATE;
+    }
+    if (mState == RUNNING && e.isAborted()) {
+        // Transition back to config completed
+        mState = CONFIG_DONE;
+        return Status::SUCCESS;
+    }
+    if (mState == RUNNING) {
+        return Status::ILLEGAL_STATE;
+    }
+    return Status::SUCCESS;
+}
+
+Status PixelStreamManager::handleStopWithFlushPhase(const RunnerEvent& e) {
+    return handleStopImmediatePhase(e);
+}
+
+Status PixelStreamManager::handleStopImmediatePhase(const RunnerEvent& e) {
+    std::lock_guard<std::mutex> lock(mStateLock);
+    if (mState == CONFIG_DONE || mState == RESET) {
+        return ILLEGAL_STATE;
+    }
+    /* Cannot have stop completed if we never entered stop state */
+    if (mState == RUNNING && (e.isAborted() || e.isTransitionComplete())) {
+        return ILLEGAL_STATE;
+    }
+    /* We are being asked to stop */
+    if (mState == RUNNING && e.isPhaseEntry()) {
+        mState = STOPPED;
+        std::thread t([this]() {
+            freeAllPackets();
+            mEngine->notifyEndOfStream();
+        });
+        t.detach();
+        return SUCCESS;
+    }
+    /* Other Components have stopped, we can transition back to CONFIG_DONE */
+    if (mState == STOPPED && e.isTransitionComplete()) {
+        mState = CONFIG_DONE;
+        return SUCCESS;
+    }
+    /* We were stopped, but stop was aborted. */
+    if (mState == STOPPED && e.isAborted()) {
+        mState = RUNNING;
+        return SUCCESS;
+    }
+    return SUCCESS;
+}
+
+PixelStreamManager::PixelStreamManager(std::string name, int streamId)
+    : StreamManager(name, proto::PacketType::PIXEL_DATA), mStreamId(streamId) {
+}
+
+}  // namespace stream_manager
+}  // namespace runner
+}  // namespace computepipe
+}  // namespace automotive
+}  // namespace android
diff --git a/computepipe/runner/stream_manager/PixelStreamManager.h b/computepipe/runner/stream_manager/PixelStreamManager.h
new file mode 100644
index 0000000..99ae942
--- /dev/null
+++ b/computepipe/runner/stream_manager/PixelStreamManager.h
@@ -0,0 +1,107 @@
+// Copyright 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+#ifndef COMPUTEPIPE_RUNNER_STREAM_MANAGER_PIXEL_STREAM_MANAGER_H
+#define COMPUTEPIPE_RUNNER_STREAM_MANAGER_PIXEL_STREAM_MANAGER_H
+
+#include <vndk/hardware_buffer.h>
+
+#include <map>
+#include <memory>
+#include <mutex>
+#include <vector>
+
+#include "InputFrame.h"
+#include "MemHandle.h"
+#include "RunnerComponent.h"
+#include "StreamManager.h"
+#include "StreamManagerInit.h"
+
+namespace android {
+namespace automotive {
+namespace computepipe {
+namespace runner {
+namespace stream_manager {
+
+class PixelMemHandle : public MemHandle {
+  public:
+    explicit PixelMemHandle(int bufferId, int streamId, int additionalUsageFlags = 0);
+
+    virtual ~PixelMemHandle();
+
+    // Delete copy and move constructors as well as copy and move assignment operators.
+    PixelMemHandle(const PixelMemHandle&) = delete;
+    PixelMemHandle(PixelMemHandle&&) = delete;
+    PixelMemHandle operator=(const PixelMemHandle&) = delete;
+    PixelMemHandle operator=(PixelMemHandle&&) = delete;
+
+    /**
+     * Overrides mem handle methods
+     */
+    int getStreamId() const override;
+    int getBufferId() const override;
+    proto::PacketType getType() const override;
+    uint64_t getTimeStamp() const override;
+    uint32_t getSize() const override;
+    const char* getData() const override;
+    AHardwareBuffer* getHardwareBuffer() const override;
+
+    /* Sets frame info */
+    Status setFrameData(uint64_t timestamp, const InputFrame& inputFrame);
+
+  private:
+    const int mBufferId;
+    const int mStreamId;
+    AHardwareBuffer_Desc mDesc;
+    AHardwareBuffer* mBuffer;
+    uint64_t mTimestamp;
+    int mUsage;
+};
+
+class PixelStreamManager : public StreamManager, StreamManagerInit {
+  public:
+    void setEngineInterface(std::shared_ptr<StreamEngineInterface> engine) override;
+    // Set Max in flight packets based on client specification
+    Status setMaxInFlightPackets(uint32_t maxPackets) override;
+    // Free previously dispatched packet. Once client has confirmed usage
+    Status freePacket(int bufferId) override;
+    // Queue packet produced by graph stream
+    Status queuePacket(const char* data, const uint32_t size, uint64_t timestamp) override;
+    // Queues pixel packet produced by graph stream
+    Status queuePacket(const InputFrame& frame, uint64_t timestamp) override;
+
+    Status handleExecutionPhase(const RunnerEvent& e) override;
+    Status handleStopWithFlushPhase(const RunnerEvent& e) override;
+    Status handleStopImmediatePhase(const RunnerEvent& e) override;
+
+    explicit PixelStreamManager(std::string name, int streamId);
+    ~PixelStreamManager() = default;
+
+  private:
+    void freeAllPackets();
+    std::mutex mLock;
+    std::mutex mStateLock;
+    int mStreamId;
+    uint32_t mMaxInFlightPackets;
+    std::shared_ptr<StreamEngineInterface> mEngine;
+    std::map<int, std::shared_ptr<PixelMemHandle>> mBuffersInUse;
+    std::vector<std::shared_ptr<PixelMemHandle>> mBuffersReady;
+};
+
+}  // namespace stream_manager
+}  // namespace runner
+}  // namespace computepipe
+}  // namespace automotive
+}  // namespace android
+
+#endif  // COMPUTEPIPE_RUNNER_STREAM_MANAGER_PIXEL_STREAM_MANAGER_H
diff --git a/computepipe/runner/stream_manager/SemanticManager.cpp b/computepipe/runner/stream_manager/SemanticManager.cpp
new file mode 100644
index 0000000..9b63e5f
--- /dev/null
+++ b/computepipe/runner/stream_manager/SemanticManager.cpp
@@ -0,0 +1,177 @@
+#include "SemanticManager.h"
+
+#include <android-base/logging.h>
+
+#include <cstdlib>
+#include <thread>
+
+#include "InputFrame.h"
+#include "types/Status.h"
+
+namespace android {
+namespace automotive {
+namespace computepipe {
+namespace runner {
+namespace stream_manager {
+
+proto::PacketType SemanticHandle::getType() const {
+    return mType;
+}
+
+uint64_t SemanticHandle::getTimeStamp() const {
+    return mTimestamp;
+}
+
+uint32_t SemanticHandle::getSize() const {
+    return mSize;
+}
+
+const char* SemanticHandle::getData() const {
+    return mData;
+}
+
+AHardwareBuffer* SemanticHandle::getHardwareBuffer() const {
+    return nullptr;
+}
+
+int SemanticHandle::getStreamId() const {
+    return mStreamId;
+}
+
+// Buffer id is not tracked for semantic handle as they do not need a
+// doneWithPacket() call.
+int SemanticHandle::getBufferId() const {
+    return -1;
+}
+
+Status SemanticHandle::setMemInfo(int streamId, const char* data, uint32_t size, uint64_t timestamp,
+                                  const proto::PacketType& type) {
+    if (data == nullptr || size == 0 || size > kMaxSemanticDataSize) {
+        return INVALID_ARGUMENT;
+    }
+    mStreamId = streamId;
+    mData = (char*)malloc(size);
+    if (!mData) {
+        return NO_MEMORY;
+    }
+    memcpy(mData, data, size);
+    mType = type;
+    mTimestamp = timestamp;
+    mSize = size;
+    return SUCCESS;
+}
+
+/* Destroy local copy */
+SemanticHandle::~SemanticHandle() {
+    free(mData);
+}
+
+void SemanticManager::setEngineInterface(std::shared_ptr<StreamEngineInterface> engine) {
+    mEngine = engine;
+    std::lock_guard<std::mutex> lock(mStateLock);
+    mState = RESET;
+}
+
+void SemanticManager::notifyEndOfStream() {
+    mEngine->notifyEndOfStream();
+}
+
+// TODO: b/146495240 Add support for batching
+Status SemanticManager::setMaxInFlightPackets(uint32_t /* maxPackets */) {
+    if (!mEngine) {
+        return ILLEGAL_STATE;
+    }
+    mState = CONFIG_DONE;
+    return SUCCESS;
+}
+
+Status SemanticManager::handleExecutionPhase(const RunnerEvent& e) {
+    std::lock_guard<std::mutex> lock(mStateLock);
+    if (mState == CONFIG_DONE && e.isPhaseEntry()) {
+        mState = RUNNING;
+        return SUCCESS;
+    }
+    if (mState == RESET) {
+        /* Cannot get to running phase from reset state without config phase*/
+        return ILLEGAL_STATE;
+    }
+    if (mState == RUNNING && e.isAborted()) {
+        /* Transition back to config completed */
+        mState = CONFIG_DONE;
+        return SUCCESS;
+    }
+    if (mState == RUNNING) {
+        return ILLEGAL_STATE;
+    }
+    return SUCCESS;
+}
+
+Status SemanticManager::handleStopWithFlushPhase(const RunnerEvent& e) {
+    std::lock_guard<std::mutex> lock(mStateLock);
+    if (mState == CONFIG_DONE || mState == RESET) {
+        return ILLEGAL_STATE;
+    }
+    /* Cannot have stop completed if we never entered stop state */
+    if (mState == RUNNING && (e.isAborted() || e.isTransitionComplete())) {
+        return ILLEGAL_STATE;
+    }
+    /* We are being asked to stop */
+    if (mState == RUNNING && e.isPhaseEntry()) {
+        mState = STOPPED;
+        std::thread t(&SemanticManager::notifyEndOfStream, this);
+        t.detach();
+        return SUCCESS;
+    }
+    /* Other Components have stopped, we can transition back to CONFIG_DONE */
+    if (mState == STOPPED && e.isTransitionComplete()) {
+        mState = CONFIG_DONE;
+        return SUCCESS;
+    }
+    /* We were stopped, but stop was aborted. */
+    if (mState == STOPPED && e.isAborted()) {
+        mState = RUNNING;
+        return SUCCESS;
+    }
+    return SUCCESS;
+}
+
+Status SemanticManager::handleStopImmediatePhase(const RunnerEvent& e) {
+    return handleStopWithFlushPhase(e);
+}
+
+Status SemanticManager::freePacket(int /* bufferId */) {
+    return SUCCESS;
+}
+
+Status SemanticManager::queuePacket(const char* data, const uint32_t size, uint64_t timestamp) {
+    std::lock_guard<std::mutex> lock(mStateLock);
+    // We drop the packet since we have received the stop notifications.
+    if (mState != RUNNING) {
+        return SUCCESS;
+    }
+    // Invalid state.
+    if (mEngine == nullptr) {
+        return INTERNAL_ERROR;
+    }
+    auto memHandle = std::make_shared<SemanticHandle>();
+    auto status = memHandle->setMemInfo(mStreamId, data, size, timestamp, mType);
+    if (status != SUCCESS) {
+        return status;
+    }
+    mEngine->dispatchPacket(memHandle);
+    return SUCCESS;
+}
+
+Status SemanticManager::queuePacket(const InputFrame& /*inputData*/, uint64_t /*timestamp*/) {
+    LOG(ERROR) << "Unexpected call to queue a pixel packet from a semantic stream manager.";
+    return Status::ILLEGAL_STATE;
+}
+
+SemanticManager::SemanticManager(std::string name, int streamId, const proto::PacketType& type)
+    : StreamManager(name, type), mStreamId(streamId) {
+}
+}  // namespace stream_manager
+}  // namespace runner
+}  // namespace computepipe
+}  // namespace automotive
+}  // namespace android
diff --git a/computepipe/runner/stream_manager/SemanticManager.h b/computepipe/runner/stream_manager/SemanticManager.h
new file mode 100644
index 0000000..5be71c5
--- /dev/null
+++ b/computepipe/runner/stream_manager/SemanticManager.h
@@ -0,0 +1,90 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef COMPUTEPIPE_RUNNER_STREAM_MANAGER_SEMANTIC_MANAGER_H
+#define COMPUTEPIPE_RUNNER_STREAM_MANAGER_SEMANTIC_MANAGER_H
+
+#include <mutex>
+
+#include "InputFrame.h"
+#include "OutputConfig.pb.h"
+#include "RunnerComponent.h"
+#include "StreamManager.h"
+#include "StreamManagerInit.h"
+
+namespace android {
+namespace automotive {
+namespace computepipe {
+namespace runner {
+namespace stream_manager {
+
+class SemanticHandle : public MemHandle {
+  public:
+    static constexpr uint32_t kMaxSemanticDataSize = 1024;
+    /**
+     * Override mem handle methods
+     */
+    int getStreamId() const override;
+    int getBufferId() const override;
+    proto::PacketType getType() const override;
+    uint64_t getTimeStamp() const override;
+    uint32_t getSize() const override;
+    const char* getData() const override;
+    AHardwareBuffer* getHardwareBuffer() const override;
+    /* set info for the memory. Make a copy */
+    Status setMemInfo(int streamId, const char* data, uint32_t size, uint64_t timestamp,
+                      const proto::PacketType& type);
+    /* Destroy local copy */
+    ~SemanticHandle();
+
+  private:
+    char* mData = nullptr;
+    uint32_t mSize;
+    uint64_t mTimestamp;
+    proto::PacketType mType;
+    int mStreamId;
+};
+
+class SemanticManager : public StreamManager, StreamManagerInit {
+  public:
+    void setEngineInterface(std::shared_ptr<StreamEngineInterface> engine) override;
+    /* Set Max in flight packets based on client specification */
+    Status setMaxInFlightPackets(uint32_t maxPackets) override;
+    /* Free previously dispatched packet. Once client has confirmed usage */
+    Status freePacket(int bufferId) override;
+    /* Queue packet produced by graph stream */
+    Status queuePacket(const char* data, const uint32_t size, uint64_t timestamp) override;
+    /* Queues an image packet produced by graph stream */
+    Status queuePacket(const InputFrame& inputData, uint64_t timestamp) override;
+    /* Override handling of Runner Engine Events */
+    void notifyEndOfStream();
+
+    Status handleExecutionPhase(const RunnerEvent& e) override;
+    Status handleStopWithFlushPhase(const RunnerEvent& e) override;
+    Status handleStopImmediatePhase(const RunnerEvent& e) override;
+
+    explicit SemanticManager(std::string name, int streamId, const proto::PacketType& type);
+    ~SemanticManager() = default;
+
+  private:
+    std::mutex mStateLock;
+    int mStreamId;
+    std::shared_ptr<StreamEngineInterface> mEngine;
+};
+}  // namespace stream_manager
+}  // namespace runner
+}  // namespace computepipe
+}  // namespace automotive
+}  // namespace android
+#endif
diff --git a/computepipe/runner/stream_manager/StreamManagerInit.h b/computepipe/runner/stream_manager/StreamManagerInit.h
new file mode 100644
index 0000000..2464ca3
--- /dev/null
+++ b/computepipe/runner/stream_manager/StreamManagerInit.h
@@ -0,0 +1,44 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef COMPUTEPIPE_RUNNER_STREAM_MANAGER_INIT_H
+#define COMPUTEPIPE_RUNNER_STREAM_MANAGER_INIT_H
+
+#include <functional>
+#include <memory>
+
+#include "MemHandle.h"
+#include "StreamEngineInterface.h"
+#include "types/Status.h"
+
+namespace android {
+namespace automotive {
+namespace computepipe {
+namespace runner {
+namespace stream_manager {
+
+class StreamManagerInit {
+  public:
+    virtual void setEngineInterface(std::shared_ptr<StreamEngineInterface> engine) = 0;
+    /* Set Max in flight packets based on client specification */
+    virtual Status setMaxInFlightPackets(uint32_t maxPackets) = 0;
+    virtual ~StreamManagerInit() = default;
+};
+
+}  // namespace stream_manager
+}  // namespace runner
+}  // namespace computepipe
+}  // namespace automotive
+}  // namespace android
+#endif
diff --git a/computepipe/runner/stream_manager/include/StreamEngineInterface.h b/computepipe/runner/stream_manager/include/StreamEngineInterface.h
new file mode 100644
index 0000000..418adeb
--- /dev/null
+++ b/computepipe/runner/stream_manager/include/StreamEngineInterface.h
@@ -0,0 +1,56 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef COMPUTEPIPE_RUNNER_STREAM_ENGINE_INTERFACE_H
+#define COMPUTEPIPE_RUNNER_STREAM_ENGINE_INTERFACE_H
+
+#include "MemHandle.h"
+#include "types/Status.h"
+
+namespace android {
+namespace automotive {
+namespace computepipe {
+namespace runner {
+namespace stream_manager {
+
+/**
+ * Stream manager -> Engine interface.
+ */
+class StreamEngineInterface {
+  public:
+    /**
+     * Does not block on the remote client to handle the packet.
+     */
+    virtual Status dispatchPacket(const std::shared_ptr<MemHandle>& outData) = 0;
+    /**
+     * After receiving StopWithFlush, once all outstanding packets have been
+     * freed by the client, notify the engine of end of stream.
+     * Should not be called in the thread that initiates the StopWithFlush, but
+     * rather in a separate thread
+     */
+    virtual void notifyEndOfStream() = 0;
+    /**
+     * Notify engine of error
+     */
+    virtual void notifyError(std::string msg) = 0;
+    virtual ~StreamEngineInterface() = default;
+};
+
+}  // namespace stream_manager
+}  // namespace runner
+}  // namespace computepipe
+}  // namespace automotive
+}  // namespace android
+
+#endif
diff --git a/computepipe/runner/stream_manager/include/StreamManager.h b/computepipe/runner/stream_manager/include/StreamManager.h
new file mode 100644
index 0000000..eb69a23
--- /dev/null
+++ b/computepipe/runner/stream_manager/include/StreamManager.h
@@ -0,0 +1,103 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef COMPUTEPIPE_RUNNER_STREAM_MANAGER_H
+#define COMPUTEPIPE_RUNNER_STREAM_MANAGER_H
+
+#include <functional>
+#include <memory>
+#include <string>
+
+#include "InputFrame.h"
+#include "MemHandle.h"
+#include "OutputConfig.pb.h"
+#include "RunnerComponent.h"
+#include "StreamEngineInterface.h"
+#include "types/Status.h"
+
+namespace android {
+namespace automotive {
+namespace computepipe {
+namespace runner {
+namespace stream_manager {
+
+/**
+ * Manager the operations of an output stream from the graph.
+ * Should be constructed using the StreamManagerFactory.
+ * The manager instance for a given stream depends on the streams
+ * description specified in OuputConfig.
+ */
+
+class StreamManager : public RunnerComponentInterface {
+  public:
+    enum State {
+        /* State on construction. */
+        RESET = 0,
+        /* State once inflight packets set */
+        CONFIG_DONE = 1,
+        /* State once handleRunPhase() notifies of entry to run phase */
+        RUNNING = 2,
+        /**
+         * State once stop issued
+         * Returns to config done, once all in flight packets handled.
+         */
+        STOPPED = 3,
+    };
+    /* Constructor for StreamManager Base class */
+    explicit StreamManager(std::string streamName, const proto::PacketType& type)
+        : mName(streamName), mType(type) {
+    }
+    /* Retrieves the current state */
+    State getState() {
+        return mState;
+    }
+    /* Frees previously dispatched packet based on bufferID. Once client has confirmed usage */
+    virtual Status freePacket(int bufferId) = 0;
+    /* Queue's packet produced by graph stream */
+    virtual Status queuePacket(const char* data, const uint32_t size, uint64_t timestamp) = 0;
+    /* Queues a pixel stream packet produced by graph stream */
+    virtual Status queuePacket(const InputFrame& pixelData, uint64_t timestamp) = 0;
+    /* Destructor */
+    virtual ~StreamManager() = default;
+
+  protected:
+    std::string mName;
+    proto::PacketType mType;
+    State mState = RESET;
+};
+
+/**
+ * Factory for generating stream manager instances
+ * It initializes the instances for a given client configuration prior to
+ * returning. (Follows RAII semantics)
+ */
+class StreamManagerFactory {
+  public:
+    std::unique_ptr<StreamManager> getStreamManager(const proto::OutputConfig& config,
+                                                    std::shared_ptr<StreamEngineInterface> engine,
+                                                    uint32_t maxInFlightPackets);
+    StreamManagerFactory(const StreamManagerFactory&) = delete;
+    StreamManagerFactory(const StreamManagerFactory&&) = delete;
+    StreamManagerFactory& operator=(const StreamManagerFactory&&) = delete;
+    StreamManagerFactory& operator=(const StreamManagerFactory&) = delete;
+    StreamManagerFactory() = default;
+};
+
+}  // namespace stream_manager
+}  // namespace runner
+}  // namespace computepipe
+}  // namespace automotive
+}  // namespace android
+
+#endif
diff --git a/computepipe/sepolicy/private/computepipe_router.te b/computepipe/sepolicy/private/computepipe_router.te
new file mode 100644
index 0000000..b28dbc9
--- /dev/null
+++ b/computepipe/sepolicy/private/computepipe_router.te
@@ -0,0 +1,13 @@
+# computepipe router
+type computepipe_router_exec, exec_type, file_type, system_file_type;
+
+# allow init to launch processes in this context
+init_daemon_domain(computepipe_router)
+
+# allow this domain to instantiate query and registration interface
+add_service(computepipe_router, computepipe_router_query_service)
+add_service(computepipe_router, computepipe_router_registration_service)
+
+binder_use(computepipe_router)
+
+binder_service(computepipe_router)
diff --git a/computepipe/sepolicy/private/computepipeclientdomain.te b/computepipe/sepolicy/private/computepipeclientdomain.te
new file mode 100644
index 0000000..cbf0116
--- /dev/null
+++ b/computepipe/sepolicy/private/computepipeclientdomain.te
@@ -0,0 +1,9 @@
+###
+### Rules for all domains which are computepipe clients
+###
+
+# Allow binder use
+binder_use(computepipe_client_domain)
+
+# Allow look up using service manager
+allow computepipe_client_domain computepipe_router_query_service:service_manager find;
diff --git a/computepipe/sepolicy/private/computepiperunnerdomain.te b/computepipe/sepolicy/private/computepiperunnerdomain.te
new file mode 100644
index 0000000..65bafd3
--- /dev/null
+++ b/computepipe/sepolicy/private/computepiperunnerdomain.te
@@ -0,0 +1,9 @@
+###
+### Rules for all domains which are computepipe clients
+###
+
+# Allow binder use
+binder_use(computepipe_runner_domain)
+
+# Allow look up using service manager
+allow computepipe_runner_domain computepipe_router_registration_service:service_manager find;
diff --git a/computepipe/sepolicy/private/file_contexts b/computepipe/sepolicy/private/file_contexts
new file mode 100644
index 0000000..663cc7b
--- /dev/null
+++ b/computepipe/sepolicy/private/file_contexts
@@ -0,0 +1 @@
+/system/bin/android\.automotive\.computepipe\.router@1\.0  u:object_r:computepipe_router_exec:s0
diff --git a/computepipe/sepolicy/private/service_contexts b/computepipe/sepolicy/private/service_contexts
new file mode 100644
index 0000000..3ca3cd7
--- /dev/null
+++ b/computepipe/sepolicy/private/service_contexts
@@ -0,0 +1,2 @@
+android.automotive.computepipe.registry.IPipeQuery/router u:object_r:computepipe_router_query_service:s0
+android.automotive.computepipe.registry.IPipeRegistration/router u:object_r:computepipe_router_registration_service:s0
diff --git a/computepipe/sepolicy/public/attributes b/computepipe/sepolicy/public/attributes
new file mode 100644
index 0000000..6620ef0
--- /dev/null
+++ b/computepipe/sepolicy/public/attributes
@@ -0,0 +1,6 @@
+# All computepipe clients
+attribute computepipe_client_domain;
+expandattribute computepipe_client_domain true;
+
+# All computepipe runners
+attribute computepipe_runner_domain;
diff --git a/computepipe/sepolicy/public/computepipe_router.te b/computepipe/sepolicy/public/computepipe_router.te
new file mode 100644
index 0000000..1079226
--- /dev/null
+++ b/computepipe/sepolicy/public/computepipe_router.te
@@ -0,0 +1 @@
+type computepipe_router, domain, coredomain;
diff --git a/computepipe/sepolicy/public/service.te b/computepipe/sepolicy/public/service.te
new file mode 100644
index 0000000..1b17106
--- /dev/null
+++ b/computepipe/sepolicy/public/service.te
@@ -0,0 +1,3 @@
+type computepipe_router_query_service, service_manager_type;
+type computepipe_router_registration_service, service_manager_type;
+type computepipe_router_service, service_manager_type;
diff --git a/computepipe/tests/Android.bp b/computepipe/tests/Android.bp
new file mode 100644
index 0000000..ec26177
--- /dev/null
+++ b/computepipe/tests/Android.bp
@@ -0,0 +1,61 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+cc_test {
+    name: "piperegistration_test",
+    test_suites: ["device-tests"],
+    srcs: [
+        "PipeRegistrationTest.cpp",
+        "FakeRunner.cpp",
+    ],
+    static_libs: [
+        "libgtest",
+        "libgmock",
+    ],
+    header_libs: [
+        "computepipe_router_headers",
+    ],
+    shared_libs: [
+        "libbinder_ndk",
+        "libbinder",
+        "libutils",
+        "android.automotive.computepipe.router@1.0-impl",
+        "android.automotive.computepipe.runner-ndk_platform",
+        "android.automotive.computepipe.registry-ndk_platform",
+    ],
+}
+
+cc_test {
+    name: "pipequery_test",
+    test_suites: ["device-tests"],
+    srcs: [
+        "PipeQueryTest.cpp",
+	"FakeRunner.cpp",
+    ],
+    static_libs: [
+        "libgtest",
+        "libgmock",
+    ],
+    header_libs: [
+      "computepipe_router_headers",
+    ],
+    shared_libs: [
+        "libbinder",
+        "libbinder_ndk",
+        "libutils",
+        "android.automotive.computepipe.router@1.0-impl",
+        "android.automotive.computepipe.runner-ndk_platform",
+        "android.automotive.computepipe.registry-ndk_platform",
+    ],
+}
diff --git a/computepipe/tests/FakeRunner.cpp b/computepipe/tests/FakeRunner.cpp
new file mode 100644
index 0000000..388b7ff
--- /dev/null
+++ b/computepipe/tests/FakeRunner.cpp
@@ -0,0 +1,104 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "FakeRunner.h"
+
+namespace android {
+namespace automotive {
+namespace computepipe {
+namespace tests {
+
+using namespace aidl::android::automotive::computepipe::runner;
+
+// Methods from ::android::automotive::computepipe::runner::V1_0::IFakeRunnerV1_0 follow.
+
+::ndk::ScopedAStatus FakeRunner::init(
+    const std::shared_ptr<
+        ::aidl::android::automotive::computepipe::runner::IPipeStateCallback>& /* in_statecb */) {
+    return ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus FakeRunner::getPipeDescriptor(
+    ::aidl::android::automotive::computepipe::runner::PipeDescriptor* desc) {
+    *desc = mDesc;
+    return ndk::ScopedAStatus::ok();
+}
+::ndk::ScopedAStatus FakeRunner::setPipeInputSource(int32_t /*in_configId*/) {
+    ::ndk::ScopedAStatus _aidl_status;
+    _aidl_status.set(AStatus_fromStatus(STATUS_UNKNOWN_TRANSACTION));
+    return _aidl_status;
+}
+::ndk::ScopedAStatus FakeRunner::setPipeOffloadOptions(int32_t /*in_configId*/) {
+    ::ndk::ScopedAStatus _aidl_status;
+    _aidl_status.set(AStatus_fromStatus(STATUS_UNKNOWN_TRANSACTION));
+    return _aidl_status;
+}
+::ndk::ScopedAStatus FakeRunner::setPipeTermination(int32_t /*in_configId*/) {
+    ::ndk::ScopedAStatus _aidl_status;
+    _aidl_status.set(AStatus_fromStatus(STATUS_UNKNOWN_TRANSACTION));
+    return _aidl_status;
+}
+
+::ndk::ScopedAStatus FakeRunner::setPipeOutputConfig(
+    int32_t /*in_configId*/, int32_t /*in_maxInFlightCount*/,
+    const std::shared_ptr<
+        ::aidl::android::automotive::computepipe::runner::IPipeStream>& /*in_handler*/) {
+    ::ndk::ScopedAStatus _aidl_status;
+    _aidl_status.set(AStatus_fromStatus(STATUS_UNKNOWN_TRANSACTION));
+    return _aidl_status;
+}
+::ndk::ScopedAStatus FakeRunner::applyPipeConfigs() {
+    ::ndk::ScopedAStatus _aidl_status;
+    _aidl_status.set(AStatus_fromStatus(STATUS_UNKNOWN_TRANSACTION));
+    return _aidl_status;
+}
+
+::ndk::ScopedAStatus FakeRunner::resetPipeConfigs() {
+    ::ndk::ScopedAStatus _aidl_status;
+    _aidl_status.set(AStatus_fromStatus(STATUS_UNKNOWN_TRANSACTION));
+    return _aidl_status;
+}
+
+::ndk::ScopedAStatus FakeRunner::startPipe() {
+    ::ndk::ScopedAStatus _aidl_status;
+    _aidl_status.set(AStatus_fromStatus(STATUS_UNKNOWN_TRANSACTION));
+    return _aidl_status;
+}
+::ndk::ScopedAStatus FakeRunner::stopPipe() {
+    ::ndk::ScopedAStatus _aidl_status;
+    _aidl_status.set(AStatus_fromStatus(STATUS_UNKNOWN_TRANSACTION));
+    return _aidl_status;
+}
+::ndk::ScopedAStatus FakeRunner::doneWithPacket(int32_t /*in_bufferId*/, int32_t /*in_streamId*/) {
+    ::ndk::ScopedAStatus _aidl_status;
+    _aidl_status.set(AStatus_fromStatus(STATUS_UNKNOWN_TRANSACTION));
+    return _aidl_status;
+}
+::ndk::ScopedAStatus FakeRunner::getPipeDebugger(
+    std::shared_ptr<::aidl::android::automotive::computepipe::runner::IPipeDebugger>* /*_aidl_return*/) {
+    ::ndk::ScopedAStatus _aidl_status;
+    _aidl_status.set(AStatus_fromStatus(STATUS_UNKNOWN_TRANSACTION));
+    return _aidl_status;
+}
+::ndk::ScopedAStatus FakeRunner::releaseRunner() {
+    ::ndk::ScopedAStatus _aidl_status;
+    _aidl_status.set(AStatus_fromStatus(STATUS_UNKNOWN_TRANSACTION));
+    return _aidl_status;
+}
+}  // namespace tests
+}  // namespace computepipe
+}  // namespace automotive
+}  // namespace android
diff --git a/computepipe/tests/FakeRunner.h b/computepipe/tests/FakeRunner.h
new file mode 100644
index 0000000..d87d0c7
--- /dev/null
+++ b/computepipe/tests/FakeRunner.h
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_AUTOMOTIVE_COMPUTEPIPE_TESTS
+#define ANDROID_AUTOMOTIVE_COMPUTEPIPE_TESTS
+
+#include <aidl/android/automotive/computepipe/runner/BnPipeRunner.h>
+#include <aidl/android/automotive/computepipe/runner/IPipeRunner.h>
+
+#include <memory>
+
+namespace android {
+namespace automotive {
+namespace computepipe {
+namespace tests {
+
+// TODO: Wrap under version flag
+
+// This is a fake runner class whose various methods can be mocked in order
+// to test the Runner logic.
+
+class FakeRunner : public aidl::android::automotive::computepipe::runner::BnPipeRunner {
+  public:
+    ::ndk::ScopedAStatus init(
+        const std::shared_ptr<::aidl::android::automotive::computepipe::runner::IPipeStateCallback>&
+            in_statecb) override;
+    ::ndk::ScopedAStatus getPipeDescriptor(
+        ::aidl::android::automotive::computepipe::runner::PipeDescriptor* _aidl_return) override;
+    ::ndk::ScopedAStatus setPipeInputSource(int32_t in_configId) override;
+    ::ndk::ScopedAStatus setPipeOffloadOptions(int32_t in_configId) override;
+    ::ndk::ScopedAStatus setPipeTermination(int32_t in_configId) override;
+    ::ndk::ScopedAStatus setPipeOutputConfig(
+        int32_t in_configId, int32_t in_maxInFlightCount,
+        const std::shared_ptr<::aidl::android::automotive::computepipe::runner::IPipeStream>&
+            in_handler) override;
+    ::ndk::ScopedAStatus applyPipeConfigs() override;
+    ::ndk::ScopedAStatus resetPipeConfigs() override;
+    ::ndk::ScopedAStatus startPipe() override;
+    ::ndk::ScopedAStatus stopPipe() override;
+    ::ndk::ScopedAStatus doneWithPacket(int32_t in_bufferId, int32_t in_streamId) override;
+    ::ndk::ScopedAStatus getPipeDebugger(
+        std::shared_ptr<::aidl::android::automotive::computepipe::runner::IPipeDebugger>*
+            _aidl_return) override;
+    ::ndk::ScopedAStatus releaseRunner() override;
+    ~FakeRunner() {
+        mOutputCallbacks.clear();
+    }
+
+  private:
+    aidl::android::automotive::computepipe::runner::PipeDescriptor mDesc;
+    std::vector<std::shared_ptr<aidl::android::automotive::computepipe::runner::IPipeStream>>
+        mOutputCallbacks;
+    std::shared_ptr<aidl::android::automotive::computepipe::runner::IPipeStateCallback> mStateCallback;
+};
+
+}  // namespace tests
+}  // namespace computepipe
+}  // namespace automotive
+}  // namespace android
+#endif
diff --git a/computepipe/tests/PipeQueryTest.cpp b/computepipe/tests/PipeQueryTest.cpp
new file mode 100644
index 0000000..634ebd0
--- /dev/null
+++ b/computepipe/tests/PipeQueryTest.cpp
@@ -0,0 +1,129 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <aidl/android/automotive/computepipe/registry/BnClientInfo.h>
+#include <binder/IInterface.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include <list>
+#include <memory>
+#include <string>
+#include <utility>
+
+#include "FakeRunner.h"
+#include "PipeClient.h"
+#include "PipeQuery.h"
+#include "PipeRunner.h"
+
+using namespace android::automotive::computepipe::router;
+using namespace android::automotive::computepipe::router::V1_0::implementation;
+using namespace android::automotive::computepipe::tests;
+using namespace aidl::android::automotive::computepipe::runner;
+using namespace aidl::android::automotive::computepipe::registry;
+using namespace ::testing;
+
+/**
+ * Fakeclass to instantiate client info for query purposes
+ */
+class FakeClientInfo : public BnClientInfo {
+  public:
+    ndk::ScopedAStatus getClientName(std::string* name) override {
+        *name = "FakeClient";
+        return ndk::ScopedAStatus::ok();
+    }
+};
+
+/**
+ * Class that exposes protected interfaces of PipeRegistry
+ * a) Used to retrieve entries without client ref counts
+ * b) Used to remove entries
+ */
+class FakeRegistry : public PipeRegistry<PipeRunner> {
+  public:
+    std ::unique_ptr<PipeHandle<PipeRunner>> getDebuggerPipeHandle(const std::string& name) {
+        return getPipeHandle(name, nullptr);
+    }
+    Error RemoveEntry(const std::string& name) {
+        return DeletePipeHandle(name);
+    }
+};
+
+/**
+ * Test Fixture class that is responsible for maintaining a registry.
+ * The registry object is used to test the query interfaces
+ */
+class PipeQueryTest : public ::testing::Test {
+  protected:
+    /**
+     * Setup for the test fixture to initialize a registry to be used in all
+     * tests
+     */
+    void SetUp() override {
+        mRegistry = std::make_shared<FakeRegistry>();
+    }
+    /**
+     * Utility to generate fake runners
+     */
+    void addFakeRunner(const std::string& name, const std::shared_ptr<IPipeRunner>& runnerIface) {
+        std::unique_ptr<PipeHandle<PipeRunner>> handle = std::make_unique<RunnerHandle>(runnerIface);
+        Error status = mRegistry->RegisterPipe(std::move(handle), name);
+        ASSERT_THAT(status, testing::Eq(Error::OK));
+    }
+    /**
+     * Utility to remove runners from the registry
+     */
+    void removeRunner(const std::string& name) {
+        ASSERT_THAT(mRegistry->RemoveEntry(name), testing::Eq(Error::OK));
+    }
+    /**
+     * Tear down to cleanup registry resources
+     */
+    void TearDown() override {
+        mRegistry = nullptr;
+    }
+    std::shared_ptr<FakeRegistry> mRegistry;
+};
+
+// Check retrieval of inserted entries
+TEST_F(PipeQueryTest, GetGraphListTest) {
+    std::shared_ptr<IPipeRunner> dummy1 = ndk::SharedRefBase::make<FakeRunner>();
+    addFakeRunner("dummy1", dummy1);
+    std::shared_ptr<IPipeRunner> dummy2 = ndk::SharedRefBase::make<FakeRunner>();
+    addFakeRunner("dummy2", dummy2);
+
+    std::vector<std::string>* outNames = new std::vector<std::string>();
+    std::shared_ptr<PipeQuery> qIface = ndk::SharedRefBase::make<PipeQuery>(mRegistry);
+    ASSERT_TRUE(qIface->getGraphList(outNames).isOk());
+
+    ASSERT_NE(outNames->size(), 0);
+    EXPECT_THAT(std::find(outNames->begin(), outNames->end(), "dummy1"),
+                testing::Ne(outNames->end()));
+    EXPECT_THAT(std::find(outNames->begin(), outNames->end(), "dummy2"),
+                testing::Ne(outNames->end()));
+}
+
+// Check successful retrieval of runner
+TEST_F(PipeQueryTest, GetRunnerTest) {
+    std::shared_ptr<IPipeRunner> dummy1 = ndk::SharedRefBase::make<FakeRunner>();
+    addFakeRunner("dummy1", dummy1);
+
+    std::shared_ptr<PipeQuery> qIface = ndk::SharedRefBase::make<PipeQuery>(mRegistry);
+    std::shared_ptr<IClientInfo> info = ndk::SharedRefBase::make<FakeClientInfo>();
+    std::shared_ptr<IPipeRunner> runner;
+    ASSERT_TRUE(qIface->getPipeRunner("dummy1", info, &runner).isOk());
+    EXPECT_THAT(runner, testing::NotNull());
+}
diff --git a/computepipe/tests/PipeRegistrationTest.cpp b/computepipe/tests/PipeRegistrationTest.cpp
new file mode 100644
index 0000000..1caf07b
--- /dev/null
+++ b/computepipe/tests/PipeRegistrationTest.cpp
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <stdio.h>
+
+#include <list>
+#include <memory>
+#include <string>
+#include <utility>
+
+#include "FakeRunner.h"
+#include "PipeRegistration.h"
+#include "PipeRunner.h"
+
+using namespace android::automotive::computepipe::router;
+using namespace android::automotive::computepipe::router::V1_0::implementation;
+using namespace android::automotive::computepipe::tests;
+using namespace aidl::android::automotive::computepipe::runner;
+using namespace aidl::android::automotive::computepipe::registry;
+/**
+ * Test fixture that manages the underlying registry creation and tear down
+ */
+class PipeRegistrationTest : public ::testing::Test {
+  protected:
+    void SetUp() override {
+        mRegistry = std::make_shared<PipeRegistry<PipeRunner>>();
+        ASSERT_THAT(mRegistry, testing::NotNull());
+    }
+
+    void TearDown() override {
+        mRegistry = nullptr;
+    }
+    std::shared_ptr<PipeRegistry<PipeRunner>> mRegistry;
+};
+
+// Valid registration succeeds
+TEST_F(PipeRegistrationTest, RegisterFakeRunner) {
+    std::shared_ptr<IPipeRunner> dummy = ndk::SharedRefBase::make<FakeRunner>();
+    std::shared_ptr<IPipeRegistration> rIface =
+        ndk::SharedRefBase::make<PipeRegistration>(this->mRegistry);
+    EXPECT_TRUE(rIface->registerPipeRunner("dummy", dummy).isOk());
+}
+
+// Duplicate registration fails
+TEST_F(PipeRegistrationTest, RegisterDuplicateRunner) {
+    std::shared_ptr<IPipeRunner> dummy = ndk::SharedRefBase::make<FakeRunner>();
+    std::shared_ptr<IPipeRegistration> rIface =
+        ndk::SharedRefBase::make<PipeRegistration>(this->mRegistry);
+    ASSERT_TRUE(rIface->registerPipeRunner("dummy", dummy).isOk());
+    EXPECT_FALSE(rIface->registerPipeRunner("dummy", dummy).isOk());
+}
diff --git a/computepipe/tests/runner/client_interface/Android.bp b/computepipe/tests/runner/client_interface/Android.bp
new file mode 100644
index 0000000..2ff5d2c
--- /dev/null
+++ b/computepipe/tests/runner/client_interface/Android.bp
@@ -0,0 +1,43 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+cc_test {
+    name: "clientinterface_test",
+    test_suites: ["device-tests"],
+    srcs: [
+        "ClientInterfaceTest.cc",
+	"PipeOptionsConverterTest.cpp",
+    ],
+    static_libs: [
+        "libgtest",
+        "libgmock",
+        "libcomputepipeprotos",
+    ],
+    header_libs: [
+        "computepipe_router_headers",
+        "computepipe_runner_includes",
+    ],
+    include_dirs: ["packages/services/Car/computepipe"],
+    shared_libs: [
+        "libbinder_ndk",
+        "libutils",
+	"libnativewindow",
+        "computepipe_client_interface",
+        "computepipe_runner_component",
+        "android.automotive.computepipe.registry-ndk_platform",
+        "android.automotive.computepipe.runner-ndk_platform",
+        "android.automotive.computepipe.runner-ndk_platform",
+        "libprotobuf-cpp-lite",
+    ],
+}
diff --git a/computepipe/tests/runner/client_interface/ClientInterfaceTest.cc b/computepipe/tests/runner/client_interface/ClientInterfaceTest.cc
new file mode 100644
index 0000000..619c23e
--- /dev/null
+++ b/computepipe/tests/runner/client_interface/ClientInterfaceTest.cc
@@ -0,0 +1,405 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <aidl/android/automotive/computepipe/registry/BnClientInfo.h>
+#include <aidl/android/automotive/computepipe/registry/IPipeQuery.h>
+#include <aidl/android/automotive/computepipe/registry/IPipeRegistration.h>
+#include <aidl/android/automotive/computepipe/runner/BnPipeStateCallback.h>
+#include <aidl/android/automotive/computepipe/runner/BnPipeStream.h>
+#include <aidl/android/automotive/computepipe/runner/PipeState.h>
+#include <android/binder_manager.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include <utility>
+#include <vector>
+
+#include "ConfigurationCommand.pb.h"
+#include "ControlCommand.pb.h"
+#include "MemHandle.h"
+#include "MockEngine.h"
+#include "MockMemHandle.h"
+#include "MockRunnerEvent.h"
+#include "Options.pb.h"
+#include "runner/client_interface/AidlClient.h"
+#include "runner/client_interface/include/ClientEngineInterface.h"
+#include "types/Status.h"
+
+namespace android {
+namespace automotive {
+namespace computepipe {
+namespace runner {
+namespace client_interface {
+namespace aidl_client {
+namespace {
+
+using ::aidl::android::automotive::computepipe::registry::BnClientInfo;
+using ::aidl::android::automotive::computepipe::registry::IPipeQuery;
+using ::aidl::android::automotive::computepipe::runner::BnPipeStateCallback;
+using ::aidl::android::automotive::computepipe::runner::BnPipeStream;
+using ::aidl::android::automotive::computepipe::runner::IPipeRunner;
+using ::aidl::android::automotive::computepipe::runner::IPipeStateCallback;
+using ::aidl::android::automotive::computepipe::runner::PacketDescriptor;
+using ::aidl::android::automotive::computepipe::runner::PipeState;
+using ::android::automotive::computepipe::runner::tests::MockRunnerEvent;
+using ::android::automotive::computepipe::tests::MockMemHandle;
+using ::ndk::ScopedAStatus;
+using ::ndk::SharedRefBase;
+using ::testing::_;
+using ::testing::AnyNumber;
+using ::testing::AtLeast;
+using ::testing::DoAll;
+using ::testing::Return;
+using ::testing::SaveArg;
+
+const char kRegistryInterfaceName[] = "router";
+int testIx = 0;
+
+class StateChangeCallback : public BnPipeStateCallback {
+  public:
+    ScopedAStatus handleState(PipeState state) {
+        mState = state;
+        return ScopedAStatus::ok();
+    }
+    PipeState mState = PipeState::RESET;
+};
+
+class StreamCallback : public BnPipeStream {
+  public:
+    ScopedAStatus deliverPacket(const PacketDescriptor& in_packet) override {
+        data = std::string(in_packet.data.begin(), in_packet.data.end());
+        timestamp = in_packet.sourceTimeStampMillis;
+        return ScopedAStatus::ok();
+    }
+    std::string data;
+    uint64_t timestamp;
+};
+
+class ClientInfo : public BnClientInfo {
+  public:
+    ScopedAStatus getClientName(std::string* _aidl_return) {
+        if (_aidl_return) {
+            *_aidl_return = "ClientInfo";
+            return ScopedAStatus::ok();
+        }
+        return ScopedAStatus::fromExceptionCode(EX_TRANSACTION_FAILED);
+    }
+};
+
+class ClientInterface : public ::testing::Test {
+  protected:
+    void SetUp() override {
+        const std::string graphName = "graph " + std::to_string(++testIx);
+        proto::Options options;
+        options.set_graph_name(graphName);
+        mAidlClient = std::make_unique<AidlClient>(options, mEngine);
+
+        // Register the instance with router.
+        EXPECT_EQ(mAidlClient->activate(), Status::SUCCESS);
+
+        // Init is not a blocking call, so sleep for 3 seconds to allow the runner to register with
+        // router.
+        sleep(3);
+
+        // Retrieve router query instance from service manager.
+        std::string instanceName =
+            std::string() + IPipeQuery::descriptor + "/" + kRegistryInterfaceName;
+        ndk::SpAIBinder binder(AServiceManager_getService(instanceName.c_str()));
+        ASSERT_TRUE(binder.get() != nullptr);
+        std::shared_ptr<IPipeQuery> queryService = IPipeQuery::fromBinder(binder);
+
+        // Retrieve pipe runner instance from the router.
+        std::shared_ptr<ClientInfo> clientInfo = ndk::SharedRefBase::make<ClientInfo>();
+        ASSERT_TRUE(queryService->getPipeRunner(graphName, clientInfo, &mPipeRunner).isOk());
+    }
+
+    std::shared_ptr<tests::MockEngine> mEngine = std::make_unique<tests::MockEngine>();
+    std::shared_ptr<AidlClient> mAidlClient = nullptr;
+    std::shared_ptr<IPipeRunner> mPipeRunner = nullptr;
+};
+
+TEST_F(ClientInterface, TestSetConfiguration) {
+    proto::ConfigurationCommand command;
+
+    // Configure runner to return success.
+    EXPECT_CALL(*mEngine, processClientConfigUpdate(_))
+        .Times(AtLeast(4))
+        .WillRepeatedly(DoAll(SaveArg<0>(&command), Return(Status::SUCCESS)));
+
+    // Initialize pipe runner.
+    std::shared_ptr<StateChangeCallback> stateCallback =
+        ndk::SharedRefBase::make<StateChangeCallback>();
+    EXPECT_TRUE(mPipeRunner->init(stateCallback).isOk());
+
+    // Test that set input source returns ok status.
+    EXPECT_TRUE(mPipeRunner->setPipeInputSource(1).isOk());
+    EXPECT_EQ(command.has_set_input_source(), true);
+    EXPECT_EQ(command.set_input_source().source_id(), 1);
+
+    // Test that set offload option returns ok status.
+    EXPECT_TRUE(mPipeRunner->setPipeOffloadOptions(5).isOk());
+    EXPECT_EQ(command.has_set_offload_offload(), true);
+    EXPECT_EQ(command.set_offload_offload().offload_option_id(), 5);
+
+    // Test that set termination option returns ok status.
+    EXPECT_TRUE(mPipeRunner->setPipeTermination(3).isOk());
+    EXPECT_EQ(command.has_set_termination_option(), true);
+    EXPECT_EQ(command.set_termination_option().termination_option_id(), 3);
+
+    // Test that set output callback returns ok status.
+    std::shared_ptr<StreamCallback> streamCb = ndk::SharedRefBase::make<StreamCallback>();
+    EXPECT_TRUE(mPipeRunner->setPipeOutputConfig(0, 10, streamCb).isOk());
+    EXPECT_EQ(command.has_set_output_stream(), true);
+    EXPECT_EQ(command.set_output_stream().stream_id(), 0);
+    EXPECT_EQ(command.set_output_stream().max_inflight_packets_count(), 10);
+
+    // Release runner here. This should remove registry entry from router registry.
+    mAidlClient.reset();
+}
+
+TEST_F(ClientInterface, TestSetConfigurationError) {
+    proto::ConfigurationCommand command;
+
+    // Configure runner to return error.
+    EXPECT_CALL(*mEngine, processClientConfigUpdate(_))
+        .Times(AtLeast(4))
+        .WillRepeatedly(DoAll(SaveArg<0>(&command), Return(Status::INTERNAL_ERROR)));
+
+    ScopedAStatus status;
+
+    // Initialize pipe runner.
+    std::shared_ptr<StateChangeCallback> stateCallback =
+        ndk::SharedRefBase::make<StateChangeCallback>();
+    EXPECT_TRUE(mPipeRunner->init(stateCallback).isOk());
+
+    // Test that set input source returns error status.
+    status = mPipeRunner->setPipeInputSource(1);
+    EXPECT_EQ(status.getExceptionCode(), EX_TRANSACTION_FAILED);
+    EXPECT_EQ(command.has_set_input_source(), true);
+    EXPECT_EQ(command.set_input_source().source_id(), 1);
+
+    // Test that set offload option returns error status.
+    status = mPipeRunner->setPipeOffloadOptions(5);
+    EXPECT_EQ(status.getExceptionCode(), EX_TRANSACTION_FAILED);
+    EXPECT_EQ(command.has_set_offload_offload(), true);
+    EXPECT_EQ(command.set_offload_offload().offload_option_id(), 5);
+
+    // Test that set termination option returns error status.
+    status = mPipeRunner->setPipeTermination(3);
+    EXPECT_EQ(status.getExceptionCode(), EX_TRANSACTION_FAILED);
+    EXPECT_EQ(command.has_set_termination_option(), true);
+    EXPECT_EQ(command.set_termination_option().termination_option_id(), 3);
+
+    // Test that set output callback returns error status.
+    std::shared_ptr<StreamCallback> streamCb = ndk::SharedRefBase::make<StreamCallback>();
+    status = mPipeRunner->setPipeOutputConfig(0, 10, streamCb);
+    EXPECT_EQ(status.getExceptionCode(), EX_TRANSACTION_FAILED);
+    EXPECT_EQ(command.has_set_output_stream(), true);
+    EXPECT_EQ(command.set_output_stream().stream_id(), 0);
+    EXPECT_EQ(command.set_output_stream().max_inflight_packets_count(), 10);
+
+    // Release runner here. This should remove registry entry from router registry.
+    mAidlClient.reset();
+}
+
+TEST_F(ClientInterface, TestControlCommands) {
+    proto::ControlCommand command;
+    // Configure runner to return success.
+    EXPECT_CALL(*mEngine, processClientCommand(_))
+        .Times(AtLeast(4))
+        .WillRepeatedly(DoAll(SaveArg<0>(&command), Return(Status::SUCCESS)));
+
+    // Initialize pipe runner.
+    std::shared_ptr<StateChangeCallback> stateCallback =
+        ndk::SharedRefBase::make<StateChangeCallback>();
+    EXPECT_TRUE(mPipeRunner->init(stateCallback).isOk());
+
+    // Test that apply-configs api returns ok status.
+    EXPECT_TRUE(mPipeRunner->applyPipeConfigs().isOk());
+    EXPECT_EQ(command.has_apply_configs(), true);
+
+    // Test that set stop graph api returns ok status.
+    EXPECT_TRUE(mPipeRunner->resetPipeConfigs().isOk());
+    EXPECT_EQ(command.has_reset_configs(), true);
+
+    // Test that set start graph api returns ok status.
+    EXPECT_TRUE(mPipeRunner->startPipe().isOk());
+    EXPECT_EQ(command.has_start_graph(), true);
+
+    // Test that set stop graph api returns ok status.
+    EXPECT_TRUE(mPipeRunner->stopPipe().isOk());
+    EXPECT_EQ(command.has_stop_graph(), true);
+
+    // Release runner here. This should remove registry entry from router registry.
+    mAidlClient.reset();
+}
+
+TEST_F(ClientInterface, TestControlCommandsFailure) {
+    proto::ControlCommand command;
+
+    // Configure runner to return success.
+    EXPECT_CALL(*mEngine, processClientCommand(_))
+        .Times(AtLeast(4))
+        .WillRepeatedly(DoAll(SaveArg<0>(&command), Return(Status::INTERNAL_ERROR)));
+    ScopedAStatus status;
+
+    // Initialize pipe runner.
+    std::shared_ptr<StateChangeCallback> stateCallback =
+        ndk::SharedRefBase::make<StateChangeCallback>();
+    EXPECT_TRUE(mPipeRunner->init(stateCallback).isOk());
+
+    // Test that apply-configs api returns error status.
+    status = mPipeRunner->applyPipeConfigs();
+    EXPECT_EQ(status.getExceptionCode(), EX_TRANSACTION_FAILED);
+    EXPECT_EQ(command.has_apply_configs(), true);
+
+    status = mPipeRunner->resetPipeConfigs();
+    EXPECT_EQ(status.getExceptionCode(), EX_TRANSACTION_FAILED);
+    EXPECT_EQ(command.has_reset_configs(), true);
+
+    // Test that start graph api returns error status.
+    status = mPipeRunner->startPipe();
+    EXPECT_EQ(status.getExceptionCode(), EX_TRANSACTION_FAILED);
+    EXPECT_EQ(command.has_start_graph(), true);
+
+    // Test that stop graph api returns error status.
+    status = mPipeRunner->stopPipe();
+    EXPECT_EQ(status.getExceptionCode(), EX_TRANSACTION_FAILED);
+    EXPECT_EQ(command.has_stop_graph(), true);
+
+    // Release runner here. This should remove registry entry from router registry.
+    mAidlClient.reset();
+}
+
+TEST_F(ClientInterface, TestFailureWithoutInit) {
+    EXPECT_CALL(*mEngine, processClientConfigUpdate(_)).Times(0);
+    EXPECT_CALL(*mEngine, processClientCommand(_)).Times(0);
+
+    // Pipe runner is not initalized here, test that a configuration command returns error status.
+    ScopedAStatus status;
+    status = mPipeRunner->setPipeInputSource(1);
+    EXPECT_EQ(status.getExceptionCode(), EX_ILLEGAL_STATE);
+
+    // Test that a control command returns error status.
+    status = mPipeRunner->applyPipeConfigs();
+    EXPECT_EQ(status.getExceptionCode(), EX_ILLEGAL_STATE);
+}
+
+TEST_F(ClientInterface, TestStateChangeNotification) {
+    EXPECT_CALL(*mEngine, processClientConfigUpdate(_)).Times(0);
+    EXPECT_CALL(*mEngine, processClientCommand(_)).Times(0);
+
+    // Initialize pipe runner.
+    std::shared_ptr<StateChangeCallback> stateCallback =
+        ndk::SharedRefBase::make<StateChangeCallback>();
+    EXPECT_TRUE(mPipeRunner->init(stateCallback).isOk());
+
+    // Test that config complete status is conveyed to client.
+    std::map<int, int> m;
+    ClientConfig config(0, 0, 0, m);
+    config.setPhaseState(TRANSITION_COMPLETE);
+    EXPECT_EQ(mAidlClient->handleConfigPhase(config), Status::SUCCESS);
+    EXPECT_EQ(stateCallback->mState, PipeState::CONFIG_DONE);
+
+    MockRunnerEvent event;
+    EXPECT_CALL(event, isTransitionComplete()).Times(AnyNumber()).WillRepeatedly(Return(true));
+    EXPECT_CALL(event, isPhaseEntry()).Times(AnyNumber()).WillRepeatedly(Return(false));
+
+    // Test that reset status is conveyed to client.
+    EXPECT_EQ(mAidlClient->handleResetPhase(event), Status::SUCCESS);
+    EXPECT_EQ(stateCallback->mState, PipeState::RESET);
+
+    // Test that execution start status is conveyed to client.
+    EXPECT_EQ(mAidlClient->handleExecutionPhase(event), Status::SUCCESS);
+    EXPECT_EQ(stateCallback->mState, PipeState::RUNNING);
+
+    // Test that execution complete status is conveyed to client.
+    EXPECT_EQ(mAidlClient->handleStopWithFlushPhase(event), Status::SUCCESS);
+    EXPECT_EQ(stateCallback->mState, PipeState::DONE);
+
+    // Test that execution error status is conveyed to client.
+    EXPECT_EQ(mAidlClient->handleStopImmediatePhase(event), Status::SUCCESS);
+    EXPECT_EQ(stateCallback->mState, PipeState::ERR_HALT);
+}
+
+TEST_F(ClientInterface, TestStateChangeToError) {
+    EXPECT_CALL(*mEngine, processClientConfigUpdate(_)).Times(0);
+    EXPECT_CALL(*mEngine, processClientCommand(_)).Times(0);
+
+    // Initialize pipe runner.
+    std::shared_ptr<StateChangeCallback> stateCallback =
+        ndk::SharedRefBase::make<StateChangeCallback>();
+    EXPECT_TRUE(mPipeRunner->init(stateCallback).isOk());
+
+    // Test that error while applying config is conveyed to client.
+    std::map<int, int> m;
+    ClientConfig config(0, 0, 0, m);
+    config.setPhaseState(ABORTED);
+    EXPECT_EQ(mAidlClient->handleConfigPhase(config), Status::SUCCESS);
+    EXPECT_EQ(stateCallback->mState, PipeState::ERR_HALT);
+
+    MockRunnerEvent event;
+    EXPECT_CALL(event, isTransitionComplete()).Times(AnyNumber()).WillRepeatedly(Return(false));
+    EXPECT_CALL(event, isPhaseEntry()).Times(AnyNumber()).WillRepeatedly(Return(false));
+    EXPECT_CALL(event, isAborted()).Times(AnyNumber()).WillRepeatedly(Return(true));
+
+    // Test that error while starting pipe execution is conveyed to client.
+    EXPECT_EQ(mAidlClient->handleExecutionPhase(event), Status::SUCCESS);
+    EXPECT_EQ(stateCallback->mState, PipeState::ERR_HALT);
+}
+
+TEST_F(ClientInterface, TestPacketDelivery) {
+    proto::ConfigurationCommand command;
+
+    // Configure runner to return success.
+    EXPECT_CALL(*mEngine, processClientConfigUpdate(_))
+        .Times(1)
+        .WillOnce(DoAll(SaveArg<0>(&command), Return(Status::SUCCESS)));
+
+    // Initialize pipe runner.
+    std::shared_ptr<StateChangeCallback> stateCallback =
+        ndk::SharedRefBase::make<StateChangeCallback>();
+    EXPECT_TRUE(mPipeRunner->init(stateCallback).isOk());
+
+    // Set callback for stream id 0.
+    std::shared_ptr<StreamCallback> streamCb = ndk::SharedRefBase::make<StreamCallback>();
+    EXPECT_TRUE(mPipeRunner->setPipeOutputConfig(0, 10, streamCb).isOk());
+    EXPECT_EQ(command.has_set_output_stream(), true);
+    EXPECT_EQ(command.set_output_stream().stream_id(), 0);
+    EXPECT_EQ(command.set_output_stream().max_inflight_packets_count(), 10);
+
+    // Send a packet to client and verify the packet.
+    std::shared_ptr<MockMemHandle> packet = std::make_unique<MockMemHandle>();
+    uint64_t timestamp = 100;
+    const std::string testData = "Test String.";
+    EXPECT_CALL(*packet, getType())
+        .Times(AtLeast(1))
+        .WillRepeatedly(Return(proto::PacketType::SEMANTIC_DATA));
+    EXPECT_CALL(*packet, getTimeStamp()).Times(AtLeast(1)).WillRepeatedly(Return(timestamp));
+    EXPECT_CALL(*packet, getSize()).Times(AtLeast(1)).WillRepeatedly(Return(testData.size()));
+    EXPECT_CALL(*packet, getData()).Times(AtLeast(1)).WillRepeatedly(Return(testData.c_str()));
+    EXPECT_EQ(
+        mAidlClient->dispatchPacketToClient(0, static_cast<std::shared_ptr<MemHandle>>(packet)),
+        Status::SUCCESS);
+    EXPECT_EQ(streamCb->data, packet->getData());
+    EXPECT_EQ(streamCb->timestamp, packet->getTimeStamp());
+}
+
+}  // namespace
+}  // namespace aidl_client
+}  // namespace client_interface
+}  // namespace runner
+}  // namespace computepipe
+}  // namespace automotive
+}  // namespace android
diff --git a/computepipe/tests/runner/client_interface/MockEngine.h b/computepipe/tests/runner/client_interface/MockEngine.h
new file mode 100644
index 0000000..07b3484
--- /dev/null
+++ b/computepipe/tests/runner/client_interface/MockEngine.h
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef COMPUTEPIPE_TESTS_RUNNER_CLIENTINTERFACE_MOCKENGINE_H
+#define COMPUTEPIPE_TESTS_RUNNER_CLIENTINTERFACE_MOCKENGINE_H
+
+#include <gmock/gmock.h>
+
+#include "runner/client_interface/include/ClientEngineInterface.h"
+
+namespace android {
+namespace automotive {
+namespace computepipe {
+namespace runner {
+namespace client_interface {
+namespace tests {
+
+class MockEngine : public ClientEngineInterface {
+  public:
+    MOCK_METHOD1(processClientConfigUpdate, Status(const proto::ConfigurationCommand&));
+    MOCK_METHOD1(processClientCommand, Status(const proto::ControlCommand&));
+    MOCK_METHOD2(freePacket, Status(int bufferId, int streamId));
+};
+
+}  // namespace tests
+}  // namespace client_interface
+}  // namespace runner
+}  // namespace computepipe
+}  // namespace automotive
+}  // namespace android
+
+#endif  // COMPUTEPIPE_TESTS_RUNNER_CLIENTINTERFACE_MOCKENGINE_H
diff --git a/computepipe/tests/runner/client_interface/MockMemHandle.h b/computepipe/tests/runner/client_interface/MockMemHandle.h
new file mode 100644
index 0000000..919cbde
--- /dev/null
+++ b/computepipe/tests/runner/client_interface/MockMemHandle.h
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef COMPUTEPIPE_TESTS_RUNNER_CLIENTINTERFACE_MOCKMEMHANDLE_H
+#define COMPUTEPIPE_TESTS_RUNNER_CLIENTINTERFACE_MOCKMEMHANDLE_H
+
+#include <gmock/gmock.h>
+
+#include "MemHandle.h"
+
+namespace android {
+namespace automotive {
+namespace computepipe {
+namespace tests {
+
+class MockMemHandle : public MemHandle {
+  public:
+    MOCK_CONST_METHOD0(getStreamId, int());
+    MOCK_CONST_METHOD0(getBufferId, int());
+    MOCK_CONST_METHOD0(getType, proto::PacketType());
+    MOCK_CONST_METHOD0(getTimeStamp, uint64_t());
+    MOCK_CONST_METHOD0(getSize, uint32_t());
+    MOCK_CONST_METHOD0(getData, const char*());
+    MOCK_CONST_METHOD0(getHardwareBuffer, AHardwareBuffer*());
+};
+
+}  // namespace tests
+}  // namespace computepipe
+}  // namespace automotive
+}  // namespace android
+
+#endif  // COMPUTEPIPE_TESTS_RUNNER_CLIENTINTERFACE_MOCKMEMHANDLE_H
diff --git a/computepipe/tests/runner/client_interface/MockRunnerEvent.h b/computepipe/tests/runner/client_interface/MockRunnerEvent.h
new file mode 100644
index 0000000..d6f9039
--- /dev/null
+++ b/computepipe/tests/runner/client_interface/MockRunnerEvent.h
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef COMPUTEPIPE_TESTS_RUNNER_CLIENTINTERFACE_MOCKRUNNEREVENT_H
+#define COMPUTEPIPE_TESTS_RUNNER_CLIENTINTERFACE_MOCKRUNNEREVENT_H
+
+#include <gmock/gmock.h>
+
+#include "RunnerComponent.h"
+
+namespace android {
+namespace automotive {
+namespace computepipe {
+namespace runner {
+namespace tests {
+
+class MockRunnerEvent : public RunnerEvent {
+  public:
+    MOCK_CONST_METHOD0(isPhaseEntry, bool());
+    MOCK_CONST_METHOD0(isTransitionComplete, bool());
+    MOCK_CONST_METHOD0(isAborted, bool());
+    MOCK_METHOD1(
+        dispatchToComponent, Status(const std::shared_ptr<RunnerComponentInterface>& iface));
+};
+
+}  // namespace tests
+}  // namespce runner
+}  // namespace computepipe
+}  // namespace automotive
+}  // namespace android
+
+#endif  // COMPUTEPIPE_TESTS_RUNNER_CLIENTINTERFACE_MOCKRUNNEREVENT_H
diff --git a/computepipe/tests/runner/client_interface/PipeOptionsConverterTest.cpp b/computepipe/tests/runner/client_interface/PipeOptionsConverterTest.cpp
new file mode 100644
index 0000000..5a45dd4
--- /dev/null
+++ b/computepipe/tests/runner/client_interface/PipeOptionsConverterTest.cpp
@@ -0,0 +1,184 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+#include <string>
+
+#include "InputConfig.pb.h"
+#include "Options.pb.h"
+#include "gmock/gmock-matchers.h"
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "runner/client_interface/PipeOptionsConverter.h"
+#include "types/Status.h"
+
+using ::aidl::android::automotive::computepipe::runner::PipeDescriptor;
+using ::aidl::android::automotive::computepipe::runner::PipeInputConfigCameraType;
+using ::aidl::android::automotive::computepipe::runner::PipeInputConfigFormatType;
+using ::aidl::android::automotive::computepipe::runner::PipeInputConfigImageFileType;
+using ::aidl::android::automotive::computepipe::runner::PipeInputConfigInputType;
+using ::aidl::android::automotive::computepipe::runner::PipeInputConfigVideoFileType;
+
+namespace android {
+namespace automotive {
+namespace computepipe {
+namespace runner {
+namespace client_interface {
+namespace aidl_client {
+namespace {
+
+TEST(OptionsToPipeDescriptorTest, InputTypesConvertAsExpected) {
+    proto::Options options;
+
+    // Add a driver view camera type
+    options.add_input_configs()->add_input_stream()->set_type(
+        proto::InputStreamConfig_InputType_CAMERA);
+    options.mutable_input_configs(0)->mutable_input_stream(0)->mutable_cam_config()->set_camera_type(
+        proto::CameraConfig_CameraType_DRIVER_VIEW_CAMERA);
+    options.mutable_input_configs(0)->set_config_id(0);
+
+    // Add an occupant view camera type
+    options.add_input_configs()->add_input_stream()->set_type(
+        proto::InputStreamConfig_InputType_CAMERA);
+    options.mutable_input_configs(1)->mutable_input_stream(0)->mutable_cam_config()->set_camera_type(
+        proto::CameraConfig_CameraType_OCCUPANT_VIEW_CAMERA);
+    options.mutable_input_configs(1)->set_config_id(1);
+
+    // Add external camera type
+    options.add_input_configs()->add_input_stream()->set_type(
+        proto::InputStreamConfig_InputType_CAMERA);
+    options.mutable_input_configs(2)->mutable_input_stream(0)->mutable_cam_config()->set_camera_type(
+        proto::CameraConfig_CameraType_EXTERNAL_CAMERA);
+    options.mutable_input_configs(2)->set_config_id(2);
+
+    // Add surround camera type
+    options.add_input_configs()->add_input_stream()->set_type(
+        proto::InputStreamConfig_InputType_CAMERA);
+    options.mutable_input_configs(3)->mutable_input_stream(0)->mutable_cam_config()->set_camera_type(
+        proto::CameraConfig_CameraType_SURROUND_VIEW_CAMERA);
+    options.mutable_input_configs(3)->set_config_id(3);
+
+    // Add image file type
+    options.add_input_configs()->add_input_stream()->set_type(
+        proto::InputStreamConfig_InputType_IMAGE_FILES);
+    options.mutable_input_configs(4)->mutable_input_stream(0)->mutable_image_config()->set_file_type(
+        proto::ImageFileConfig_ImageFileType_PNG);
+    options.mutable_input_configs(4)->set_config_id(4);
+
+    // Add video file type
+    options.add_input_configs()->add_input_stream()->set_type(
+        proto::InputStreamConfig_InputType_VIDEO_FILE);
+    options.mutable_input_configs(5)->mutable_input_stream(0)->mutable_video_config()->set_file_type(
+        proto::VideoFileConfig_VideoFileType_MPEG);
+    options.mutable_input_configs(5)->set_config_id(5);
+
+    PipeDescriptor desc = OptionsToPipeDescriptor(options);
+
+    ASSERT_EQ(desc.inputConfig.size(), 6);
+    ASSERT_EQ(desc.inputConfig[0].inputSources.size(), 1);
+    EXPECT_EQ(desc.inputConfig[0].inputSources[0].type, PipeInputConfigInputType::CAMERA);
+    EXPECT_EQ(desc.inputConfig[0].inputSources[0].camDesc.type,
+              PipeInputConfigCameraType::DRIVER_VIEW_CAMERA);
+    EXPECT_EQ(desc.inputConfig[0].configId, 0);
+
+    ASSERT_EQ(desc.inputConfig[1].inputSources.size(), 1);
+    EXPECT_EQ(desc.inputConfig[1].inputSources[0].type, PipeInputConfigInputType::CAMERA);
+    EXPECT_EQ(desc.inputConfig[1].inputSources[0].camDesc.type,
+              PipeInputConfigCameraType::OCCUPANT_VIEW_CAMERA);
+    EXPECT_EQ(desc.inputConfig[1].configId, 1);
+
+    ASSERT_EQ(desc.inputConfig[2].inputSources.size(), 1);
+    EXPECT_EQ(desc.inputConfig[2].inputSources[0].type, PipeInputConfigInputType::CAMERA);
+    EXPECT_EQ(desc.inputConfig[2].inputSources[0].camDesc.type,
+              PipeInputConfigCameraType::EXTERNAL_CAMERA);
+    EXPECT_EQ(desc.inputConfig[2].configId, 2);
+
+    ASSERT_EQ(desc.inputConfig[3].inputSources.size(), 1);
+    EXPECT_EQ(desc.inputConfig[3].inputSources[0].type, PipeInputConfigInputType::CAMERA);
+    EXPECT_EQ(desc.inputConfig[3].inputSources[0].camDesc.type,
+              PipeInputConfigCameraType::SURROUND_VIEW_CAMERA);
+    EXPECT_EQ(desc.inputConfig[3].configId, 3);
+
+    ASSERT_EQ(desc.inputConfig[4].inputSources.size(), 1);
+    EXPECT_EQ(desc.inputConfig[4].inputSources[0].type, PipeInputConfigInputType::IMAGE_FILES);
+    EXPECT_EQ(desc.inputConfig[4].inputSources[0].imageDesc.fileType,
+              PipeInputConfigImageFileType::PNG);
+    EXPECT_EQ(desc.inputConfig[4].configId, 4);
+
+    ASSERT_EQ(desc.inputConfig[5].inputSources.size(), 1);
+    EXPECT_EQ(desc.inputConfig[5].inputSources[0].type, PipeInputConfigInputType::VIDEO_FILE);
+    EXPECT_EQ(desc.inputConfig[5].inputSources[0].videoDesc.fileType,
+              PipeInputConfigVideoFileType::MPEG);
+    EXPECT_EQ(desc.inputConfig[5].configId, 5);
+}
+
+TEST(OptionsToPipeDescriptorTest, FormatTypesConvertAsExpected) {
+    proto::Options options;
+
+    // Add an RGB format
+    options.add_input_configs()->add_input_stream()->set_format(
+        proto::InputStreamConfig_FormatType_RGB);
+
+    options.add_input_configs()->add_input_stream()->set_format(
+        proto::InputStreamConfig_FormatType_NIR);
+
+    options.add_input_configs()->add_input_stream()->set_format(
+        proto::InputStreamConfig_FormatType_NIR_DEPTH);
+
+    PipeDescriptor desc = OptionsToPipeDescriptor(options);
+
+    ASSERT_EQ(desc.inputConfig.size(), 3);
+    ASSERT_EQ(desc.inputConfig[0].inputSources.size(), 1);
+    EXPECT_EQ(desc.inputConfig[0].inputSources[0].format, PipeInputConfigFormatType::RGB);
+
+    ASSERT_EQ(desc.inputConfig[1].inputSources.size(), 1);
+    EXPECT_EQ(desc.inputConfig[1].inputSources[0].format, PipeInputConfigFormatType::NIR);
+
+    ASSERT_EQ(desc.inputConfig[2].inputSources.size(), 1);
+    EXPECT_EQ(desc.inputConfig[2].inputSources[0].format, PipeInputConfigFormatType::NIR_DEPTH);
+}
+
+TEST(OptionsToPipeDescriptorTest, ImageDimensionsAreTranslatedCorrectly) {
+    proto::Options options;
+
+    options.add_input_configs()->add_input_stream()->set_width(640);
+    options.mutable_input_configs(0)->mutable_input_stream(0)->set_height(480);
+    options.mutable_input_configs(0)->mutable_input_stream(0)->set_stride(640 * 3);
+    options.mutable_input_configs(0)->mutable_input_stream(0)->set_stride(640 * 3);
+
+    PipeDescriptor desc = OptionsToPipeDescriptor(options);
+    ASSERT_EQ(desc.inputConfig.size(), 1);
+    ASSERT_EQ(desc.inputConfig[0].inputSources.size(), 1);
+    ASSERT_EQ(desc.inputConfig[0].inputSources[0].width, 640);
+    ASSERT_EQ(desc.inputConfig[0].inputSources[0].height, 480);
+    ASSERT_EQ(desc.inputConfig[0].inputSources[0].stride, 640 * 3);
+}
+
+TEST(OptionsToPipeDescriptorTest, CameraIdIsReflectedCorrectly) {
+    proto::Options options;
+    std::string expectedCameraName = "Camera 1";
+    options.add_input_configs()->add_input_stream()->mutable_cam_config()->set_cam_id(
+        expectedCameraName);
+
+    PipeDescriptor desc = OptionsToPipeDescriptor(options);
+    ASSERT_EQ(desc.inputConfig.size(), 1);
+    ASSERT_EQ(desc.inputConfig[0].inputSources.size(), 1);
+    EXPECT_EQ(desc.inputConfig[0].inputSources[0].camDesc.camId, expectedCameraName);
+}
+
+}  // namespace
+}  // namespace aidl_client
+}  // namespace client_interface
+}  // namespace runner
+}  // namespace computepipe
+}  // namespace automotive
+}  // namespace android
diff --git a/computepipe/tests/runner/graph/Android.bp b/computepipe/tests/runner/graph/Android.bp
new file mode 100644
index 0000000..10e40f2
--- /dev/null
+++ b/computepipe/tests/runner/graph/Android.bp
@@ -0,0 +1,44 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+cc_test {
+    name: "computepipe_prebuilt_graph_test",
+    test_suites: ["device-tests"],
+    srcs: [
+        "EnumConversionTest.cpp",
+        "PrebuiltGraphTest.cpp",
+    ],
+    static_libs: [
+        "computepipe_prebuilt_graph",
+        "computepipe_runner_component",
+        "libgtest",
+        "libgmock",
+	      "libcomputepipeprotos",
+    ],
+    shared_libs: [
+        "libstubgraphimpl",
+        "libprotobuf-cpp-lite",
+        "liblog",
+        "libdl",
+        "libbase",
+    ],
+    header_libs: [
+        "computepipe_runner_includes",
+    ],
+    include_dirs: [
+        "packages/services/Car/computepipe",
+        "packages/services/Car/computepipe/runner/graph",
+    ],
+
+}
diff --git a/computepipe/tests/runner/graph/EnumConversionTest.cpp b/computepipe/tests/runner/graph/EnumConversionTest.cpp
new file mode 100644
index 0000000..bbb9408
--- /dev/null
+++ b/computepipe/tests/runner/graph/EnumConversionTest.cpp
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "gmock/gmock-matchers.h"
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "prebuilt_interface.h"
+#include "types/Status.h"
+
+namespace android {
+namespace automotive {
+namespace computepipe {
+namespace graph {
+namespace {
+
+TEST(EnumConversionTest, StatusToErrorCodeEnums) {
+    EXPECT_EQ(static_cast<int>(PrebuiltComputepipeRunner_ErrorCode::SUCCESS),
+              static_cast<int>(Status::SUCCESS));
+    EXPECT_EQ(static_cast<int>(PrebuiltComputepipeRunner_ErrorCode::INTERNAL_ERROR),
+              static_cast<int>(Status::INTERNAL_ERROR));
+    EXPECT_EQ(PrebuiltComputepipeRunner_ErrorCode::INVALID_ARGUMENT,
+              static_cast<int>(Status::INVALID_ARGUMENT));
+    EXPECT_EQ(PrebuiltComputepipeRunner_ErrorCode::ILLEGAL_STATE,
+              static_cast<int>(Status::ILLEGAL_STATE));
+    EXPECT_EQ(PrebuiltComputepipeRunner_ErrorCode::NO_MEMORY, static_cast<int>(Status::NO_MEMORY));
+    EXPECT_EQ(PrebuiltComputepipeRunner_ErrorCode::FATAL_ERROR,
+              static_cast<int>(Status::FATAL_ERROR));
+    EXPECT_EQ(PrebuiltComputepipeRunner_ErrorCode::ERROR_CODE_MAX,
+              static_cast<int>(Status::STATUS_MAX));
+}
+enum PrebuiltComputepipeRunner_PixelDataFormat {
+    RGB = 0,
+    RGBA = 1,
+    GRAY = 2,
+    PIXEL_DATA_FORMAT_MAX = 3,
+};
+TEST(EnumConversionTest, PixelFormatEnums) {
+    EXPECT_EQ(static_cast<int>(PrebuiltComputepipeRunner_PixelDataFormat::RGB),
+              static_cast<int>(PixelFormat::RGB));
+    EXPECT_EQ(static_cast<int>(PrebuiltComputepipeRunner_PixelDataFormat::RGBA),
+              static_cast<int>(PixelFormat::RGBA));
+    EXPECT_EQ(PrebuiltComputepipeRunner_PixelDataFormat::GRAY, static_cast<int>(PixelFormat::GRAY));
+    EXPECT_EQ(PrebuiltComputepipeRunner_PixelDataFormat::PIXEL_DATA_FORMAT_MAX,
+              static_cast<int>(PixelFormat::PIXELFORMAT_MAX));
+}
+
+}  // namespace
+}  // namespace graph
+}  // namespace computepipe
+}  // namespace automotive
+}  // namespace android
diff --git a/computepipe/tests/runner/graph/PrebuiltGraphTest.cpp b/computepipe/tests/runner/graph/PrebuiltGraphTest.cpp
new file mode 100644
index 0000000..8935ce4
--- /dev/null
+++ b/computepipe/tests/runner/graph/PrebuiltGraphTest.cpp
@@ -0,0 +1,221 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include <string>
+
+#include "ClientConfig.pb.h"
+#include "PrebuiltEngineInterface.h"
+#include "PrebuiltGraph.h"
+#include "RunnerComponent.h"
+#include "gmock/gmock-matchers.h"
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "types/Status.h"
+
+using ::android::automotive::computepipe::runner::ClientConfig;
+using ::android::automotive::computepipe::runner::RunnerComponentInterface;
+using ::android::automotive::computepipe::runner::RunnerEvent;
+using ::testing::HasSubstr;
+
+namespace android {
+namespace automotive {
+namespace computepipe {
+namespace graph {
+namespace {
+
+// Barebones implementation of the PrebuiltEngineInterface. This implementation should suffice for
+// basic cases. More complicated use cases might need their own implementation of it.
+typedef std::function<void(int, int64_t, const runner::InputFrame&)> PixelCallback;
+typedef std::function<void(int, int64_t, std::string&&)> SerializedStreamCallback;
+typedef std::function<void(Status, std::string&&)> GraphTerminationCallback;
+class PrebuiltEngineInterfaceImpl : public PrebuiltEngineInterface {
+  private:
+    PixelCallback mPixelCallbackFn;
+    SerializedStreamCallback mSerializedStreamCallbackFn;
+    GraphTerminationCallback mGraphTerminationCallbackFn;
+
+  public:
+    virtual ~PrebuiltEngineInterfaceImpl() = default;
+
+    void DispatchPixelData(int streamId, int64_t timestamp, const runner::InputFrame& frame) override {
+        mPixelCallbackFn(streamId, timestamp, frame);
+    }
+
+    void DispatchSerializedData(int streamId, int64_t timestamp, std::string&& data) override {
+        mSerializedStreamCallbackFn(streamId, timestamp, std::move(data));
+    }
+
+    void DispatchGraphTerminationMessage(Status status, std::string&& msg) override {
+        mGraphTerminationCallbackFn(status, std::move(msg));
+    }
+
+    void SetPixelCallback(PixelCallback callback) {
+        mPixelCallbackFn = callback;
+    }
+
+    void SetSerializedStreamCallback(SerializedStreamCallback callback) {
+        mSerializedStreamCallbackFn = callback;
+    }
+
+    void SetGraphTerminationCallback(GraphTerminationCallback callback) {
+        mGraphTerminationCallbackFn = callback;
+    }
+};
+
+// The stub graph implementation is a passthrough implementation that does not run
+// any graph and returns success for all implementations. The only useful things that
+// it does for the tests are
+//
+//    1. Stores the name of the function last visited and returns that with GetErrorMessage call
+//    2. When an input stream is set, it immediately returns an output callback with the same input
+//       data and timestamp. Similar callback is issued for when input stream pixel data is set too
+//
+// The above two properties are used to test that the prebuilt graph wrapper calls the correct
+// functions and callbacks are issued as expected. These tests do not test the internals of the
+// graph themselves and such tests must be written along with the graph implementation.
+TEST(PrebuiltGraphTest, FunctionMappingFromLibraryIsSuccessful) {
+    PrebuiltEngineInterfaceImpl callback;
+    std::shared_ptr<PrebuiltEngineInterface> engineInterface =
+        std::static_pointer_cast<PrebuiltEngineInterface, PrebuiltEngineInterfaceImpl>(
+            std::make_shared<PrebuiltEngineInterfaceImpl>(callback));
+    PrebuiltGraph* graph =
+        PrebuiltGraph::GetPrebuiltGraphFromLibrary("libstubgraphimpl.so", engineInterface);
+    ASSERT_TRUE(graph);
+    EXPECT_NE(graph->GetGraphState(), PrebuiltGraphState::UNINITIALIZED);
+    EXPECT_EQ(graph->GetSupportedGraphConfigs().graph_name(), "stub_graph");
+}
+
+TEST(PrebuiltGraphTest, GraphConfigurationIssuesCorrectFunctionCalls) {
+    PrebuiltEngineInterfaceImpl callback;
+    std::shared_ptr<PrebuiltEngineInterface> engineInterface =
+        std::static_pointer_cast<PrebuiltEngineInterface, PrebuiltEngineInterfaceImpl>(
+            std::make_shared<PrebuiltEngineInterfaceImpl>(callback));
+    PrebuiltGraph* graph =
+        PrebuiltGraph::GetPrebuiltGraphFromLibrary("libstubgraphimpl.so", engineInterface);
+    ASSERT_TRUE(graph);
+    ASSERT_NE(graph->GetGraphState(), PrebuiltGraphState::UNINITIALIZED);
+
+    graph->GetSupportedGraphConfigs();
+    std::string functionVisited = graph->GetErrorMessage();
+    EXPECT_THAT(functionVisited, HasSubstr("GetSupportedGraphConfigs"));
+
+    std::map<int, int> maxOutputPacketsPerStream;
+    ClientConfig e(0, 0, 0, maxOutputPacketsPerStream);
+    e.setPhaseState(runner::PhaseState::ENTRY);
+    EXPECT_EQ(graph->handleConfigPhase(e), Status::SUCCESS);
+    functionVisited = graph->GetErrorMessage();
+
+    EXPECT_EQ(graph->GetStatus(), Status::SUCCESS);
+    functionVisited = graph->GetErrorMessage();
+    EXPECT_THAT(functionVisited, HasSubstr("GetErrorCode"));
+}
+
+TEST(PrebuiltGraphTest, GraphOperationEndToEndIsSuccessful) {
+    bool graphHasTerminated = false;
+    int numOutputStreamCallbacksReceived[4] = {0, 0, 0, 0};
+
+    PrebuiltEngineInterfaceImpl callback;
+    callback.SetGraphTerminationCallback(
+        [&graphHasTerminated](Status, std::string) { graphHasTerminated = true; });
+
+    // Add multiple pixel stream callback functions to see if all of them register.
+    callback.SetPixelCallback([&numOutputStreamCallbacksReceived](int streamIndex, int64_t,
+                                                                  const runner::InputFrame&) {
+        ASSERT_TRUE(streamIndex == 0 || streamIndex == 1);
+        numOutputStreamCallbacksReceived[streamIndex]++;
+    });
+
+    // Add multiple stream callback functions to see if all of them register.
+    callback.SetSerializedStreamCallback(
+        [&numOutputStreamCallbacksReceived](int streamIndex, int64_t, std::string&&) {
+            ASSERT_TRUE(streamIndex == 2 || streamIndex == 3);
+            numOutputStreamCallbacksReceived[streamIndex]++;
+        });
+
+    std::shared_ptr<PrebuiltEngineInterface> engineInterface =
+        std::static_pointer_cast<PrebuiltEngineInterface, PrebuiltEngineInterfaceImpl>(
+            std::make_shared<PrebuiltEngineInterfaceImpl>(callback));
+
+    PrebuiltGraph* graph =
+        PrebuiltGraph::GetPrebuiltGraphFromLibrary("libstubgraphimpl.so", engineInterface);
+
+    ASSERT_NE(graph->GetGraphState(), PrebuiltGraphState::UNINITIALIZED);
+
+    graph->GetSupportedGraphConfigs();
+    std::string functionVisited = graph->GetErrorMessage();
+    EXPECT_THAT(functionVisited, HasSubstr("GetSupportedGraphConfigs"));
+
+    std::map<int, int> maxOutputPacketsPerStream;
+    ClientConfig e(0, 0, 0, maxOutputPacketsPerStream);
+    e.setPhaseState(runner::PhaseState::ENTRY);
+    EXPECT_EQ(graph->handleConfigPhase(e), Status::SUCCESS);
+    functionVisited = graph->GetErrorMessage();
+
+    EXPECT_EQ(graph->handleExecutionPhase(e), Status::SUCCESS);
+    functionVisited = graph->GetErrorMessage();
+    EXPECT_THAT(functionVisited, HasSubstr("StartGraphExecution"));
+
+    runner::InputFrame inputFrame(0, 0, PixelFormat::RGB, 0, nullptr);
+    EXPECT_EQ(graph->SetInputStreamPixelData(
+                  /*streamIndex =*/0, /*timestamp =*/0, /*inputFrame =*/inputFrame),
+              Status::SUCCESS);
+    EXPECT_EQ(graph->SetInputStreamPixelData(
+                  /*streamIndex =*/0, /*timestamp =*/0, /*inputFrame =*/inputFrame),
+              Status::SUCCESS);
+    EXPECT_EQ(graph->SetInputStreamPixelData(
+                  /*streamIndex =*/0, /*timestamp =*/0, /*inputFrame =*/inputFrame),
+              Status::SUCCESS);
+    EXPECT_EQ(graph->SetInputStreamPixelData(
+                  /*streamIndex =*/0, /*timestamp =*/0, /*inputFrame =*/inputFrame),
+              Status::SUCCESS);
+    EXPECT_EQ(graph->SetInputStreamPixelData(
+                  /*streamIndex =*/0, /*timestamp =*/0, /*inputFrame =*/inputFrame),
+              Status::SUCCESS);
+    functionVisited = graph->GetErrorMessage();
+    EXPECT_THAT(functionVisited, HasSubstr("SetInputStreamPixelData"));
+
+    EXPECT_EQ(graph->SetInputStreamData(/*streamIndex =*/2, /* timestamp =*/0, /* data =*/""),
+              Status::SUCCESS);
+    EXPECT_EQ(graph->SetInputStreamData(/*streamIndex =*/2, /* timestamp =*/0, /* data =*/""),
+              Status::SUCCESS);
+    EXPECT_EQ(graph->SetInputStreamData(/*streamIndex =*/2, /* timestamp =*/0, /* data =*/""),
+              Status::SUCCESS);
+    EXPECT_EQ(graph->SetInputStreamData(/*streamIndex =*/3, /* timestamp =*/0, /* data =*/""),
+              Status::SUCCESS);
+    EXPECT_EQ(graph->SetInputStreamData(/*streamIndex =*/3, /* timestamp =*/0, /* data =*/""),
+              Status::SUCCESS);
+    functionVisited = graph->GetErrorMessage();
+    EXPECT_THAT(functionVisited, HasSubstr("SetInputStreamData"));
+
+    EXPECT_EQ(numOutputStreamCallbacksReceived[0], 3);
+    EXPECT_EQ(numOutputStreamCallbacksReceived[1], 2);
+    EXPECT_EQ(numOutputStreamCallbacksReceived[2], 3);
+    EXPECT_EQ(numOutputStreamCallbacksReceived[3], 2);
+
+    EXPECT_FALSE(graphHasTerminated);
+    EXPECT_EQ(graph->handleStopImmediatePhase(e), Status::SUCCESS);
+
+    EXPECT_EQ(graph->handleResetPhase(e), Status::SUCCESS);
+    functionVisited = graph->GetErrorMessage();
+    EXPECT_THAT(functionVisited, HasSubstr("ResetGraph"));
+
+    EXPECT_TRUE(graphHasTerminated);
+}
+
+}  // namespace
+}  // namespace graph
+}  // namespace computepipe
+}  // namespace automotive
+}  // namespace android
diff --git a/computepipe/tests/runner/graph/stubgraph/Android.bp b/computepipe/tests/runner/graph/stubgraph/Android.bp
new file mode 100644
index 0000000..d01b69f
--- /dev/null
+++ b/computepipe/tests/runner/graph/stubgraph/Android.bp
@@ -0,0 +1,41 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+cc_prebuilt_library_shared {
+    name: "libstubgraphimpl",
+    target: {
+        android_arm64: {
+            srcs: ["arm64/libstubgraphimpl.so"],
+        },
+        android_arm: {
+            srcs: ["arm/libstubgraphimpl.so"],
+        },
+        android_x86: {
+            srcs: ["x86/libstubgraphimpl.so"],
+        },
+        android_x86_64: {
+            srcs: ["x86_64/libstubgraphimpl.so"],
+        },
+    },
+
+    shared_libs: [
+        "libc",
+        "libdl",
+        "liblog",
+        "libm"
+    ],
+    strip: {
+        keep_symbols: true,
+    }
+}
diff --git a/computepipe/tests/runner/graph/stubgraph/arm/libstubgraphimpl.so b/computepipe/tests/runner/graph/stubgraph/arm/libstubgraphimpl.so
new file mode 100755
index 0000000..3f17e52
--- /dev/null
+++ b/computepipe/tests/runner/graph/stubgraph/arm/libstubgraphimpl.so
Binary files differ
diff --git a/computepipe/tests/runner/graph/stubgraph/arm64/libstubgraphimpl.so b/computepipe/tests/runner/graph/stubgraph/arm64/libstubgraphimpl.so
new file mode 100755
index 0000000..da7d014
--- /dev/null
+++ b/computepipe/tests/runner/graph/stubgraph/arm64/libstubgraphimpl.so
Binary files differ
diff --git a/computepipe/tests/runner/graph/stubgraph/x86/libstubgraphimpl.so b/computepipe/tests/runner/graph/stubgraph/x86/libstubgraphimpl.so
new file mode 100755
index 0000000..4b9904c
--- /dev/null
+++ b/computepipe/tests/runner/graph/stubgraph/x86/libstubgraphimpl.so
Binary files differ
diff --git a/computepipe/tests/runner/graph/stubgraph/x86_64/libstubgraphimpl.so b/computepipe/tests/runner/graph/stubgraph/x86_64/libstubgraphimpl.so
new file mode 100755
index 0000000..96e1004
--- /dev/null
+++ b/computepipe/tests/runner/graph/stubgraph/x86_64/libstubgraphimpl.so
Binary files differ
diff --git a/computepipe/tests/runner/stream_manager/Android.bp b/computepipe/tests/runner/stream_manager/Android.bp
new file mode 100644
index 0000000..d5543c6
--- /dev/null
+++ b/computepipe/tests/runner/stream_manager/Android.bp
@@ -0,0 +1,71 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+cc_test {
+    name: "computepipe_semantic_manager_test",
+    test_suites: ["device-tests"],
+    srcs: [
+        "SemanticManagerTest.cpp",
+    ],
+    static_libs: [
+        "computepipe_stream_manager",
+        "computepipe_runner_component",
+        "mock_stream_engine_interface",
+        "libgtest",
+        "libgmock",
+        "libcomputepipeprotos",
+    ],
+    shared_libs: [
+        "libbase",
+        "liblog",
+        "libnativewindow",
+        "libprotobuf-cpp-lite",
+    ],
+    header_libs: [
+        "computepipe_runner_includes",
+    ],
+    include_dirs: [
+        "packages/services/Car/computepipe",
+        "packages/services/Car/computepipe/runner/stream_manager",
+    ],
+}
+
+cc_test {
+    name: "computepipe_pixel_manager_test",
+    test_suites: ["device-tests"],
+    srcs: [
+        "PixelStreamManagerTest.cpp",
+    ],
+    static_libs: [
+        "computepipe_stream_manager",
+        "computepipe_runner_component",
+        "mock_stream_engine_interface",
+        "libgtest",
+        "libgmock",
+        "libcomputepipeprotos",
+    ],
+    shared_libs: [
+        "libbase",
+        "liblog",
+        "libnativewindow",
+        "libprotobuf-cpp-lite",
+    ],
+    header_libs: [
+        "computepipe_runner_includes",
+    ],
+    include_dirs: [
+        "packages/services/Car/computepipe",
+        "packages/services/Car/computepipe/runner/stream_manager",
+    ],
+}
diff --git a/computepipe/tests/runner/stream_manager/PixelStreamManagerTest.cpp b/computepipe/tests/runner/stream_manager/PixelStreamManagerTest.cpp
new file mode 100644
index 0000000..46da0a3
--- /dev/null
+++ b/computepipe/tests/runner/stream_manager/PixelStreamManagerTest.cpp
@@ -0,0 +1,350 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <vndk/hardware_buffer.h>
+
+#include "EventGenerator.h"
+#include "InputFrame.h"
+#include "MockEngine.h"
+#include "OutputConfig.pb.h"
+#include "PixelStreamManager.h"
+#include "RunnerComponent.h"
+#include "StreamEngineInterface.h"
+#include "StreamManager.h"
+#include "gmock/gmock-matchers.h"
+#include "types/Status.h"
+
+using ::android::automotive::computepipe::runner::RunnerComponentInterface;
+using ::android::automotive::computepipe::runner::RunnerEvent;
+using ::android::automotive::computepipe::runner::generator::DefaultEvent;
+using ::testing::Contains;
+using ::testing::Not;
+using ::testing::Return;
+
+namespace android {
+namespace automotive {
+namespace computepipe {
+namespace runner {
+namespace stream_manager {
+
+AHardwareBuffer_Format PixelFormatToHardwareBufferFormat(PixelFormat pixelFormat);
+int numBytesPerPixel(PixelFormat pixelFormat);
+
+namespace {
+
+MATCHER_P(ContainsDataFromFrame, data, "") {
+    const uint8_t* dataPtr = data->getFramePtr();
+    FrameInfo info = data->getFrameInfo();
+    AHardwareBuffer_Desc desc;
+    AHardwareBuffer_describe(arg, &desc);
+
+    if (desc.width != info.width) {
+        *result_listener << "Width does not match with values " << desc.width << " and "
+                         << info.width;
+        return false;
+    }
+
+    if (desc.height != info.height) {
+        *result_listener << "Height does not match with values " << desc.height << " and "
+                         << info.height;
+        return false;
+    }
+
+    AHardwareBuffer_Format expectedFormat = PixelFormatToHardwareBufferFormat(info.format);
+    if (expectedFormat != desc.format) {
+        *result_listener << "Format does not match";
+        return false;
+    }
+
+    void* mappedBuffer = nullptr;
+    int err = AHardwareBuffer_lock(arg, AHARDWAREBUFFER_USAGE_CPU_READ_RARELY, -1, nullptr,
+                                   &mappedBuffer);
+    if (err != 0 || mappedBuffer == nullptr) {
+        *result_listener << "Unable to lock the buffer for reading and comparing";
+        return false;
+    }
+
+    bool dataMatched = true;
+    int bytesPerPixel = numBytesPerPixel(info.format);
+    for (int y = 0; y < info.height; y++) {
+        uint8_t* mappedRow = (uint8_t*)mappedBuffer + y * desc.stride * bytesPerPixel;
+        if (memcmp(mappedRow, dataPtr + y * info.stride,
+                   std::min(info.stride, desc.stride * bytesPerPixel))) {
+            *result_listener << "Row " << y << " does not match";
+            dataMatched = false;
+            break;
+        }
+    }
+    AHardwareBuffer_unlock(arg, nullptr);
+    return dataMatched;
+}
+
+TEST(PixelMemHandleTest, SuccessfullyCreatesMemHandleOnFirstAttempt) {
+    int bufferId = 10;
+    int streamId = 1;
+    uint64_t timestamp = 100;
+    PixelMemHandle memHandle(bufferId, streamId, AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN);
+
+    EXPECT_EQ(memHandle.getBufferId(), bufferId);
+    EXPECT_EQ(memHandle.getStreamId(), streamId);
+    EXPECT_EQ(memHandle.getHardwareBuffer(), nullptr);
+
+    std::vector<uint8_t> data(16 * 16 * 3, 0);
+    InputFrame frame(16, 16, PixelFormat::RGB, 16 * 3, &data[0]);
+    Status status = memHandle.setFrameData(timestamp, frame);
+    EXPECT_EQ(status, Status::SUCCESS);
+    ASSERT_NE(memHandle.getHardwareBuffer(), nullptr);
+
+    AHardwareBuffer_Desc desc;
+    AHardwareBuffer* buffer = memHandle.getHardwareBuffer();
+    AHardwareBuffer_describe(buffer, &desc);
+    EXPECT_EQ(desc.height, 16);
+    EXPECT_EQ(desc.width, 16);
+    EXPECT_EQ(desc.usage,
+              AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN | AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN);
+    EXPECT_EQ(desc.format, AHardwareBuffer_Format::AHARDWAREBUFFER_FORMAT_R8G8B8_UNORM);
+
+    EXPECT_THAT(buffer, ContainsDataFromFrame(&frame));
+}
+
+TEST(PixelMemHandleTest, FailsToOverwriteFrameDataWithDifferentImageFormat) {
+    int bufferId = 10;
+    int streamId = 1;
+    uint64_t timestamp = 100;
+    PixelMemHandle memHandle(bufferId, streamId, AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN);
+
+    EXPECT_EQ(memHandle.getBufferId(), bufferId);
+    EXPECT_EQ(memHandle.getStreamId(), streamId);
+    EXPECT_EQ(memHandle.getHardwareBuffer(), nullptr);
+
+    uint8_t data[16 * 16 * 3] = {0};
+    InputFrame frame(16, 16, PixelFormat::RGB, 16 * 3, &data[0]);
+    Status status = memHandle.setFrameData(timestamp, frame);
+    EXPECT_EQ(status, Status::SUCCESS);
+    ASSERT_NE(memHandle.getHardwareBuffer(), nullptr);
+
+    InputFrame frameWithNewFormat(16, 16, PixelFormat::RGBA, 16 * 4, nullptr);
+    status = memHandle.setFrameData(timestamp, frameWithNewFormat);
+    EXPECT_EQ(status, Status::INVALID_ARGUMENT);
+
+    InputFrame frameWithNewDimensions(8, 8, PixelFormat::RGB, 8 * 3, nullptr);
+    status = memHandle.setFrameData(timestamp, frameWithNewDimensions);
+    EXPECT_EQ(status, Status::INVALID_ARGUMENT);
+}
+
+TEST(PixelMemHandleTest, SuccessfullyOverwritesOldData) {
+    int bufferId = 10;
+    int streamId = 1;
+    uint64_t timestamp = 100;
+    PixelMemHandle memHandle(bufferId, streamId, AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN);
+
+    EXPECT_EQ(memHandle.getBufferId(), bufferId);
+    EXPECT_EQ(memHandle.getStreamId(), streamId);
+    EXPECT_EQ(memHandle.getHardwareBuffer(), nullptr);
+
+    std::vector<uint8_t> data(16 * 16 * 3, 0);
+    InputFrame frame(16, 16, PixelFormat::RGB, 16 * 3, &data[0]);
+    Status status = memHandle.setFrameData(timestamp, frame);
+    EXPECT_EQ(status, Status::SUCCESS);
+    ASSERT_NE(memHandle.getHardwareBuffer(), nullptr);
+    EXPECT_THAT(memHandle.getHardwareBuffer(), ContainsDataFromFrame(&frame));
+
+    std::vector<uint8_t> newData(16 * 16 * 3, 1);
+    uint64_t newTimestamp = 200;
+    InputFrame newFrame(16, 16, PixelFormat::RGB, 16 * 3, &newData[0]);
+    memHandle.setFrameData(newTimestamp, newFrame);
+    EXPECT_THAT(memHandle.getHardwareBuffer(), ContainsDataFromFrame(&newFrame));
+    EXPECT_THAT(memHandle.getTimeStamp(), newTimestamp);
+}
+
+TEST(PixelMemHandleTest, CreatesBuffersOfExpectedFormats) {
+    int bufferId = 10;
+    int streamId = 1;
+    uint64_t timestamp = 100;
+
+    std::vector<uint8_t> rgbData(16 * 16 * 3, 10);
+    InputFrame rgbFrame(16, 16, PixelFormat::RGB, 16 * 3, &rgbData[0]);
+    PixelMemHandle rgbHandle(bufferId, streamId, AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN);
+    rgbHandle.setFrameData(timestamp, rgbFrame);
+    EXPECT_THAT(rgbHandle.getHardwareBuffer(), ContainsDataFromFrame(&rgbFrame));
+
+    std::vector<uint8_t> rgbaData(16 * 16 * 4, 20);
+    InputFrame rgbaFrame(16, 16, PixelFormat::RGBA, 16 * 4, &rgbaData[0]);
+    PixelMemHandle rgbaHandle(bufferId, streamId, AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN);
+    rgbaHandle.setFrameData(timestamp, rgbaFrame);
+    EXPECT_THAT(rgbaHandle.getHardwareBuffer(), ContainsDataFromFrame(&rgbaFrame));
+
+    std::vector<uint8_t> yData(16 * 16, 40);
+    InputFrame yFrame(16, 16, PixelFormat::GRAY, 16, &yData[0]);
+    PixelMemHandle yHandle(bufferId, streamId, AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN);
+    yHandle.setFrameData(timestamp, yFrame);
+    EXPECT_THAT(yHandle.getHardwareBuffer(), ContainsDataFromFrame(&yFrame));
+}
+
+std::pair<std::shared_ptr<MockEngine>, std::unique_ptr<StreamManager>> CreateStreamManagerAndEngine(
+    int maxInFlightPackets) {
+    StreamManagerFactory factory;
+    proto::OutputConfig outputConfig;
+    outputConfig.set_type(proto::PacketType::PIXEL_DATA);
+    outputConfig.set_stream_name("pixel_stream");
+    std::shared_ptr<MockEngine> mockEngine = std::make_shared<MockEngine>();
+    std::unique_ptr<StreamManager> manager =
+        factory.getStreamManager(outputConfig, mockEngine, maxInFlightPackets);
+
+    return std::pair(mockEngine, std::move(manager));
+}
+
+TEST(PixelStreamManagerTest, PacketQueueingProducesACallback) {
+    // Create stream manager
+    int maxInFlightPackets = 1;
+    auto [mockEngine, manager] = CreateStreamManagerAndEngine(maxInFlightPackets);
+
+    DefaultEvent e = DefaultEvent::generateEntryEvent(DefaultEvent::Phase::RUN);
+
+    ASSERT_EQ(manager->handleExecutionPhase(e), Status::SUCCESS);
+    std::vector<uint8_t> data(16 * 16 * 3, 100);
+    InputFrame frame(16, 16, PixelFormat::RGB, 16 * 3, &data[0]);
+
+    std::shared_ptr<MemHandle> memHandle;
+    EXPECT_CALL((*mockEngine), dispatchPacket)
+        .WillOnce(testing::DoAll(testing::SaveArg<0>(&memHandle), (Return(Status::SUCCESS))));
+
+    EXPECT_EQ(manager->queuePacket(frame, 0), Status::SUCCESS);
+    sleep(1);
+    ASSERT_NE(memHandle, nullptr);
+    EXPECT_THAT(memHandle->getHardwareBuffer(), ContainsDataFromFrame(&frame));
+    EXPECT_THAT(memHandle->getTimeStamp(), 0);
+    EXPECT_THAT(memHandle->getStreamId(), 0);
+}
+
+TEST(PixelStreamManagerTest, MorePacketsThanMaxInFlightAreNotDispatched) {
+    int maxInFlightPackets = 3;
+    auto [mockEngine, manager] = CreateStreamManagerAndEngine(maxInFlightPackets);
+
+    DefaultEvent e = DefaultEvent::generateEntryEvent(DefaultEvent::Phase::RUN);
+
+    ASSERT_EQ(manager->handleExecutionPhase(e), Status::SUCCESS);
+    std::vector<uint8_t> data(16 * 16 * 3, 100);
+    InputFrame frame(16, 16, PixelFormat::RGB, 16 * 3, &data[0]);
+    std::set<int> activeBufferIds;
+
+    std::shared_ptr<MemHandle> memHandle;
+    EXPECT_CALL((*mockEngine), dispatchPacket)
+        .Times(3)
+        .WillRepeatedly(testing::DoAll(testing::SaveArg<0>(&memHandle), (Return(Status::SUCCESS))));
+
+    EXPECT_EQ(manager->queuePacket(frame, 0), Status::SUCCESS);
+    sleep(1);
+    ASSERT_NE(memHandle, nullptr);
+    EXPECT_THAT(memHandle->getHardwareBuffer(), ContainsDataFromFrame(&frame));
+    EXPECT_THAT(memHandle->getTimeStamp(), 0);
+    EXPECT_THAT(memHandle->getStreamId(), 0);
+    activeBufferIds.insert(memHandle->getBufferId());
+
+    EXPECT_EQ(manager->queuePacket(frame, 10), Status::SUCCESS);
+    sleep(1);
+    ASSERT_NE(memHandle, nullptr);
+    EXPECT_THAT(memHandle->getHardwareBuffer(), ContainsDataFromFrame(&frame));
+    EXPECT_THAT(memHandle->getTimeStamp(), 10);
+    EXPECT_THAT(memHandle->getStreamId(), 0);
+    EXPECT_THAT(activeBufferIds, Not(Contains(memHandle->getBufferId())));
+    activeBufferIds.insert(memHandle->getBufferId());
+
+    EXPECT_EQ(manager->queuePacket(frame, 20), Status::SUCCESS);
+    sleep(1);
+    ASSERT_NE(memHandle, nullptr);
+    EXPECT_THAT(memHandle->getHardwareBuffer(), ContainsDataFromFrame(&frame));
+    EXPECT_THAT(memHandle->getTimeStamp(), 20);
+    EXPECT_THAT(memHandle->getStreamId(), 0);
+    EXPECT_THAT(activeBufferIds, Not(Contains(memHandle->getBufferId())));
+    activeBufferIds.insert(memHandle->getBufferId());
+
+    // No new packet is produced as we have now reached the limit of number of
+    // packets.
+    EXPECT_EQ(manager->queuePacket(frame, 30), Status::SUCCESS);
+    sleep(1);
+    EXPECT_THAT(memHandle->getTimeStamp(), 20);
+    EXPECT_THAT(activeBufferIds, Contains(memHandle->getBufferId()));
+}
+
+TEST(PixelStreamManagerTest, DoneWithPacketCallReleasesAPacket) {
+    int maxInFlightPackets = 1;
+    auto [mockEngine, manager] = CreateStreamManagerAndEngine(maxInFlightPackets);
+    std::set<int> activeBufferIds;
+
+    DefaultEvent e = DefaultEvent::generateEntryEvent(DefaultEvent::Phase::RUN);
+
+    ASSERT_EQ(manager->handleExecutionPhase(e), Status::SUCCESS);
+    std::vector<uint8_t> data(16 * 16 * 3, 100);
+    InputFrame frame(16, 16, PixelFormat::RGB, 16 * 3, &data[0]);
+
+    std::shared_ptr<MemHandle> memHandle;
+    EXPECT_CALL((*mockEngine), dispatchPacket)
+        .Times(2)
+        .WillRepeatedly(testing::DoAll(testing::SaveArg<0>(&memHandle), (Return(Status::SUCCESS))));
+
+    EXPECT_EQ(manager->queuePacket(frame, 10), Status::SUCCESS);
+    sleep(1);
+    ASSERT_NE(memHandle, nullptr);
+    activeBufferIds.insert(memHandle->getBufferId());
+    EXPECT_THAT(memHandle->getHardwareBuffer(), ContainsDataFromFrame(&frame));
+    EXPECT_THAT(memHandle->getTimeStamp(), 10);
+    EXPECT_THAT(memHandle->getStreamId(), 0);
+
+    // Check that new packet has not been dispatched as the old packet has not been released yet.
+    EXPECT_EQ(manager->queuePacket(frame, 20), Status::SUCCESS);
+    sleep(1);
+    ASSERT_NE(memHandle, nullptr);
+    EXPECT_THAT(memHandle->getTimeStamp(), 10);
+
+    EXPECT_THAT(manager->freePacket(memHandle->getBufferId()), Status::SUCCESS);
+    EXPECT_EQ(manager->queuePacket(frame, 30), Status::SUCCESS);
+    sleep(1);
+    ASSERT_NE(memHandle, nullptr);
+    EXPECT_THAT(memHandle->getTimeStamp(), 30);
+}
+
+TEST(PixelStreamManagerTest, EngineReceivesEndOfStreamCallbackOnStoppage) {
+    int maxInFlightPackets = 1;
+    auto [mockEngine, manager] = CreateStreamManagerAndEngine(maxInFlightPackets);
+
+    DefaultEvent e = DefaultEvent::generateEntryEvent(DefaultEvent::Phase::RUN);
+
+    ASSERT_EQ(manager->handleExecutionPhase(e), Status::SUCCESS);
+    std::vector<uint8_t> data(16 * 16 * 3, 100);
+    InputFrame frame(16, 16, PixelFormat::RGB, 16 * 3, &data[0]);
+
+    std::shared_ptr<MemHandle> memHandle;
+    EXPECT_CALL((*mockEngine), dispatchPacket)
+        .WillOnce(testing::DoAll(testing::SaveArg<0>(&memHandle), (Return(Status::SUCCESS))));
+
+    EXPECT_EQ(manager->queuePacket(frame, 10), Status::SUCCESS);
+    sleep(1);
+    ASSERT_NE(memHandle, nullptr);
+    EXPECT_THAT(memHandle->getHardwareBuffer(), ContainsDataFromFrame(&frame));
+    EXPECT_THAT(memHandle->getTimeStamp(), 10);
+    EXPECT_THAT(memHandle->getStreamId(), 0);
+    EXPECT_EQ(manager->handleStopImmediatePhase(e), Status::SUCCESS);
+}
+
+}  // namespace
+}  // namespace stream_manager
+}  // namespace runner
+}  // namespace computepipe
+}  // namespace automotive
+}  // namespace android
diff --git a/computepipe/tests/runner/stream_manager/SemanticManagerTest.cpp b/computepipe/tests/runner/stream_manager/SemanticManagerTest.cpp
new file mode 100644
index 0000000..9550cc1
--- /dev/null
+++ b/computepipe/tests/runner/stream_manager/SemanticManagerTest.cpp
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include "EventGenerator.h"
+#include "MockEngine.h"
+#include "OutputConfig.pb.h"
+#include "RunnerComponent.h"
+#include "StreamEngineInterface.h"
+#include "StreamManager.h"
+#include "gmock/gmock-matchers.h"
+#include "types/Status.h"
+
+using namespace android::automotive::computepipe::runner::stream_manager;
+using namespace android::automotive::computepipe;
+using android::automotive::computepipe::runner::RunnerComponentInterface;
+using android::automotive::computepipe::runner::RunnerEvent;
+using android::automotive::computepipe::runner::generator::DefaultEvent;
+using testing::Return;
+
+class SemanticManagerTest : public ::testing::Test {
+  protected:
+    static constexpr uint32_t kMaxSemanticDataSize = 1024;
+    /**
+     * Setup for the test fixture to initialize the semantic manager
+     * After this, the semantic manager should be in RESET state.
+     */
+    void SetUp() override {
+    }
+    void TearDown() override {
+        if (mCurrentPacket) {
+            mCurrentPacket = nullptr;
+            ;
+        }
+    }
+
+    std::unique_ptr<StreamManager> SetupStreamManager(std::shared_ptr<MockEngine>& engine) {
+        proto::OutputConfig config;
+        config.set_type(proto::PacketType::SEMANTIC_DATA);
+        config.set_stream_name("semantic_stream");
+
+        return mFactory.getStreamManager(config, engine, 0);
+    }
+    StreamManagerFactory mFactory;
+    std::shared_ptr<MemHandle> mCurrentPacket;
+};
+
+/**
+ * Checks Packet Queing without start.
+ * Checks Packet Queuing with bad arguments.
+ * Checks successful packet queuing.
+ */
+TEST_F(SemanticManagerTest, PacketQueueTest) {
+    DefaultEvent e = DefaultEvent::generateEntryEvent(DefaultEvent::Phase::RUN);
+    std::shared_ptr<MockEngine> mockEngine = std::make_shared<MockEngine>();
+    std::unique_ptr<StreamManager> manager = SetupStreamManager(mockEngine);
+    ASSERT_EQ(manager->handleExecutionPhase(e), Status::SUCCESS);
+    std::string fakeData("FakeData");
+    uint32_t size = fakeData.size();
+    EXPECT_EQ(manager->queuePacket(nullptr, size, 0), Status::INVALID_ARGUMENT);
+    EXPECT_EQ(manager->queuePacket(fakeData.c_str(), kMaxSemanticDataSize + 1, 0),
+              Status::INVALID_ARGUMENT);
+    EXPECT_CALL((*mockEngine), dispatchPacket)
+        .WillOnce(testing::DoAll(testing::SaveArg<0>(&mCurrentPacket), (Return(Status::SUCCESS))));
+
+    manager->queuePacket(fakeData.c_str(), size, 0);
+    EXPECT_STREQ(mCurrentPacket->getData(), fakeData.c_str());
+}
diff --git a/computepipe/types/GraphState.h b/computepipe/types/GraphState.h
new file mode 100644
index 0000000..a5899b9
--- /dev/null
+++ b/computepipe/types/GraphState.h
@@ -0,0 +1,34 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef COMPUTEPIPE_TYPES_GRAPHSTATE_H_
+#define COMPUTEPIPE_TYPES_GRAPHSTATE_H_
+
+namespace android {
+namespace automotive {
+namespace computepipe {
+
+enum GraphState {
+    RESET = 0,
+    CONFIG_DONE,
+    RUNNING,
+    DONE,
+    ERR_HALT,
+};
+
+}  // namespace computepipe
+}  // namespace automotive
+}  // namespace android
+
+#endif  // COMPUTEPIPE_TYPES_GRAPHSTATE_H_
diff --git a/computepipe/types/Status.h b/computepipe/types/Status.h
new file mode 100644
index 0000000..af05c6a
--- /dev/null
+++ b/computepipe/types/Status.h
@@ -0,0 +1,43 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef COMPUTEPIPE_TYPES_STATUS_H_
+#define COMPUTEPIPE_TYPES_STATUS_H_
+
+namespace android {
+namespace automotive {
+namespace computepipe {
+
+enum Status {
+    SUCCESS = 0,
+    INTERNAL_ERROR,
+    INVALID_ARGUMENT,
+    ILLEGAL_STATE,
+    NO_MEMORY,
+    FATAL_ERROR,
+    STATUS_MAX,
+};
+
+enum PixelFormat {
+    RGB = 0,
+    RGBA,
+    GRAY,
+    PIXELFORMAT_MAX,
+};
+
+}  // namespace computepipe
+}  // namespace automotive
+}  // namespace android
+
+#endif  // COMPUTEPIPE_TYPES_STATUS_H_
diff --git a/evs/Android.bp b/evs/Android.bp
new file mode 100644
index 0000000..fee3461
--- /dev/null
+++ b/evs/Android.bp
@@ -0,0 +1,17 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//
+
+// Include the sub-makefiles
diff --git a/evs/Android.mk b/evs/Android.mk
deleted file mode 100644
index 01647b0..0000000
--- a/evs/Android.mk
+++ /dev/null
@@ -1,21 +0,0 @@
-# Copyright (C) 2016 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-#
-
-LOCAL_PATH := $(call my-dir)
-include $(CLEAR_VARS)
-
-# Include the sub-makefiles
-include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/evs/OWNERS b/evs/OWNERS
index a644193..cc4938d 100644
--- a/evs/OWNERS
+++ b/evs/OWNERS
@@ -2,4 +2,3 @@
 changyeon@google.com
 haoxiangl@google.com
 swan@google.com
-randolphs@google.com
diff --git a/evs/app/Android.mk b/evs/app/Android.mk
deleted file mode 100644
index cecbba5..0000000
--- a/evs/app/Android.mk
+++ /dev/null
@@ -1,83 +0,0 @@
-LOCAL_PATH:= $(call my-dir)
-
-##################################
-include $(CLEAR_VARS)
-
-LOCAL_SRC_FILES := \
-    evs_app.cpp \
-    EvsStateControl.cpp \
-    RenderBase.cpp \
-    RenderDirectView.cpp \
-    RenderTopView.cpp \
-    ConfigManager.cpp \
-    glError.cpp \
-    shader.cpp \
-    TexWrapper.cpp \
-    VideoTex.cpp \
-    StreamHandler.cpp \
-    WindowSurface.cpp \
-    FormatConvert.cpp \
-    RenderPixelCopy.cpp
-
-LOCAL_SHARED_LIBRARIES := \
-    libbinder \
-    libcutils \
-    liblog \
-    libutils \
-    libui \
-    libgui \
-    libhidlbase \
-    libhidltransport \
-    libEGL \
-    libGLESv2 \
-    libhardware \
-    libpng \
-    android.hardware.automotive.evs@1.0 \
-    android.hardware.automotive.vehicle@2.0 \
-
-LOCAL_STATIC_LIBRARIES := \
-    libmath \
-    libjsoncpp \
-
-LOCAL_STRIP_MODULE := keep_symbols
-
-LOCAL_INIT_RC := evs_app.rc
-
-LOCAL_MODULE:= evs_app
-LOCAL_MODULE_TAGS := optional
-
-LOCAL_CFLAGS += -DLOG_TAG=\"EvsApp\"
-LOCAL_CFLAGS += -DGL_GLEXT_PROTOTYPES -DEGL_EGLEXT_PROTOTYPES
-LOCAL_CFLAGS += -Wall -Werror -Wunused -Wunreachable-code
-
-include $(BUILD_EXECUTABLE)
-
-
-include $(CLEAR_VARS)
-LOCAL_MODULE := config.json
-LOCAL_MODULE_CLASS := ETC
-LOCAL_MODULE_PATH := $(TARGET_OUT_ETC)/automotive/evs
-LOCAL_SRC_FILES := $(LOCAL_MODULE)
-include $(BUILD_PREBUILT)
-
-include $(CLEAR_VARS)
-LOCAL_MODULE := CarFromTop.png
-LOCAL_MODULE_CLASS := ETC
-LOCAL_MODULE_PATH := $(TARGET_OUT_ETC)/automotive/evs
-LOCAL_SRC_FILES := $(LOCAL_MODULE)
-include $(BUILD_PREBUILT)
-
-include $(CLEAR_VARS)
-LOCAL_MODULE := LabeledChecker.png
-LOCAL_MODULE_CLASS := ETC
-LOCAL_MODULE_PATH := $(TARGET_OUT_ETC)/automotive/evs
-LOCAL_SRC_FILES := $(LOCAL_MODULE)
-include $(BUILD_PREBUILT)
-
-include $(CLEAR_VARS)
-LOCAL_MODULE := evs_app_default_resources
-LOCAL_REQUIRED_MODULES := \
-    config.json \
-    CarFromTop.png \
-    LabeledChecker.png
-include $(BUILD_PHONY_PACKAGE)
\ No newline at end of file
diff --git a/evs/app/ConfigManager.cpp b/evs/app/ConfigManager.cpp
deleted file mode 100644
index 07e570d..0000000
--- a/evs/app/ConfigManager.cpp
+++ /dev/null
@@ -1,188 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-#include "ConfigManager.h"
-
-#include "json/json.h"
-
-#include <fstream>
-#include <math.h>
-#include <assert.h>
-
-
-static const float kDegreesToRadians = M_PI / 180.0f;
-
-
-static float normalizeToPlusMinus180degrees(float theta) {
-    const float wraps = floor((theta+180.0f) / 360.0f);
-    return theta - wraps*360.0f;
-}
-
-
-static bool readChildNodeAsFloat(const char* groupName,
-                                 const Json::Value& parentNode,
-                                 const char* childName,
-                                 float* value) {
-    // Must have a place to put the value!
-    assert(value);
-
-    Json::Value childNode = parentNode[childName];
-    if (!childNode.isNumeric()) {
-        printf("Missing or invalid field %s in record %s", childName, groupName);
-        return false;
-    }
-
-    *value = childNode.asFloat();
-    return true;
-}
-
-
-bool ConfigManager::initialize(const char* configFileName)
-{
-    bool complete = true;
-
-    // Set up a stream to read in the input file
-    std::ifstream configStream(configFileName);
-
-    // Parse the stream into JSON objects
-    Json::Reader reader;
-    Json::Value rootNode;
-    bool parseOk = reader.parse(configStream, rootNode, false /* don't need comments */);
-    if (!parseOk) {
-        printf("Failed to read configuration file %s\n", configFileName);
-        printf("%s\n", reader.getFormatedErrorMessages().c_str());
-        return false;
-    }
-
-
-    //
-    // Read car information
-    //
-    {
-        Json::Value car = rootNode["car"];
-        if (!car.isObject()) {
-            printf("Invalid configuration format -- we expect a car description\n");
-            return false;
-        }
-        complete &= readChildNodeAsFloat("car", car, "width",       &mCarWidth);
-        complete &= readChildNodeAsFloat("car", car, "wheelBase",   &mWheelBase);
-        complete &= readChildNodeAsFloat("car", car, "frontExtent", &mFrontExtent);
-        complete &= readChildNodeAsFloat("car", car, "rearExtent",  &mRearExtent);
-    }
-
-
-    //
-    // Read display layout information
-    //
-    {
-        Json::Value displayNode = rootNode["display"];
-        if (!displayNode.isObject()) {
-            printf("Invalid configuration format -- we expect a display description\n");
-            return false;
-        }
-        complete &= readChildNodeAsFloat("display", displayNode, "frontRange", &mFrontRangeInCarSpace);
-        complete &= readChildNodeAsFloat("display", displayNode, "rearRange",  &mRearRangeInCarSpace);
-    }
-
-
-    //
-    // Car top view texture properties for top down view
-    //
-    {
-        Json::Value graphicNode = rootNode["graphic"];
-        if (!graphicNode.isObject()) {
-            printf("Invalid configuration format -- we expect a graphic description\n");
-            return false;
-        }
-        complete &= readChildNodeAsFloat("graphic", graphicNode, "frontPixel", &mCarGraphicFrontPixel);
-        complete &= readChildNodeAsFloat("display", graphicNode, "rearPixel",  &mCarGraphicRearPixel);
-    }
-
-
-    //
-    // Read camera information
-    // NOTE:  Missing positions and angles are not reported, but instead default to zero
-    //
-    {
-        Json::Value cameraArray = rootNode["cameras"];
-        if (!cameraArray.isArray()) {
-            printf("Invalid configuration format -- we expect an array of cameras\n");
-            return false;
-        }
-
-        mCameras.reserve(cameraArray.size());
-        for (auto&& node: cameraArray) {
-            // Get data from the configuration file
-            Json::Value nameNode = node.get("cameraId", "MISSING");
-            const char *cameraId = nameNode.asCString();
-
-            Json::Value usageNode = node.get("function", "");
-            const char *function = usageNode.asCString();
-
-            float yaw   = node.get("yaw", 0).asFloat();
-            float pitch = node.get("pitch", 0).asFloat();
-            float hfov  = node.get("hfov", 0).asFloat();
-            float vfov  = node.get("vfov", 0).asFloat();
-
-            // Wrap the direction angles to be in the 180deg to -180deg range
-            // Rotate 180 in yaw if necessary to flip the pitch into the +/-90degree range
-            pitch = normalizeToPlusMinus180degrees(pitch);
-            if (pitch > 90.0f) {
-                yaw += 180.0f;
-                pitch = 180.0f - pitch;
-            }
-            if (pitch < -90.0f) {
-                yaw += 180.0f;
-                pitch = -180.0f + pitch;
-            }
-            yaw = normalizeToPlusMinus180degrees(yaw);
-
-            // Range check the FOV values to ensure they are postive and less than 180degrees
-            if (hfov > 179.0f) {
-                printf("Pathological horizontal field of view %f clamped to 179 degrees\n", hfov);
-                hfov = 179.0f;
-            }
-            if (hfov < 1.0f) {
-                printf("Pathological horizontal field of view %f clamped to 1 degree\n", hfov);
-                hfov = 1.0f;
-            }
-            if (vfov > 179.0f) {
-                printf("Pathological horizontal field of view %f clamped to 179 degrees\n", vfov);
-                vfov = 179.0f;
-            }
-            if (vfov < 1.0f) {
-                printf("Pathological horizontal field of view %f clamped to 1 degree\n", vfov);
-                vfov = 1.0f;
-            }
-
-            // Store the camera info (converting degrees to radians in the process)
-            CameraInfo info;
-            info.position[0] = node.get("x", 0).asFloat();
-            info.position[1] = node.get("y", 0).asFloat();
-            info.position[2] = node.get("z", 0).asFloat();
-            info.yaw         = yaw   * kDegreesToRadians;
-            info.pitch       = pitch * kDegreesToRadians;
-            info.hfov        = hfov  * kDegreesToRadians;
-            info.vfov        = vfov  * kDegreesToRadians;
-            info.cameraId    = cameraId;
-            info.function    = function;
-
-            mCameras.push_back(info);
-        }
-    }
-
-    // If we got this far, we were successful as long as we found all our child fields
-    return complete;
-}
diff --git a/evs/app/ConfigManager.h b/evs/app/ConfigManager.h
deleted file mode 100644
index 0d24919..0000000
--- a/evs/app/ConfigManager.h
+++ /dev/null
@@ -1,94 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-#ifndef CONFIG_MANAGER_H
-#define CONFIG_MANAGER_H
-
-#include <vector>
-#include <string>
-
-
-class ConfigManager {
-public:
-    struct CameraInfo {
-        std::string cameraId = "";  // The name of the camera from the point of view of the HAL
-        std::string function = "";  // The expected use for this camera ("reverse", "left", "right")
-        float position[3] = {0};    // x, y, z -> right, fwd, up in the units of car space
-        float yaw   = 0;    // radians positive to the left (right hand rule about global z axis)
-        float pitch = 0;    // positive upward (ie: right hand rule about local x axis)
-        float hfov  = 0;    // radians
-        float vfov  = 0;    // radians
-    };
-
-    bool initialize(const char* configFileName);
-
-    // World space dimensions of the car
-    float getCarWidth() const   { return mCarWidth; };
-    float getCarLength() const  { return mWheelBase + mFrontExtent + mRearExtent; };
-    float getWheelBase() const  { return mWheelBase; };
-
-    // Car space (world space centered on the rear axel) edges of the car
-    float getFrontLocation() const  { return mWheelBase + mFrontExtent; };
-    float getRearLocation() const   { return -mRearExtent; };
-    float getRightLocation() const  { return mCarWidth*0.5f; };
-    float getLeftLocation() const   { return -mCarWidth*0.5f; };
-
-    // Where are the edges of the top down display in car space?
-    float getDisplayTopLocation() const {
-        // From the rear axel (origin) to the front bumper, and then beyond by the front range
-        return mWheelBase + mFrontExtent + mFrontRangeInCarSpace;
-    };
-    float getDisplayBottomLocation() const {
-        // From the rear axel (origin) to the back bumper, and then beyond by the back range
-        return -mRearExtent - mRearRangeInCarSpace;
-    };
-    float getDisplayRightLocation(float aspectRatio) const   {
-        // Given the display aspect ratio (width over height), how far can we see to the right?
-        return (getDisplayTopLocation() - getDisplayBottomLocation()) * 0.5f * aspectRatio;
-    };
-    float getDisplayLeftLocation(float aspectRatio) const {
-        // Given the display aspect ratio (width over height), how far can we see to the left?
-        return -getDisplayRightLocation(aspectRatio);
-    };
-
-    // At which texel (vertically in the image) are the front and rear bumpers of the car?
-    float carGraphicFrontPixel() const      { return mCarGraphicFrontPixel; };
-    float carGraphicRearPixel() const       { return mCarGraphicRearPixel; };
-
-    const std::vector<CameraInfo>& getCameras() const   { return mCameras; };
-
-private:
-    // Camera information
-    std::vector<CameraInfo> mCameras;
-
-    // Car body information (assumes front wheel steering and origin at center of rear axel)
-    // Note that units aren't specified and don't matter as long as all length units are consistent
-    // within the JSON file from which we parse.  That is, if everything is in meters, that's fine.
-    // Everything in mm?  That's fine too.
-    float mCarWidth;
-    float mWheelBase;
-    float mFrontExtent;
-    float mRearExtent;
-
-    // Display information
-    float    mFrontRangeInCarSpace;     // How far the display extends in front of the car
-    float    mRearRangeInCarSpace;      // How far the display extends behind the car
-
-    // Top view car image information
-    float mCarGraphicFrontPixel;    // How many pixels from the top of the image does the car start
-    float mCarGraphicRearPixel;     // How many pixels from the top of the image does the car end
-};
-
-#endif // CONFIG_MANAGER_H
\ No newline at end of file
diff --git a/evs/app/EvsStateControl.cpp b/evs/app/EvsStateControl.cpp
deleted file mode 100644
index 8922260..0000000
--- a/evs/app/EvsStateControl.cpp
+++ /dev/null
@@ -1,371 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-#include "EvsStateControl.h"
-#include "RenderDirectView.h"
-#include "RenderTopView.h"
-#include "RenderPixelCopy.h"
-
-#include <stdio.h>
-#include <string.h>
-
-#include <log/log.h>
-#include <inttypes.h>
-#include <utils/SystemClock.h>
-#include <binder/IServiceManager.h>
-
-static bool isSfReady() {
-    const android::String16 serviceName("SurfaceFlinger");
-    return android::defaultServiceManager()->checkService(serviceName) != nullptr;
-}
-
-// TODO:  Seems like it'd be nice if the Vehicle HAL provided such helpers (but how & where?)
-inline constexpr VehiclePropertyType getPropType(VehicleProperty prop) {
-    return static_cast<VehiclePropertyType>(
-            static_cast<int32_t>(prop)
-            & static_cast<int32_t>(VehiclePropertyType::MASK));
-}
-
-
-EvsStateControl::EvsStateControl(android::sp <IVehicle>       pVnet,
-                                 android::sp <IEvsEnumerator> pEvs,
-                                 android::sp <IEvsDisplay>    pDisplay,
-                                 const ConfigManager&         config) :
-    mVehicle(pVnet),
-    mEvs(pEvs),
-    mDisplay(pDisplay),
-    mConfig(config),
-    mCurrentState(OFF) {
-
-    // Initialize the property value containers we'll be updating (they'll be zeroed by default)
-    static_assert(getPropType(VehicleProperty::GEAR_SELECTION) == VehiclePropertyType::INT32,
-                  "Unexpected type for GEAR_SELECTION property");
-    static_assert(getPropType(VehicleProperty::TURN_SIGNAL_STATE) == VehiclePropertyType::INT32,
-                  "Unexpected type for TURN_SIGNAL_STATE property");
-
-    mGearValue.prop       = static_cast<int32_t>(VehicleProperty::GEAR_SELECTION);
-    mTurnSignalValue.prop = static_cast<int32_t>(VehicleProperty::TURN_SIGNAL_STATE);
-
-#if 1
-    // This way we only ever deal with cameras which exist in the system
-    // Build our set of cameras for the states we support
-    ALOGD("Requesting camera list");
-    mEvs->getCameraList([this, &config](hidl_vec<CameraDesc> cameraList) {
-                            ALOGI("Camera list callback received %zu cameras",
-                                  cameraList.size());
-                            for (auto&& cam: cameraList) {
-                                ALOGD("Found camera %s", cam.cameraId.c_str());
-                                bool cameraConfigFound = false;
-
-                                // Check our configuration for information about this camera
-                                // Note that a camera can have a compound function string
-                                // such that a camera can be "right/reverse" and be used for both.
-                                // If more than one camera is listed for a given function, we'll
-                                // list all of them and let the UX/rendering logic use one, some
-                                // or all of them as appropriate.
-                                for (auto&& info: config.getCameras()) {
-                                    if (cam.cameraId == info.cameraId) {
-                                        // We found a match!
-                                        if (info.function.find("reverse") != std::string::npos) {
-                                            mCameraList[State::REVERSE].push_back(info);
-                                        }
-                                        if (info.function.find("right") != std::string::npos) {
-                                            mCameraList[State::RIGHT].push_back(info);
-                                        }
-                                        if (info.function.find("left") != std::string::npos) {
-                                            mCameraList[State::LEFT].push_back(info);
-                                        }
-                                        if (info.function.find("park") != std::string::npos) {
-                                            mCameraList[State::PARKING].push_back(info);
-                                        }
-                                        cameraConfigFound = true;
-                                        break;
-                                    }
-                                }
-                                if (!cameraConfigFound) {
-                                    ALOGW("No config information for hardware camera %s",
-                                          cam.cameraId.c_str());
-                                }
-                            }
-                        }
-    );
-#else // This way we use placeholders for cameras in the configuration but not reported by EVS
-    // Build our set of cameras for the states we support
-    ALOGD("Requesting camera list");
-    for (auto&& info: config.getCameras()) {
-        if (info.function.find("reverse") != std::string::npos) {
-            mCameraList[State::REVERSE].push_back(info);
-        }
-        if (info.function.find("right") != std::string::npos) {
-            mCameraList[State::RIGHT].push_back(info);
-        }
-        if (info.function.find("left") != std::string::npos) {
-            mCameraList[State::LEFT].push_back(info);
-        }
-        if (info.function.find("park") != std::string::npos) {
-            mCameraList[State::PARKING].push_back(info);
-        }
-    }
-#endif
-
-    ALOGD("State controller ready");
-}
-
-
-bool EvsStateControl::startUpdateLoop() {
-    // Create the thread and report success if it gets started
-    mRenderThread = std::thread([this](){ updateLoop(); });
-    return mRenderThread.joinable();
-}
-
-
-void EvsStateControl::postCommand(const Command& cmd) {
-    // Push the command onto the queue watched by updateLoop
-    mLock.lock();
-    mCommandQueue.push(cmd);
-    mLock.unlock();
-
-    // Send a signal to wake updateLoop in case it is asleep
-    mWakeSignal.notify_all();
-}
-
-
-void EvsStateControl::updateLoop() {
-    ALOGD("Starting EvsStateControl update loop");
-
-    bool run = true;
-    while (run) {
-        // Process incoming commands
-        {
-            std::lock_guard <std::mutex> lock(mLock);
-            while (!mCommandQueue.empty()) {
-                const Command& cmd = mCommandQueue.front();
-                switch (cmd.operation) {
-                case Op::EXIT:
-                    run = false;
-                    break;
-                case Op::CHECK_VEHICLE_STATE:
-                    // Just running selectStateForCurrentConditions below will take care of this
-                    break;
-                case Op::TOUCH_EVENT:
-                    // Implement this given the x/y location of the touch event
-                    break;
-                }
-                mCommandQueue.pop();
-            }
-        }
-
-        // Review vehicle state and choose an appropriate renderer
-        if (!selectStateForCurrentConditions()) {
-            ALOGE("selectStateForCurrentConditions failed so we're going to die");
-            break;
-        }
-
-        // If we have an active renderer, give it a chance to draw
-        if (mCurrentRenderer) {
-            // Get the output buffer we'll use to display the imagery
-            BufferDesc tgtBuffer = {};
-            mDisplay->getTargetBuffer([&tgtBuffer](const BufferDesc& buff) {
-                                          tgtBuffer = buff;
-                                      }
-            );
-
-            if (tgtBuffer.memHandle == nullptr) {
-                ALOGE("Didn't get requested output buffer -- skipping this frame.");
-            } else {
-                // Generate our output image
-                if (!mCurrentRenderer->drawFrame(tgtBuffer)) {
-                    // If drawing failed, we want to exit quickly so an app restart can happen
-                    run = false;
-                }
-
-                // Send the finished image back for display
-                mDisplay->returnTargetBufferForDisplay(tgtBuffer);
-            }
-        } else {
-            // No active renderer, so sleep until somebody wakes us with another command
-            std::unique_lock<std::mutex> lock(mLock);
-            mWakeSignal.wait(lock);
-        }
-    }
-
-    ALOGW("EvsStateControl update loop ending");
-
-    // TODO:  Fix it so we can exit cleanly from the main thread instead
-    printf("Shutting down app due to state control loop ending\n");
-    ALOGE("KILLING THE APP FROM THE EvsStateControl LOOP ON DRAW FAILURE!!!");
-    exit(1);
-}
-
-
-bool EvsStateControl::selectStateForCurrentConditions() {
-    static int32_t sDummyGear   = int32_t(VehicleGear::GEAR_REVERSE);
-    static int32_t sDummySignal = int32_t(VehicleTurnSignal::NONE);
-
-    if (mVehicle != nullptr) {
-        // Query the car state
-        if (invokeGet(&mGearValue) != StatusCode::OK) {
-            ALOGE("GEAR_SELECTION not available from vehicle.  Exiting.");
-            return false;
-        }
-        if ((mTurnSignalValue.prop == 0) || (invokeGet(&mTurnSignalValue) != StatusCode::OK)) {
-            // Silently treat missing turn signal state as no turn signal active
-            mTurnSignalValue.value.int32Values.setToExternal(&sDummySignal, 1);
-            mTurnSignalValue.prop = 0;
-        }
-    } else {
-        // While testing without a vehicle, behave as if we're in reverse for the first 20 seconds
-        static const int kShowTime = 20;    // seconds
-
-        // See if it's time to turn off the default reverse camera
-        static std::chrono::steady_clock::time_point start = std::chrono::steady_clock::now();
-        std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now();
-        if (std::chrono::duration_cast<std::chrono::seconds>(now - start).count() > kShowTime) {
-            // Switch to drive (which should turn off the reverse camera)
-            sDummyGear = int32_t(VehicleGear::GEAR_DRIVE);
-        }
-
-        // Build the dummy vehicle state values (treating single values as 1 element vectors)
-        mGearValue.value.int32Values.setToExternal(&sDummyGear, 1);
-        mTurnSignalValue.value.int32Values.setToExternal(&sDummySignal, 1);
-    }
-
-    // Choose our desired EVS state based on the current car state
-    // TODO:  Update this logic, and consider user input when choosing if a view should be presented
-    State desiredState = OFF;
-    if (mGearValue.value.int32Values[0] == int32_t(VehicleGear::GEAR_REVERSE)) {
-        desiredState = REVERSE;
-    } else if (mTurnSignalValue.value.int32Values[0] == int32_t(VehicleTurnSignal::RIGHT)) {
-        desiredState = RIGHT;
-    } else if (mTurnSignalValue.value.int32Values[0] == int32_t(VehicleTurnSignal::LEFT)) {
-        desiredState = LEFT;
-    } else if (mGearValue.value.int32Values[0] == int32_t(VehicleGear::GEAR_PARK)) {
-        desiredState = PARKING;
-    }
-
-    // Apply the desire state
-    return configureEvsPipeline(desiredState);
-}
-
-
-StatusCode EvsStateControl::invokeGet(VehiclePropValue *pRequestedPropValue) {
-    StatusCode status = StatusCode::TRY_AGAIN;
-
-    // Call the Vehicle HAL, which will block until the callback is complete
-    mVehicle->get(*pRequestedPropValue,
-                  [pRequestedPropValue, &status]
-                  (StatusCode s, const VehiclePropValue& v) {
-                       status = s;
-                       if (s == StatusCode::OK) {
-                           *pRequestedPropValue = v;
-                       }
-                  }
-    );
-
-    return status;
-}
-
-
-bool EvsStateControl::configureEvsPipeline(State desiredState) {
-    static bool isGlReady = false;
-
-    if (mCurrentState == desiredState) {
-        // Nothing to do here...
-        return true;
-    }
-
-    ALOGD("Switching to state %d.", desiredState);
-    ALOGD("  Current state %d has %zu cameras", mCurrentState,
-          mCameraList[mCurrentState].size());
-    ALOGD("  Desired state %d has %zu cameras", desiredState,
-          mCameraList[desiredState].size());
-
-    if (!isGlReady && !isSfReady()) {
-        // Graphics is not ready yet; using CPU renderer.
-        if (mCameraList[desiredState].size() >= 1) {
-            mDesiredRenderer = std::make_unique<RenderPixelCopy>(mEvs,
-                                                                 mCameraList[desiredState][0]);
-            if (!mDesiredRenderer) {
-                ALOGE("Failed to construct Pixel Copy renderer.  Skipping state change.");
-                return false;
-            }
-        } else {
-            ALOGD("Unsupported, desiredState %d has %u cameras.",
-                  desiredState, static_cast<unsigned int>(mCameraList[desiredState].size()));
-        }
-    } else {
-        // Assumes that SurfaceFlinger is available always after being launched.
-
-        // Do we need a new direct view renderer?
-        if (mCameraList[desiredState].size() == 1) {
-            // We have a camera assigned to this state for direct view.
-            mDesiredRenderer = std::make_unique<RenderDirectView>(mEvs,
-                                                                  mCameraList[desiredState][0]);
-            if (!mDesiredRenderer) {
-                ALOGE("Failed to construct direct renderer.  Skipping state change.");
-                return false;
-            }
-        } else if (mCameraList[desiredState].size() > 1 || desiredState == PARKING) {
-            mDesiredRenderer = std::make_unique<RenderTopView>(mEvs,
-                                                               mCameraList[desiredState],
-                                                               mConfig);
-            if (!mDesiredRenderer) {
-                ALOGE("Failed to construct top view renderer.  Skipping state change.");
-                return false;
-            }
-        } else {
-            ALOGD("Unsupported, desiredState %d has %u cameras.",
-                  desiredState, static_cast<unsigned int>(mCameraList[desiredState].size()));
-        }
-
-        // GL renderer is now ready.
-        isGlReady = true;
-    }
-
-    // Since we're changing states, shut down the current renderer
-    if (mCurrentRenderer != nullptr) {
-        mCurrentRenderer->deactivate();
-        mCurrentRenderer = nullptr; // It's a smart pointer, so destructs on assignment to null
-    }
-
-    // Now set the display state based on whether we have a video feed to show
-    if (mDesiredRenderer == nullptr) {
-        ALOGD("Turning off the display");
-        mDisplay->setDisplayState(DisplayState::NOT_VISIBLE);
-    } else {
-        mCurrentRenderer = std::move(mDesiredRenderer);
-
-        // Start the camera stream
-        ALOGD("EvsStartCameraStreamTiming start time: %" PRId64 "ms", android::elapsedRealtime());
-        if (!mCurrentRenderer->activate()) {
-            ALOGE("New renderer failed to activate");
-            return false;
-        }
-
-        // Activate the display
-        ALOGD("EvsActivateDisplayTiming start time: %" PRId64 "ms", android::elapsedRealtime());
-        Return<EvsResult> result = mDisplay->setDisplayState(DisplayState::VISIBLE_ON_NEXT_FRAME);
-        if (result != EvsResult::OK) {
-            ALOGE("setDisplayState returned an error (%d)", (EvsResult)result);
-            return false;
-        }
-    }
-
-    // Record our current state
-    ALOGI("Activated state %d.", desiredState);
-    mCurrentState = desiredState;
-
-    return true;
-}
diff --git a/evs/app/EvsStateControl.h b/evs/app/EvsStateControl.h
deleted file mode 100644
index 9f7f967..0000000
--- a/evs/app/EvsStateControl.h
+++ /dev/null
@@ -1,109 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef CAR_EVS_APP_EVSSTATECONTROL_H
-#define CAR_EVS_APP_EVSSTATECONTROL_H
-
-#include "StreamHandler.h"
-#include "ConfigManager.h"
-#include "RenderBase.h"
-
-#include <android/hardware/automotive/vehicle/2.0/IVehicle.h>
-#include <android/hardware/automotive/evs/1.0/IEvsEnumerator.h>
-#include <android/hardware/automotive/evs/1.0/IEvsDisplay.h>
-#include <android/hardware/automotive/evs/1.0/IEvsCamera.h>
-
-#include <thread>
-
-
-using namespace ::android::hardware::automotive::evs::V1_0;
-using namespace ::android::hardware::automotive::vehicle::V2_0;
-using ::android::hardware::Return;
-using ::android::hardware::Void;
-using ::android::hardware::hidl_vec;
-using ::android::hardware::hidl_handle;
-using ::android::sp;
-
-
-/*
- * This class runs the main update loop for the EVS application.  It will sleep when it has
- * nothing to do.  It provides a thread safe way for other threads to wake it and pass commands
- * to it.
- */
-class EvsStateControl {
-public:
-    EvsStateControl(android::sp <IVehicle>       pVnet,
-                    android::sp <IEvsEnumerator> pEvs,
-                    android::sp <IEvsDisplay>    pDisplay,
-                    const ConfigManager&         config);
-
-    enum State {
-        OFF = 0,
-        REVERSE,
-        LEFT,
-        RIGHT,
-        PARKING,
-        NUM_STATES  // Must come last
-    };
-
-    enum class Op {
-        EXIT,
-        CHECK_VEHICLE_STATE,
-        TOUCH_EVENT,
-    };
-
-    struct Command {
-        Op          operation;
-        uint32_t    arg1;
-        uint32_t    arg2;
-    };
-
-    // This spawns a new thread that is expected to run continuously
-    bool startUpdateLoop();
-
-    // Safe to be called from other threads
-    void postCommand(const Command& cmd);
-
-private:
-    void updateLoop();
-    StatusCode invokeGet(VehiclePropValue *pRequestedPropValue);
-    bool selectStateForCurrentConditions();
-    bool configureEvsPipeline(State desiredState);  // Only call from one thread!
-
-    sp<IVehicle>                mVehicle;
-    sp<IEvsEnumerator>          mEvs;
-    sp<IEvsDisplay>             mDisplay;
-    const ConfigManager&        mConfig;
-
-    VehiclePropValue            mGearValue;
-    VehiclePropValue            mTurnSignalValue;
-
-    State                       mCurrentState = OFF;
-
-    std::vector<ConfigManager::CameraInfo>  mCameraList[NUM_STATES];
-    std::unique_ptr<RenderBase> mCurrentRenderer;
-    std::unique_ptr<RenderBase> mDesiredRenderer;
-
-    std::thread                 mRenderThread;  // The thread that runs the main rendering loop
-
-    // Other threads may want to spur us into action, so we provide a thread safe way to do that
-    std::mutex                  mLock;
-    std::condition_variable     mWakeSignal;
-    std::queue<Command>         mCommandQueue;
-};
-
-
-#endif //CAR_EVS_APP_EVSSTATECONTROL_H
diff --git a/evs/app/FormatConvert.cpp b/evs/app/FormatConvert.cpp
deleted file mode 100644
index bd83ba3..0000000
--- a/evs/app/FormatConvert.cpp
+++ /dev/null
@@ -1,169 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "FormatConvert.h"
-
-
-// Round up to the nearest multiple of the given alignment value
-template<unsigned alignment>
-int align(int value) {
-    static_assert((alignment && !(alignment & (alignment - 1))),
-                  "alignment must be a power of 2");
-
-    unsigned mask = alignment - 1;
-    return (value + mask) & ~mask;
-}
-
-
-// Limit the given value to the provided range.  :)
-static inline float clamp(float v, float min, float max) {
-    if (v < min) return min;
-    if (v > max) return max;
-    return v;
-}
-
-
-static uint32_t yuvToRgbx(const unsigned char Y, const unsigned char Uin, const unsigned char Vin) {
-    // Don't use this if you want to see the best performance.  :)
-    // Better to do this in a pixel shader if we really have to, but on actual
-    // embedded hardware we expect to be able to texture directly from the YUV data
-    float U = Uin - 128.0f;
-    float V = Vin - 128.0f;
-
-    float Rf = Y + 1.140f*V;
-    float Gf = Y - 0.395f*U - 0.581f*V;
-    float Bf = Y + 2.032f*U;
-    unsigned char R = (unsigned char)clamp(Rf, 0.0f, 255.0f);
-    unsigned char G = (unsigned char)clamp(Gf, 0.0f, 255.0f);
-    unsigned char B = (unsigned char)clamp(Bf, 0.0f, 255.0f);
-
-    return (R      ) |
-           (G <<  8) |
-           (B << 16) |
-           0xFF000000;  // Fill the alpha channel with ones
-}
-
-
-void copyNV21toRGB32(unsigned width, unsigned height,
-                     uint8_t* src,
-                     uint32_t* dst, unsigned dstStridePixels)
-{
-    // The NV21 format provides a Y array of 8bit values, followed by a 1/2 x 1/2 interleaved
-    // U/V array.  It assumes an even width and height for the overall image, and a horizontal
-    // stride that is an even multiple of 16 bytes for both the Y and UV arrays.
-    unsigned strideLum = align<16>(width);
-    unsigned sizeY = strideLum * height;
-    unsigned strideColor = strideLum;   // 1/2 the samples, but two interleaved channels
-    unsigned offsetUV = sizeY;
-
-    uint8_t* srcY = src;
-    uint8_t* srcUV = src+offsetUV;
-
-    for (unsigned r = 0; r < height; r++) {
-        // Note that we're walking the same UV row twice for even/odd luminance rows
-        uint8_t* rowY  = srcY  + r*strideLum;
-        uint8_t* rowUV = srcUV + (r/2 * strideColor);
-
-        uint32_t* rowDest = dst + r*dstStridePixels;
-
-        for (unsigned c = 0; c < width; c++) {
-            unsigned uCol = (c & ~1);   // uCol is always even and repeats 1:2 with Y values
-            unsigned vCol = uCol | 1;   // vCol is always odd
-            rowDest[c] = yuvToRgbx(rowY[c], rowUV[uCol], rowUV[vCol]);
-        }
-    }
-}
-
-
-void copyYV12toRGB32(unsigned width, unsigned height,
-                     uint8_t* src,
-                     uint32_t* dst, unsigned dstStridePixels)
-{
-    // The YV12 format provides a Y array of 8bit values, followed by a 1/2 x 1/2 U array, followed
-    // by another 1/2 x 1/2 V array.  It assumes an even width and height for the overall image,
-    // and a horizontal stride that is an even multiple of 16 bytes for each of the Y, U,
-    // and V arrays.
-    unsigned strideLum = align<16>(width);
-    unsigned sizeY = strideLum * height;
-    unsigned strideColor = align<16>(strideLum/2);
-    unsigned sizeColor = strideColor * height/2;
-    unsigned offsetU = sizeY;
-    unsigned offsetV = sizeY + sizeColor;
-
-    uint8_t* srcY = src;
-    uint8_t* srcU = src+offsetU;
-    uint8_t* srcV = src+offsetV;
-
-    for (unsigned r = 0; r < height; r++) {
-        // Note that we're walking the same U and V rows twice for even/odd luminance rows
-        uint8_t* rowY = srcY + r*strideLum;
-        uint8_t* rowU = srcU + (r/2 * strideColor);
-        uint8_t* rowV = srcV + (r/2 * strideColor);
-
-        uint32_t* rowDest = dst + r*dstStridePixels;
-
-        for (unsigned c = 0; c < width; c++) {
-            rowDest[c] = yuvToRgbx(rowY[c], rowU[c], rowV[c]);
-        }
-    }
-}
-
-
-void copyYUYVtoRGB32(unsigned width, unsigned height,
-                     uint8_t* src, unsigned srcStridePixels,
-                     uint32_t* dst, unsigned dstStridePixels)
-{
-    uint32_t* srcWords = (uint32_t*)src;
-
-    const int srcRowPadding32 = srcStridePixels/2 - width/2;  // 2 bytes per pixel, 4 bytes per word
-    const int dstRowPadding32 = dstStridePixels   - width;    // 4 bytes per pixel, 4 bytes per word
-
-    for (unsigned r = 0; r < height; r++) {
-        for (unsigned c = 0; c < width/2; c++) {
-            // Note:  we're walking two pixels at a time here (even/odd)
-            uint32_t srcPixel = *srcWords++;
-
-            uint8_t Y1 = (srcPixel)       & 0xFF;
-            uint8_t U  = (srcPixel >> 8)  & 0xFF;
-            uint8_t Y2 = (srcPixel >> 16) & 0xFF;
-            uint8_t V  = (srcPixel >> 24) & 0xFF;
-
-            // On the RGB output, we're writing one pixel at a time
-            *(dst+0) = yuvToRgbx(Y1, U, V);
-            *(dst+1) = yuvToRgbx(Y2, U, V);
-            dst += 2;
-        }
-
-        // Skip over any extra data or end of row alignment padding
-        srcWords += srcRowPadding32;
-        dst += dstRowPadding32;
-    }
-}
-
-
-void copyMatchedInterleavedFormats(unsigned width, unsigned height,
-                                   void* src, unsigned srcStridePixels,
-                                   void* dst, unsigned dstStridePixels,
-                                   unsigned pixelSize) {
-    for (unsigned row = 0; row < height; row++) {
-        // Copy the entire row of pixel data
-        memcpy(dst, src, width * pixelSize);
-
-        // Advance to the next row (keeping in mind that stride here is in units of pixels)
-        src = (uint8_t*)src + srcStridePixels * pixelSize;
-        dst = (uint8_t*)dst + dstStridePixels * pixelSize;
-    }
-}
diff --git a/evs/app/FormatConvert.h b/evs/app/FormatConvert.h
deleted file mode 100644
index 3ff1eec..0000000
--- a/evs/app/FormatConvert.h
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef EVS_VTS_FORMATCONVERT_H
-#define EVS_VTS_FORMATCONVERT_H
-
-#include <queue>
-#include <stdint.h>
-
-
-// Given an image buffer in NV21 format (HAL_PIXEL_FORMAT_YCRCB_420_SP), output 32bit RGBx values.
-// The NV21 format provides a Y array of 8bit values, followed by a 1/2 x 1/2 interleaved
-// U/V array.  It assumes an even width and height for the overall image, and a horizontal
-// stride that is an even multiple of 16 bytes for both the Y and UV arrays.
-void copyNV21toRGB32(unsigned width, unsigned height,
-                     uint8_t* src,
-                     uint32_t* dst, unsigned dstStridePixels);
-
-
-// Given an image buffer in YV12 format (HAL_PIXEL_FORMAT_YV12), output 32bit RGBx values.
-// The YV12 format provides a Y array of 8bit values, followed by a 1/2 x 1/2 U array, followed
-// by another 1/2 x 1/2 V array.  It assumes an even width and height for the overall image,
-// and a horizontal stride that is an even multiple of 16 bytes for each of the Y, U,
-// and V arrays.
-void copyYV12toRGB32(unsigned width, unsigned height,
-                     uint8_t* src,
-                     uint32_t* dst, unsigned dstStridePixels);
-
-
-// Given an image buffer in YUYV format (HAL_PIXEL_FORMAT_YCBCR_422_I), output 32bit RGBx values.
-// The NV21 format provides a Y array of 8bit values, followed by a 1/2 x 1/2 interleaved
-// U/V array.  It assumes an even width and height for the overall image, and a horizontal
-// stride that is an even multiple of 16 bytes for both the Y and UV arrays.
-void copyYUYVtoRGB32(unsigned width, unsigned height,
-                     uint8_t* src, unsigned srcStrideBytes,
-                     uint32_t* dst, unsigned dstStrideBytes);
-
-
-// Given an simple rectangular image buffer with an integer number of bytes per pixel,
-// copy the pixel values into a new rectangular buffer (potentially with a different stride).
-// This is typically used to copy RGBx data into an RGBx output buffer.
-void copyMatchedInterleavedFormats(unsigned width, unsigned height,
-                                   void* src, unsigned srcStridePixels,
-                                   void* dst, unsigned dstStridePixels,
-                                   unsigned pixelSize);
-
-#endif // EVS_VTS_FORMATCONVERT_H
diff --git a/evs/app/RenderBase.cpp b/evs/app/RenderBase.cpp
deleted file mode 100644
index cb6aa93..0000000
--- a/evs/app/RenderBase.cpp
+++ /dev/null
@@ -1,217 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "RenderBase.h"
-#include "glError.h"
-
-#include <log/log.h>
-#include <ui/GraphicBuffer.h>
-
-// Eventually we shouldn't need this dependency, but for now the
-// graphics allocator interface isn't fully supported on all platforms
-// and this is our work around.
-using ::android::GraphicBuffer;
-
-
-// OpenGL state shared among all renderers
-EGLDisplay   RenderBase::sDisplay = EGL_NO_DISPLAY;
-EGLContext   RenderBase::sContext = EGL_NO_CONTEXT;
-EGLSurface   RenderBase::sDummySurface = EGL_NO_SURFACE;
-GLuint       RenderBase::sFrameBuffer = -1;
-GLuint       RenderBase::sColorBuffer = -1;
-GLuint       RenderBase::sDepthBuffer = -1;
-EGLImageKHR  RenderBase::sKHRimage = EGL_NO_IMAGE_KHR;
-unsigned     RenderBase::sWidth  = 0;
-unsigned     RenderBase::sHeight = 0;
-float        RenderBase::sAspectRatio = 0.0f;
-
-
-bool RenderBase::prepareGL() {
-    // Just trivially return success if we're already prepared
-    if (sDisplay != EGL_NO_DISPLAY) {
-        return true;
-    }
-
-    // Hardcoded to RGBx output display
-    const EGLint config_attribs[] = {
-        // Tag                  Value
-        EGL_RENDERABLE_TYPE,    EGL_OPENGL_ES2_BIT,
-        EGL_RED_SIZE,           8,
-        EGL_GREEN_SIZE,         8,
-        EGL_BLUE_SIZE,          8,
-        EGL_NONE
-    };
-
-    // Select OpenGL ES v 3
-    const EGLint context_attribs[] = {EGL_CONTEXT_CLIENT_VERSION, 3, EGL_NONE};
-
-
-    // Set up our OpenGL ES context associated with the default display (though we won't be visible)
-    EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
-    if (display == EGL_NO_DISPLAY) {
-        ALOGE("Failed to get egl display");
-        return false;
-    }
-
-    EGLint major = 0;
-    EGLint minor = 0;
-    if (!eglInitialize(display, &major, &minor)) {
-        ALOGE("Failed to initialize EGL: %s", getEGLError());
-        return false;
-    } else {
-        ALOGI("Intiialized EGL at %d.%d", major, minor);
-    }
-
-
-    // Select the configuration that "best" matches our desired characteristics
-    EGLConfig egl_config;
-    EGLint num_configs;
-    if (!eglChooseConfig(display, config_attribs, &egl_config, 1, &num_configs)) {
-        ALOGE("eglChooseConfig() failed with error: %s", getEGLError());
-        return false;
-    }
-
-
-    // Create a dummy pbuffer so we have a surface to bind -- we never intend to draw to this
-    // because attachRenderTarget will be called first.
-    EGLint surface_attribs[] = { EGL_WIDTH, 1, EGL_HEIGHT, 1, EGL_NONE };
-    sDummySurface = eglCreatePbufferSurface(display, egl_config, surface_attribs);
-    if (sDummySurface == EGL_NO_SURFACE) {
-        ALOGE("Failed to create OpenGL ES Dummy surface: %s", getEGLError());
-        return false;
-    } else {
-        ALOGI("Dummy surface looks good!  :)");
-    }
-
-
-    //
-    // Create the EGL context
-    //
-    EGLContext context = eglCreateContext(display, egl_config, EGL_NO_CONTEXT, context_attribs);
-    if (context == EGL_NO_CONTEXT) {
-        ALOGE("Failed to create OpenGL ES Context: %s", getEGLError());
-        return false;
-    }
-
-
-    // Activate our render target for drawing
-    if (!eglMakeCurrent(display, sDummySurface, sDummySurface, context)) {
-        ALOGE("Failed to make the OpenGL ES Context current: %s", getEGLError());
-        return false;
-    } else {
-        ALOGI("We made our context current!  :)");
-    }
-
-
-    // Report the extensions available on this implementation
-    const char* gl_extensions = (const char*) glGetString(GL_EXTENSIONS);
-    ALOGI("GL EXTENSIONS:\n  %s", gl_extensions);
-
-
-    // Reserve handles for the color and depth targets we'll be setting up
-    glGenRenderbuffers(1, &sColorBuffer);
-    glGenRenderbuffers(1, &sDepthBuffer);
-
-    // Set up the frame buffer object we can modify and use for off screen rendering
-    glGenFramebuffers(1, &sFrameBuffer);
-    glBindFramebuffer(GL_FRAMEBUFFER, sFrameBuffer);
-
-
-    // Now that we're assured success, store object handles we constructed
-    sDisplay = display;
-    sContext = context;
-
-    return true;
-}
-
-
-bool RenderBase::attachRenderTarget(const BufferDesc& tgtBuffer) {
-    // Hardcoded to RGBx for now
-    if (tgtBuffer.format != HAL_PIXEL_FORMAT_RGBA_8888) {
-        ALOGE("Unsupported target buffer format");
-        return false;
-    }
-
-    // create a GraphicBuffer from the existing handle
-    sp<GraphicBuffer> pGfxBuffer = new GraphicBuffer(tgtBuffer.memHandle,
-                                                     GraphicBuffer::CLONE_HANDLE,
-                                                     tgtBuffer.width, tgtBuffer.height,
-                                                     tgtBuffer.format, 1, // layer count
-                                                     GRALLOC_USAGE_HW_RENDER,
-                                                     tgtBuffer.stride);
-    if (pGfxBuffer.get() == nullptr) {
-        ALOGE("Failed to allocate GraphicBuffer to wrap image handle");
-        return false;
-    }
-
-    // Get a GL compatible reference to the graphics buffer we've been given
-    EGLint eglImageAttributes[] = {EGL_IMAGE_PRESERVED_KHR, EGL_TRUE, EGL_NONE};
-    EGLClientBuffer clientBuf = static_cast<EGLClientBuffer>(pGfxBuffer->getNativeBuffer());
-    sKHRimage = eglCreateImageKHR(sDisplay, EGL_NO_CONTEXT,
-                                  EGL_NATIVE_BUFFER_ANDROID, clientBuf,
-                                  eglImageAttributes);
-    if (sKHRimage == EGL_NO_IMAGE_KHR) {
-        ALOGE("error creating EGLImage for target buffer: %s", getEGLError());
-        return false;
-    }
-
-    // Construct a render buffer around the external buffer
-    glBindRenderbuffer(GL_RENDERBUFFER, sColorBuffer);
-    glEGLImageTargetRenderbufferStorageOES(GL_RENDERBUFFER, static_cast<GLeglImageOES>(sKHRimage));
-    if (eglGetError() != EGL_SUCCESS) {
-        ALOGI("glEGLImageTargetRenderbufferStorageOES => %s", getEGLError());
-        return false;
-    }
-
-    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, sColorBuffer);
-    if (eglGetError() != EGL_SUCCESS) {
-        ALOGE("glFramebufferRenderbuffer => %s", getEGLError());
-        return false;
-    }
-
-    GLenum checkResult = glCheckFramebufferStatus(GL_FRAMEBUFFER);
-    if (checkResult != GL_FRAMEBUFFER_COMPLETE) {
-        ALOGE("Offscreen framebuffer not configured successfully (%d: %s)",
-              checkResult, getGLFramebufferError());
-        return false;
-    }
-
-    // Store the size of our target buffer
-    sWidth = tgtBuffer.width;
-    sHeight = tgtBuffer.height;
-    sAspectRatio = (float)sWidth / sHeight;
-
-    // Set the viewport
-    glViewport(0, 0, sWidth, sHeight);
-
-#if 1   // We don't actually need the clear if we're going to cover the whole screen anyway
-    // Clear the color buffer
-    glClearColor(0.8f, 0.1f, 0.2f, 1.0f);
-    glClear(GL_COLOR_BUFFER_BIT);
-#endif
-
-
-    return true;
-}
-
-
-void RenderBase::detachRenderTarget() {
-    // Drop our external render target
-    if (sKHRimage != EGL_NO_IMAGE_KHR) {
-        eglDestroyImageKHR(sDisplay, sKHRimage);
-        sKHRimage = EGL_NO_IMAGE_KHR;
-    }
-}
\ No newline at end of file
diff --git a/evs/app/RenderBase.h b/evs/app/RenderBase.h
deleted file mode 100644
index 25474d5..0000000
--- a/evs/app/RenderBase.h
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef CAR_EVS_APP_RENDERBASE_H
-#define CAR_EVS_APP_RENDERBASE_H
-
-#include <EGL/egl.h>
-#include <EGL/eglext.h>
-#include <GLES2/gl2.h>
-#include <GLES2/gl2ext.h>
-#include <GLES3/gl3.h>
-#include <GLES3/gl3ext.h>
-
-#include <android/hardware/automotive/evs/1.0/IEvsEnumerator.h>
-
-using namespace ::android::hardware::automotive::evs::V1_0;
-using ::android::sp;
-
-
-/*
- * Abstract base class for the workhorse classes that handle the user interaction and display for
- * each mode of the EVS application.
- */
-class RenderBase {
-public:
-    virtual ~RenderBase() {};
-
-    virtual bool activate() = 0;
-    virtual void deactivate() = 0;
-
-    virtual bool drawFrame(const BufferDesc& tgtBuffer) = 0;
-
-protected:
-    static bool prepareGL();
-
-    static bool attachRenderTarget(const BufferDesc& tgtBuffer);
-    static void detachRenderTarget();
-
-    // OpenGL state shared among all renderers
-    static EGLDisplay   sDisplay;
-    static EGLContext   sContext;
-    static EGLSurface   sDummySurface;
-    static GLuint       sFrameBuffer;
-    static GLuint       sColorBuffer;
-    static GLuint       sDepthBuffer;
-
-    static EGLImageKHR  sKHRimage;
-
-    static unsigned     sWidth;
-    static unsigned     sHeight;
-    static float        sAspectRatio;
-};
-
-
-#endif //CAR_EVS_APP_RENDERBASE_H
diff --git a/evs/app/RenderDirectView.cpp b/evs/app/RenderDirectView.cpp
deleted file mode 100644
index 5244b26..0000000
--- a/evs/app/RenderDirectView.cpp
+++ /dev/null
@@ -1,144 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "RenderDirectView.h"
-#include "VideoTex.h"
-#include "glError.h"
-#include "shader.h"
-#include "shader_simpleTex.h"
-
-#include <log/log.h>
-#include <math/mat4.h>
-
-
-RenderDirectView::RenderDirectView(sp<IEvsEnumerator> enumerator,
-                                   const ConfigManager::CameraInfo& cam) {
-    mEnumerator = enumerator;
-    mCameraInfo = cam;
-}
-
-
-bool RenderDirectView::activate() {
-    // Ensure GL is ready to go...
-    if (!prepareGL()) {
-        ALOGE("Error initializing GL");
-        return false;
-    }
-
-    // Load our shader program if we don't have it already
-    if (!mShaderProgram) {
-        mShaderProgram = buildShaderProgram(vtxShader_simpleTexture,
-                                            pixShader_simpleTexture,
-                                            "simpleTexture");
-        if (!mShaderProgram) {
-            ALOGE("Error buliding shader program");
-            return false;
-        }
-    }
-
-    // Construct our video texture
-    mTexture.reset(createVideoTexture(mEnumerator, mCameraInfo.cameraId.c_str(), sDisplay));
-    if (!mTexture) {
-        ALOGE("Failed to set up video texture for %s (%s)",
-              mCameraInfo.cameraId.c_str(), mCameraInfo.function.c_str());
-// TODO:  For production use, we may actually want to fail in this case, but not yet...
-//       return false;
-    }
-
-    return true;
-}
-
-
-void RenderDirectView::deactivate() {
-    // Release our video texture
-    // We can't hold onto it because some other Render object might need the same camera
-    // TODO(b/131492626):  investigate whether sharing video textures can save
-    // the time.
-  mTexture = nullptr;
-}
-
-
-bool RenderDirectView::drawFrame(const BufferDesc& tgtBuffer) {
-    // Tell GL to render to the given buffer
-    if (!attachRenderTarget(tgtBuffer)) {
-        ALOGE("Failed to attached render target");
-        return false;
-    }
-
-    // Select our screen space simple texture shader
-    glUseProgram(mShaderProgram);
-
-    // Set up the model to clip space transform (identity matrix if we're modeling in screen space)
-    GLint loc = glGetUniformLocation(mShaderProgram, "cameraMat");
-    if (loc < 0) {
-        ALOGE("Couldn't set shader parameter 'cameraMat'");
-        return false;
-    } else {
-        const android::mat4 identityMatrix;
-        glUniformMatrix4fv(loc, 1, false, identityMatrix.asArray());
-    }
-
-
-    // Bind the texture and assign it to the shader's sampler
-    mTexture->refresh();
-    glActiveTexture(GL_TEXTURE0);
-    glBindTexture(GL_TEXTURE_2D, mTexture->glId());
-
-
-    GLint sampler = glGetUniformLocation(mShaderProgram, "tex");
-    if (sampler < 0) {
-        ALOGE("Couldn't set shader parameter 'tex'");
-        return false;
-    } else {
-        // Tell the sampler we looked up from the shader to use texture slot 0 as its source
-        glUniform1i(sampler, 0);
-    }
-
-    // We want our image to show up opaque regardless of alpha values
-    glDisable(GL_BLEND);
-
-
-    // Draw a rectangle on the screen
-    GLfloat vertsCarPos[] = { -1.0,  1.0, 0.0f,   // left top in window space
-                               1.0,  1.0, 0.0f,   // right top
-                              -1.0, -1.0, 0.0f,   // left bottom
-                               1.0, -1.0, 0.0f    // right bottom
-    };
-    // TODO:  We're flipping horizontally here, but should do it only for specified cameras!
-    GLfloat vertsCarTex[] = { 1.0f, 1.0f,   // left top
-                              0.0f, 1.0f,   // right top
-                              1.0f, 0.0f,   // left bottom
-                              0.0f, 0.0f    // right bottom
-    };
-    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, vertsCarPos);
-    glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 0, vertsCarTex);
-    glEnableVertexAttribArray(0);
-    glEnableVertexAttribArray(1);
-
-    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
-
-    glDisableVertexAttribArray(0);
-    glDisableVertexAttribArray(1);
-
-
-    // Now that everything is submitted, release our hold on the texture resource
-    detachRenderTarget();
-
-    // Wait for the rendering to finish
-    glFinish();
-    detachRenderTarget();
-    return true;
-}
diff --git a/evs/app/RenderDirectView.h b/evs/app/RenderDirectView.h
deleted file mode 100644
index 1543fce..0000000
--- a/evs/app/RenderDirectView.h
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef CAR_EVS_APP_RENDERDIRECTVIEW_H
-#define CAR_EVS_APP_RENDERDIRECTVIEW_H
-
-
-#include "RenderBase.h"
-
-#include <android/hardware/automotive/evs/1.0/IEvsEnumerator.h>
-#include "ConfigManager.h"
-#include "VideoTex.h"
-
-
-using namespace ::android::hardware::automotive::evs::V1_0;
-
-
-/*
- * Renders the view from a single specified camera directly to the full display.
- */
-class RenderDirectView: public RenderBase {
-public:
-    RenderDirectView(sp<IEvsEnumerator> enumerator, const ConfigManager::CameraInfo& cam);
-
-    virtual bool activate() override;
-    virtual void deactivate() override;
-
-    virtual bool drawFrame(const BufferDesc& tgtBuffer);
-
-protected:
-    sp<IEvsEnumerator>              mEnumerator;
-    ConfigManager::CameraInfo       mCameraInfo;
-
-    std::unique_ptr<VideoTex>       mTexture;
-
-    GLuint                          mShaderProgram = 0;
-};
-
-
-#endif //CAR_EVS_APP_RENDERDIRECTVIEW_H
diff --git a/evs/app/RenderPixelCopy.cpp b/evs/app/RenderPixelCopy.cpp
deleted file mode 100644
index 0a586a4..0000000
--- a/evs/app/RenderPixelCopy.cpp
+++ /dev/null
@@ -1,133 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "RenderPixelCopy.h"
-#include "FormatConvert.h"
-
-#include <log/log.h>
-
-
-RenderPixelCopy::RenderPixelCopy(sp<IEvsEnumerator> enumerator,
-                                   const ConfigManager::CameraInfo& cam) {
-    mEnumerator = enumerator;
-    mCameraInfo = cam;
-}
-
-
-bool RenderPixelCopy::activate() {
-    // Set up the camera to feed this texture
-    sp<IEvsCamera> pCamera = mEnumerator->openCamera(mCameraInfo.cameraId.c_str());
-    if (pCamera.get() == nullptr) {
-        ALOGE("Failed to allocate new EVS Camera interface");
-        return false;
-    }
-
-    // Initialize the stream that will help us update this texture's contents
-    sp<StreamHandler> pStreamHandler = new StreamHandler(pCamera);
-    if (pStreamHandler.get() == nullptr) {
-        ALOGE("failed to allocate FrameHandler");
-        return false;
-    }
-
-    // Start the video stream
-    if (!pStreamHandler->startStream()) {
-        ALOGE("start stream failed");
-        return false;
-    }
-
-    mStreamHandler = pStreamHandler;
-
-    return true;
-}
-
-
-void RenderPixelCopy::deactivate() {
-    mStreamHandler = nullptr;
-}
-
-
-bool RenderPixelCopy::drawFrame(const BufferDesc& tgtBuffer) {
-    bool success = true;
-
-    sp<android::GraphicBuffer> tgt = new android::GraphicBuffer(
-            tgtBuffer.memHandle, android::GraphicBuffer::CLONE_HANDLE,
-            tgtBuffer.width, tgtBuffer.height, tgtBuffer.format, 1, tgtBuffer.usage,
-            tgtBuffer.stride);
-
-    // Lock our target buffer for writing (should be RGBA8888 format)
-    uint32_t* tgtPixels = nullptr;
-    tgt->lock(GRALLOC_USAGE_SW_WRITE_OFTEN, (void**)&tgtPixels);
-
-    if (tgtPixels) {
-        if (tgtBuffer.format != HAL_PIXEL_FORMAT_RGBA_8888) {
-            // We always expect 32 bit RGB for the display output for now.  Is there a need for 565?
-            ALOGE("Diplay buffer is always expected to be 32bit RGBA");
-            success = false;
-        } else {
-            // Make sure we have the latest frame data
-            if (mStreamHandler->newFrameAvailable()) {
-                const BufferDesc& srcBuffer = mStreamHandler->getNewFrame();
-
-                // Lock our source buffer for reading (current expectation are for this to be NV21 format)
-                sp<android::GraphicBuffer> src = new android::GraphicBuffer(
-                        srcBuffer.memHandle, android::GraphicBuffer::CLONE_HANDLE,
-                        srcBuffer.width, srcBuffer.height, srcBuffer.format, 1, srcBuffer.usage,
-                        srcBuffer.stride);
-                unsigned char* srcPixels = nullptr;
-                src->lock(GRALLOC_USAGE_SW_READ_OFTEN, (void**)&srcPixels);
-                if (!srcPixels) {
-                    ALOGE("Failed to get pointer into src image data");
-                }
-
-                // Make sure we don't run off the end of either buffer
-                const unsigned width     = std::min(tgtBuffer.width,
-                                                    srcBuffer.width);
-                const unsigned height    = std::min(tgtBuffer.height,
-                                                    srcBuffer.height);
-
-                if (srcBuffer.format == HAL_PIXEL_FORMAT_YCRCB_420_SP) {   // 420SP == NV21
-                    copyNV21toRGB32(width, height,
-                                    srcPixels,
-                                    tgtPixels, tgtBuffer.stride);
-                } else if (srcBuffer.format == HAL_PIXEL_FORMAT_YV12) { // YUV_420P == YV12
-                    copyYV12toRGB32(width, height,
-                                    srcPixels,
-                                    tgtPixels, tgtBuffer.stride);
-                } else if (srcBuffer.format == HAL_PIXEL_FORMAT_YCBCR_422_I) { // YUYV
-                    copyYUYVtoRGB32(width, height,
-                                    srcPixels, srcBuffer.stride,
-                                    tgtPixels, tgtBuffer.stride);
-                } else if (srcBuffer.format == tgtBuffer.format) {  // 32bit RGBA
-                    copyMatchedInterleavedFormats(width, height,
-                                                  srcPixels, srcBuffer.stride,
-                                                  tgtPixels, tgtBuffer.stride,
-                                                  tgtBuffer.pixelSize);
-                }
-
-                mStreamHandler->doneWithFrame(srcBuffer);
-            }
-        }
-    } else {
-        ALOGE("Failed to lock buffer contents for contents transfer");
-        success = false;
-    }
-
-    if (tgtPixels) {
-        tgt->unlock();
-    }
-
-    return success;
-}
diff --git a/evs/app/RenderPixelCopy.h b/evs/app/RenderPixelCopy.h
deleted file mode 100644
index ee6eede..0000000
--- a/evs/app/RenderPixelCopy.h
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef CAR_EVS_APP_RENDERPIXELCOPY_H
-#define CAR_EVS_APP_RENDERPIXELCOPY_H
-
-
-#include "RenderBase.h"
-
-#include <android/hardware/automotive/evs/1.0/IEvsEnumerator.h>
-#include "ConfigManager.h"
-#include "VideoTex.h"
-
-
-using namespace ::android::hardware::automotive::evs::V1_0;
-
-
-/*
- * Renders the view from a single specified camera directly to the full display.
- */
-class RenderPixelCopy: public RenderBase {
-public:
-    RenderPixelCopy(sp<IEvsEnumerator> enumerator, const ConfigManager::CameraInfo& cam);
-
-    virtual bool activate() override;
-    virtual void deactivate() override;
-
-    virtual bool drawFrame(const BufferDesc& tgtBuffer);
-
-protected:
-    sp<IEvsEnumerator>              mEnumerator;
-    ConfigManager::CameraInfo       mCameraInfo;
-
-    sp<StreamHandler>               mStreamHandler;
-};
-
-
-#endif //CAR_EVS_APP_RENDERPIXELCOPY_H
diff --git a/evs/app/RenderTopView.cpp b/evs/app/RenderTopView.cpp
deleted file mode 100644
index 1ffa7cf..0000000
--- a/evs/app/RenderTopView.cpp
+++ /dev/null
@@ -1,343 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "RenderTopView.h"
-#include "VideoTex.h"
-#include "glError.h"
-#include "shader.h"
-#include "shader_simpleTex.h"
-#include "shader_projectedTex.h"
-
-#include <log/log.h>
-#include <math/mat4.h>
-#include <math/vec3.h>
-
-
-// Simple aliases to make geometric math using vectors more readable
-static const unsigned X = 0;
-static const unsigned Y = 1;
-static const unsigned Z = 2;
-//static const unsigned W = 3;
-
-
-// Since we assume no roll in these views, we can simplify the required math
-static android::vec3 unitVectorFromPitchAndYaw(float pitch, float yaw) {
-    float sinPitch, cosPitch;
-    sincosf(pitch, &sinPitch, &cosPitch);
-    float sinYaw, cosYaw;
-    sincosf(yaw, &sinYaw, &cosYaw);
-    return android::vec3(cosPitch * -sinYaw,
-                         cosPitch * cosYaw,
-                         sinPitch);
-}
-
-
-// Helper function to set up a perspective matrix with independent horizontal and vertical
-// angles of view.
-static android::mat4 perspective(float hfov, float vfov, float near, float far) {
-    const float tanHalfFovX = tanf(hfov * 0.5f);
-    const float tanHalfFovY = tanf(vfov * 0.5f);
-
-    android::mat4 p(0.0f);
-    p[0][0] = 1.0f / tanHalfFovX;
-    p[1][1] = 1.0f / tanHalfFovY;
-    p[2][2] = - (far + near) / (far - near);
-    p[2][3] = -1.0f;
-    p[3][2] = - (2.0f * far * near) / (far - near);
-    return p;
-}
-
-
-// Helper function to set up a view matrix for a camera given it's yaw & pitch & location
-// Yes, with a bit of work, we could use lookAt, but it does a lot of extra work
-// internally that we can short cut.
-static android::mat4 cameraLookMatrix(const ConfigManager::CameraInfo& cam) {
-    float sinYaw, cosYaw;
-    sincosf(cam.yaw, &sinYaw, &cosYaw);
-
-    // Construct principal unit vectors
-    android::vec3 vAt = unitVectorFromPitchAndYaw(cam.pitch, cam.yaw);
-    android::vec3 vRt = android::vec3(cosYaw, sinYaw, 0.0f);
-    android::vec3 vUp = -cross(vAt, vRt);
-    android::vec3 eye = android::vec3(cam.position[X], cam.position[Y], cam.position[Z]);
-
-    android::mat4 Result(1.0f);
-    Result[0][0] = vRt.x;
-    Result[1][0] = vRt.y;
-    Result[2][0] = vRt.z;
-    Result[0][1] = vUp.x;
-    Result[1][1] = vUp.y;
-    Result[2][1] = vUp.z;
-    Result[0][2] =-vAt.x;
-    Result[1][2] =-vAt.y;
-    Result[2][2] =-vAt.z;
-    Result[3][0] =-dot(vRt, eye);
-    Result[3][1] =-dot(vUp, eye);
-    Result[3][2] = dot(vAt, eye);
-    return Result;
-}
-
-
-RenderTopView::RenderTopView(sp<IEvsEnumerator> enumerator,
-                             const std::vector<ConfigManager::CameraInfo>& camList,
-                             const ConfigManager& mConfig) :
-    mEnumerator(enumerator),
-    mConfig(mConfig) {
-
-    // Copy the list of cameras we're to employ into our local storage.  We'll create and
-    // associate a streaming video texture when we are activated.
-    mActiveCameras.reserve(camList.size());
-    for (unsigned i=0; i<camList.size(); i++) {
-        mActiveCameras.emplace_back(camList[i]);
-    }
-}
-
-
-bool RenderTopView::activate() {
-    // Ensure GL is ready to go...
-    if (!prepareGL()) {
-        ALOGE("Error initializing GL");
-        return false;
-    }
-
-    // Load our shader programs
-    mPgmAssets.simpleTexture = buildShaderProgram(vtxShader_simpleTexture,
-                                                 pixShader_simpleTexture,
-                                                 "simpleTexture");
-    if (!mPgmAssets.simpleTexture) {
-        ALOGE("Failed to build shader program");
-        return false;
-    }
-    mPgmAssets.projectedTexture = buildShaderProgram(vtxShader_projectedTexture,
-                                                    pixShader_projectedTexture,
-                                                    "projectedTexture");
-    if (!mPgmAssets.projectedTexture) {
-        ALOGE("Failed to build shader program");
-        return false;
-    }
-
-
-    // Load the checkerboard text image
-    mTexAssets.checkerBoard.reset(createTextureFromPng(
-                                  "/system/etc/automotive/evs/LabeledChecker.png"));
-    if (!mTexAssets.checkerBoard) {
-        ALOGE("Failed to load checkerboard texture");
-        return false;
-    }
-
-    // Load the car image
-    mTexAssets.carTopView.reset(createTextureFromPng(
-                                "/system/etc/automotive/evs/CarFromTop.png"));
-    if (!mTexAssets.carTopView) {
-        ALOGE("Failed to load carTopView texture");
-        return false;
-    }
-
-
-    // Set up streaming video textures for our associated cameras
-    for (auto&& cam: mActiveCameras) {
-        cam.tex.reset(createVideoTexture(mEnumerator, cam.info.cameraId.c_str(), sDisplay));
-        if (!cam.tex) {
-            ALOGE("Failed to set up video texture for %s (%s)",
-                  cam.info.cameraId.c_str(), cam.info.function.c_str());
-// TODO:  For production use, we may actually want to fail in this case, but not yet...
-//            return false;
-        }
-    }
-
-    return true;
-}
-
-
-void RenderTopView::deactivate() {
-    // Release our video textures
-    // We can't hold onto it because some other Render object might need the same camera
-    // TODO(b/131492626):  investigate whether sharing video textures can save
-    // the time.
-    for (auto&& cam: mActiveCameras) {
-        cam.tex = nullptr;
-    }
-}
-
-
-bool RenderTopView::drawFrame(const BufferDesc& tgtBuffer) {
-    // Tell GL to render to the given buffer
-    if (!attachRenderTarget(tgtBuffer)) {
-        ALOGE("Failed to attached render target");
-        return false;
-    }
-
-    // Set up our top down projection matrix from car space (world units, Xfwd, Yright, Zup)
-    // to view space (-1 to 1)
-    const float top    = mConfig.getDisplayTopLocation();
-    const float bottom = mConfig.getDisplayBottomLocation();
-    const float right  = mConfig.getDisplayRightLocation(sAspectRatio);
-    const float left   = mConfig.getDisplayLeftLocation(sAspectRatio);
-
-    const float near = 10.0f;   // arbitrary top of view volume
-    const float far = 0.0f;     // ground plane is at zero
-
-    // We can use a simple, unrotated ortho view since the screen and car space axis are
-    // naturally aligned in the top down view.
-    // TODO:  Not sure if flipping top/bottom here is "correct" or a double reverse...
-//    orthoMatrix = android::mat4::ortho(left, right, bottom, top, near, far);
-    orthoMatrix = android::mat4::ortho(left, right, top, bottom, near, far);
-
-
-    // Refresh our video texture contents.  We do it all at once in hopes of getting
-    // better coherence among images.  This does not guarantee synchronization, of course...
-    for (auto&& cam: mActiveCameras) {
-        if (cam.tex) {
-            cam.tex->refresh();
-        }
-    }
-
-    // Iterate over all the cameras and project their images onto the ground plane
-    for (auto&& cam: mActiveCameras) {
-        renderCameraOntoGroundPlane(cam);
-    }
-
-    // Draw the car image
-    renderCarTopView();
-
-    // Now that everythign is submitted, release our hold on the texture resource
-    detachRenderTarget();
-
-    // Wait for the rendering to finish
-    glFinish();
-    detachRenderTarget();
-    return true;
-}
-
-
-//
-// Responsible for drawing the car's self image in the top down view.
-// Draws in car model space (units of meters with origin at center of rear axel)
-// NOTE:  We probably want to eventually switch to using a VertexArray based model system.
-//
-void RenderTopView::renderCarTopView() {
-    // Compute the corners of our image footprint in car space
-    const float carLengthInTexels = mConfig.carGraphicRearPixel() - mConfig.carGraphicFrontPixel();
-    const float carSpaceUnitsPerTexel = mConfig.getCarLength() / carLengthInTexels;
-    const float textureHeightInCarSpace = mTexAssets.carTopView->height() * carSpaceUnitsPerTexel;
-    const float textureAspectRatio = (float)mTexAssets.carTopView->width() /
-                                            mTexAssets.carTopView->height();
-    const float pixelsBehindCarInImage = mTexAssets.carTopView->height() -
-                                         mConfig.carGraphicRearPixel();
-    const float textureExtentBehindCarInCarSpace = pixelsBehindCarInImage * carSpaceUnitsPerTexel;
-
-    const float btCS = mConfig.getRearLocation() - textureExtentBehindCarInCarSpace;
-    const float tpCS = textureHeightInCarSpace + btCS;
-    const float ltCS = 0.5f * textureHeightInCarSpace * textureAspectRatio;
-    const float rtCS = -ltCS;
-
-    GLfloat vertsCarPos[] = { ltCS, tpCS, 0.0f,   // left top in car space
-                              rtCS, tpCS, 0.0f,   // right top
-                              ltCS, btCS, 0.0f,   // left bottom
-                              rtCS, btCS, 0.0f    // right bottom
-    };
-    // NOTE:  We didn't flip the image in the texture, so V=0 is actually the top of the image
-    GLfloat vertsCarTex[] = { 0.0f, 0.0f,   // left top
-                              1.0f, 0.0f,   // right top
-                              0.0f, 1.0f,   // left bottom
-                              1.0f, 1.0f    // right bottom
-    };
-    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, vertsCarPos);
-    glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 0, vertsCarTex);
-    glEnableVertexAttribArray(0);
-    glEnableVertexAttribArray(1);
-
-
-    glEnable(GL_BLEND);
-    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
-
-    glUseProgram(mPgmAssets.simpleTexture);
-    GLint loc = glGetUniformLocation(mPgmAssets.simpleTexture, "cameraMat");
-    glUniformMatrix4fv(loc, 1, false, orthoMatrix.asArray());
-    glBindTexture(GL_TEXTURE_2D, mTexAssets.carTopView->glId());
-
-    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
-
-
-    glDisable(GL_BLEND);
-
-    glDisableVertexAttribArray(0);
-    glDisableVertexAttribArray(1);
-}
-
-
-// NOTE:  Might be worth reviewing the ideas at
-// http://math.stackexchange.com/questions/1691895/inverse-of-perspective-matrix
-// to see if that simplifies the math, although we'll still want to compute the actual ground
-// interception points taking into account the pitchLimit as below.
-void RenderTopView::renderCameraOntoGroundPlane(const ActiveCamera& cam) {
-    // How far is the farthest any camera should even consider projecting it's image?
-    const float visibleSizeV = mConfig.getDisplayTopLocation() - mConfig.getDisplayBottomLocation();
-    const float visibleSizeH = visibleSizeV * sAspectRatio;
-    const float maxRange = (visibleSizeH > visibleSizeV) ? visibleSizeH : visibleSizeV;
-
-    // Construct the projection matrix (View + Projection) associated with this sensor
-    // TODO:  Consider just hard coding the far plane distance as it likely doesn't matter
-    const android::mat4 V = cameraLookMatrix(cam.info);
-    const android::mat4 P = perspective(cam.info.hfov, cam.info.vfov, cam.info.position[Z], maxRange);
-    const android::mat4 projectionMatix = P*V;
-
-    // Just draw the whole darn ground plane for now -- we're wasting fill rate, but so what?
-    // A 2x optimization would be to draw only the 1/2 space of the window in the direction
-    // the sensor is facing.  A more complex solution would be to construct the intersection
-    // of the sensor volume with the ground plane and render only that geometry.
-    const float top = mConfig.getDisplayTopLocation();
-    const float bottom = mConfig.getDisplayBottomLocation();
-    const float wsHeight = top - bottom;
-    const float wsWidth = wsHeight * sAspectRatio;
-    const float right =  wsWidth * 0.5f;
-    const float left = -right;
-
-    const android::vec3 topLeft(left, top, 0.0f);
-    const android::vec3 topRight(right, top, 0.0f);
-    const android::vec3 botLeft(left, bottom, 0.0f);
-    const android::vec3 botRight(right, bottom, 0.0f);
-
-    GLfloat vertsPos[] = { topLeft[X],  topLeft[Y],  topLeft[Z],
-                           topRight[X], topRight[Y], topRight[Z],
-                           botLeft[X],  botLeft[Y],  botLeft[Z],
-                           botRight[X], botRight[Y], botRight[Z],
-    };
-    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, vertsPos);
-    glEnableVertexAttribArray(0);
-
-
-    glDisable(GL_BLEND);
-
-    glUseProgram(mPgmAssets.projectedTexture);
-    GLint locCam = glGetUniformLocation(mPgmAssets.projectedTexture, "cameraMat");
-    glUniformMatrix4fv(locCam, 1, false, orthoMatrix.asArray());
-    GLint locProj = glGetUniformLocation(mPgmAssets.projectedTexture, "projectionMat");
-    glUniformMatrix4fv(locProj, 1, false, projectionMatix.asArray());
-
-    GLuint texId;
-    if (cam.tex) {
-        texId = cam.tex->glId();
-    } else {
-        texId = mTexAssets.checkerBoard->glId();
-    }
-    glBindTexture(GL_TEXTURE_2D, texId);
-
-    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
-
-
-    glDisableVertexAttribArray(0);
-}
diff --git a/evs/app/RenderTopView.h b/evs/app/RenderTopView.h
deleted file mode 100644
index 570718f..0000000
--- a/evs/app/RenderTopView.h
+++ /dev/null
@@ -1,76 +0,0 @@
-
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef CAR_EVS_APP_RENDERTOPVIEW_H
-#define CAR_EVS_APP_RENDERTOPVIEW_H
-
-
-#include "RenderBase.h"
-
-#include <android/hardware/automotive/evs/1.0/IEvsEnumerator.h>
-#include "ConfigManager.h"
-#include "VideoTex.h"
-#include <math/mat4.h>
-
-
-using namespace ::android::hardware::automotive::evs::V1_0;
-
-
-/*
- * Combines the views from all available cameras into one reprojected top down view.
- */
-class RenderTopView: public RenderBase {
-public:
-    RenderTopView(sp<IEvsEnumerator> enumerator,
-                  const std::vector<ConfigManager::CameraInfo>& camList,
-                  const ConfigManager& config);
-
-    virtual bool activate() override;
-    virtual void deactivate() override;
-
-    virtual bool drawFrame(const BufferDesc& tgtBuffer);
-
-protected:
-    struct ActiveCamera {
-        const ConfigManager::CameraInfo&    info;
-        std::unique_ptr<VideoTex>           tex;
-
-        ActiveCamera(const ConfigManager::CameraInfo& c) : info(c) {};
-    };
-
-    void renderCarTopView();
-    void renderCameraOntoGroundPlane(const ActiveCamera& cam);
-
-    sp<IEvsEnumerator>              mEnumerator;
-    const ConfigManager&            mConfig;
-    std::vector<ActiveCamera>       mActiveCameras;
-
-    struct {
-        std::unique_ptr<TexWrapper> checkerBoard;
-        std::unique_ptr<TexWrapper> carTopView;
-    } mTexAssets;
-
-    struct {
-        GLuint simpleTexture;
-        GLuint projectedTexture;
-    } mPgmAssets;
-
-    android::mat4   orthoMatrix;
-};
-
-
-#endif //CAR_EVS_APP_RENDERTOPVIEW_H
diff --git a/evs/app/StreamHandler.cpp b/evs/app/StreamHandler.cpp
deleted file mode 100644
index 28da6df..0000000
--- a/evs/app/StreamHandler.cpp
+++ /dev/null
@@ -1,165 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "StreamHandler.h"
-
-#include <stdio.h>
-#include <string.h>
-
-#include <log/log.h>
-#include <cutils/native_handle.h>
-
-
-StreamHandler::StreamHandler(android::sp <IEvsCamera> pCamera) :
-    mCamera(pCamera)
-{
-    // We rely on the camera having at least two buffers available since we'll hold one and
-    // expect the camera to be able to capture a new image in the background.
-    pCamera->setMaxFramesInFlight(2);
-}
-
-
-void StreamHandler::shutdown()
-{
-    // Make sure we're not still streaming
-    blockingStopStream();
-
-    // At this point, the receiver thread is no longer running, so we can safely drop
-    // our remote object references so they can be freed
-    mCamera = nullptr;
-}
-
-
-bool StreamHandler::startStream() {
-    std::unique_lock<std::mutex> lock(mLock);
-
-    if (!mRunning) {
-        // Tell the camera to start streaming
-        Return <EvsResult> result = mCamera->startVideoStream(this);
-        if (result != EvsResult::OK) {
-            return false;
-        }
-
-        // Mark ourselves as running
-        mRunning = true;
-    }
-
-    return true;
-}
-
-
-void StreamHandler::asyncStopStream() {
-    // Tell the camera to stop streaming.
-    // This will result in a null frame being delivered when the stream actually stops.
-    mCamera->stopVideoStream();
-}
-
-
-void StreamHandler::blockingStopStream() {
-    // Tell the stream to stop
-    asyncStopStream();
-
-    // Wait until the stream has actually stopped
-    std::unique_lock<std::mutex> lock(mLock);
-    if (mRunning) {
-        mSignal.wait(lock, [this]() { return !mRunning; });
-    }
-}
-
-
-bool StreamHandler::isRunning() {
-    std::unique_lock<std::mutex> lock(mLock);
-    return mRunning;
-}
-
-
-bool StreamHandler::newFrameAvailable() {
-    std::unique_lock<std::mutex> lock(mLock);
-    return (mReadyBuffer >= 0);
-}
-
-
-const BufferDesc& StreamHandler::getNewFrame() {
-    std::unique_lock<std::mutex> lock(mLock);
-
-    if (mHeldBuffer >= 0) {
-        ALOGE("Ignored call for new frame while still holding the old one.");
-    } else {
-        if (mReadyBuffer < 0) {
-            ALOGE("Returning invalid buffer because we don't have any.  Call newFrameAvailable first?");
-            mReadyBuffer = 0;   // This is a lie!
-        }
-
-        // Move the ready buffer into the held position, and clear the ready position
-        mHeldBuffer = mReadyBuffer;
-        mReadyBuffer = -1;
-    }
-
-    return mBuffers[mHeldBuffer];
-}
-
-
-void StreamHandler::doneWithFrame(const BufferDesc& buffer) {
-    std::unique_lock<std::mutex> lock(mLock);
-
-    // We better be getting back the buffer we original delivered!
-    if ((mHeldBuffer < 0) || (buffer.bufferId != mBuffers[mHeldBuffer].bufferId)) {
-        ALOGE("StreamHandler::doneWithFrame got an unexpected buffer!");
-    }
-
-    // Send the buffer back to the underlying camera
-    mCamera->doneWithFrame(mBuffers[mHeldBuffer]);
-
-    // Clear the held position
-    mHeldBuffer = -1;
-}
-
-
-Return<void> StreamHandler::deliverFrame(const BufferDesc& buffer) {
-    ALOGD("Received a frame from the camera (%p)", buffer.memHandle.getNativeHandle());
-
-    // Take the lock to protect our frame slots and running state variable
-    {
-        std::unique_lock <std::mutex> lock(mLock);
-
-        if (buffer.memHandle.getNativeHandle() == nullptr) {
-            // Signal that the last frame has been received and the stream is stopped
-            mRunning = false;
-        } else {
-            // Do we already have a "ready" frame?
-            if (mReadyBuffer >= 0) {
-                // Send the previously saved buffer back to the camera unused
-                mCamera->doneWithFrame(mBuffers[mReadyBuffer]);
-
-                // We'll reuse the same ready buffer index
-            } else if (mHeldBuffer >= 0) {
-                // The client is holding a buffer, so use the other slot for "on deck"
-                mReadyBuffer = 1 - mHeldBuffer;
-            } else {
-                // This is our first buffer, so just pick a slot
-                mReadyBuffer = 0;
-            }
-
-            // Save this frame until our client is interested in it
-            mBuffers[mReadyBuffer] = buffer;
-        }
-    }
-
-    // Notify anybody who cares that things have changed
-    mSignal.notify_all();
-
-    return Void();
-}
diff --git a/evs/app/StreamHandler.h b/evs/app/StreamHandler.h
deleted file mode 100644
index 9e1d3b7..0000000
--- a/evs/app/StreamHandler.h
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef EVS_VTS_STREAMHANDLER_H
-#define EVS_VTS_STREAMHANDLER_H
-
-#include <queue>
-
-#include "ui/GraphicBuffer.h"
-
-#include <android/hardware/automotive/evs/1.0/IEvsCameraStream.h>
-#include <android/hardware/automotive/evs/1.0/IEvsCamera.h>
-#include <android/hardware/automotive/evs/1.0/IEvsDisplay.h>
-
-using namespace ::android::hardware::automotive::evs::V1_0;
-using ::android::hardware::Return;
-using ::android::hardware::Void;
-using ::android::hardware::hidl_vec;
-using ::android::hardware::hidl_handle;
-using ::android::sp;
-
-
-/*
- * StreamHandler:
- * This class can be used to receive camera imagery from an IEvsCamera implementation.  It will
- * hold onto the most recent image buffer, returning older ones.
- * Note that the video frames are delivered on a background thread, while the control interface
- * is actuated from the applications foreground thread.
- */
-class StreamHandler : public IEvsCameraStream {
-public:
-    virtual ~StreamHandler() { shutdown(); };
-
-    StreamHandler(android::sp <IEvsCamera> pCamera);
-    void shutdown();
-
-    bool startStream();
-    void asyncStopStream();
-    void blockingStopStream();
-
-    bool isRunning();
-
-    bool newFrameAvailable();
-    const BufferDesc& getNewFrame();
-    void doneWithFrame(const BufferDesc& buffer);
-
-private:
-    // Implementation for ::android::hardware::automotive::evs::V1_0::ICarCameraStream
-    Return<void> deliverFrame(const BufferDesc& buffer)  override;
-
-    // Values initialized as startup
-    android::sp <IEvsCamera>    mCamera;
-
-    // Since we get frames delivered to us asnchronously via the ICarCameraStream interface,
-    // we need to protect all member variables that may be modified while we're streaming
-    // (ie: those below)
-    std::mutex                  mLock;
-    std::condition_variable     mSignal;
-
-    bool                        mRunning = false;
-
-    BufferDesc                  mBuffers[2];
-    int                         mHeldBuffer = -1;   // Index of the one currently held by the client
-    int                         mReadyBuffer = -1;  // Index of the newest available buffer
-};
-
-
-#endif //EVS_VTS_STREAMHANDLER_H
diff --git a/evs/app/VideoTex.cpp b/evs/app/VideoTex.cpp
deleted file mode 100644
index 10d54bd..0000000
--- a/evs/app/VideoTex.cpp
+++ /dev/null
@@ -1,155 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-#include <vector>
-#include <stdio.h>
-#include <fcntl.h>
-#include <alloca.h>
-#include <unistd.h>
-#include <sys/ioctl.h>
-#include <malloc.h>
-#include <png.h>
-
-#include "VideoTex.h"
-#include "glError.h"
-
-#include <ui/GraphicBuffer.h>
-
-// Eventually we shouldn't need this dependency, but for now the
-// graphics allocator interface isn't fully supported on all platforms
-// and this is our work around.
-using ::android::GraphicBuffer;
-
-
-VideoTex::VideoTex(sp<IEvsEnumerator> pEnum,
-                   sp<IEvsCamera> pCamera,
-                   sp<StreamHandler> pStreamHandler,
-                   EGLDisplay glDisplay)
-    : TexWrapper()
-    , mEnumerator(pEnum)
-    , mCamera(pCamera)
-    , mStreamHandler(pStreamHandler)
-    , mDisplay(glDisplay) {
-    // Nothing but initialization here...
-}
-
-VideoTex::~VideoTex() {
-    // Tell the stream to stop flowing
-    mStreamHandler->asyncStopStream();
-
-    // Close the camera
-    mEnumerator->closeCamera(mCamera);
-
-    // Drop our device texture image
-    if (mKHRimage != EGL_NO_IMAGE_KHR) {
-        eglDestroyImageKHR(mDisplay, mKHRimage);
-        mKHRimage = EGL_NO_IMAGE_KHR;
-    }
-}
-
-
-// Return true if the texture contents are changed
-bool VideoTex::refresh() {
-    if (!mStreamHandler->newFrameAvailable()) {
-        // No new image has been delivered, so there's nothing to do here
-        return false;
-    }
-
-    // If we already have an image backing us, then it's time to return it
-    if (mImageBuffer.memHandle.getNativeHandle() != nullptr) {
-        // Drop our device texture image
-        if (mKHRimage != EGL_NO_IMAGE_KHR) {
-            eglDestroyImageKHR(mDisplay, mKHRimage);
-            mKHRimage = EGL_NO_IMAGE_KHR;
-        }
-
-        // Return it since we're done with it
-        mStreamHandler->doneWithFrame(mImageBuffer);
-    }
-
-    // Get the new image we want to use as our contents
-    mImageBuffer = mStreamHandler->getNewFrame();
-
-
-    // create a GraphicBuffer from the existing handle
-    sp<GraphicBuffer> pGfxBuffer = new GraphicBuffer(mImageBuffer.memHandle,
-                                                     GraphicBuffer::CLONE_HANDLE,
-                                                     mImageBuffer.width, mImageBuffer.height,
-                                                     mImageBuffer.format, 1, // layer count
-                                                     GRALLOC_USAGE_HW_TEXTURE,
-                                                     mImageBuffer.stride);
-    if (pGfxBuffer.get() == nullptr) {
-        ALOGE("Failed to allocate GraphicBuffer to wrap image handle");
-        // Returning "true" in this error condition because we already released the
-        // previous image (if any) and so the texture may change in unpredictable ways now!
-        return true;
-    }
-
-    // Get a GL compatible reference to the graphics buffer we've been given
-    EGLint eglImageAttributes[] = {EGL_IMAGE_PRESERVED_KHR, EGL_TRUE, EGL_NONE};
-    EGLClientBuffer clientBuf = static_cast<EGLClientBuffer>(pGfxBuffer->getNativeBuffer());
-    mKHRimage = eglCreateImageKHR(mDisplay, EGL_NO_CONTEXT,
-                                  EGL_NATIVE_BUFFER_ANDROID, clientBuf,
-                                  eglImageAttributes);
-    if (mKHRimage == EGL_NO_IMAGE_KHR) {
-        const char *msg = getEGLError();
-        ALOGE("error creating EGLImage: %s", msg);
-    } else {
-        // Update the texture handle we already created to refer to this gralloc buffer
-        glActiveTexture(GL_TEXTURE0);
-        glBindTexture(GL_TEXTURE_2D, glId());
-        glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, static_cast<GLeglImageOES>(mKHRimage));
-
-        // Initialize the sampling properties (it seems the sample may not work if this isn't done)
-        // The user of this texture may very well want to set their own filtering, but we're going
-        // to pay the (minor) price of setting this up for them to avoid the dreaded "black image"
-        // if they forget.
-        // TODO:  Can we do this once for the texture ID rather than ever refresh?
-        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
-        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
-        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
-        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
-    }
-
-    return true;
-}
-
-
-VideoTex* createVideoTexture(sp<IEvsEnumerator> pEnum,
-                             const char* evsCameraId,
-                             EGLDisplay glDisplay) {
-    // Set up the camera to feed this texture
-    sp<IEvsCamera> pCamera = pEnum->openCamera(evsCameraId);
-    if (pCamera.get() == nullptr) {
-        ALOGE("Failed to allocate new EVS Camera interface for %s", evsCameraId);
-        return nullptr;
-    }
-
-    // Initialize the stream that will help us update this texture's contents
-    sp<StreamHandler> pStreamHandler = new StreamHandler(pCamera);
-    if (pStreamHandler.get() == nullptr) {
-        ALOGE("failed to allocate FrameHandler");
-        return nullptr;
-    }
-
-    // Start the video stream
-    if (!pStreamHandler->startStream()) {
-        printf("Couldn't start the camera stream (%s)\n", evsCameraId);
-        ALOGE("start stream failed for %s", evsCameraId);
-        return nullptr;
-    }
-
-    return new VideoTex(pEnum, pCamera, pStreamHandler, glDisplay);
-}
diff --git a/evs/app/VideoTex.h b/evs/app/VideoTex.h
deleted file mode 100644
index 0b95c1d..0000000
--- a/evs/app/VideoTex.h
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-#ifndef VIDEOTEX_H
-#define VIDEOTEX_H
-
-#include <EGL/egl.h>
-#include <EGL/eglext.h>
-#include <GLES2/gl2.h>
-#include <GLES2/gl2ext.h>
-#include <GLES3/gl3.h>
-#include <GLES3/gl3ext.h>
-
-#include <android/hardware/automotive/evs/1.0/IEvsEnumerator.h>
-
-#include "TexWrapper.h"
-#include "StreamHandler.h"
-
-
-using namespace ::android::hardware::automotive::evs::V1_0;
-
-
-class VideoTex: public TexWrapper {
-    friend VideoTex* createVideoTexture(sp<IEvsEnumerator> pEnum,
-                                        const char * evsCameraId,
-                                        EGLDisplay glDisplay);
-
-public:
-    VideoTex() = delete;
-    virtual ~VideoTex();
-
-    bool refresh();     // returns true if the texture contents were updated
-
-private:
-    VideoTex(sp<IEvsEnumerator> pEnum,
-             sp<IEvsCamera> pCamera,
-             sp<StreamHandler> pStreamHandler,
-             EGLDisplay glDisplay);
-
-    sp<IEvsEnumerator>  mEnumerator;
-    sp<IEvsCamera>      mCamera;
-    sp<StreamHandler>   mStreamHandler;
-    BufferDesc          mImageBuffer;
-
-    EGLDisplay          mDisplay;
-    EGLImageKHR mKHRimage = EGL_NO_IMAGE_KHR;
-};
-
-
-VideoTex* createVideoTexture(sp<IEvsEnumerator> pEnum,
-                             const char * deviceName,
-                             EGLDisplay glDisplay);
-
-#endif // VIDEOTEX_H
\ No newline at end of file
diff --git a/evs/app/WindowSurface.cpp b/evs/app/WindowSurface.cpp
deleted file mode 100644
index dbd245b..0000000
--- a/evs/app/WindowSurface.cpp
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * Copyright 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "WindowSurface.h"
-
-#include <gui/SurfaceComposerClient.h>
-#include <gui/ISurfaceComposer.h>
-#include <gui/Surface.h>
-#include <ui/DisplayInfo.h>
-
-using namespace android;
-
-WindowSurface::WindowSurface() {
-    status_t err;
-
-    sp<SurfaceComposerClient> surfaceComposerClient = new SurfaceComposerClient;
-    err = surfaceComposerClient->initCheck();
-    if (err != NO_ERROR) {
-        fprintf(stderr, "SurfaceComposerClient::initCheck error: %#x\n", err);
-        return;
-    }
-
-    // Get main display parameters.
-    sp<IBinder> mainDpy = SurfaceComposerClient::getInternalDisplayToken();
-    if (mainDpy == nullptr) {
-        fprintf(stderr, "ERROR: no internal display\n");
-        return;
-    }
-
-    DisplayInfo mainDpyInfo;
-    err = SurfaceComposerClient::getDisplayInfo(mainDpy, &mainDpyInfo);
-    if (err != NO_ERROR) {
-        fprintf(stderr, "ERROR: unable to get display characteristics\n");
-        return;
-    }
-
-    uint32_t width, height;
-    if (mainDpyInfo.orientation != DISPLAY_ORIENTATION_0 &&
-            mainDpyInfo.orientation != DISPLAY_ORIENTATION_180) {
-        // rotated
-        width = mainDpyInfo.h;
-        height = mainDpyInfo.w;
-    } else {
-        width = mainDpyInfo.w;
-        height = mainDpyInfo.h;
-    }
-
-    sp<SurfaceControl> sc = surfaceComposerClient->createSurface(
-            String8("Benchmark"), width, height,
-            PIXEL_FORMAT_RGBX_8888, ISurfaceComposerClient::eOpaque);
-    if (sc == NULL || !sc->isValid()) {
-        fprintf(stderr, "Failed to create SurfaceControl\n");
-        return;
-    }
-
-    SurfaceComposerClient::Transaction{}
-            .setLayer(sc, 0x7FFFFFFF)     // always on top
-            .show(sc)
-            .apply();
-
-    mSurfaceControl = sc;
-}
-
-EGLNativeWindowType WindowSurface::getSurface() const {
-    sp<ANativeWindow> anw = mSurfaceControl->getSurface();
-    return (EGLNativeWindowType) anw.get();
-}
-
diff --git a/evs/app/WindowSurface.h b/evs/app/WindowSurface.h
deleted file mode 100644
index 966ea11..0000000
--- a/evs/app/WindowSurface.h
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef OPENGL_TESTS_WINDOWSURFACE_H
-#define OPENGL_TESTS_WINDOWSURFACE_H
-
-#include <gui/SurfaceControl.h>
-
-#include <EGL/egl.h>
-
-
-/*
- * A window that covers the entire display surface.
- *
- * The window is destroyed when this object is destroyed, so don't try
- * to use the surface after that point.
- */
-class WindowSurface {
-public:
-    // Creates the window.
-    WindowSurface();
-
-    // Retrieves a handle to the window.
-    EGLNativeWindowType getSurface() const;
-
-private:
-    WindowSurface(const WindowSurface&) = delete;
-    WindowSurface& operator=(const WindowSurface&) = delete;
-
-    android::sp<android::SurfaceControl> mSurfaceControl;
-};
-
-#endif /* OPENGL_TESTS_WINDOWSURFACE_H */
diff --git a/evs/app/config.json.readme b/evs/app/config.json.readme
deleted file mode 100644
index 982e3c9..0000000
--- a/evs/app/config.json.readme
+++ /dev/null
@@ -1,42 +0,0 @@
-// With comments included, this file is no longer legal JSON, but serves to illustrate
-// the format of the configuration file the evs_app expects to read at startup to configure itself
-// for a specific car.
-// In addition to the configuration file, an image to be used to represent the car is expected
-// to be provided in CarFromTop.png.
-// Throughout this file, units of length are arbitrary, but must all be the same units.
-// X is right, Y is forward, Z is up (right handed coordinate system).
-// The origin is at the center of the read axel at ground level.
-// Units for angles are in degrees.
-// Yaw is measured from the front of the car, positive to the left (postive Z rotation).
-// Pitch is measured from the horizon, positive upward (postive X rotation).
-// Roll is always assumed to be zero.
-
-{
-  "car" : {                     // This section describes the geometry of the car
-    "width"  : 76.7,            // The width of the car body
-    "wheelBase" : 117.9,        // The distance between the front and read axel
-    "frontExtent" : 44.7,       // The extent of the car body ahead of the front axel
-    "rearExtent" : 40           // The extent of the car body behind the read axel
-  },
-  "display" : {                 // This configures the dimensions of the surround view display
-    "frontRange" : 100,         // How far to render the view in front of the front bumper
-    "rearRange" : 100           // How far the view extends behind the rear bumper
-  },
-  "graphic" : {                 // This maps the car texture into the projected view space
-    "frontPixel" : 23,          // The pixel row in CarFromTop.png at which the front bumper appears
-    "rearPixel" : 223           // The pixel row in CarFromTop.png at which the back bumper ends
-  },
-  "cameras" : [                 // This describes the cameras potentially available on the car
-    {
-      "cameraId" : "/dev/video32",  // Camera ID exposed by EVS HAL
-      "function" : "reverse,park",  // set of modes to which this camera contributes
-      "x" : 0.0,                    // Optical center distance right of vehicle center
-      "y" : -40.0,                  // Optical center distance forward of rear axel
-      "z" : 48,                     // Optical center distance above ground
-      "yaw" : 180,                  // Optical axis degrees to the left of straight ahead
-      "pitch" : -30,                // Optical axis degrees above the horizon
-      "hfov" : 125,                 // Horizontal field of view in degrees
-      "vfov" :103                   // Vertical field of view in degrees
-    }
-  ]
-}
diff --git a/evs/app/evs_app.cpp b/evs/app/evs_app.cpp
deleted file mode 100644
index 31e5ab6..0000000
--- a/evs/app/evs_app.cpp
+++ /dev/null
@@ -1,171 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <stdio.h>
-
-#include <hidl/HidlTransportSupport.h>
-#include <utils/Errors.h>
-#include <utils/StrongPointer.h>
-#include <utils/Log.h>
-
-#include "android-base/macros.h"    // arraysize
-
-#include <android/hardware/automotive/evs/1.0/IEvsEnumerator.h>
-#include <android/hardware/automotive/evs/1.0/IEvsDisplay.h>
-
-#include <hwbinder/ProcessState.h>
-
-#include "EvsStateControl.h"
-#include "EvsVehicleListener.h"
-#include "ConfigManager.h"
-
-
-// libhidl:
-using android::hardware::configureRpcThreadpool;
-using android::hardware::joinRpcThreadpool;
-
-
-// Helper to subscribe to VHal notifications
-static bool subscribeToVHal(sp<IVehicle> pVnet,
-                            sp<IVehicleCallback> listener,
-                            VehicleProperty propertyId) {
-    assert(pVnet != nullptr);
-    assert(listener != nullptr);
-
-    // Register for vehicle state change callbacks we care about
-    // Changes in these values are what will trigger a reconfiguration of the EVS pipeline
-    SubscribeOptions optionsData[] = {
-        {
-            .propId = static_cast<int32_t>(propertyId),
-            .flags  = SubscribeFlags::EVENTS_FROM_CAR
-        },
-    };
-    hidl_vec <SubscribeOptions> options;
-    options.setToExternal(optionsData, arraysize(optionsData));
-    StatusCode status = pVnet->subscribe(listener, options);
-    if (status != StatusCode::OK) {
-        ALOGW("VHAL subscription for property 0x%08X failed with code %d.", propertyId, status);
-        return false;
-    }
-
-    return true;
-}
-
-
-// Main entry point
-int main(int argc, char** argv)
-{
-    ALOGI("EVS app starting\n");
-
-    // Set up default behavior, then check for command line options
-    bool useVehicleHal = true;
-    bool printHelp = false;
-    const char* evsServiceName = "default";
-    for (int i=1; i< argc; i++) {
-        if (strcmp(argv[i], "--test") == 0) {
-            useVehicleHal = false;
-        } else if (strcmp(argv[i], "--hw") == 0) {
-            evsServiceName = "EvsEnumeratorHw";
-        } else if (strcmp(argv[i], "--mock") == 0) {
-            evsServiceName = "EvsEnumeratorHw-Mock";
-        } else if (strcmp(argv[i], "--help") == 0) {
-            printHelp = true;
-        } else {
-            printf("Ignoring unrecognized command line arg '%s'\n", argv[i]);
-            printHelp = true;
-        }
-    }
-    if (printHelp) {
-        printf("Options include:\n");
-        printf("  --test   Do not talk to Vehicle Hal, but simulate 'reverse' instead\n");
-        printf("  --hw     Bypass EvsManager by connecting directly to EvsEnumeratorHw\n");
-        printf("  --mock   Connect directly to EvsEnumeratorHw-Mock\n");
-    }
-
-    // Load our configuration information
-    ConfigManager config;
-    if (!config.initialize("/system/etc/automotive/evs/config.json")) {
-        ALOGE("Missing or improper configuration for the EVS application.  Exiting.");
-        return 1;
-    }
-
-    // Set thread pool size to one to avoid concurrent events from the HAL.
-    // This pool will handle the EvsCameraStream callbacks.
-    // Note:  This _will_ run in parallel with the EvsListener run() loop below which
-    // runs the application logic that reacts to the async events.
-    configureRpcThreadpool(1, false /* callerWillJoin */);
-
-    // Construct our async helper object
-    sp<EvsVehicleListener> pEvsListener = new EvsVehicleListener();
-
-    // Get the EVS manager service
-    ALOGI("Acquiring EVS Enumerator");
-    android::sp<IEvsEnumerator> pEvs = IEvsEnumerator::getService(evsServiceName);
-    if (pEvs.get() == nullptr) {
-        ALOGE("getService(%s) returned NULL.  Exiting.", evsServiceName);
-        return 1;
-    }
-
-    // Request exclusive access to the EVS display
-    ALOGI("Acquiring EVS Display");
-    android::sp <IEvsDisplay> pDisplay;
-    pDisplay = pEvs->openDisplay();
-    if (pDisplay.get() == nullptr) {
-        ALOGE("EVS Display unavailable.  Exiting.");
-        return 1;
-    }
-
-    // Connect to the Vehicle HAL so we can monitor state
-    sp<IVehicle> pVnet;
-    if (useVehicleHal) {
-        ALOGI("Connecting to Vehicle HAL");
-        pVnet = IVehicle::getService();
-        if (pVnet.get() == nullptr) {
-            ALOGE("Vehicle HAL getService returned NULL.  Exiting.");
-            return 1;
-        } else {
-            // Register for vehicle state change callbacks we care about
-            // Changes in these values are what will trigger a reconfiguration of the EVS pipeline
-            if (!subscribeToVHal(pVnet, pEvsListener, VehicleProperty::GEAR_SELECTION)) {
-                ALOGE("Without gear notification, we can't support EVS.  Exiting.");
-                return 1;
-            }
-            if (!subscribeToVHal(pVnet, pEvsListener, VehicleProperty::TURN_SIGNAL_STATE)) {
-                ALOGW("Didn't get turn signal notificaitons, so we'll ignore those.");
-            }
-        }
-    } else {
-        ALOGW("Test mode selected, so not talking to Vehicle HAL");
-    }
-
-    // Configure ourselves for the current vehicle state at startup
-    ALOGI("Constructing state controller");
-    EvsStateControl *pStateController = new EvsStateControl(pVnet, pEvs, pDisplay, config);
-    if (!pStateController->startUpdateLoop()) {
-        ALOGE("Initial configuration failed.  Exiting.");
-        return 1;
-    }
-
-    // Run forever, reacting to events as necessary
-    ALOGI("Entering running state");
-    pEvsListener->run(pStateController);
-
-    // In normal operation, we expect to run forever, but in some error conditions we'll quit.
-    // One known example is if another process preempts our registration for our service name.
-    ALOGE("EVS Listener stopped.  Exiting.");
-
-    return 0;
-}
diff --git a/evs/apps/default/Android.bp b/evs/apps/default/Android.bp
new file mode 100644
index 0000000..413e4ed
--- /dev/null
+++ b/evs/apps/default/Android.bp
@@ -0,0 +1,106 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//
+
+//#################################
+cc_binary {
+    name: "evs_app",
+
+    srcs: [
+        "evs_app.cpp",
+        "EvsStateControl.cpp",
+        "RenderBase.cpp",
+        "RenderDirectView.cpp",
+        "RenderTopView.cpp",
+        "ConfigManager.cpp",
+        "glError.cpp",
+        "shader.cpp",
+        "TexWrapper.cpp",
+        "VideoTex.cpp",
+        "StreamHandler.cpp",
+        "FormatConvert.cpp",
+        "RenderPixelCopy.cpp",
+    ],
+
+    shared_libs: [
+        "libbinder",
+        "libcutils",
+        "liblog",
+        "libutils",
+        "libui",
+        "libhidlbase",
+        "libEGL",
+        "libGLESv2",
+        "libhardware",
+        "libpng",
+        "libcamera_metadata",
+        "android.hardware.camera.device@3.2",
+        "android.hardware.automotive.evs@1.0",
+        "android.hardware.automotive.evs@1.1",
+        "android.hardware.automotive.vehicle@2.0",
+    ],
+
+    static_libs: [
+        "libmath",
+        "libjsoncpp",
+    ],
+
+    required: [
+        "config.json",
+        "CarFromTop.png",
+        "LabeledChecker.png",
+    ],
+
+    strip: {
+        keep_symbols: true,
+    },
+
+    init_rc: ["evs_app.rc"],
+
+    cflags: ["-DLOG_TAG=\"EvsApp\""] + [
+        "-DGL_GLEXT_PROTOTYPES",
+        "-DEGL_EGLEXT_PROTOTYPES",
+    ] + [
+        "-Wall",
+        "-Werror",
+        "-Wunused",
+        "-Wunreachable-code",
+    ],
+
+}
+
+prebuilt_etc {
+    name: "config.json",
+
+    src: "config.json",
+    sub_dir: "automotive/evs",
+
+}
+
+prebuilt_etc {
+    name: "CarFromTop.png",
+
+    src: "CarFromTop.png",
+    sub_dir: "automotive/evs",
+
+}
+
+prebuilt_etc {
+    name: "LabeledChecker.png",
+
+    src: "LabeledChecker.png",
+    sub_dir: "automotive/evs",
+
+}
diff --git a/evs/app/CarFromTop.png b/evs/apps/default/CarFromTop.png
similarity index 100%
rename from evs/app/CarFromTop.png
rename to evs/apps/default/CarFromTop.png
Binary files differ
diff --git a/evs/apps/default/ConfigManager.cpp b/evs/apps/default/ConfigManager.cpp
new file mode 100644
index 0000000..76c142c
--- /dev/null
+++ b/evs/apps/default/ConfigManager.cpp
@@ -0,0 +1,197 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "ConfigManager.h"
+
+#include "json/json.h"
+
+#include <fstream>
+#include <math.h>
+#include <assert.h>
+
+
+static const float kDegreesToRadians = M_PI / 180.0f;
+
+
+static float normalizeToPlusMinus180degrees(float theta) {
+    const float wraps = floor((theta+180.0f) / 360.0f);
+    return theta - wraps*360.0f;
+}
+
+
+static bool readChildNodeAsFloat(const char* groupName,
+                                 const Json::Value& parentNode,
+                                 const char* childName,
+                                 float* value) {
+    // Must have a place to put the value!
+    assert(value);
+
+    Json::Value childNode = parentNode[childName];
+    if (!childNode.isNumeric()) {
+        printf("Missing or invalid field %s in record %s", childName, groupName);
+        return false;
+    }
+
+    *value = childNode.asFloat();
+    return true;
+}
+
+
+bool ConfigManager::initialize(const char* configFileName)
+{
+    bool complete = true;
+
+    // Set up a stream to read in the input file
+    std::ifstream configStream(configFileName);
+
+    // Parse the stream into JSON objects
+    Json::Reader reader;
+    Json::Value rootNode;
+    bool parseOk = reader.parse(configStream, rootNode, false /* don't need comments */);
+    if (!parseOk) {
+        printf("Failed to read configuration file %s\n", configFileName);
+        printf("%s\n", reader.getFormatedErrorMessages().c_str());
+        return false;
+    }
+
+
+    //
+    // Read car information
+    //
+    {
+        Json::Value car = rootNode["car"];
+        if (!car.isObject()) {
+            printf("Invalid configuration format -- we expect a car description\n");
+            return false;
+        }
+        complete &= readChildNodeAsFloat("car", car, "width",       &mCarWidth);
+        complete &= readChildNodeAsFloat("car", car, "wheelBase",   &mWheelBase);
+        complete &= readChildNodeAsFloat("car", car, "frontExtent", &mFrontExtent);
+        complete &= readChildNodeAsFloat("car", car, "rearExtent",  &mRearExtent);
+    }
+
+
+    //
+    // Read display layout information
+    //
+    {
+        Json::Value displayArray = rootNode["displays"];
+        if (!displayArray.isArray()) {
+            printf("Invalid configuration format -- we expect an array of displays\n");
+            return false;
+        }
+
+        mDisplays.reserve(displayArray.size());
+        for (auto&& node : displayArray) {
+            DisplayInfo info;
+            info.port = node.get("displayPort", 0).asUInt();
+            info.function = node.get("function", "").asCString();
+            info.frontRangeInCarSpace = node.get("frontRange", -1).asFloat();
+            info.rearRangeInCarSpace = node.get("rearRange", -1).asFloat();
+
+            mDisplays.emplace_back(info);
+        }
+    }
+
+
+    //
+    // Car top view texture properties for top down view
+    //
+    {
+        Json::Value graphicNode = rootNode["graphic"];
+        if (!graphicNode.isObject()) {
+            printf("Invalid configuration format -- we expect a graphic description\n");
+            return false;
+        }
+        complete &= readChildNodeAsFloat("graphic", graphicNode, "frontPixel", &mCarGraphicFrontPixel);
+        complete &= readChildNodeAsFloat("display", graphicNode, "rearPixel",  &mCarGraphicRearPixel);
+    }
+
+
+    //
+    // Read camera information
+    // NOTE:  Missing positions and angles are not reported, but instead default to zero
+    //
+    {
+        Json::Value cameraArray = rootNode["cameras"];
+        if (!cameraArray.isArray()) {
+            printf("Invalid configuration format -- we expect an array of cameras\n");
+            return false;
+        }
+
+        mCameras.reserve(cameraArray.size());
+        for (auto&& node: cameraArray) {
+            // Get data from the configuration file
+            Json::Value nameNode = node.get("cameraId", "MISSING");
+            const char *cameraId = nameNode.asCString();
+
+            Json::Value usageNode = node.get("function", "");
+            const char *function = usageNode.asCString();
+
+            float yaw   = node.get("yaw", 0).asFloat();
+            float pitch = node.get("pitch", 0).asFloat();
+            float hfov  = node.get("hfov", 0).asFloat();
+            float vfov  = node.get("vfov", 0).asFloat();
+
+            // Wrap the direction angles to be in the 180deg to -180deg range
+            // Rotate 180 in yaw if necessary to flip the pitch into the +/-90degree range
+            pitch = normalizeToPlusMinus180degrees(pitch);
+            if (pitch > 90.0f) {
+                yaw += 180.0f;
+                pitch = 180.0f - pitch;
+            }
+            if (pitch < -90.0f) {
+                yaw += 180.0f;
+                pitch = -180.0f + pitch;
+            }
+            yaw = normalizeToPlusMinus180degrees(yaw);
+
+            // Range check the FOV values to ensure they are postive and less than 180degrees
+            if (hfov > 179.0f) {
+                printf("Pathological horizontal field of view %f clamped to 179 degrees\n", hfov);
+                hfov = 179.0f;
+            }
+            if (hfov < 1.0f) {
+                printf("Pathological horizontal field of view %f clamped to 1 degree\n", hfov);
+                hfov = 1.0f;
+            }
+            if (vfov > 179.0f) {
+                printf("Pathological horizontal field of view %f clamped to 179 degrees\n", vfov);
+                vfov = 179.0f;
+            }
+            if (vfov < 1.0f) {
+                printf("Pathological horizontal field of view %f clamped to 1 degree\n", vfov);
+                vfov = 1.0f;
+            }
+
+            // Store the camera info (converting degrees to radians in the process)
+            CameraInfo info;
+            info.position[0] = node.get("x", 0).asFloat();
+            info.position[1] = node.get("y", 0).asFloat();
+            info.position[2] = node.get("z", 0).asFloat();
+            info.yaw         = yaw   * kDegreesToRadians;
+            info.pitch       = pitch * kDegreesToRadians;
+            info.hfov        = hfov  * kDegreesToRadians;
+            info.vfov        = vfov  * kDegreesToRadians;
+            info.cameraId    = cameraId;
+            info.function    = function;
+
+            mCameras.emplace_back(info);
+        }
+    }
+
+    // If we got this far, we were successful as long as we found all our child fields
+    return complete;
+}
diff --git a/evs/apps/default/ConfigManager.h b/evs/apps/default/ConfigManager.h
new file mode 100644
index 0000000..b3d283e
--- /dev/null
+++ b/evs/apps/default/ConfigManager.h
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#ifndef CONFIG_MANAGER_H
+#define CONFIG_MANAGER_H
+
+#include <vector>
+#include <string>
+
+
+class ConfigManager {
+public:
+    struct CameraInfo {
+        std::string cameraId = "";  // The name of the camera from the point of view of the HAL
+        std::string function = "";  // The expected use for this camera ("reverse", "left", "right")
+        float position[3] = {0};    // x, y, z -> right, fwd, up in the units of car space
+        float yaw   = 0;    // radians positive to the left (right hand rule about global z axis)
+        float pitch = 0;    // positive upward (ie: right hand rule about local x axis)
+        float hfov  = 0;    // radians
+        float vfov  = 0;    // radians
+    };
+
+    struct DisplayInfo {
+        uint8_t port = 0;           // Display port number to use
+        std::string function = "";  // The expected use for this display.
+        float frontRangeInCarSpace; // How far the display extends in front of the car
+        float rearRangeInCarSpace;  // How far the display extends behind the car
+    };
+
+    bool initialize(const char* configFileName);
+
+    // World space dimensions of the car
+    float getCarWidth() const   { return mCarWidth; };
+    float getCarLength() const  { return mWheelBase + mFrontExtent + mRearExtent; };
+    float getWheelBase() const  { return mWheelBase; };
+
+    // Car space (world space centered on the rear axel) edges of the car
+    float getFrontLocation() const  { return mWheelBase + mFrontExtent; };
+    float getRearLocation() const   { return -mRearExtent; };
+    float getRightLocation() const  { return mCarWidth*0.5f; };
+    float getLeftLocation() const   { return -mCarWidth*0.5f; };
+
+    // Where are the edges of the top down display in car space?
+    float getDisplayTopLocation() const {
+        // From the rear axel (origin) to the front bumper, and then beyond by the front range
+        return mWheelBase + mFrontExtent + mDisplays[mActiveDisplayId].frontRangeInCarSpace;
+    };
+    float getDisplayBottomLocation() const {
+        // From the rear axel (origin) to the back bumper, and then beyond by the back range
+        return -mRearExtent - mDisplays[mActiveDisplayId].rearRangeInCarSpace;
+    };
+    float getDisplayRightLocation(float aspectRatio) const   {
+        // Given the display aspect ratio (width over height), how far can we see to the right?
+        return (getDisplayTopLocation() - getDisplayBottomLocation()) * 0.5f * aspectRatio;
+    };
+    float getDisplayLeftLocation(float aspectRatio) const {
+        // Given the display aspect ratio (width over height), how far can we see to the left?
+        return -getDisplayRightLocation(aspectRatio);
+    };
+
+    // At which texel (vertically in the image) are the front and rear bumpers of the car?
+    float carGraphicFrontPixel() const      { return mCarGraphicFrontPixel; };
+    float carGraphicRearPixel() const       { return mCarGraphicRearPixel; };
+
+    const std::vector<CameraInfo>& getCameras() const   { return mCameras; };
+
+    bool  setActiveDisplayId(int displayId) {
+        if (displayId < 0 or displayId > mDisplays.size()) {
+            printf("Display %d is invalid.  Current active display is display %d.",
+                   displayId, mActiveDisplayId);
+            return false;
+        }
+
+        mActiveDisplayId = displayId;
+
+        return true;
+    }
+    const std::vector<DisplayInfo>& getDisplays() const { return mDisplays; };
+    const DisplayInfo& getActiveDisplay() const { return mDisplays[mActiveDisplayId]; };
+
+private:
+    // Camera information
+    std::vector<CameraInfo> mCameras;
+
+    // Display information
+    std::vector<DisplayInfo> mDisplays;
+    int mActiveDisplayId;
+
+    // Car body information (assumes front wheel steering and origin at center of rear axel)
+    // Note that units aren't specified and don't matter as long as all length units are consistent
+    // within the JSON file from which we parse.  That is, if everything is in meters, that's fine.
+    // Everything in mm?  That's fine too.
+    float mCarWidth;
+    float mWheelBase;
+    float mFrontExtent;
+    float mRearExtent;
+
+    // Top view car image information
+    float mCarGraphicFrontPixel;    // How many pixels from the top of the image does the car start
+    float mCarGraphicRearPixel;     // How many pixels from the top of the image does the car end
+};
+
+#endif // CONFIG_MANAGER_H
diff --git a/evs/apps/default/EvsStateControl.cpp b/evs/apps/default/EvsStateControl.cpp
new file mode 100644
index 0000000..3f98f6e
--- /dev/null
+++ b/evs/apps/default/EvsStateControl.cpp
@@ -0,0 +1,365 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "EvsStateControl.h"
+#include "RenderDirectView.h"
+#include "RenderTopView.h"
+#include "RenderPixelCopy.h"
+#include "FormatConvert.h"
+
+#include <stdio.h>
+#include <string.h>
+
+#include <log/log.h>
+#include <inttypes.h>
+#include <utils/SystemClock.h>
+#include <binder/IServiceManager.h>
+
+using ::android::hardware::automotive::evs::V1_0::EvsResult;
+using EvsDisplayState = ::android::hardware::automotive::evs::V1_0::DisplayState;
+using BufferDesc_1_0  = ::android::hardware::automotive::evs::V1_0::BufferDesc;
+using BufferDesc_1_1  = ::android::hardware::automotive::evs::V1_1::BufferDesc;
+
+static bool isSfReady() {
+    const android::String16 serviceName("SurfaceFlinger");
+    return android::defaultServiceManager()->checkService(serviceName) != nullptr;
+}
+
+// TODO:  Seems like it'd be nice if the Vehicle HAL provided such helpers (but how & where?)
+inline constexpr VehiclePropertyType getPropType(VehicleProperty prop) {
+    return static_cast<VehiclePropertyType>(
+            static_cast<int32_t>(prop)
+            & static_cast<int32_t>(VehiclePropertyType::MASK));
+}
+
+
+EvsStateControl::EvsStateControl(android::sp <IVehicle>       pVnet,
+                                 android::sp <IEvsEnumerator> pEvs,
+                                 android::sp <IEvsDisplay>    pDisplay,
+                                 const ConfigManager&         config) :
+    mVehicle(pVnet),
+    mEvs(pEvs),
+    mDisplay(pDisplay),
+    mConfig(config),
+    mCurrentState(OFF) {
+
+    // Initialize the property value containers we'll be updating (they'll be zeroed by default)
+    static_assert(getPropType(VehicleProperty::GEAR_SELECTION) == VehiclePropertyType::INT32,
+                  "Unexpected type for GEAR_SELECTION property");
+    static_assert(getPropType(VehicleProperty::TURN_SIGNAL_STATE) == VehiclePropertyType::INT32,
+                  "Unexpected type for TURN_SIGNAL_STATE property");
+
+    mGearValue.prop       = static_cast<int32_t>(VehicleProperty::GEAR_SELECTION);
+    mTurnSignalValue.prop = static_cast<int32_t>(VehicleProperty::TURN_SIGNAL_STATE);
+
+    // This way we only ever deal with cameras which exist in the system
+    // Build our set of cameras for the states we support
+    ALOGD("Requesting camera list");
+    mEvs->getCameraList_1_1(
+        [this, &config](hidl_vec<CameraDesc> cameraList) {
+            ALOGI("Camera list callback received %zu cameras",
+                  cameraList.size());
+            for (auto&& cam: cameraList) {
+                ALOGD("Found camera %s", cam.v1.cameraId.c_str());
+                bool cameraConfigFound = false;
+
+                // Check our configuration for information about this camera
+                // Note that a camera can have a compound function string
+                // such that a camera can be "right/reverse" and be used for both.
+                // If more than one camera is listed for a given function, we'll
+                // list all of them and let the UX/rendering logic use one, some
+                // or all of them as appropriate.
+                for (auto&& info: config.getCameras()) {
+                    if (cam.v1.cameraId == info.cameraId) {
+                        // We found a match!
+                        if (info.function.find("reverse") != std::string::npos) {
+                            mCameraList[State::REVERSE].emplace_back(info);
+                            mCameraDescList[State::REVERSE].emplace_back(cam);
+                        }
+                        if (info.function.find("right") != std::string::npos) {
+                            mCameraList[State::RIGHT].emplace_back(info);
+                            mCameraDescList[State::RIGHT].emplace_back(cam);
+                        }
+                        if (info.function.find("left") != std::string::npos) {
+                            mCameraList[State::LEFT].emplace_back(info);
+                            mCameraDescList[State::LEFT].emplace_back(cam);
+                        }
+                        if (info.function.find("park") != std::string::npos) {
+                            mCameraList[State::PARKING].emplace_back(info);
+                            mCameraDescList[State::PARKING].emplace_back(cam);
+                        }
+                        cameraConfigFound = true;
+                        break;
+                    }
+                }
+                if (!cameraConfigFound) {
+                    ALOGW("No config information for hardware camera %s",
+                          cam.v1.cameraId.c_str());
+                }
+            }
+        }
+    );
+
+    ALOGD("State controller ready");
+}
+
+
+bool EvsStateControl::startUpdateLoop() {
+    // Create the thread and report success if it gets started
+    mRenderThread = std::thread([this](){ updateLoop(); });
+    return mRenderThread.joinable();
+}
+
+
+void EvsStateControl::postCommand(const Command& cmd) {
+    // Push the command onto the queue watched by updateLoop
+    mLock.lock();
+    mCommandQueue.push(cmd);
+    mLock.unlock();
+
+    // Send a signal to wake updateLoop in case it is asleep
+    mWakeSignal.notify_all();
+}
+
+
+void EvsStateControl::updateLoop() {
+    ALOGD("Starting EvsStateControl update loop");
+
+    bool run = true;
+    while (run) {
+        // Process incoming commands
+        {
+            std::lock_guard <std::mutex> lock(mLock);
+            while (!mCommandQueue.empty()) {
+                const Command& cmd = mCommandQueue.front();
+                switch (cmd.operation) {
+                case Op::EXIT:
+                    run = false;
+                    break;
+                case Op::CHECK_VEHICLE_STATE:
+                    // Just running selectStateForCurrentConditions below will take care of this
+                    break;
+                case Op::TOUCH_EVENT:
+                    // Implement this given the x/y location of the touch event
+                    break;
+                }
+                mCommandQueue.pop();
+            }
+        }
+
+        // Review vehicle state and choose an appropriate renderer
+        if (!selectStateForCurrentConditions()) {
+            ALOGE("selectStateForCurrentConditions failed so we're going to die");
+            break;
+        }
+
+        // If we have an active renderer, give it a chance to draw
+        if (mCurrentRenderer) {
+            // Get the output buffer we'll use to display the imagery
+            BufferDesc_1_0 tgtBuffer = {};
+            mDisplay->getTargetBuffer([&tgtBuffer](const BufferDesc_1_0& buff) {
+                                          tgtBuffer = buff;
+                                      }
+            );
+
+            if (tgtBuffer.memHandle == nullptr) {
+                ALOGE("Didn't get requested output buffer -- skipping this frame.");
+            } else {
+                // Generate our output image
+                if (!mCurrentRenderer->drawFrame(convertBufferDesc(tgtBuffer))) {
+                    // If drawing failed, we want to exit quickly so an app restart can happen
+                    run = false;
+                }
+
+                // Send the finished image back for display
+                mDisplay->returnTargetBufferForDisplay(tgtBuffer);
+            }
+        } else {
+            // No active renderer, so sleep until somebody wakes us with another command
+            std::unique_lock<std::mutex> lock(mLock);
+            mWakeSignal.wait(lock);
+        }
+    }
+
+    ALOGW("EvsStateControl update loop ending");
+
+    // TODO:  Fix it so we can exit cleanly from the main thread instead
+    printf("Shutting down app due to state control loop ending\n");
+    ALOGE("KILLING THE APP FROM THE EvsStateControl LOOP ON DRAW FAILURE!!!");
+    exit(1);
+}
+
+
+bool EvsStateControl::selectStateForCurrentConditions() {
+    static int32_t sDummyGear   = int32_t(VehicleGear::GEAR_REVERSE);
+    static int32_t sDummySignal = int32_t(VehicleTurnSignal::NONE);
+
+    if (mVehicle != nullptr) {
+        // Query the car state
+        if (invokeGet(&mGearValue) != StatusCode::OK) {
+            ALOGE("GEAR_SELECTION not available from vehicle.  Exiting.");
+            return false;
+        }
+        if ((mTurnSignalValue.prop == 0) || (invokeGet(&mTurnSignalValue) != StatusCode::OK)) {
+            // Silently treat missing turn signal state as no turn signal active
+            mTurnSignalValue.value.int32Values.setToExternal(&sDummySignal, 1);
+            mTurnSignalValue.prop = 0;
+        }
+    } else {
+        // While testing without a vehicle, behave as if we're in reverse for the first 20 seconds
+        static const int kShowTime = 20;    // seconds
+
+        // See if it's time to turn off the default reverse camera
+        static std::chrono::steady_clock::time_point start = std::chrono::steady_clock::now();
+        std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now();
+        if (std::chrono::duration_cast<std::chrono::seconds>(now - start).count() > kShowTime) {
+            // Switch to drive (which should turn off the reverse camera)
+            sDummyGear = int32_t(VehicleGear::GEAR_DRIVE);
+        }
+
+        // Build the dummy vehicle state values (treating single values as 1 element vectors)
+        mGearValue.value.int32Values.setToExternal(&sDummyGear, 1);
+        mTurnSignalValue.value.int32Values.setToExternal(&sDummySignal, 1);
+    }
+
+    // Choose our desired EVS state based on the current car state
+    // TODO:  Update this logic, and consider user input when choosing if a view should be presented
+    State desiredState = OFF;
+    if (mGearValue.value.int32Values[0] == int32_t(VehicleGear::GEAR_REVERSE)) {
+        desiredState = REVERSE;
+    } else if (mTurnSignalValue.value.int32Values[0] == int32_t(VehicleTurnSignal::RIGHT)) {
+        desiredState = RIGHT;
+    } else if (mTurnSignalValue.value.int32Values[0] == int32_t(VehicleTurnSignal::LEFT)) {
+        desiredState = LEFT;
+    } else if (mGearValue.value.int32Values[0] == int32_t(VehicleGear::GEAR_PARK)) {
+        desiredState = PARKING;
+    }
+
+    // Apply the desire state
+    return configureEvsPipeline(desiredState);
+}
+
+
+StatusCode EvsStateControl::invokeGet(VehiclePropValue *pRequestedPropValue) {
+    StatusCode status = StatusCode::TRY_AGAIN;
+
+    // Call the Vehicle HAL, which will block until the callback is complete
+    mVehicle->get(*pRequestedPropValue,
+                  [pRequestedPropValue, &status]
+                  (StatusCode s, const VehiclePropValue& v) {
+                       status = s;
+                       if (s == StatusCode::OK) {
+                           *pRequestedPropValue = v;
+                       }
+                  }
+    );
+
+    return status;
+}
+
+
+bool EvsStateControl::configureEvsPipeline(State desiredState) {
+    static bool isGlReady = false;
+
+    if (mCurrentState == desiredState) {
+        // Nothing to do here...
+        return true;
+    }
+
+    ALOGD("Switching to state %d.", desiredState);
+    ALOGD("  Current state %d has %zu cameras", mCurrentState,
+          mCameraList[mCurrentState].size());
+    ALOGD("  Desired state %d has %zu cameras", desiredState,
+          mCameraList[desiredState].size());
+
+    if (!isGlReady && !isSfReady()) {
+        // Graphics is not ready yet; using CPU renderer.
+        if (mCameraList[desiredState].size() >= 1) {
+            mDesiredRenderer = std::make_unique<RenderPixelCopy>(mEvs,
+                                                                 mCameraList[desiredState][0]);
+            if (!mDesiredRenderer) {
+                ALOGE("Failed to construct Pixel Copy renderer.  Skipping state change.");
+                return false;
+            }
+        } else {
+            ALOGD("Unsupported, desiredState %d has %u cameras.",
+                  desiredState, static_cast<unsigned int>(mCameraList[desiredState].size()));
+        }
+    } else {
+        // Assumes that SurfaceFlinger is available always after being launched.
+
+        // Do we need a new direct view renderer?
+        if (mCameraList[desiredState].size() == 1) {
+            // We have a camera assigned to this state for direct view.
+            mDesiredRenderer = std::make_unique<RenderDirectView>(mEvs,
+                                                                  mCameraDescList[desiredState][0]);
+            if (!mDesiredRenderer) {
+                ALOGE("Failed to construct direct renderer.  Skipping state change.");
+                return false;
+            }
+        } else if (mCameraList[desiredState].size() > 1 || desiredState == PARKING) {
+            //TODO(b/140668179): RenderTopView needs to be updated to use new
+            //                   ConfigManager.
+            mDesiredRenderer = std::make_unique<RenderTopView>(mEvs,
+                                                               mCameraList[desiredState],
+                                                               mConfig);
+            if (!mDesiredRenderer) {
+                ALOGE("Failed to construct top view renderer.  Skipping state change.");
+                return false;
+            }
+        } else {
+            ALOGD("Unsupported, desiredState %d has %u cameras.",
+                  desiredState, static_cast<unsigned int>(mCameraList[desiredState].size()));
+        }
+
+        // GL renderer is now ready.
+        isGlReady = true;
+    }
+
+    // Since we're changing states, shut down the current renderer
+    if (mCurrentRenderer != nullptr) {
+        mCurrentRenderer->deactivate();
+        mCurrentRenderer = nullptr; // It's a smart pointer, so destructs on assignment to null
+    }
+
+    // Now set the display state based on whether we have a video feed to show
+    if (mDesiredRenderer == nullptr) {
+        ALOGD("Turning off the display");
+        mDisplay->setDisplayState(EvsDisplayState::NOT_VISIBLE);
+    } else {
+        mCurrentRenderer = std::move(mDesiredRenderer);
+
+        // Start the camera stream
+        ALOGD("EvsStartCameraStreamTiming start time: %" PRId64 "ms", android::elapsedRealtime());
+        if (!mCurrentRenderer->activate()) {
+            ALOGE("New renderer failed to activate");
+            return false;
+        }
+
+        // Activate the display
+        ALOGD("EvsActivateDisplayTiming start time: %" PRId64 "ms", android::elapsedRealtime());
+        Return<EvsResult> result = mDisplay->setDisplayState(EvsDisplayState::VISIBLE_ON_NEXT_FRAME);
+        if (result != EvsResult::OK) {
+            ALOGE("setDisplayState returned an error (%d)", (EvsResult)result);
+            return false;
+        }
+    }
+
+    // Record our current state
+    ALOGI("Activated state %d.", desiredState);
+    mCurrentState = desiredState;
+
+    return true;
+}
diff --git a/evs/apps/default/EvsStateControl.h b/evs/apps/default/EvsStateControl.h
new file mode 100644
index 0000000..5ee9f47
--- /dev/null
+++ b/evs/apps/default/EvsStateControl.h
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef CAR_EVS_APP_EVSSTATECONTROL_H
+#define CAR_EVS_APP_EVSSTATECONTROL_H
+
+#include "StreamHandler.h"
+#include "ConfigManager.h"
+#include "RenderBase.h"
+
+#include <android/hardware/automotive/vehicle/2.0/IVehicle.h>
+#include <android/hardware/automotive/evs/1.1/IEvsEnumerator.h>
+#include <android/hardware/automotive/evs/1.1/IEvsDisplay.h>
+#include <android/hardware/automotive/evs/1.1/IEvsCamera.h>
+
+#include <thread>
+
+
+using namespace ::android::hardware::automotive::evs::V1_1;
+using namespace ::android::hardware::automotive::vehicle::V2_0;
+using ::android::hardware::Return;
+using ::android::hardware::Void;
+using ::android::hardware::hidl_vec;
+using ::android::hardware::hidl_handle;
+using ::android::sp;
+using ::android::hardware::automotive::evs::V1_1::IEvsDisplay;
+using ::android::hardware::camera::device::V3_2::Stream;
+
+
+/*
+ * This class runs the main update loop for the EVS application.  It will sleep when it has
+ * nothing to do.  It provides a thread safe way for other threads to wake it and pass commands
+ * to it.
+ */
+class EvsStateControl {
+public:
+    EvsStateControl(android::sp <IVehicle>       pVnet,
+                    android::sp <IEvsEnumerator> pEvs,
+                    android::sp <IEvsDisplay>    pDisplay,
+                    const ConfigManager&         config);
+
+    enum State {
+        OFF = 0,
+        REVERSE,
+        LEFT,
+        RIGHT,
+        PARKING,
+        NUM_STATES  // Must come last
+    };
+
+    enum class Op {
+        EXIT,
+        CHECK_VEHICLE_STATE,
+        TOUCH_EVENT,
+    };
+
+    struct Command {
+        Op          operation;
+        uint32_t    arg1;
+        uint32_t    arg2;
+    };
+
+    // This spawns a new thread that is expected to run continuously
+    bool startUpdateLoop();
+
+    // Safe to be called from other threads
+    void postCommand(const Command& cmd);
+
+private:
+    void updateLoop();
+    StatusCode invokeGet(VehiclePropValue *pRequestedPropValue);
+    bool selectStateForCurrentConditions();
+    bool configureEvsPipeline(State desiredState);  // Only call from one thread!
+
+    sp<IVehicle>                mVehicle;
+    sp<IEvsEnumerator>          mEvs;
+    sp<IEvsDisplay>             mDisplay;
+    const ConfigManager&        mConfig;
+
+    VehiclePropValue            mGearValue;
+    VehiclePropValue            mTurnSignalValue;
+
+    State                       mCurrentState = OFF;
+
+    // mCameraList is a redundant storage for camera device info, which is also
+    // stored in mCameraDescList and, however, not removed for backward
+    // compatibility.
+    std::vector<ConfigManager::CameraInfo>  mCameraList[NUM_STATES];
+    std::unique_ptr<RenderBase> mCurrentRenderer;
+    std::unique_ptr<RenderBase> mDesiredRenderer;
+    std::vector<CameraDesc>     mCameraDescList[NUM_STATES];
+
+    std::thread                 mRenderThread;  // The thread that runs the main rendering loop
+
+    // Other threads may want to spur us into action, so we provide a thread safe way to do that
+    std::mutex                  mLock;
+    std::condition_variable     mWakeSignal;
+    std::queue<Command>         mCommandQueue;
+};
+
+
+#endif //CAR_EVS_APP_EVSSTATECONTROL_H
diff --git a/evs/app/EvsVehicleListener.h b/evs/apps/default/EvsVehicleListener.h
similarity index 100%
rename from evs/app/EvsVehicleListener.h
rename to evs/apps/default/EvsVehicleListener.h
diff --git a/evs/apps/default/FormatConvert.cpp b/evs/apps/default/FormatConvert.cpp
new file mode 100644
index 0000000..c562c79
--- /dev/null
+++ b/evs/apps/default/FormatConvert.cpp
@@ -0,0 +1,189 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <android/hardware_buffer.h>
+#include "FormatConvert.h"
+
+
+// Round up to the nearest multiple of the given alignment value
+template<unsigned alignment>
+int align(int value) {
+    static_assert((alignment && !(alignment & (alignment - 1))),
+                  "alignment must be a power of 2");
+
+    unsigned mask = alignment - 1;
+    return (value + mask) & ~mask;
+}
+
+
+// Limit the given value to the provided range.  :)
+static inline float clamp(float v, float min, float max) {
+    if (v < min) return min;
+    if (v > max) return max;
+    return v;
+}
+
+
+static uint32_t yuvToRgbx(const unsigned char Y, const unsigned char Uin, const unsigned char Vin) {
+    // Don't use this if you want to see the best performance.  :)
+    // Better to do this in a pixel shader if we really have to, but on actual
+    // embedded hardware we expect to be able to texture directly from the YUV data
+    float U = Uin - 128.0f;
+    float V = Vin - 128.0f;
+
+    float Rf = Y + 1.140f*V;
+    float Gf = Y - 0.395f*U - 0.581f*V;
+    float Bf = Y + 2.032f*U;
+    unsigned char R = (unsigned char)clamp(Rf, 0.0f, 255.0f);
+    unsigned char G = (unsigned char)clamp(Gf, 0.0f, 255.0f);
+    unsigned char B = (unsigned char)clamp(Bf, 0.0f, 255.0f);
+
+    return (R      ) |
+           (G <<  8) |
+           (B << 16) |
+           0xFF000000;  // Fill the alpha channel with ones
+}
+
+
+void copyNV21toRGB32(unsigned width, unsigned height,
+                     uint8_t* src,
+                     uint32_t* dst, unsigned dstStridePixels)
+{
+    // The NV21 format provides a Y array of 8bit values, followed by a 1/2 x 1/2 interleaved
+    // U/V array.  It assumes an even width and height for the overall image, and a horizontal
+    // stride that is an even multiple of 16 bytes for both the Y and UV arrays.
+    unsigned strideLum = align<16>(width);
+    unsigned sizeY = strideLum * height;
+    unsigned strideColor = strideLum;   // 1/2 the samples, but two interleaved channels
+    unsigned offsetUV = sizeY;
+
+    uint8_t* srcY = src;
+    uint8_t* srcUV = src+offsetUV;
+
+    for (unsigned r = 0; r < height; r++) {
+        // Note that we're walking the same UV row twice for even/odd luminance rows
+        uint8_t* rowY  = srcY  + r*strideLum;
+        uint8_t* rowUV = srcUV + (r/2 * strideColor);
+
+        uint32_t* rowDest = dst + r*dstStridePixels;
+
+        for (unsigned c = 0; c < width; c++) {
+            unsigned uCol = (c & ~1);   // uCol is always even and repeats 1:2 with Y values
+            unsigned vCol = uCol | 1;   // vCol is always odd
+            rowDest[c] = yuvToRgbx(rowY[c], rowUV[uCol], rowUV[vCol]);
+        }
+    }
+}
+
+
+void copyYV12toRGB32(unsigned width, unsigned height,
+                     uint8_t* src,
+                     uint32_t* dst, unsigned dstStridePixels)
+{
+    // The YV12 format provides a Y array of 8bit values, followed by a 1/2 x 1/2 U array, followed
+    // by another 1/2 x 1/2 V array.  It assumes an even width and height for the overall image,
+    // and a horizontal stride that is an even multiple of 16 bytes for each of the Y, U,
+    // and V arrays.
+    unsigned strideLum = align<16>(width);
+    unsigned sizeY = strideLum * height;
+    unsigned strideColor = align<16>(strideLum/2);
+    unsigned sizeColor = strideColor * height/2;
+    unsigned offsetU = sizeY;
+    unsigned offsetV = sizeY + sizeColor;
+
+    uint8_t* srcY = src;
+    uint8_t* srcU = src+offsetU;
+    uint8_t* srcV = src+offsetV;
+
+    for (unsigned r = 0; r < height; r++) {
+        // Note that we're walking the same U and V rows twice for even/odd luminance rows
+        uint8_t* rowY = srcY + r*strideLum;
+        uint8_t* rowU = srcU + (r/2 * strideColor);
+        uint8_t* rowV = srcV + (r/2 * strideColor);
+
+        uint32_t* rowDest = dst + r*dstStridePixels;
+
+        for (unsigned c = 0; c < width; c++) {
+            rowDest[c] = yuvToRgbx(rowY[c], rowU[c], rowV[c]);
+        }
+    }
+}
+
+
+void copyYUYVtoRGB32(unsigned width, unsigned height,
+                     uint8_t* src, unsigned srcStridePixels,
+                     uint32_t* dst, unsigned dstStridePixels)
+{
+    uint32_t* srcWords = (uint32_t*)src;
+
+    const int srcRowPadding32 = srcStridePixels/2 - width/2;  // 2 bytes per pixel, 4 bytes per word
+    const int dstRowPadding32 = dstStridePixels   - width;    // 4 bytes per pixel, 4 bytes per word
+
+    for (unsigned r = 0; r < height; r++) {
+        for (unsigned c = 0; c < width/2; c++) {
+            // Note:  we're walking two pixels at a time here (even/odd)
+            uint32_t srcPixel = *srcWords++;
+
+            uint8_t Y1 = (srcPixel)       & 0xFF;
+            uint8_t U  = (srcPixel >> 8)  & 0xFF;
+            uint8_t Y2 = (srcPixel >> 16) & 0xFF;
+            uint8_t V  = (srcPixel >> 24) & 0xFF;
+
+            // On the RGB output, we're writing one pixel at a time
+            *(dst+0) = yuvToRgbx(Y1, U, V);
+            *(dst+1) = yuvToRgbx(Y2, U, V);
+            dst += 2;
+        }
+
+        // Skip over any extra data or end of row alignment padding
+        srcWords += srcRowPadding32;
+        dst += dstRowPadding32;
+    }
+}
+
+
+void copyMatchedInterleavedFormats(unsigned width, unsigned height,
+                                   void* src, unsigned srcStridePixels,
+                                   void* dst, unsigned dstStridePixels,
+                                   unsigned pixelSize) {
+    for (unsigned row = 0; row < height; row++) {
+        // Copy the entire row of pixel data
+        memcpy(dst, src, width * pixelSize);
+
+        // Advance to the next row (keeping in mind that stride here is in units of pixels)
+        src = (uint8_t*)src + srcStridePixels * pixelSize;
+        dst = (uint8_t*)dst + dstStridePixels * pixelSize;
+    }
+}
+
+
+BufferDesc_1_1 convertBufferDesc(const BufferDesc_1_0& src) {
+    BufferDesc_1_1 dst = {};
+    AHardwareBuffer_Desc* pDesc =
+        reinterpret_cast<AHardwareBuffer_Desc *>(&dst.buffer.description);
+    pDesc->width  = src.width;
+    pDesc->height = src.height;
+    pDesc->layers = 1;
+    pDesc->format = src.format;
+    pDesc->usage  = static_cast<uint64_t>(src.usage);
+    pDesc->stride = src.stride;
+
+    dst.buffer.nativeHandle = src.memHandle;
+    dst.pixelSize = src.pixelSize;
+    dst.bufferId = src.bufferId;
+
+    return dst;
+}
diff --git a/evs/apps/default/FormatConvert.h b/evs/apps/default/FormatConvert.h
new file mode 100644
index 0000000..474ce6d
--- /dev/null
+++ b/evs/apps/default/FormatConvert.h
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef EVS_VTS_FORMATCONVERT_H
+#define EVS_VTS_FORMATCONVERT_H
+
+#include <queue>
+#include <stdint.h>
+
+#include <android/hardware/automotive/evs/1.0/types.h>
+#include <android/hardware/automotive/evs/1.1/types.h>
+
+using BufferDesc_1_0 = ::android::hardware::automotive::evs::V1_0::BufferDesc;
+using BufferDesc_1_1 = ::android::hardware::automotive::evs::V1_1::BufferDesc;
+
+
+// Given an image buffer in NV21 format (HAL_PIXEL_FORMAT_YCRCB_420_SP), output 32bit RGBx values.
+// The NV21 format provides a Y array of 8bit values, followed by a 1/2 x 1/2 interleaved
+// U/V array.  It assumes an even width and height for the overall image, and a horizontal
+// stride that is an even multiple of 16 bytes for both the Y and UV arrays.
+void copyNV21toRGB32(unsigned width, unsigned height,
+                     uint8_t* src,
+                     uint32_t* dst, unsigned dstStridePixels);
+
+
+// Given an image buffer in YV12 format (HAL_PIXEL_FORMAT_YV12), output 32bit RGBx values.
+// The YV12 format provides a Y array of 8bit values, followed by a 1/2 x 1/2 U array, followed
+// by another 1/2 x 1/2 V array.  It assumes an even width and height for the overall image,
+// and a horizontal stride that is an even multiple of 16 bytes for each of the Y, U,
+// and V arrays.
+void copyYV12toRGB32(unsigned width, unsigned height,
+                     uint8_t* src,
+                     uint32_t* dst, unsigned dstStridePixels);
+
+
+// Given an image buffer in YUYV format (HAL_PIXEL_FORMAT_YCBCR_422_I), output 32bit RGBx values.
+// The NV21 format provides a Y array of 8bit values, followed by a 1/2 x 1/2 interleaved
+// U/V array.  It assumes an even width and height for the overall image, and a horizontal
+// stride that is an even multiple of 16 bytes for both the Y and UV arrays.
+void copyYUYVtoRGB32(unsigned width, unsigned height,
+                     uint8_t* src, unsigned srcStrideBytes,
+                     uint32_t* dst, unsigned dstStrideBytes);
+
+
+// Given an simple rectangular image buffer with an integer number of bytes per pixel,
+// copy the pixel values into a new rectangular buffer (potentially with a different stride).
+// This is typically used to copy RGBx data into an RGBx output buffer.
+void copyMatchedInterleavedFormats(unsigned width, unsigned height,
+                                   void* src, unsigned srcStridePixels,
+                                   void* dst, unsigned dstStridePixels,
+                                   unsigned pixelSize);
+
+// Fill BufferDesc v1.1 with a given BufferDesc v1.0 data.
+BufferDesc_1_1 convertBufferDesc(const BufferDesc_1_0& src);
+
+#endif // EVS_VTS_FORMATCONVERT_H
diff --git a/evs/app/LabeledChecker.png b/evs/apps/default/LabeledChecker.png
similarity index 100%
rename from evs/app/LabeledChecker.png
rename to evs/apps/default/LabeledChecker.png
Binary files differ
diff --git a/evs/apps/default/RenderBase.cpp b/evs/apps/default/RenderBase.cpp
new file mode 100644
index 0000000..319fefe
--- /dev/null
+++ b/evs/apps/default/RenderBase.cpp
@@ -0,0 +1,219 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "RenderBase.h"
+#include "glError.h"
+
+#include <log/log.h>
+#include <ui/GraphicBuffer.h>
+
+// Eventually we shouldn't need this dependency, but for now the
+// graphics allocator interface isn't fully supported on all platforms
+// and this is our work around.
+using ::android::GraphicBuffer;
+
+
+// OpenGL state shared among all renderers
+EGLDisplay   RenderBase::sDisplay = EGL_NO_DISPLAY;
+EGLContext   RenderBase::sContext = EGL_NO_CONTEXT;
+EGLSurface   RenderBase::sDummySurface = EGL_NO_SURFACE;
+GLuint       RenderBase::sFrameBuffer = -1;
+GLuint       RenderBase::sColorBuffer = -1;
+GLuint       RenderBase::sDepthBuffer = -1;
+EGLImageKHR  RenderBase::sKHRimage = EGL_NO_IMAGE_KHR;
+unsigned     RenderBase::sWidth  = 0;
+unsigned     RenderBase::sHeight = 0;
+float        RenderBase::sAspectRatio = 0.0f;
+
+
+bool RenderBase::prepareGL() {
+    // Just trivially return success if we're already prepared
+    if (sDisplay != EGL_NO_DISPLAY) {
+        return true;
+    }
+
+    // Hardcoded to RGBx output display
+    const EGLint config_attribs[] = {
+        // Tag                  Value
+        EGL_RENDERABLE_TYPE,    EGL_OPENGL_ES2_BIT,
+        EGL_RED_SIZE,           8,
+        EGL_GREEN_SIZE,         8,
+        EGL_BLUE_SIZE,          8,
+        EGL_NONE
+    };
+
+    // Select OpenGL ES v 3
+    const EGLint context_attribs[] = {EGL_CONTEXT_CLIENT_VERSION, 3, EGL_NONE};
+
+
+    // Set up our OpenGL ES context associated with the default display (though we won't be visible)
+    EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
+    if (display == EGL_NO_DISPLAY) {
+        ALOGE("Failed to get egl display");
+        return false;
+    }
+
+    EGLint major = 0;
+    EGLint minor = 0;
+    if (!eglInitialize(display, &major, &minor)) {
+        ALOGE("Failed to initialize EGL: %s", getEGLError());
+        return false;
+    } else {
+        ALOGI("Intiialized EGL at %d.%d", major, minor);
+    }
+
+
+    // Select the configuration that "best" matches our desired characteristics
+    EGLConfig egl_config;
+    EGLint num_configs;
+    if (!eglChooseConfig(display, config_attribs, &egl_config, 1, &num_configs)) {
+        ALOGE("eglChooseConfig() failed with error: %s", getEGLError());
+        return false;
+    }
+
+
+    // Create a dummy pbuffer so we have a surface to bind -- we never intend to draw to this
+    // because attachRenderTarget will be called first.
+    EGLint surface_attribs[] = { EGL_WIDTH, 1, EGL_HEIGHT, 1, EGL_NONE };
+    sDummySurface = eglCreatePbufferSurface(display, egl_config, surface_attribs);
+    if (sDummySurface == EGL_NO_SURFACE) {
+        ALOGE("Failed to create OpenGL ES Dummy surface: %s", getEGLError());
+        return false;
+    } else {
+        ALOGI("Dummy surface looks good!  :)");
+    }
+
+
+    //
+    // Create the EGL context
+    //
+    EGLContext context = eglCreateContext(display, egl_config, EGL_NO_CONTEXT, context_attribs);
+    if (context == EGL_NO_CONTEXT) {
+        ALOGE("Failed to create OpenGL ES Context: %s", getEGLError());
+        return false;
+    }
+
+
+    // Activate our render target for drawing
+    if (!eglMakeCurrent(display, sDummySurface, sDummySurface, context)) {
+        ALOGE("Failed to make the OpenGL ES Context current: %s", getEGLError());
+        return false;
+    } else {
+        ALOGI("We made our context current!  :)");
+    }
+
+
+    // Report the extensions available on this implementation
+    const char* gl_extensions = (const char*) glGetString(GL_EXTENSIONS);
+    ALOGI("GL EXTENSIONS:\n  %s", gl_extensions);
+
+
+    // Reserve handles for the color and depth targets we'll be setting up
+    glGenRenderbuffers(1, &sColorBuffer);
+    glGenRenderbuffers(1, &sDepthBuffer);
+
+    // Set up the frame buffer object we can modify and use for off screen rendering
+    glGenFramebuffers(1, &sFrameBuffer);
+    glBindFramebuffer(GL_FRAMEBUFFER, sFrameBuffer);
+
+
+    // Now that we're assured success, store object handles we constructed
+    sDisplay = display;
+    sContext = context;
+
+    return true;
+}
+
+
+bool RenderBase::attachRenderTarget(const BufferDesc& tgtBuffer) {
+    const AHardwareBuffer_Desc* pDesc =
+        reinterpret_cast<const AHardwareBuffer_Desc *>(&tgtBuffer.buffer.description);
+    // Hardcoded to RGBx for now
+    if (pDesc->format != HAL_PIXEL_FORMAT_RGBA_8888) {
+        ALOGE("Unsupported target buffer format");
+        return false;
+    }
+
+    // create a GraphicBuffer from the existing handle
+    sp<GraphicBuffer> pGfxBuffer = new GraphicBuffer(tgtBuffer.buffer.nativeHandle,
+                                                     GraphicBuffer::CLONE_HANDLE,
+                                                     pDesc->width,
+                                                     pDesc->height,
+                                                     pDesc->format,
+                                                     pDesc->layers,
+                                                     GRALLOC_USAGE_HW_RENDER,
+                                                     pDesc->stride);
+    if (pGfxBuffer.get() == nullptr) {
+        ALOGE("Failed to allocate GraphicBuffer to wrap image handle");
+        return false;
+    }
+
+    // Get a GL compatible reference to the graphics buffer we've been given
+    EGLint eglImageAttributes[] = {EGL_IMAGE_PRESERVED_KHR, EGL_TRUE, EGL_NONE};
+    EGLClientBuffer clientBuf = static_cast<EGLClientBuffer>(pGfxBuffer->getNativeBuffer());
+    sKHRimage = eglCreateImageKHR(sDisplay, EGL_NO_CONTEXT,
+                                  EGL_NATIVE_BUFFER_ANDROID, clientBuf,
+                                  eglImageAttributes);
+    if (sKHRimage == EGL_NO_IMAGE_KHR) {
+        ALOGE("error creating EGLImage for target buffer: %s", getEGLError());
+        return false;
+    }
+
+    // Construct a render buffer around the external buffer
+    glBindRenderbuffer(GL_RENDERBUFFER, sColorBuffer);
+    glEGLImageTargetRenderbufferStorageOES(GL_RENDERBUFFER, static_cast<GLeglImageOES>(sKHRimage));
+    if (eglGetError() != EGL_SUCCESS) {
+        ALOGI("glEGLImageTargetRenderbufferStorageOES => %s", getEGLError());
+        return false;
+    }
+
+    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, sColorBuffer);
+    if (eglGetError() != EGL_SUCCESS) {
+        ALOGE("glFramebufferRenderbuffer => %s", getEGLError());
+        return false;
+    }
+
+    GLenum checkResult = glCheckFramebufferStatus(GL_FRAMEBUFFER);
+    if (checkResult != GL_FRAMEBUFFER_COMPLETE) {
+        ALOGE("Offscreen framebuffer not configured successfully (%d: %s)",
+              checkResult, getGLFramebufferError());
+        return false;
+    }
+
+    // Store the size of our target buffer
+    sWidth = pDesc->width;
+    sHeight = pDesc->height;
+    sAspectRatio = (float)sWidth / sHeight;
+
+    // Set the viewport
+    glViewport(0, 0, sWidth, sHeight);
+
+    // We don't actually need the clear if we're going to cover the whole screen anyway
+    // Clear the color buffer
+    glClearColor(0.8f, 0.1f, 0.2f, 1.0f);
+    glClear(GL_COLOR_BUFFER_BIT);
+
+    return true;
+}
+
+
+void RenderBase::detachRenderTarget() {
+    // Drop our external render target
+    if (sKHRimage != EGL_NO_IMAGE_KHR) {
+        eglDestroyImageKHR(sDisplay, sKHRimage);
+        sKHRimage = EGL_NO_IMAGE_KHR;
+    }
+}
diff --git a/evs/apps/default/RenderBase.h b/evs/apps/default/RenderBase.h
new file mode 100644
index 0000000..1226931
--- /dev/null
+++ b/evs/apps/default/RenderBase.h
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef CAR_EVS_APP_RENDERBASE_H
+#define CAR_EVS_APP_RENDERBASE_H
+
+#include <EGL/egl.h>
+#include <EGL/eglext.h>
+#include <GLES2/gl2.h>
+#include <GLES2/gl2ext.h>
+#include <GLES3/gl3.h>
+#include <GLES3/gl3ext.h>
+
+#include <android/hardware/automotive/evs/1.1/IEvsEnumerator.h>
+
+using namespace ::android::hardware::automotive::evs::V1_1;
+using ::android::sp;
+
+
+/*
+ * Abstract base class for the workhorse classes that handle the user interaction and display for
+ * each mode of the EVS application.
+ */
+class RenderBase {
+public:
+    virtual ~RenderBase() {};
+
+    virtual bool activate() = 0;
+    virtual void deactivate() = 0;
+
+    virtual bool drawFrame(const BufferDesc& tgtBuffer) = 0;
+
+protected:
+    static bool prepareGL();
+
+    static bool attachRenderTarget(const BufferDesc& tgtBuffer);
+    static void detachRenderTarget();
+
+    // OpenGL state shared among all renderers
+    static EGLDisplay   sDisplay;
+    static EGLContext   sContext;
+    static EGLSurface   sDummySurface;
+    static GLuint       sFrameBuffer;
+    static GLuint       sColorBuffer;
+    static GLuint       sDepthBuffer;
+
+    static EGLImageKHR  sKHRimage;
+
+    static unsigned     sWidth;
+    static unsigned     sHeight;
+    static float        sAspectRatio;
+};
+
+
+#endif //CAR_EVS_APP_RENDERBASE_H
diff --git a/evs/apps/default/RenderDirectView.cpp b/evs/apps/default/RenderDirectView.cpp
new file mode 100644
index 0000000..fd44851
--- /dev/null
+++ b/evs/apps/default/RenderDirectView.cpp
@@ -0,0 +1,205 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "RenderDirectView.h"
+#include "VideoTex.h"
+#include "glError.h"
+#include "shader.h"
+#include "shader_simpleTex.h"
+
+#include <log/log.h>
+#include <math/mat4.h>
+#include <system/camera_metadata.h>
+#include <android/hardware/camera/device/3.2/ICameraDevice.h>
+
+using ::android::hardware::camera::device::V3_2::Stream;
+using ::android::hardware::graphics::common::V1_0::PixelFormat;
+
+
+typedef struct {
+    int32_t id;
+    int32_t width;
+    int32_t height;
+    int32_t format;
+    int32_t direction;
+    int32_t framerate;
+} RawStreamConfig;
+
+const size_t kStreamCfgSz = sizeof(RawStreamConfig);
+
+
+RenderDirectView::RenderDirectView(sp<IEvsEnumerator> enumerator,
+                                   const CameraDesc& camDesc) :
+    mEnumerator(enumerator),
+    mCameraDesc(camDesc) {
+    /* Nothing to do */
+}
+
+
+bool RenderDirectView::activate() {
+    // Ensure GL is ready to go...
+    if (!prepareGL()) {
+        ALOGE("Error initializing GL");
+        return false;
+    }
+
+    // Load our shader program if we don't have it already
+    if (!mShaderProgram) {
+        mShaderProgram = buildShaderProgram(vtxShader_simpleTexture,
+                                            pixShader_simpleTexture,
+                                            "simpleTexture");
+        if (!mShaderProgram) {
+            ALOGE("Error buliding shader program");
+            return false;
+        }
+    }
+
+    bool foundCfg = false;
+    std::unique_ptr<Stream> targetCfg(new Stream());
+
+    if (!foundCfg) {
+        // This logic picks the first configuration in the list among them that
+        // support RGBA8888 format and its frame rate is faster than minReqFps.
+        const int32_t minReqFps = 15;
+        int32_t maxArea = 0;
+        camera_metadata_entry_t streamCfgs;
+        if (!find_camera_metadata_entry(
+                 reinterpret_cast<camera_metadata_t *>(mCameraDesc.metadata.data()),
+                 ANDROID_SCALER_AVAILABLE_STREAM_CONFIGURATIONS,
+                 &streamCfgs)) {
+            // Stream configurations are found in metadata
+            RawStreamConfig *ptr = reinterpret_cast<RawStreamConfig *>(streamCfgs.data.i32);
+            for (unsigned idx = 0; idx < streamCfgs.count; idx += kStreamCfgSz) {
+                if (ptr->direction == ANDROID_SCALER_AVAILABLE_STREAM_CONFIGURATIONS_OUTPUT &&
+                    ptr->format == HAL_PIXEL_FORMAT_RGBA_8888) {
+
+                    if (ptr->framerate >= minReqFps &&
+                        ptr->width * ptr->height > maxArea) {
+                        targetCfg->id = ptr->id;
+                        targetCfg->width = ptr->width;
+                        targetCfg->height = ptr->height;
+
+                        maxArea = ptr->width * ptr->height;
+
+                        foundCfg = true;
+                    }
+                }
+                ++ptr;
+            }
+        } else {
+            ALOGW("No stream configuration data is found; default parameters will be used.");
+        }
+    }
+
+    // This client always wants below input data format
+    targetCfg->format =
+        static_cast<PixelFormat>(HAL_PIXEL_FORMAT_RGBA_8888);
+
+    // Construct our video texture
+    mTexture.reset(createVideoTexture(mEnumerator,
+                                      mCameraDesc.v1.cameraId.c_str(),
+                                      foundCfg ? std::move(targetCfg) : nullptr,
+                                      sDisplay));
+    if (!mTexture) {
+        ALOGE("Failed to set up video texture for %s", mCameraDesc.v1.cameraId.c_str());
+// TODO:  For production use, we may actually want to fail in this case, but not yet...
+//       return false;
+    }
+
+    return true;
+}
+
+
+void RenderDirectView::deactivate() {
+    // Release our video texture
+    // We can't hold onto it because some other Render object might need the same camera
+    // TODO(b/131492626):  investigate whether sharing video textures can save
+    // the time.
+  mTexture = nullptr;
+}
+
+
+bool RenderDirectView::drawFrame(const BufferDesc& tgtBuffer) {
+    // Tell GL to render to the given buffer
+    if (!attachRenderTarget(tgtBuffer)) {
+        ALOGE("Failed to attached render target");
+        return false;
+    }
+
+    // Select our screen space simple texture shader
+    glUseProgram(mShaderProgram);
+
+    // Set up the model to clip space transform (identity matrix if we're modeling in screen space)
+    GLint loc = glGetUniformLocation(mShaderProgram, "cameraMat");
+    if (loc < 0) {
+        ALOGE("Couldn't set shader parameter 'cameraMat'");
+        return false;
+    } else {
+        const android::mat4 identityMatrix;
+        glUniformMatrix4fv(loc, 1, false, identityMatrix.asArray());
+    }
+
+
+    // Bind the texture and assign it to the shader's sampler
+    mTexture->refresh();
+    glActiveTexture(GL_TEXTURE0);
+    glBindTexture(GL_TEXTURE_2D, mTexture->glId());
+
+
+    GLint sampler = glGetUniformLocation(mShaderProgram, "tex");
+    if (sampler < 0) {
+        ALOGE("Couldn't set shader parameter 'tex'");
+        return false;
+    } else {
+        // Tell the sampler we looked up from the shader to use texture slot 0 as its source
+        glUniform1i(sampler, 0);
+    }
+
+    // We want our image to show up opaque regardless of alpha values
+    glDisable(GL_BLEND);
+
+
+    // Draw a rectangle on the screen
+    GLfloat vertsCarPos[] = { -1.0,  1.0, 0.0f,   // left top in window space
+                               1.0,  1.0, 0.0f,   // right top
+                              -1.0, -1.0, 0.0f,   // left bottom
+                               1.0, -1.0, 0.0f    // right bottom
+    };
+    // TODO:  We're flipping horizontally here, but should do it only for specified cameras!
+    GLfloat vertsCarTex[] = { 1.0f, 1.0f,   // left top
+                              0.0f, 1.0f,   // right top
+                              1.0f, 0.0f,   // left bottom
+                              0.0f, 0.0f    // right bottom
+    };
+    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, vertsCarPos);
+    glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 0, vertsCarTex);
+    glEnableVertexAttribArray(0);
+    glEnableVertexAttribArray(1);
+
+    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
+
+    glDisableVertexAttribArray(0);
+    glDisableVertexAttribArray(1);
+
+
+    // Now that everything is submitted, release our hold on the texture resource
+    detachRenderTarget();
+
+    // Wait for the rendering to finish
+    glFinish();
+    detachRenderTarget();
+    return true;
+}
diff --git a/evs/apps/default/RenderDirectView.h b/evs/apps/default/RenderDirectView.h
new file mode 100644
index 0000000..3514e05
--- /dev/null
+++ b/evs/apps/default/RenderDirectView.h
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef CAR_EVS_APP_RENDERDIRECTVIEW_H
+#define CAR_EVS_APP_RENDERDIRECTVIEW_H
+
+
+#include "RenderBase.h"
+
+#include <android/hardware/automotive/evs/1.1/IEvsEnumerator.h>
+#include "ConfigManager.h"
+#include "VideoTex.h"
+
+
+using namespace ::android::hardware::automotive::evs::V1_1;
+using ::android::hardware::camera::device::V3_2::Stream;
+
+
+/*
+ * Renders the view from a single specified camera directly to the full display.
+ */
+class RenderDirectView: public RenderBase {
+public:
+    RenderDirectView(sp<IEvsEnumerator> enumerator,
+                     const CameraDesc& camDesc);
+
+    virtual bool activate() override;
+    virtual void deactivate() override;
+
+    virtual bool drawFrame(const BufferDesc& tgtBuffer);
+
+protected:
+    sp<IEvsEnumerator>              mEnumerator;
+    ConfigManager::CameraInfo       mCameraInfo;
+    CameraDesc                      mCameraDesc;
+
+    std::unique_ptr<VideoTex>       mTexture;
+
+    GLuint                          mShaderProgram = 0;
+};
+
+
+#endif //CAR_EVS_APP_RENDERDIRECTVIEW_H
diff --git a/evs/apps/default/RenderPixelCopy.cpp b/evs/apps/default/RenderPixelCopy.cpp
new file mode 100644
index 0000000..ddfacb0
--- /dev/null
+++ b/evs/apps/default/RenderPixelCopy.cpp
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "RenderPixelCopy.h"
+#include "FormatConvert.h"
+
+#include <log/log.h>
+
+
+RenderPixelCopy::RenderPixelCopy(sp<IEvsEnumerator> enumerator,
+                                   const ConfigManager::CameraInfo& cam) {
+    mEnumerator = enumerator;
+    mCameraInfo = cam;
+}
+
+
+bool RenderPixelCopy::activate() {
+    // Set up the camera to feed this texture
+    sp<IEvsCamera> pCamera =
+        IEvsCamera::castFrom(mEnumerator->openCamera(mCameraInfo.cameraId.c_str()))
+        .withDefault(nullptr);
+
+    if (pCamera.get() == nullptr) {
+        ALOGE("Failed to allocate new EVS Camera interface");
+        return false;
+    }
+
+    // Initialize the stream that will help us update this texture's contents
+    sp<StreamHandler> pStreamHandler = new StreamHandler(pCamera);
+    if (pStreamHandler.get() == nullptr) {
+        ALOGE("failed to allocate FrameHandler");
+        return false;
+    }
+
+    // Start the video stream
+    if (!pStreamHandler->startStream()) {
+        ALOGE("start stream failed");
+        return false;
+    }
+
+    mStreamHandler = pStreamHandler;
+
+    return true;
+}
+
+
+void RenderPixelCopy::deactivate() {
+    mStreamHandler = nullptr;
+}
+
+
+bool RenderPixelCopy::drawFrame(const BufferDesc& tgtBuffer) {
+    bool success = true;
+    const AHardwareBuffer_Desc* pTgtDesc =
+        reinterpret_cast<const AHardwareBuffer_Desc *>(&tgtBuffer.buffer.description);
+
+    sp<android::GraphicBuffer> tgt = new android::GraphicBuffer(tgtBuffer.buffer.nativeHandle,
+                                                                android::GraphicBuffer::CLONE_HANDLE,
+                                                                pTgtDesc->width,
+                                                                pTgtDesc->height,
+                                                                pTgtDesc->format,
+                                                                pTgtDesc->layers,
+                                                                pTgtDesc->usage,
+                                                                pTgtDesc->stride);
+
+    // Lock our target buffer for writing (should be RGBA8888 format)
+    uint32_t* tgtPixels = nullptr;
+    tgt->lock(GRALLOC_USAGE_SW_WRITE_OFTEN, (void**)&tgtPixels);
+
+    if (tgtPixels) {
+        if (pTgtDesc->format != HAL_PIXEL_FORMAT_RGBA_8888) {
+            // We always expect 32 bit RGB for the display output for now.  Is there a need for 565?
+            ALOGE("Diplay buffer is always expected to be 32bit RGBA");
+            success = false;
+        } else {
+            // Make sure we have the latest frame data
+            if (mStreamHandler->newFrameAvailable()) {
+                const BufferDesc& srcBuffer = mStreamHandler->getNewFrame();
+                const AHardwareBuffer_Desc* pSrcDesc =
+                    reinterpret_cast<const AHardwareBuffer_Desc *>(&tgtBuffer.buffer.description);
+
+                // Lock our source buffer for reading (current expectation are for this to be NV21 format)
+                sp<android::GraphicBuffer> src = new android::GraphicBuffer(srcBuffer.buffer.nativeHandle,
+                                                                            android::GraphicBuffer::CLONE_HANDLE,
+                                                                            pSrcDesc->width,
+                                                                            pSrcDesc->height,
+                                                                            pSrcDesc->format,
+                                                                            pSrcDesc->layers,
+                                                                            pSrcDesc->usage,
+                                                                            pSrcDesc->stride);
+                unsigned char* srcPixels = nullptr;
+                src->lock(GRALLOC_USAGE_SW_READ_OFTEN, (void**)&srcPixels);
+                if (!srcPixels) {
+                    ALOGE("Failed to get pointer into src image data");
+                }
+
+                // Make sure we don't run off the end of either buffer
+                const unsigned width  = std::min(pTgtDesc->width,
+                                                 pSrcDesc->width);
+                const unsigned height = std::min(pTgtDesc->height,
+                                                 pSrcDesc->height);
+
+                if (pSrcDesc->format == HAL_PIXEL_FORMAT_YCRCB_420_SP) {   // 420SP == NV21
+                    copyNV21toRGB32(width, height,
+                                    srcPixels,
+                                    tgtPixels, pTgtDesc->stride);
+                } else if (pSrcDesc->format == HAL_PIXEL_FORMAT_YV12) { // YUV_420P == YV12
+                    copyYV12toRGB32(width, height,
+                                    srcPixels,
+                                    tgtPixels, pTgtDesc->stride);
+                } else if (pSrcDesc->format == HAL_PIXEL_FORMAT_YCBCR_422_I) { // YUYV
+                    copyYUYVtoRGB32(width, height,
+                                    srcPixels, pSrcDesc->stride,
+                                    tgtPixels, pTgtDesc->stride);
+                } else if (pSrcDesc->format == pTgtDesc->format) {  // 32bit RGBA
+                    copyMatchedInterleavedFormats(width, height,
+                                                  srcPixels, pSrcDesc->stride,
+                                                  tgtPixels, pTgtDesc->stride,
+                                                  tgtBuffer.pixelSize);
+                }
+
+                mStreamHandler->doneWithFrame(srcBuffer);
+            }
+        }
+    } else {
+        ALOGE("Failed to lock buffer contents for contents transfer");
+        success = false;
+    }
+
+    if (tgtPixels) {
+        tgt->unlock();
+    }
+
+    return success;
+}
diff --git a/evs/apps/default/RenderPixelCopy.h b/evs/apps/default/RenderPixelCopy.h
new file mode 100644
index 0000000..4673a36
--- /dev/null
+++ b/evs/apps/default/RenderPixelCopy.h
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef CAR_EVS_APP_RENDERPIXELCOPY_H
+#define CAR_EVS_APP_RENDERPIXELCOPY_H
+
+
+#include "RenderBase.h"
+
+#include <android/hardware/automotive/evs/1.1/IEvsEnumerator.h>
+#include "ConfigManager.h"
+#include "VideoTex.h"
+
+
+using namespace ::android::hardware::automotive::evs::V1_1;
+
+
+/*
+ * Renders the view from a single specified camera directly to the full display.
+ */
+class RenderPixelCopy: public RenderBase {
+public:
+    RenderPixelCopy(sp<IEvsEnumerator> enumerator, const ConfigManager::CameraInfo& cam);
+
+    virtual bool activate() override;
+    virtual void deactivate() override;
+
+    virtual bool drawFrame(const BufferDesc& tgtBuffer);
+
+protected:
+    sp<IEvsEnumerator>              mEnumerator;
+    ConfigManager::CameraInfo       mCameraInfo;
+
+    sp<StreamHandler>               mStreamHandler;
+};
+
+
+#endif //CAR_EVS_APP_RENDERPIXELCOPY_H
diff --git a/evs/apps/default/RenderTopView.cpp b/evs/apps/default/RenderTopView.cpp
new file mode 100644
index 0000000..7c82226
--- /dev/null
+++ b/evs/apps/default/RenderTopView.cpp
@@ -0,0 +1,349 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "RenderTopView.h"
+#include "VideoTex.h"
+#include "glError.h"
+#include "shader.h"
+#include "shader_simpleTex.h"
+#include "shader_projectedTex.h"
+
+#include <log/log.h>
+#include <math/mat4.h>
+#include <math/vec3.h>
+#include <android/hardware/camera/device/3.2/ICameraDevice.h>
+
+using ::android::hardware::camera::device::V3_2::Stream;
+
+
+// Simple aliases to make geometric math using vectors more readable
+static const unsigned X = 0;
+static const unsigned Y = 1;
+static const unsigned Z = 2;
+//static const unsigned W = 3;
+
+
+// Since we assume no roll in these views, we can simplify the required math
+static android::vec3 unitVectorFromPitchAndYaw(float pitch, float yaw) {
+    float sinPitch, cosPitch;
+    sincosf(pitch, &sinPitch, &cosPitch);
+    float sinYaw, cosYaw;
+    sincosf(yaw, &sinYaw, &cosYaw);
+    return android::vec3(cosPitch * -sinYaw,
+                         cosPitch * cosYaw,
+                         sinPitch);
+}
+
+
+// Helper function to set up a perspective matrix with independent horizontal and vertical
+// angles of view.
+static android::mat4 perspective(float hfov, float vfov, float near, float far) {
+    const float tanHalfFovX = tanf(hfov * 0.5f);
+    const float tanHalfFovY = tanf(vfov * 0.5f);
+
+    android::mat4 p(0.0f);
+    p[0][0] = 1.0f / tanHalfFovX;
+    p[1][1] = 1.0f / tanHalfFovY;
+    p[2][2] = - (far + near) / (far - near);
+    p[2][3] = -1.0f;
+    p[3][2] = - (2.0f * far * near) / (far - near);
+    return p;
+}
+
+
+// Helper function to set up a view matrix for a camera given it's yaw & pitch & location
+// Yes, with a bit of work, we could use lookAt, but it does a lot of extra work
+// internally that we can short cut.
+static android::mat4 cameraLookMatrix(const ConfigManager::CameraInfo& cam) {
+    float sinYaw, cosYaw;
+    sincosf(cam.yaw, &sinYaw, &cosYaw);
+
+    // Construct principal unit vectors
+    android::vec3 vAt = unitVectorFromPitchAndYaw(cam.pitch, cam.yaw);
+    android::vec3 vRt = android::vec3(cosYaw, sinYaw, 0.0f);
+    android::vec3 vUp = -cross(vAt, vRt);
+    android::vec3 eye = android::vec3(cam.position[X], cam.position[Y], cam.position[Z]);
+
+    android::mat4 Result(1.0f);
+    Result[0][0] = vRt.x;
+    Result[1][0] = vRt.y;
+    Result[2][0] = vRt.z;
+    Result[0][1] = vUp.x;
+    Result[1][1] = vUp.y;
+    Result[2][1] = vUp.z;
+    Result[0][2] =-vAt.x;
+    Result[1][2] =-vAt.y;
+    Result[2][2] =-vAt.z;
+    Result[3][0] =-dot(vRt, eye);
+    Result[3][1] =-dot(vUp, eye);
+    Result[3][2] = dot(vAt, eye);
+    return Result;
+}
+
+
+RenderTopView::RenderTopView(sp<IEvsEnumerator> enumerator,
+                             const std::vector<ConfigManager::CameraInfo>& camList,
+                             const ConfigManager& mConfig) :
+    mEnumerator(enumerator),
+    mConfig(mConfig) {
+
+    // Copy the list of cameras we're to employ into our local storage.  We'll create and
+    // associate a streaming video texture when we are activated.
+    mActiveCameras.reserve(camList.size());
+    for (unsigned i=0; i<camList.size(); i++) {
+        mActiveCameras.emplace_back(camList[i]);
+    }
+}
+
+
+bool RenderTopView::activate() {
+    // Ensure GL is ready to go...
+    if (!prepareGL()) {
+        ALOGE("Error initializing GL");
+        return false;
+    }
+
+    // Load our shader programs
+    mPgmAssets.simpleTexture = buildShaderProgram(vtxShader_simpleTexture,
+                                                 pixShader_simpleTexture,
+                                                 "simpleTexture");
+    if (!mPgmAssets.simpleTexture) {
+        ALOGE("Failed to build shader program");
+        return false;
+    }
+    mPgmAssets.projectedTexture = buildShaderProgram(vtxShader_projectedTexture,
+                                                    pixShader_projectedTexture,
+                                                    "projectedTexture");
+    if (!mPgmAssets.projectedTexture) {
+        ALOGE("Failed to build shader program");
+        return false;
+    }
+
+
+    // Load the checkerboard text image
+    mTexAssets.checkerBoard.reset(createTextureFromPng(
+                                  "/system/etc/automotive/evs/LabeledChecker.png"));
+    if (!mTexAssets.checkerBoard) {
+        ALOGE("Failed to load checkerboard texture");
+        return false;
+    }
+
+    // Load the car image
+    mTexAssets.carTopView.reset(createTextureFromPng(
+                                "/system/etc/automotive/evs/CarFromTop.png"));
+    if (!mTexAssets.carTopView) {
+        ALOGE("Failed to load carTopView texture");
+        return false;
+    }
+
+
+    // Set up streaming video textures for our associated cameras
+    for (auto&& cam: mActiveCameras) {
+        cam.tex.reset(createVideoTexture(mEnumerator,
+                                         cam.info.cameraId.c_str(),
+                                         nullptr,
+                                         sDisplay));
+        if (!cam.tex) {
+            ALOGE("Failed to set up video texture for %s (%s)",
+                  cam.info.cameraId.c_str(), cam.info.function.c_str());
+// TODO:  For production use, we may actually want to fail in this case, but not yet...
+//            return false;
+        }
+    }
+
+    return true;
+}
+
+
+void RenderTopView::deactivate() {
+    // Release our video textures
+    // We can't hold onto it because some other Render object might need the same camera
+    // TODO(b/131492626):  investigate whether sharing video textures can save
+    // the time.
+    for (auto&& cam: mActiveCameras) {
+        cam.tex = nullptr;
+    }
+}
+
+
+bool RenderTopView::drawFrame(const BufferDesc& tgtBuffer) {
+    // Tell GL to render to the given buffer
+    if (!attachRenderTarget(tgtBuffer)) {
+        ALOGE("Failed to attached render target");
+        return false;
+    }
+
+    // Set up our top down projection matrix from car space (world units, Xfwd, Yright, Zup)
+    // to view space (-1 to 1)
+    const float top    = mConfig.getDisplayTopLocation();
+    const float bottom = mConfig.getDisplayBottomLocation();
+    const float right  = mConfig.getDisplayRightLocation(sAspectRatio);
+    const float left   = mConfig.getDisplayLeftLocation(sAspectRatio);
+
+    const float near = 10.0f;   // arbitrary top of view volume
+    const float far = 0.0f;     // ground plane is at zero
+
+    // We can use a simple, unrotated ortho view since the screen and car space axis are
+    // naturally aligned in the top down view.
+    // TODO:  Not sure if flipping top/bottom here is "correct" or a double reverse...
+//    orthoMatrix = android::mat4::ortho(left, right, bottom, top, near, far);
+    orthoMatrix = android::mat4::ortho(left, right, top, bottom, near, far);
+
+
+    // Refresh our video texture contents.  We do it all at once in hopes of getting
+    // better coherence among images.  This does not guarantee synchronization, of course...
+    for (auto&& cam: mActiveCameras) {
+        if (cam.tex) {
+            cam.tex->refresh();
+        }
+    }
+
+    // Iterate over all the cameras and project their images onto the ground plane
+    for (auto&& cam: mActiveCameras) {
+        renderCameraOntoGroundPlane(cam);
+    }
+
+    // Draw the car image
+    renderCarTopView();
+
+    // Now that everythign is submitted, release our hold on the texture resource
+    detachRenderTarget();
+
+    // Wait for the rendering to finish
+    glFinish();
+    detachRenderTarget();
+    return true;
+}
+
+
+//
+// Responsible for drawing the car's self image in the top down view.
+// Draws in car model space (units of meters with origin at center of rear axel)
+// NOTE:  We probably want to eventually switch to using a VertexArray based model system.
+//
+void RenderTopView::renderCarTopView() {
+    // Compute the corners of our image footprint in car space
+    const float carLengthInTexels = mConfig.carGraphicRearPixel() - mConfig.carGraphicFrontPixel();
+    const float carSpaceUnitsPerTexel = mConfig.getCarLength() / carLengthInTexels;
+    const float textureHeightInCarSpace = mTexAssets.carTopView->height() * carSpaceUnitsPerTexel;
+    const float textureAspectRatio = (float)mTexAssets.carTopView->width() /
+                                            mTexAssets.carTopView->height();
+    const float pixelsBehindCarInImage = mTexAssets.carTopView->height() -
+                                         mConfig.carGraphicRearPixel();
+    const float textureExtentBehindCarInCarSpace = pixelsBehindCarInImage * carSpaceUnitsPerTexel;
+
+    const float btCS = mConfig.getRearLocation() - textureExtentBehindCarInCarSpace;
+    const float tpCS = textureHeightInCarSpace + btCS;
+    const float ltCS = 0.5f * textureHeightInCarSpace * textureAspectRatio;
+    const float rtCS = -ltCS;
+
+    GLfloat vertsCarPos[] = { ltCS, tpCS, 0.0f,   // left top in car space
+                              rtCS, tpCS, 0.0f,   // right top
+                              ltCS, btCS, 0.0f,   // left bottom
+                              rtCS, btCS, 0.0f    // right bottom
+    };
+    // NOTE:  We didn't flip the image in the texture, so V=0 is actually the top of the image
+    GLfloat vertsCarTex[] = { 0.0f, 0.0f,   // left top
+                              1.0f, 0.0f,   // right top
+                              0.0f, 1.0f,   // left bottom
+                              1.0f, 1.0f    // right bottom
+    };
+    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, vertsCarPos);
+    glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 0, vertsCarTex);
+    glEnableVertexAttribArray(0);
+    glEnableVertexAttribArray(1);
+
+
+    glEnable(GL_BLEND);
+    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+
+    glUseProgram(mPgmAssets.simpleTexture);
+    GLint loc = glGetUniformLocation(mPgmAssets.simpleTexture, "cameraMat");
+    glUniformMatrix4fv(loc, 1, false, orthoMatrix.asArray());
+    glBindTexture(GL_TEXTURE_2D, mTexAssets.carTopView->glId());
+
+    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
+
+
+    glDisable(GL_BLEND);
+
+    glDisableVertexAttribArray(0);
+    glDisableVertexAttribArray(1);
+}
+
+
+// NOTE:  Might be worth reviewing the ideas at
+// http://math.stackexchange.com/questions/1691895/inverse-of-perspective-matrix
+// to see if that simplifies the math, although we'll still want to compute the actual ground
+// interception points taking into account the pitchLimit as below.
+void RenderTopView::renderCameraOntoGroundPlane(const ActiveCamera& cam) {
+    // How far is the farthest any camera should even consider projecting it's image?
+    const float visibleSizeV = mConfig.getDisplayTopLocation() - mConfig.getDisplayBottomLocation();
+    const float visibleSizeH = visibleSizeV * sAspectRatio;
+    const float maxRange = (visibleSizeH > visibleSizeV) ? visibleSizeH : visibleSizeV;
+
+    // Construct the projection matrix (View + Projection) associated with this sensor
+    // TODO:  Consider just hard coding the far plane distance as it likely doesn't matter
+    const android::mat4 V = cameraLookMatrix(cam.info);
+    const android::mat4 P = perspective(cam.info.hfov, cam.info.vfov, cam.info.position[Z], maxRange);
+    const android::mat4 projectionMatix = P*V;
+
+    // Just draw the whole darn ground plane for now -- we're wasting fill rate, but so what?
+    // A 2x optimization would be to draw only the 1/2 space of the window in the direction
+    // the sensor is facing.  A more complex solution would be to construct the intersection
+    // of the sensor volume with the ground plane and render only that geometry.
+    const float top = mConfig.getDisplayTopLocation();
+    const float bottom = mConfig.getDisplayBottomLocation();
+    const float wsHeight = top - bottom;
+    const float wsWidth = wsHeight * sAspectRatio;
+    const float right =  wsWidth * 0.5f;
+    const float left = -right;
+
+    const android::vec3 topLeft(left, top, 0.0f);
+    const android::vec3 topRight(right, top, 0.0f);
+    const android::vec3 botLeft(left, bottom, 0.0f);
+    const android::vec3 botRight(right, bottom, 0.0f);
+
+    GLfloat vertsPos[] = { topLeft[X],  topLeft[Y],  topLeft[Z],
+                           topRight[X], topRight[Y], topRight[Z],
+                           botLeft[X],  botLeft[Y],  botLeft[Z],
+                           botRight[X], botRight[Y], botRight[Z],
+    };
+    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, vertsPos);
+    glEnableVertexAttribArray(0);
+
+
+    glDisable(GL_BLEND);
+
+    glUseProgram(mPgmAssets.projectedTexture);
+    GLint locCam = glGetUniformLocation(mPgmAssets.projectedTexture, "cameraMat");
+    glUniformMatrix4fv(locCam, 1, false, orthoMatrix.asArray());
+    GLint locProj = glGetUniformLocation(mPgmAssets.projectedTexture, "projectionMat");
+    glUniformMatrix4fv(locProj, 1, false, projectionMatix.asArray());
+
+    GLuint texId;
+    if (cam.tex) {
+        texId = cam.tex->glId();
+    } else {
+        texId = mTexAssets.checkerBoard->glId();
+    }
+    glBindTexture(GL_TEXTURE_2D, texId);
+
+    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
+
+
+    glDisableVertexAttribArray(0);
+}
diff --git a/evs/apps/default/RenderTopView.h b/evs/apps/default/RenderTopView.h
new file mode 100644
index 0000000..4d30a32
--- /dev/null
+++ b/evs/apps/default/RenderTopView.h
@@ -0,0 +1,76 @@
+
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef CAR_EVS_APP_RENDERTOPVIEW_H
+#define CAR_EVS_APP_RENDERTOPVIEW_H
+
+
+#include "RenderBase.h"
+
+#include <android/hardware/automotive/evs/1.1/IEvsEnumerator.h>
+#include "ConfigManager.h"
+#include "VideoTex.h"
+#include <math/mat4.h>
+
+
+using namespace ::android::hardware::automotive::evs::V1_1;
+
+
+/*
+ * Combines the views from all available cameras into one reprojected top down view.
+ */
+class RenderTopView: public RenderBase {
+public:
+    RenderTopView(sp<IEvsEnumerator> enumerator,
+                  const std::vector<ConfigManager::CameraInfo>& camList,
+                  const ConfigManager& config);
+
+    virtual bool activate() override;
+    virtual void deactivate() override;
+
+    virtual bool drawFrame(const BufferDesc& tgtBuffer);
+
+protected:
+    struct ActiveCamera {
+        const ConfigManager::CameraInfo&    info;
+        std::unique_ptr<VideoTex>           tex;
+
+        ActiveCamera(const ConfigManager::CameraInfo& c) : info(c) {};
+    };
+
+    void renderCarTopView();
+    void renderCameraOntoGroundPlane(const ActiveCamera& cam);
+
+    sp<IEvsEnumerator>              mEnumerator;
+    const ConfigManager&            mConfig;
+    std::vector<ActiveCamera>       mActiveCameras;
+
+    struct {
+        std::unique_ptr<TexWrapper> checkerBoard;
+        std::unique_ptr<TexWrapper> carTopView;
+    } mTexAssets;
+
+    struct {
+        GLuint simpleTexture;
+        GLuint projectedTexture;
+    } mPgmAssets;
+
+    android::mat4   orthoMatrix;
+};
+
+
+#endif //CAR_EVS_APP_RENDERTOPVIEW_H
diff --git a/evs/apps/default/StreamHandler.cpp b/evs/apps/default/StreamHandler.cpp
new file mode 100644
index 0000000..125d76a
--- /dev/null
+++ b/evs/apps/default/StreamHandler.cpp
@@ -0,0 +1,216 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "StreamHandler.h"
+
+#include <stdio.h>
+#include <string.h>
+
+#include <log/log.h>
+#include <cutils/native_handle.h>
+
+using ::android::hardware::automotive::evs::V1_0::EvsResult;
+
+
+StreamHandler::StreamHandler(android::sp <IEvsCamera> pCamera) :
+    mCamera(pCamera)
+{
+    // We rely on the camera having at least two buffers available since we'll hold one and
+    // expect the camera to be able to capture a new image in the background.
+    pCamera->setMaxFramesInFlight(2);
+}
+
+
+void StreamHandler::shutdown()
+{
+    // Make sure we're not still streaming
+    blockingStopStream();
+
+    // At this point, the receiver thread is no longer running, so we can safely drop
+    // our remote object references so they can be freed
+    mCamera = nullptr;
+}
+
+
+bool StreamHandler::startStream() {
+    std::unique_lock<std::mutex> lock(mLock);
+
+    if (!mRunning) {
+        // Tell the camera to start streaming
+        Return <EvsResult> result = mCamera->startVideoStream(this);
+        if (result != EvsResult::OK) {
+            return false;
+        }
+
+        // Mark ourselves as running
+        mRunning = true;
+    }
+
+    return true;
+}
+
+
+void StreamHandler::asyncStopStream() {
+    // Tell the camera to stop streaming.
+    // This will result in a null frame being delivered when the stream actually stops.
+    mCamera->stopVideoStream();
+}
+
+
+void StreamHandler::blockingStopStream() {
+    // Tell the stream to stop
+    asyncStopStream();
+
+    // Wait until the stream has actually stopped
+    std::unique_lock<std::mutex> lock(mLock);
+    if (mRunning) {
+        mSignal.wait(lock, [this]() { return !mRunning; });
+    }
+}
+
+
+bool StreamHandler::isRunning() {
+    std::unique_lock<std::mutex> lock(mLock);
+    return mRunning;
+}
+
+
+bool StreamHandler::newFrameAvailable() {
+    std::unique_lock<std::mutex> lock(mLock);
+    return (mReadyBuffer >= 0);
+}
+
+
+const BufferDesc_1_1& StreamHandler::getNewFrame() {
+    std::unique_lock<std::mutex> lock(mLock);
+
+    if (mHeldBuffer >= 0) {
+        ALOGE("Ignored call for new frame while still holding the old one.");
+    } else {
+        if (mReadyBuffer < 0) {
+            ALOGE("Returning invalid buffer because we don't have any.  Call newFrameAvailable first?");
+            mReadyBuffer = 0;   // This is a lie!
+        }
+
+        // Move the ready buffer into the held position, and clear the ready position
+        mHeldBuffer = mReadyBuffer;
+        mReadyBuffer = -1;
+    }
+
+    return mBuffers[mHeldBuffer];
+}
+
+
+void StreamHandler::doneWithFrame(const BufferDesc_1_1& bufDesc_1_1) {
+    std::unique_lock<std::mutex> lock(mLock);
+
+    // We better be getting back the buffer we original delivered!
+    if ((mHeldBuffer < 0) || (bufDesc_1_1.bufferId != mBuffers[mHeldBuffer].bufferId)) {
+        ALOGE("StreamHandler::doneWithFrame got an unexpected bufDesc_1_1!");
+    }
+
+    // Send the buffer back to the underlying camera
+    hidl_vec<BufferDesc_1_1> frames;
+    frames.resize(1);
+    frames[0] = mBuffers[mHeldBuffer];
+    mCamera->doneWithFrame_1_1(frames);
+
+    // Clear the held position
+    mHeldBuffer = -1;
+}
+
+
+Return<void> StreamHandler::deliverFrame(const BufferDesc_1_0& bufDesc_1_0) {
+    ALOGI("Ignores a frame delivered from v1.0 EVS service.");
+    mCamera->doneWithFrame(bufDesc_1_0);
+
+    return Void();
+}
+
+
+Return<void> StreamHandler::deliverFrame_1_1(const hidl_vec<BufferDesc_1_1>& buffers) {
+    ALOGD("Received frames from the camera");
+
+    // Take the lock to protect our frame slots and running state variable
+    std::unique_lock <std::mutex> lock(mLock);
+    BufferDesc_1_1 bufDesc = buffers[0];
+    if (bufDesc.buffer.nativeHandle.getNativeHandle() == nullptr) {
+        // Signal that the last frame has been received and the stream is stopped
+        ALOGW("Invalid null frame (id: 0x%X) is ignored", bufDesc.bufferId);
+    } else {
+        // Do we already have a "ready" frame?
+        if (mReadyBuffer >= 0) {
+            // Send the previously saved buffer back to the camera unused
+            hidl_vec<BufferDesc_1_1> frames;
+            frames.resize(1);
+            frames[0] = mBuffers[mReadyBuffer];
+            mCamera->doneWithFrame_1_1(frames);
+
+            // We'll reuse the same ready buffer index
+        } else if (mHeldBuffer >= 0) {
+            // The client is holding a buffer, so use the other slot for "on deck"
+            mReadyBuffer = 1 - mHeldBuffer;
+        } else {
+            // This is our first buffer, so just pick a slot
+            mReadyBuffer = 0;
+        }
+
+        // Save this frame until our client is interested in it
+        mBuffers[mReadyBuffer] = bufDesc;
+    }
+
+    // Notify anybody who cares that things have changed
+    lock.unlock();
+    mSignal.notify_all();
+
+    return Void();
+}
+
+
+Return<void> StreamHandler::notify(const EvsEventDesc& event) {
+    switch(event.aType) {
+        case EvsEventType::STREAM_STOPPED:
+        {
+            {
+                std::lock_guard<std::mutex> lock(mLock);
+
+                // Signal that the last frame has been received and the stream is stopped
+                mRunning = false;
+            }
+            ALOGI("Received a STREAM_STOPPED event");
+            break;
+        }
+
+        case EvsEventType::PARAMETER_CHANGED:
+            ALOGI("Camera parameter 0x%X is set to 0x%X", event.payload[0], event.payload[1]);
+            break;
+
+        // Below events are ignored in reference implementation.
+        case EvsEventType::STREAM_STARTED:
+        [[fallthrough]];
+        case EvsEventType::FRAME_DROPPED:
+        [[fallthrough]];
+        case EvsEventType::TIMEOUT:
+            ALOGI("Event 0x%X is received but ignored", event.aType);
+            break;
+        default:
+            ALOGE("Unknown event id 0x%X", event.aType);
+            break;
+    }
+
+    return Void();
+}
+
diff --git a/evs/apps/default/StreamHandler.h b/evs/apps/default/StreamHandler.h
new file mode 100644
index 0000000..cb7a288
--- /dev/null
+++ b/evs/apps/default/StreamHandler.h
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef EVS_VTS_STREAMHANDLER_H
+#define EVS_VTS_STREAMHANDLER_H
+
+#include <queue>
+
+#include "ui/GraphicBuffer.h"
+
+#include <android/hardware/automotive/evs/1.1/IEvsCameraStream.h>
+#include <android/hardware/automotive/evs/1.1/IEvsCamera.h>
+#include <android/hardware/automotive/evs/1.1/IEvsDisplay.h>
+
+using namespace ::android::hardware::automotive::evs::V1_1;
+using ::android::hardware::Return;
+using ::android::hardware::Void;
+using ::android::hardware::hidl_vec;
+using ::android::hardware::hidl_handle;
+using ::android::sp;
+using ::android::hardware::automotive::evs::V1_1::IEvsDisplay;
+using EvsDisplayState = ::android::hardware::automotive::evs::V1_0::DisplayState;
+using BufferDesc_1_0  = ::android::hardware::automotive::evs::V1_0::BufferDesc;
+using BufferDesc_1_1  = ::android::hardware::automotive::evs::V1_1::BufferDesc;
+
+
+/*
+ * StreamHandler:
+ * This class can be used to receive camera imagery from an IEvsCamera implementation.  It will
+ * hold onto the most recent image buffer, returning older ones.
+ * Note that the video frames are delivered on a background thread, while the control interface
+ * is actuated from the applications foreground thread.
+ */
+class StreamHandler : public IEvsCameraStream {
+public:
+    virtual ~StreamHandler() { shutdown(); };
+
+    StreamHandler(android::sp <IEvsCamera> pCamera);
+    void shutdown();
+
+    bool startStream();
+    void asyncStopStream();
+    void blockingStopStream();
+
+    bool isRunning();
+
+    bool newFrameAvailable();
+    const BufferDesc_1_1& getNewFrame();
+    void doneWithFrame(const BufferDesc_1_1& buffer);
+
+private:
+    // Implementation for ::android::hardware::automotive::evs::V1_0::IEvsCameraStream
+    Return<void> deliverFrame(const BufferDesc_1_0& buffer)  override;
+
+    // Implementation for ::android::hardware::automotive::evs::V1_1::IEvsCameraStream
+    Return<void> deliverFrame_1_1(const hidl_vec<BufferDesc_1_1>& buffer)  override;
+    Return<void> notify(const EvsEventDesc& event) override;
+
+    // Values initialized as startup
+    android::sp <IEvsCamera>    mCamera;
+
+    // Since we get frames delivered to us asnchronously via the ICarCameraStream interface,
+    // we need to protect all member variables that may be modified while we're streaming
+    // (ie: those below)
+    std::mutex                  mLock;
+    std::condition_variable     mSignal;
+
+    bool                        mRunning = false;
+
+    BufferDesc                  mBuffers[2];
+    int                         mHeldBuffer = -1;   // Index of the one currently held by the client
+    int                         mReadyBuffer = -1;  // Index of the newest available buffer
+};
+
+
+#endif //EVS_VTS_STREAMHANDLER_H
diff --git a/evs/app/TexWrapper.cpp b/evs/apps/default/TexWrapper.cpp
similarity index 100%
rename from evs/app/TexWrapper.cpp
rename to evs/apps/default/TexWrapper.cpp
diff --git a/evs/app/TexWrapper.h b/evs/apps/default/TexWrapper.h
similarity index 100%
rename from evs/app/TexWrapper.h
rename to evs/apps/default/TexWrapper.h
diff --git a/evs/apps/default/VideoTex.cpp b/evs/apps/default/VideoTex.cpp
new file mode 100644
index 0000000..e918a39
--- /dev/null
+++ b/evs/apps/default/VideoTex.cpp
@@ -0,0 +1,169 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include <vector>
+#include <stdio.h>
+#include <fcntl.h>
+#include <alloca.h>
+#include <unistd.h>
+#include <sys/ioctl.h>
+#include <malloc.h>
+#include <png.h>
+
+#include "VideoTex.h"
+#include "glError.h"
+
+#include <ui/GraphicBuffer.h>
+#include <android/hardware/camera/device/3.2/ICameraDevice.h>
+
+// Eventually we shouldn't need this dependency, but for now the
+// graphics allocator interface isn't fully supported on all platforms
+// and this is our work around.
+using ::android::GraphicBuffer;
+
+
+VideoTex::VideoTex(sp<IEvsEnumerator> pEnum,
+                   sp<IEvsCamera> pCamera,
+                   sp<StreamHandler> pStreamHandler,
+                   EGLDisplay glDisplay)
+    : TexWrapper()
+    , mEnumerator(pEnum)
+    , mCamera(pCamera)
+    , mStreamHandler(pStreamHandler)
+    , mDisplay(glDisplay) {
+    // Nothing but initialization here...
+}
+
+VideoTex::~VideoTex() {
+    // Tell the stream to stop flowing
+    mStreamHandler->asyncStopStream();
+
+    // Close the camera
+    mEnumerator->closeCamera(mCamera);
+
+    // Drop our device texture image
+    if (mKHRimage != EGL_NO_IMAGE_KHR) {
+        eglDestroyImageKHR(mDisplay, mKHRimage);
+        mKHRimage = EGL_NO_IMAGE_KHR;
+    }
+}
+
+
+// Return true if the texture contents are changed
+bool VideoTex::refresh() {
+    if (!mStreamHandler->newFrameAvailable()) {
+        // No new image has been delivered, so there's nothing to do here
+        return false;
+    }
+
+    // If we already have an image backing us, then it's time to return it
+    if (mImageBuffer.buffer.nativeHandle.getNativeHandle() != nullptr) {
+        // Drop our device texture image
+        if (mKHRimage != EGL_NO_IMAGE_KHR) {
+            eglDestroyImageKHR(mDisplay, mKHRimage);
+            mKHRimage = EGL_NO_IMAGE_KHR;
+        }
+
+        // Return it since we're done with it
+        mStreamHandler->doneWithFrame(mImageBuffer);
+    }
+
+    // Get the new image we want to use as our contents
+    mImageBuffer = mStreamHandler->getNewFrame();
+
+
+    // create a GraphicBuffer from the existing handle
+    const AHardwareBuffer_Desc* pDesc =
+        reinterpret_cast<const AHardwareBuffer_Desc *>(&mImageBuffer.buffer.description);
+    sp<GraphicBuffer> pGfxBuffer = new GraphicBuffer(mImageBuffer.buffer.nativeHandle,
+                                                     GraphicBuffer::CLONE_HANDLE,
+                                                     pDesc->width,
+                                                     pDesc->height,
+                                                     pDesc->format,
+                                                     1,//pDesc->layers,
+                                                     GRALLOC_USAGE_HW_TEXTURE,
+                                                     pDesc->stride);
+    if (pGfxBuffer.get() == nullptr) {
+        ALOGE("Failed to allocate GraphicBuffer to wrap image handle");
+        // Returning "true" in this error condition because we already released the
+        // previous image (if any) and so the texture may change in unpredictable ways now!
+        return true;
+    }
+
+    // Get a GL compatible reference to the graphics buffer we've been given
+    EGLint eglImageAttributes[] = {EGL_IMAGE_PRESERVED_KHR, EGL_TRUE, EGL_NONE};
+    EGLClientBuffer clientBuf = static_cast<EGLClientBuffer>(pGfxBuffer->getNativeBuffer());
+    mKHRimage = eglCreateImageKHR(mDisplay, EGL_NO_CONTEXT,
+                                  EGL_NATIVE_BUFFER_ANDROID, clientBuf,
+                                  eglImageAttributes);
+    if (mKHRimage == EGL_NO_IMAGE_KHR) {
+        const char *msg = getEGLError();
+        ALOGE("error creating EGLImage: %s", msg);
+    } else {
+        // Update the texture handle we already created to refer to this gralloc buffer
+        glActiveTexture(GL_TEXTURE0);
+        glBindTexture(GL_TEXTURE_2D, glId());
+        glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, static_cast<GLeglImageOES>(mKHRimage));
+
+        // Initialize the sampling properties (it seems the sample may not work if this isn't done)
+        // The user of this texture may very well want to set their own filtering, but we're going
+        // to pay the (minor) price of setting this up for them to avoid the dreaded "black image"
+        // if they forget.
+        // TODO:  Can we do this once for the texture ID rather than ever refresh?
+        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+    }
+
+    return true;
+}
+
+
+VideoTex* createVideoTexture(sp<IEvsEnumerator> pEnum,
+                             const char* evsCameraId,
+                             std::unique_ptr<Stream> streamCfg,
+                             EGLDisplay glDisplay) {
+    // Set up the camera to feed this texture
+    sp<IEvsCamera> pCamera = nullptr;
+    if (streamCfg != nullptr) {
+        pCamera = pEnum->openCamera_1_1(evsCameraId, *streamCfg);
+    } else {
+        pCamera =
+            IEvsCamera::castFrom(pEnum->openCamera(evsCameraId))
+            .withDefault(nullptr);
+    }
+
+    if (pCamera.get() == nullptr) {
+        ALOGE("Failed to allocate new EVS Camera interface for %s", evsCameraId);
+        return nullptr;
+    }
+
+    // Initialize the stream that will help us update this texture's contents
+    sp<StreamHandler> pStreamHandler = new StreamHandler(pCamera);
+    if (pStreamHandler.get() == nullptr) {
+        ALOGE("failed to allocate FrameHandler");
+        return nullptr;
+    }
+
+    // Start the video stream
+    if (!pStreamHandler->startStream()) {
+        printf("Couldn't start the camera stream (%s)\n", evsCameraId);
+        ALOGE("start stream failed for %s", evsCameraId);
+        return nullptr;
+    }
+
+    return new VideoTex(pEnum, pCamera, pStreamHandler, glDisplay);
+}
diff --git a/evs/apps/default/VideoTex.h b/evs/apps/default/VideoTex.h
new file mode 100644
index 0000000..00e9faa
--- /dev/null
+++ b/evs/apps/default/VideoTex.h
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#ifndef VIDEOTEX_H
+#define VIDEOTEX_H
+
+#include <EGL/egl.h>
+#include <EGL/eglext.h>
+#include <GLES2/gl2.h>
+#include <GLES2/gl2ext.h>
+#include <GLES3/gl3.h>
+#include <GLES3/gl3ext.h>
+
+#include <android/hardware/automotive/evs/1.1/IEvsEnumerator.h>
+#include <android/hardware/camera/device/3.2/ICameraDevice.h>
+
+#include "TexWrapper.h"
+#include "StreamHandler.h"
+
+using ::android::hardware::camera::device::V3_2::Stream;
+using namespace ::android::hardware::automotive::evs::V1_1;
+
+
+class VideoTex: public TexWrapper {
+    friend VideoTex* createVideoTexture(sp<IEvsEnumerator> pEnum,
+                                        const char *evsCameraId,
+                                        std::unique_ptr<Stream> streamCfg,
+                                        EGLDisplay glDisplay);
+
+public:
+    VideoTex() = delete;
+    virtual ~VideoTex();
+
+    bool refresh();     // returns true if the texture contents were updated
+
+private:
+    VideoTex(sp<IEvsEnumerator> pEnum,
+             sp<IEvsCamera> pCamera,
+             sp<StreamHandler> pStreamHandler,
+             EGLDisplay glDisplay);
+
+    sp<IEvsEnumerator>  mEnumerator;
+    sp<IEvsCamera>      mCamera;
+    sp<StreamHandler>   mStreamHandler;
+    BufferDesc          mImageBuffer;
+
+    EGLDisplay          mDisplay;
+    EGLImageKHR mKHRimage = EGL_NO_IMAGE_KHR;
+};
+
+
+VideoTex* createVideoTexture(sp<IEvsEnumerator> pEnum,
+                             const char * deviceName,
+                             std::unique_ptr<Stream> streamCfg,
+                             EGLDisplay glDisplay);
+
+#endif // VIDEOTEX_H
diff --git a/evs/apps/default/config.json b/evs/apps/default/config.json
new file mode 100644
index 0000000..1762e86
--- /dev/null
+++ b/evs/apps/default/config.json
@@ -0,0 +1,38 @@
+
+{
+  "car" : {
+    "width"  : 76.7,
+    "wheelBase" : 117.9,
+    "frontExtent" : 44.7,
+    "rearExtent" : 40
+  },
+  "displays" : [
+    {
+      "displayPort" : 1,
+      "frontRange" : 100,
+      "rearRange" : 100
+    },
+    {
+      "displayPort" : 2,
+      "frontRange" : 100,
+      "rearRange" : 100
+    }
+  ],
+  "graphic" : {
+    "frontPixel" : 23,
+    "rearPixel" : 223
+  },
+  "cameras" : [
+    {
+      "cameraId" : "/dev/video3",
+      "function" : "reverse, park",
+      "x" : 0.0,
+      "y" : -40.0,
+      "z" : 48,
+      "yaw" : 180,
+      "pitch" : -30,
+      "hfov" : 125,
+      "vfov" : 103
+    }
+  ]
+}
diff --git a/evs/apps/default/config.json.readme b/evs/apps/default/config.json.readme
new file mode 100644
index 0000000..7d1ec9d
--- /dev/null
+++ b/evs/apps/default/config.json.readme
@@ -0,0 +1,45 @@
+// With comments included, this file is no longer legal JSON, but serves to illustrate
+// the format of the configuration file the evs_app expects to read at startup to configure itself
+// for a specific car.
+// In addition to the configuration file, an image to be used to represent the car is expected
+// to be provided in CarFromTop.png.
+// Throughout this file, units of length are arbitrary, but must all be the same units.
+// X is right, Y is forward, Z is up (right handed coordinate system).
+// The origin is at the center of the read axel at ground level.
+// Units for angles are in degrees.
+// Yaw is measured from the front of the car, positive to the left (postive Z rotation).
+// Pitch is measured from the horizon, positive upward (postive X rotation).
+// Roll is always assumed to be zero.
+
+{
+  "car" : {                     // This section describes the geometry of the car
+    "width"  : 76.7,            // The width of the car body
+    "wheelBase" : 117.9,        // The distance between the front and read axel
+    "frontExtent" : 44.7,       // The extent of the car body ahead of the front axel
+    "rearExtent" : 40           // The extent of the car body behind the read axel
+  },
+  "displays" : [                // This configures the dimensions of the surround view display
+    {
+      "displayPort" : 1         // Display port number, the target display is connected to.
+      "frontRange" : 100,       // How far to render the view in front of the front bumper
+      "rearRange" : 100         // How far the view extends behind the rear bumper
+    }
+  ],
+  "graphic" : {                 // This maps the car texture into the projected view space
+    "frontPixel" : 23,          // The pixel row in CarFromTop.png at which the front bumper appears
+    "rearPixel" : 223           // The pixel row in CarFromTop.png at which the back bumper ends
+  },
+  "cameras" : [                 // This describes the cameras potentially available on the car
+    {
+      "cameraId" : "/dev/video32",  // Camera ID exposed by EVS HAL
+      "function" : "reverse,park",  // set of modes to which this camera contributes
+      "x" : 0.0,                    // Optical center distance right of vehicle center
+      "y" : -40.0,                  // Optical center distance forward of rear axel
+      "z" : 48,                     // Optical center distance above ground
+      "yaw" : 180,                  // Optical axis degrees to the left of straight ahead
+      "pitch" : -30,                // Optical axis degrees above the horizon
+      "hfov" : 125,                 // Horizontal field of view in degrees
+      "vfov" :103                   // Vertical field of view in degrees
+    }
+  ]
+}
diff --git a/evs/apps/default/evs_app.cpp b/evs/apps/default/evs_app.cpp
new file mode 100644
index 0000000..bc1b307
--- /dev/null
+++ b/evs/apps/default/evs_app.cpp
@@ -0,0 +1,175 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stdio.h>
+
+#include <hidl/HidlTransportSupport.h>
+#include <utils/Errors.h>
+#include <utils/StrongPointer.h>
+#include <utils/Log.h>
+
+#include "android-base/macros.h"    // arraysize
+
+#include <android/hardware/automotive/evs/1.1/IEvsEnumerator.h>
+#include <android/hardware/automotive/evs/1.1/IEvsDisplay.h>
+
+#include <hwbinder/ProcessState.h>
+
+#include "EvsStateControl.h"
+#include "EvsVehicleListener.h"
+#include "ConfigManager.h"
+
+// libhidl:
+using android::hardware::configureRpcThreadpool;
+using android::hardware::joinRpcThreadpool;
+
+
+// Helper to subscribe to VHal notifications
+static bool subscribeToVHal(sp<IVehicle> pVnet,
+                            sp<IVehicleCallback> listener,
+                            VehicleProperty propertyId) {
+    assert(pVnet != nullptr);
+    assert(listener != nullptr);
+
+    // Register for vehicle state change callbacks we care about
+    // Changes in these values are what will trigger a reconfiguration of the EVS pipeline
+    SubscribeOptions optionsData[] = {
+        {
+            .propId = static_cast<int32_t>(propertyId),
+            .flags  = SubscribeFlags::EVENTS_FROM_CAR
+        },
+    };
+    hidl_vec <SubscribeOptions> options;
+    options.setToExternal(optionsData, arraysize(optionsData));
+    StatusCode status = pVnet->subscribe(listener, options);
+    if (status != StatusCode::OK) {
+        ALOGW("VHAL subscription for property 0x%08X failed with code %d.", propertyId, status);
+        return false;
+    }
+
+    return true;
+}
+
+
+// Main entry point
+int main(int argc, char** argv)
+{
+    ALOGI("EVS app starting\n");
+
+    // Set up default behavior, then check for command line options
+    bool useVehicleHal = true;
+    bool printHelp = false;
+    const char* evsServiceName = "default";
+    int displayId = 0;
+    for (int i=1; i< argc; i++) {
+        if (strcmp(argv[i], "--test") == 0) {
+            useVehicleHal = false;
+        } else if (strcmp(argv[i], "--hw") == 0) {
+            evsServiceName = "EvsEnumeratorHw";
+        } else if (strcmp(argv[i], "--mock") == 0) {
+            evsServiceName = "EvsEnumeratorHw-Mock";
+        } else if (strcmp(argv[i], "--help") == 0) {
+            printHelp = true;
+        } else if (strcmp(argv[i], "--display") == 0) {
+            displayId = std::stoi(argv[++i]);
+        } else {
+            printf("Ignoring unrecognized command line arg '%s'\n", argv[i]);
+            printHelp = true;
+        }
+    }
+    if (printHelp) {
+        printf("Options include:\n");
+        printf("  --test   Do not talk to Vehicle Hal, but simulate 'reverse' instead\n");
+        printf("  --hw     Bypass EvsManager by connecting directly to EvsEnumeratorHw\n");
+        printf("  --mock   Connect directly to EvsEnumeratorHw-Mock\n");
+    }
+
+    // Load our configuration information
+    ConfigManager config;
+    if (!config.initialize("/system/etc/automotive/evs/config.json")) {
+        ALOGE("Missing or improper configuration for the EVS application.  Exiting.");
+        return 1;
+    }
+
+    // Set thread pool size to one to avoid concurrent events from the HAL.
+    // This pool will handle the EvsCameraStream callbacks.
+    // Note:  This _will_ run in parallel with the EvsListener run() loop below which
+    // runs the application logic that reacts to the async events.
+    configureRpcThreadpool(1, false /* callerWillJoin */);
+
+    // Construct our async helper object
+    sp<EvsVehicleListener> pEvsListener = new EvsVehicleListener();
+
+    // Get the EVS manager service
+    ALOGI("Acquiring EVS Enumerator");
+    android::sp<IEvsEnumerator> pEvs = IEvsEnumerator::getService(evsServiceName);
+    if (pEvs.get() == nullptr) {
+        ALOGE("getService(%s) returned NULL.  Exiting.", evsServiceName);
+        return 1;
+    }
+
+    // Request exclusive access to the EVS display
+    ALOGI("Acquiring EVS Display");
+
+    // We'll use an available display device.
+    android::sp<IEvsDisplay> pDisplay = pEvs->openDisplay_1_1(displayId);
+    if (pDisplay.get() == nullptr) {
+        ALOGE("EVS Display unavailable.  Exiting.");
+        return 1;
+    }
+    config.setActiveDisplayId(displayId);
+
+    // Connect to the Vehicle HAL so we can monitor state
+    sp<IVehicle> pVnet;
+    if (useVehicleHal) {
+        ALOGI("Connecting to Vehicle HAL");
+        pVnet = IVehicle::getService();
+        if (pVnet.get() == nullptr) {
+            ALOGE("Vehicle HAL getService returned NULL.  Exiting.");
+            return 1;
+        } else {
+            // Register for vehicle state change callbacks we care about
+            // Changes in these values are what will trigger a reconfiguration of the EVS pipeline
+            if (!subscribeToVHal(pVnet, pEvsListener, VehicleProperty::GEAR_SELECTION)) {
+                ALOGE("Without gear notification, we can't support EVS.  Exiting.");
+                return 1;
+            }
+            if (!subscribeToVHal(pVnet, pEvsListener, VehicleProperty::TURN_SIGNAL_STATE)) {
+                ALOGW("Didn't get turn signal notificaitons, so we'll ignore those.");
+            }
+        }
+    } else {
+        ALOGW("Test mode selected, so not talking to Vehicle HAL");
+    }
+
+    // Configure ourselves for the current vehicle state at startup
+    ALOGI("Constructing state controller");
+    EvsStateControl *pStateController = new EvsStateControl(pVnet, pEvs, pDisplay, config);
+    if (!pStateController->startUpdateLoop()) {
+        ALOGE("Initial configuration failed.  Exiting.");
+        return 1;
+    }
+
+    // Run forever, reacting to events as necessary
+    ALOGI("Entering running state");
+    pEvsListener->run(pStateController);
+
+    // In normal operation, we expect to run forever, but in some error conditions we'll quit.
+    // One known example is if another process preempts our registration for our service name.
+    ALOGE("EVS Listener stopped.  Exiting.");
+
+    return 0;
+}
diff --git a/evs/app/evs_app.rc b/evs/apps/default/evs_app.rc
similarity index 100%
rename from evs/app/evs_app.rc
rename to evs/apps/default/evs_app.rc
diff --git a/evs/app/glError.cpp b/evs/apps/default/glError.cpp
similarity index 100%
rename from evs/app/glError.cpp
rename to evs/apps/default/glError.cpp
diff --git a/evs/app/glError.h b/evs/apps/default/glError.h
similarity index 100%
rename from evs/app/glError.h
rename to evs/apps/default/glError.h
diff --git a/evs/app/shader.cpp b/evs/apps/default/shader.cpp
similarity index 100%
rename from evs/app/shader.cpp
rename to evs/apps/default/shader.cpp
diff --git a/evs/app/shader.h b/evs/apps/default/shader.h
similarity index 100%
rename from evs/app/shader.h
rename to evs/apps/default/shader.h
diff --git a/evs/app/shader_projectedTex.h b/evs/apps/default/shader_projectedTex.h
similarity index 100%
rename from evs/app/shader_projectedTex.h
rename to evs/apps/default/shader_projectedTex.h
diff --git a/evs/app/shader_simpleTex.h b/evs/apps/default/shader_simpleTex.h
similarity index 100%
rename from evs/app/shader_simpleTex.h
rename to evs/apps/default/shader_simpleTex.h
diff --git a/evs/apps/demo_app_evs_support_lib/Android.bp b/evs/apps/demo_app_evs_support_lib/Android.bp
new file mode 100644
index 0000000..5380a4a
--- /dev/null
+++ b/evs/apps/demo_app_evs_support_lib/Android.bp
@@ -0,0 +1,49 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//
+
+//#################################
+cc_binary {
+    name: "evs_app_support_lib",
+
+    srcs: ["evs_app_support_lib.cpp"],
+
+    shared_libs: [
+        "liblog",
+        "libui",
+        "android.hardware.automotive.evs@1.0",
+        "android.hardware.automotive.vehicle@2.0",
+        "libevssupport",
+    ],
+
+    include_dirs: ["packages/services/Car/evs/support_library"],
+
+    strip: {
+        keep_symbols: true,
+    },
+
+    init_rc: ["evs_app_support_lib.rc"],
+
+    cflags: ["-DLOG_TAG=\"EvsAppSupportLib\""] + [
+        "-DGL_GLEXT_PROTOTYPES",
+        "-DEGL_EGLEXT_PROTOTYPES",
+    ] + [
+        "-Wall",
+        "-Werror",
+        "-Wunused",
+        "-Wunreachable-code",
+    ],
+
+}
diff --git a/evs/apps/demo_app_evs_support_lib/evs_app_support_lib.cpp b/evs/apps/demo_app_evs_support_lib/evs_app_support_lib.cpp
new file mode 100644
index 0000000..8799bd2
--- /dev/null
+++ b/evs/apps/demo_app_evs_support_lib/evs_app_support_lib.cpp
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stdio.h>
+#include <utils/SystemClock.h>
+#include <string>
+#include <log/log.h>
+
+#include <DisplayUseCase.h>
+#include <AnalyzeUseCase.h>
+#include <Utils.h>
+
+using namespace ::android::automotive::evs::support;
+
+class SimpleRenderCallback : public BaseRenderCallback {
+    void render(const Frame& inputFrame, const Frame& outputFrame) {
+        ALOGI("SimpleRenderCallback::render");
+
+        if (inputFrame.data == nullptr || outputFrame.data == nullptr) {
+            ALOGE("Invalid frame data was passed to render callback");
+            return;
+        }
+
+        // TODO(b/130246434): Use OpenCV to implement a more meaningful
+        // callback.
+        // Swap the RGB channels.
+        int stride = inputFrame.stride;
+        uint8_t* inDataPtr = inputFrame.data;
+        uint8_t* outDataPtr = outputFrame.data;
+        for (int i = 0; i < inputFrame.width; i++)
+            for (int j = 0; j < inputFrame.height; j++) {
+                outDataPtr[(i + j * stride) * 4 + 0] =
+                    inDataPtr[(i + j * stride) * 4 + 1];
+                outDataPtr[(i + j * stride) * 4 + 1] =
+                    inDataPtr[(i + j * stride) * 4 + 2];
+                outDataPtr[(i + j * stride) * 4 + 2] =
+                    inDataPtr[(i + j * stride) * 4 + 0];
+                outDataPtr[(i + j * stride) * 4 + 3] =
+                    inDataPtr[(i + j * stride) * 4 + 3];
+            }
+    }
+};
+
+class SimpleAnalyzeCallback : public BaseAnalyzeCallback {
+    void analyze(const Frame &frame) {
+        ALOGD("SimpleAnalyzeCallback::analyze");
+        if (frame.data == nullptr) {
+            ALOGE("Invalid frame data was passed to analyze callback");
+            return;
+        }
+
+        // TODO(b/130246434): Now we just put a one second delay as a place
+        // holder. Replace it with an actual complicated enough algorithm.
+
+        ALOGD("SimpleAnalyzerCallback: sleep for one second");
+        std::this_thread::sleep_for(std::chrono::seconds(1));
+    };
+};
+
+// Main entry point
+int main() {
+    ALOGI("EVS app starting\n");
+
+    // Get the default rear view camera from evs support lib
+    std::string cameraId = Utils::getDefaultRearViewCameraId();
+    if (cameraId.empty()) {
+        ALOGE("Cannot find a valid camera");
+        return EXIT_FAILURE;
+    }
+
+    DisplayUseCase displayUseCase =
+        DisplayUseCase::createDefaultUseCase(cameraId, new SimpleRenderCallback());
+
+    AnalyzeUseCase analyzeUseCase =
+        AnalyzeUseCase::createDefaultUseCase(cameraId, new SimpleAnalyzeCallback());
+
+    // Run both DisplayUseCase and AnalyzeUseCase together for 10 seconds.
+    if (displayUseCase.startVideoStream()
+        && analyzeUseCase.startVideoStream()) {
+
+        std::this_thread::sleep_for(std::chrono::seconds(10));
+
+        displayUseCase.stopVideoStream();
+        analyzeUseCase.stopVideoStream();
+    }
+
+    // Run only AnalyzeUseCase for 10 seconds. The display control is back to
+    // Android framework but the camera is still occupied by AnalyzeUseCase in
+    // the background.
+    if (analyzeUseCase.startVideoStream()) {
+        std::this_thread::sleep_for(std::chrono::seconds(10));
+        analyzeUseCase.stopVideoStream();
+    }
+
+    return 0;
+}
diff --git a/evs/apps/demo_app_evs_support_lib/evs_app_support_lib.rc b/evs/apps/demo_app_evs_support_lib/evs_app_support_lib.rc
new file mode 100644
index 0000000..21f5961
--- /dev/null
+++ b/evs/apps/demo_app_evs_support_lib/evs_app_support_lib.rc
@@ -0,0 +1,6 @@
+service evs_app_support_lib /system/bin/evs_app_support_lib
+    class hal
+    priority -20
+    user automotive_evs
+    group automotive_evs
+    disabled # will not automatically start with its class; must be explictly started.
diff --git a/evs/manager/1.0/Android.bp b/evs/manager/1.0/Android.bp
new file mode 100644
index 0000000..260e0b7
--- /dev/null
+++ b/evs/manager/1.0/Android.bp
@@ -0,0 +1,56 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//
+
+
+//#################################
+cc_binary {
+    name: "android.automotive.evs.manager@1.0",
+
+    srcs: [
+        "service.cpp",
+        "Enumerator.cpp",
+        "HalCamera.cpp",
+        "VirtualCamera.cpp",
+        "HalDisplay.cpp",
+    ],
+
+    shared_libs: [
+        "libcutils",
+        "liblog",
+        "libutils",
+        "libui",
+        "libhidlbase",
+        "libhardware",
+        "android.hardware.automotive.evs@1.0",
+    ],
+
+    init_rc: ["android.automotive.evs.manager@1.0.rc"],
+
+    strip: {
+        keep_symbols: true,
+    },
+
+    cflags: ["-DLOG_TAG=\"EvsManagerV1_0\""] + [
+        "-DGL_GLEXT_PROTOTYPES",
+        "-DEGL_EGLEXT_PROTOTYPES",
+    ] + [
+        "-Wall",
+        "-Werror",
+        "-Wunused",
+        "-Wunreachable-code",
+    ],
+
+}
diff --git a/evs/manager/Enumerator.cpp b/evs/manager/1.0/Enumerator.cpp
similarity index 100%
rename from evs/manager/Enumerator.cpp
rename to evs/manager/1.0/Enumerator.cpp
diff --git a/evs/manager/Enumerator.h b/evs/manager/1.0/Enumerator.h
similarity index 100%
rename from evs/manager/Enumerator.h
rename to evs/manager/1.0/Enumerator.h
diff --git a/evs/manager/HalCamera.cpp b/evs/manager/1.0/HalCamera.cpp
similarity index 100%
rename from evs/manager/HalCamera.cpp
rename to evs/manager/1.0/HalCamera.cpp
diff --git a/evs/manager/HalCamera.h b/evs/manager/1.0/HalCamera.h
similarity index 100%
rename from evs/manager/HalCamera.h
rename to evs/manager/1.0/HalCamera.h
diff --git a/evs/manager/HalDisplay.cpp b/evs/manager/1.0/HalDisplay.cpp
similarity index 100%
rename from evs/manager/HalDisplay.cpp
rename to evs/manager/1.0/HalDisplay.cpp
diff --git a/evs/manager/HalDisplay.h b/evs/manager/1.0/HalDisplay.h
similarity index 100%
rename from evs/manager/HalDisplay.h
rename to evs/manager/1.0/HalDisplay.h
diff --git a/evs/manager/1.0/ServiceNames.h b/evs/manager/1.0/ServiceNames.h
new file mode 100644
index 0000000..bb03298
--- /dev/null
+++ b/evs/manager/1.0/ServiceNames.h
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+// This is the name as which we'll register ourselves
+const static char kManagedEnumeratorName[] = "legacy_sw/0";
+
+// This is the name of the hardware provider to which we'll bind by default
+const static char kHardwareEnumeratorName[]  = "legacy_hw/0";
+
+// This is the name of the mock hardware provider selectable via command line.
+// (should match .../hardware/interfaces/automotive/evs/1.0/default/ServiceNames.h)
+const static char kMockEnumeratorName[]  = "EvsEnumeratorHw-Mock";
+
diff --git a/evs/manager/VirtualCamera.cpp b/evs/manager/1.0/VirtualCamera.cpp
similarity index 100%
rename from evs/manager/VirtualCamera.cpp
rename to evs/manager/1.0/VirtualCamera.cpp
diff --git a/evs/manager/VirtualCamera.h b/evs/manager/1.0/VirtualCamera.h
similarity index 100%
rename from evs/manager/VirtualCamera.h
rename to evs/manager/1.0/VirtualCamera.h
diff --git a/evs/manager/1.0/android.automotive.evs.manager@1.0.rc b/evs/manager/1.0/android.automotive.evs.manager@1.0.rc
new file mode 100644
index 0000000..fac4138
--- /dev/null
+++ b/evs/manager/1.0/android.automotive.evs.manager@1.0.rc
@@ -0,0 +1,7 @@
+service evs_manager_v1_0 /system/bin/android.automotive.evs.manager@1.0
+    class hal
+    priority -20
+    user automotive_evs
+    group automotive_evs
+    onrestart restart evs_app
+    disabled # will not automatically start with its class; must be explictly started.
diff --git a/evs/manager/service.cpp b/evs/manager/1.0/service.cpp
similarity index 100%
rename from evs/manager/service.cpp
rename to evs/manager/1.0/service.cpp
diff --git a/evs/manager/1.1/Android.bp b/evs/manager/1.1/Android.bp
new file mode 100644
index 0000000..41ccce7
--- /dev/null
+++ b/evs/manager/1.1/Android.bp
@@ -0,0 +1,66 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//
+
+
+//#################################
+cc_binary {
+    name: "android.automotive.evs.manager@1.1",
+
+    srcs: [
+        "service.cpp",
+        "Enumerator.cpp",
+        "HalCamera.cpp",
+        "VirtualCamera.cpp",
+        "HalDisplay.cpp",
+        "sync/unique_fd.cpp",
+        "sync/unique_fence.cpp",
+        "sync/unique_timeline.cpp",
+    ],
+
+    shared_libs: [
+        "libcutils",
+        "liblog",
+        "libutils",
+        "libui",
+        "libsync",
+        "libhidlbase",
+        "libhardware",
+        "libcamera_metadata",
+        "android.hardware.automotive.evs@1.0",
+        "android.hardware.automotive.evs@1.1",
+    ],
+
+    init_rc: ["android.automotive.evs.manager@1.1.rc"],
+
+    strip: {
+        keep_symbols: true,
+    },
+
+    cflags: ["-DLOG_TAG=\"EvsManagerV1_1\""] + [
+        "-DGL_GLEXT_PROTOTYPES",
+        "-DEGL_EGLEXT_PROTOTYPES",
+        "-D_LIBCPP_ENABLE_THREAD_SAFETY_ANNOTATIONS",
+        "-Wall",
+        "-Werror",
+        "-Wunused",
+        "-Wunreachable-code",
+        "-Wthread-safety",
+    ],
+
+    include_dirs: [
+        "system/core/libsync",
+    ],
+}
diff --git a/evs/manager/1.1/Enumerator.cpp b/evs/manager/1.1/Enumerator.cpp
new file mode 100644
index 0000000..5c368cf
--- /dev/null
+++ b/evs/manager/1.1/Enumerator.cpp
@@ -0,0 +1,471 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <hwbinder/IPCThreadState.h>
+#include <cutils/android_filesystem_config.h>
+
+#include "Enumerator.h"
+#include "HalDisplay.h"
+
+namespace android {
+namespace automotive {
+namespace evs {
+namespace V1_1 {
+namespace implementation {
+
+using CameraDesc_1_0 = ::android::hardware::automotive::evs::V1_0::CameraDesc;
+using CameraDesc_1_1 = ::android::hardware::automotive::evs::V1_1::CameraDesc;
+
+bool Enumerator::init(const char* hardwareServiceName) {
+    ALOGD("init");
+
+    // Connect with the underlying hardware enumerator
+    mHwEnumerator = IEvsEnumerator::getService(hardwareServiceName);
+    bool result = (mHwEnumerator.get() != nullptr);
+
+    return result;
+}
+
+
+bool Enumerator::checkPermission() {
+    hardware::IPCThreadState *ipc = hardware::IPCThreadState::self();
+    if (AID_AUTOMOTIVE_EVS != ipc->getCallingUid() &&
+        AID_ROOT != ipc->getCallingUid()) {
+
+        ALOGE("EVS access denied?: pid = %d, uid = %d", ipc->getCallingPid(), ipc->getCallingUid());
+        return false;
+    }
+
+    return true;
+}
+
+
+bool Enumerator::isLogicalCamera(const camera_metadata_t *metadata) {
+    bool found = false;
+
+    if (metadata == nullptr) {
+        ALOGE("Metadata is null");
+        return found;
+    }
+
+    camera_metadata_ro_entry_t entry;
+    int rc = find_camera_metadata_ro_entry(metadata,
+                                           ANDROID_REQUEST_AVAILABLE_CAPABILITIES,
+                                           &entry);
+    if (0 != rc) {
+        // No capabilities are found in metadata.
+        ALOGD("%s does not find a target entry", __FUNCTION__);
+        return found;
+    }
+
+    for (size_t i = 0; i < entry.count; ++i) {
+        uint8_t capability = entry.data.u8[i];
+        if (capability == ANDROID_REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA) {
+            found = true;
+            break;
+        }
+    }
+
+    ALOGE_IF(!found, "%s does not find a logical multi camera cap", __FUNCTION__);
+    return found;
+}
+
+
+std::unordered_set<std::string> Enumerator::getPhysicalCameraIds(const std::string& id) {
+    std::unordered_set<std::string> physicalCameras;
+    if (mCameraDevices.find(id) == mCameraDevices.end()) {
+        ALOGE("Queried device %s does not exist!", id.c_str());
+        return physicalCameras;
+    }
+
+    const camera_metadata_t *metadata =
+        reinterpret_cast<camera_metadata_t *>(&mCameraDevices[id].metadata[0]);
+    if (!isLogicalCamera(metadata)) {
+        // EVS assumes that the device w/o a valid metadata is a physical
+        // device.
+        ALOGI("%s is not a logical camera device.", id.c_str());
+        physicalCameras.emplace(id);
+        return physicalCameras;
+    }
+
+    camera_metadata_ro_entry entry;
+    int rc = find_camera_metadata_ro_entry(metadata,
+                                           ANDROID_LOGICAL_MULTI_CAMERA_PHYSICAL_IDS,
+                                           &entry);
+    if (0 != rc) {
+        ALOGE("No physical camera ID is found for a logical camera device %s!", id.c_str());
+        return physicalCameras;
+    }
+
+    const uint8_t *ids = entry.data.u8;
+    size_t start = 0;
+    for (size_t i = 0; i < entry.count; ++i) {
+        if (ids[i] == '\0') {
+            if (start != i) {
+                std::string id(reinterpret_cast<const char *>(ids + start));
+                physicalCameras.emplace(id);
+            }
+            start = i + 1;
+        }
+    }
+
+    ALOGE("%s consists of %d physical camera devices.", id.c_str(), (int)physicalCameras.size());
+    return physicalCameras;
+}
+
+
+// Methods from ::android::hardware::automotive::evs::V1_0::IEvsEnumerator follow.
+Return<void> Enumerator::getCameraList(getCameraList_cb list_cb)  {
+    hardware::hidl_vec<CameraDesc_1_0> cameraList;
+    mHwEnumerator->getCameraList_1_1([&cameraList](auto cameraList_1_1) {
+        cameraList.resize(cameraList_1_1.size());
+        unsigned i = 0;
+        for (auto&& cam : cameraList_1_1) {
+            cameraList[i++] = cam.v1;
+        }
+    });
+
+    list_cb(cameraList);
+
+    return Void();
+}
+
+
+Return<sp<IEvsCamera_1_0>> Enumerator::openCamera(const hidl_string& cameraId) {
+    ALOGD("openCamera");
+    if (!checkPermission()) {
+        return nullptr;
+    }
+
+    // Is the underlying hardware camera already open?
+    sp<HalCamera> hwCamera;
+    if (mActiveCameras.find(cameraId) != mActiveCameras.end()) {
+        hwCamera = mActiveCameras[cameraId];
+    } else {
+        // Is the hardware camera available?
+        sp<IEvsCamera_1_1> device =
+            IEvsCamera_1_1::castFrom(mHwEnumerator->openCamera(cameraId))
+            .withDefault(nullptr);
+        if (device == nullptr) {
+            ALOGE("Failed to open hardware camera %s", cameraId.c_str());
+        } else {
+            hwCamera = new HalCamera(device, cameraId);
+            if (hwCamera == nullptr) {
+                ALOGE("Failed to allocate camera wrapper object");
+                mHwEnumerator->closeCamera(device);
+            }
+        }
+    }
+
+    // Construct a virtual camera wrapper for this hardware camera
+    sp<VirtualCamera> clientCamera;
+    if (hwCamera != nullptr) {
+        clientCamera = hwCamera->makeVirtualCamera();
+    }
+
+    // Add the hardware camera to our list, which will keep it alive via ref count
+    if (clientCamera != nullptr) {
+        mActiveCameras.try_emplace(cameraId, hwCamera);
+    } else {
+        ALOGE("Requested camera %s not found or not available", cameraId.c_str());
+    }
+
+    // Send the virtual camera object back to the client by strong pointer which will keep it alive
+    return clientCamera;
+}
+
+
+Return<void> Enumerator::closeCamera(const ::android::sp<IEvsCamera_1_0>& clientCamera) {
+    ALOGD("closeCamera");
+
+    if (clientCamera.get() == nullptr) {
+        ALOGE("Ignoring call with null camera pointer.");
+        return Void();
+    }
+
+    // All our client cameras are actually VirtualCamera objects
+    sp<VirtualCamera> virtualCamera = reinterpret_cast<VirtualCamera *>(clientCamera.get());
+
+    // Find the parent camera that backs this virtual camera
+    for (auto&& halCamera : virtualCamera->getHalCameras()) {
+        // Tell the virtual camera's parent to clean it up and drop it
+        // NOTE:  The camera objects will only actually destruct when the sp<> ref counts get to
+        //        zero, so it is important to break all cyclic references.
+        halCamera->disownVirtualCamera(virtualCamera);
+
+        // Did we just remove the last client of this camera?
+        if (halCamera->getClientCount() == 0) {
+            // Take this now unused camera out of our list
+            // NOTE:  This should drop our last reference to the camera, resulting in its
+            //        destruction.
+            mActiveCameras.erase(halCamera->getId());
+        }
+    }
+
+    // Make sure the virtual camera's stream is stopped
+    virtualCamera->stopVideoStream();
+
+    return Void();
+}
+
+
+// Methods from ::android::hardware::automotive::evs::V1_1::IEvsEnumerator follow.
+Return<sp<IEvsCamera_1_1>> Enumerator::openCamera_1_1(const hidl_string& cameraId,
+                                                      const Stream& streamCfg) {
+    ALOGD("openCamera_1_1");
+    if (!checkPermission()) {
+        return nullptr;
+    }
+
+    // If hwCamera is null, a requested camera device is either a logical camera
+    // device or a hardware camera, which is not being used now.
+    std::unordered_set<std::string> physicalCameras = getPhysicalCameraIds(cameraId);
+    std::vector<sp<HalCamera>> sourceCameras;
+    sp<HalCamera> hwCamera;
+    bool success = true;
+
+    // 1. Try to open inactive camera devices.
+    for (auto&& id : physicalCameras) {
+        auto it = mActiveCameras.find(id);
+        if (it == mActiveCameras.end()) {
+            // Try to open a hardware camera.
+            sp<IEvsCamera_1_1> device =
+                IEvsCamera_1_1::castFrom(mHwEnumerator->openCamera_1_1(id, streamCfg))
+                .withDefault(nullptr);
+            if (device == nullptr) {
+                ALOGE("Failed to open hardware camera %s", cameraId.c_str());
+                success = false;
+                break;
+            } else {
+                hwCamera = new HalCamera(device, id, streamCfg);
+                if (hwCamera == nullptr) {
+                    ALOGE("Failed to allocate camera wrapper object");
+                    mHwEnumerator->closeCamera(device);
+                    success = false;
+                    break;
+                }
+            }
+
+            // Add the hardware camera to our list, which will keep it alive via ref count
+            mActiveCameras.try_emplace(id, hwCamera);
+            sourceCameras.push_back(hwCamera);
+        } else {
+            if (it->second->getStreamConfig().id != streamCfg.id) {
+                ALOGW("Requested camera is already active in different configuration.");
+            } else {
+                sourceCameras.push_back(it->second);
+            }
+        }
+    }
+
+    if (sourceCameras.size() < 1) {
+        ALOGE("Failed to open any physical camera device");
+        return nullptr;
+    }
+
+    // TODO(b/147170360): Implement a logic to handle a failure.
+    // 3. Create a proxy camera object
+    sp<VirtualCamera> clientCamera = new VirtualCamera(sourceCameras);
+    if (clientCamera == nullptr) {
+        // TODO: Any resource needs to be cleaned up explicitly?
+        ALOGE("Failed to create a client camera object");
+    } else {
+        if (physicalCameras.size() > 1) {
+            // VirtualCamera, which represents a logical device, caches its
+            // descriptor.
+            clientCamera->setDescriptor(&mCameraDevices[cameraId]);
+        }
+
+        // 4. Owns created proxy camera object
+        for (auto&& hwCamera : sourceCameras) {
+            if (!hwCamera->ownVirtualCamera(clientCamera)) {
+                // TODO: Remove a referece to this camera from a virtual camera
+                // object.
+                ALOGE("%s failed to own a created proxy camera object.",
+                      hwCamera->getId().c_str());
+            }
+        }
+    }
+
+    // Send the virtual camera object back to the client by strong pointer which will keep it alive
+    return clientCamera;
+}
+
+
+Return<void> Enumerator::getCameraList_1_1(getCameraList_1_1_cb list_cb)  {
+    ALOGD("getCameraList");
+    if (!checkPermission()) {
+        return Void();
+    }
+
+    hardware::hidl_vec<CameraDesc_1_1> hidlCameras;
+    mHwEnumerator->getCameraList_1_1(
+        [&hidlCameras](hardware::hidl_vec<CameraDesc_1_1> enumeratedCameras) {
+            hidlCameras.resize(enumeratedCameras.size());
+            unsigned count = 0;
+            for (auto&& camdesc : enumeratedCameras) {
+                hidlCameras[count++] = camdesc;
+            }
+        }
+    );
+
+    // Update the cached device list
+    mCameraDevices.clear();
+    for (auto&& desc : hidlCameras) {
+        mCameraDevices.insert_or_assign(desc.v1.cameraId, desc);
+    }
+
+    list_cb(hidlCameras);
+    return Void();
+}
+
+
+Return<sp<IEvsDisplay_1_0>> Enumerator::openDisplay() {
+    ALOGD("openDisplay");
+
+    if (!checkPermission()) {
+        return nullptr;
+    }
+
+    // We simply keep track of the most recently opened display instance.
+    // In the underlying layers we expect that a new open will cause the previous
+    // object to be destroyed.  This avoids any race conditions associated with
+    // create/destroy order and provides a cleaner restart sequence if the previous owner
+    // is non-responsive for some reason.
+    // Request exclusive access to the EVS display
+    sp<IEvsDisplay_1_0> pActiveDisplay = mHwEnumerator->openDisplay();
+    if (pActiveDisplay == nullptr) {
+        ALOGE("EVS Display unavailable");
+
+        return nullptr;
+    }
+
+    // Remember (via weak pointer) who we think the most recently opened display is so that
+    // we can proxy state requests from other callers to it.
+    // TODO: Because of b/129284474, an additional class, HalDisplay, has been defined and
+    // wraps the IEvsDisplay object the driver returns.  We may want to remove this
+    // additional class when it is fixed properly.
+    sp<IEvsDisplay_1_0> pHalDisplay = new HalDisplay(pActiveDisplay);
+    mActiveDisplay = pHalDisplay;
+
+    return pHalDisplay;
+}
+
+
+Return<void> Enumerator::closeDisplay(const ::android::sp<IEvsDisplay_1_0>& display) {
+    ALOGD("closeDisplay");
+
+    sp<IEvsDisplay_1_0> pActiveDisplay = mActiveDisplay.promote();
+
+    // Drop the active display
+    if (display.get() != pActiveDisplay.get()) {
+        ALOGW("Ignoring call to closeDisplay with unrecognized display object.");
+    } else {
+        // Pass this request through to the hardware layer
+        sp<HalDisplay> halDisplay = reinterpret_cast<HalDisplay *>(pActiveDisplay.get());
+        mHwEnumerator->closeDisplay(halDisplay->getHwDisplay());
+        mActiveDisplay = nullptr;
+    }
+
+    return Void();
+}
+
+
+Return<EvsDisplayState> Enumerator::getDisplayState()  {
+    ALOGD("getDisplayState");
+    if (!checkPermission()) {
+        return EvsDisplayState::DEAD;
+    }
+
+    // Do we have a display object we think should be active?
+    sp<IEvsDisplay_1_0> pActiveDisplay = mActiveDisplay.promote();
+    if (pActiveDisplay != nullptr) {
+        // Pass this request through to the hardware layer
+        return pActiveDisplay->getDisplayState();
+    } else {
+        // We don't have a live display right now
+        mActiveDisplay = nullptr;
+        return EvsDisplayState::NOT_OPEN;
+    }
+}
+
+
+Return<sp<IEvsDisplay_1_1>> Enumerator::openDisplay_1_1(uint8_t id) {
+    ALOGD("%s", __FUNCTION__);
+
+    if (!checkPermission()) {
+        return nullptr;
+    }
+
+    // We simply keep track of the most recently opened display instance.
+    // In the underlying layers we expect that a new open will cause the previous
+    // object to be destroyed.  This avoids any race conditions associated with
+    // create/destroy order and provides a cleaner restart sequence if the previous owner
+    // is non-responsive for some reason.
+    // Request exclusive access to the EVS display
+    sp<IEvsDisplay_1_1> pActiveDisplay = mHwEnumerator->openDisplay_1_1(id);
+    if (pActiveDisplay == nullptr) {
+        ALOGE("EVS Display unavailable");
+
+        return nullptr;
+    }
+
+    // Remember (via weak pointer) who we think the most recently opened display is so that
+    // we can proxy state requests from other callers to it.
+    // TODO: Because of b/129284474, an additional class, HalDisplay, has been defined and
+    // wraps the IEvsDisplay object the driver returns.  We may want to remove this
+    // additional class when it is fixed properly.
+    sp<IEvsDisplay_1_1> pHalDisplay = new HalDisplay(pActiveDisplay);
+    mActiveDisplay = pHalDisplay;
+
+    return pHalDisplay;
+}
+
+
+Return<void> Enumerator::getDisplayIdList(getDisplayIdList_cb _list_cb)  {
+    return mHwEnumerator->getDisplayIdList(_list_cb);
+}
+
+
+// TODO(b/149874793): Add implementation for EVS Manager and Sample driver
+Return<void> Enumerator::getUltrasonicsArrayList(getUltrasonicsArrayList_cb _hidl_cb) {
+    hardware::hidl_vec<UltrasonicsArrayDesc> ultrasonicsArrayDesc;
+    _hidl_cb(ultrasonicsArrayDesc);
+    return Void();
+}
+
+
+// TODO(b/149874793): Add implementation for EVS Manager and Sample driver
+Return<sp<IEvsUltrasonicsArray>> Enumerator::openUltrasonicsArray(
+        const hidl_string& ultrasonicsArrayId) {
+    (void)ultrasonicsArrayId;
+    sp<IEvsUltrasonicsArray> pEvsUltrasonicsArray;
+    return pEvsUltrasonicsArray;
+}
+
+
+// TODO(b/149874793): Add implementation for EVS Manager and Sample driver
+Return<void> Enumerator::closeUltrasonicsArray(
+        const ::android::sp<IEvsUltrasonicsArray>& evsUltrasonicsArray)  {
+    (void)evsUltrasonicsArray;
+    return Void();
+}
+
+} // namespace implementation
+} // namespace V1_1
+} // namespace evs
+} // namespace automotive
+} // namespace android
diff --git a/evs/manager/1.1/Enumerator.h b/evs/manager/1.1/Enumerator.h
new file mode 100644
index 0000000..72619cd
--- /dev/null
+++ b/evs/manager/1.1/Enumerator.h
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_AUTOMOTIVE_EVS_V1_1_EVSCAMERAENUMERATOR_H
+#define ANDROID_AUTOMOTIVE_EVS_V1_1_EVSCAMERAENUMERATOR_H
+
+#include <list>
+#include <unordered_map>
+#include <unordered_set>
+
+#include "HalCamera.h"
+#include "VirtualCamera.h"
+
+#include <android/hardware/automotive/evs/1.1/IEvsEnumerator.h>
+#include <android/hardware/automotive/evs/1.1/IEvsDisplay.h>
+#include <android/hardware/camera/device/3.2/ICameraDevice.h>
+#include <system/camera_metadata.h>
+
+using namespace ::android::hardware::automotive::evs::V1_1;
+using ::android::hardware::Return;
+using ::android::hardware::hidl_string;
+using ::android::hardware::camera::device::V3_2::Stream;
+
+namespace android {
+namespace automotive {
+namespace evs {
+namespace V1_1 {
+namespace implementation {
+
+using IEvsCamera_1_0     = ::android::hardware::automotive::evs::V1_0::IEvsCamera;
+using IEvsCamera_1_1     = ::android::hardware::automotive::evs::V1_1::IEvsCamera;
+using IEvsEnumerator_1_0 = ::android::hardware::automotive::evs::V1_0::IEvsEnumerator;
+using IEvsEnumerator_1_1 = ::android::hardware::automotive::evs::V1_1::IEvsEnumerator;
+using IEvsDisplay_1_0    = ::android::hardware::automotive::evs::V1_0::IEvsDisplay;
+using IEvsDisplay_1_1    = ::android::hardware::automotive::evs::V1_1::IEvsDisplay;
+using EvsDisplayState    = ::android::hardware::automotive::evs::V1_0::DisplayState;
+
+class Enumerator : public IEvsEnumerator {
+public:
+    // Methods from ::android::hardware::automotive::evs::V1_0::IEvsEnumerator follow.
+    Return<void>                getCameraList(getCameraList_cb _hidl_cb)  override;
+    Return<sp<IEvsCamera_1_0>>  openCamera(const hidl_string& cameraId)  override;
+    Return<void>                closeCamera(const ::android::sp<IEvsCamera_1_0>& virtualCamera)  override;
+    Return<sp<IEvsDisplay_1_0>> openDisplay()  override;
+    Return<void>                closeDisplay(const ::android::sp<IEvsDisplay_1_0>& display)  override;
+    Return<EvsDisplayState>     getDisplayState()  override;
+
+    // Methods from ::android::hardware::automotive::evs::V1_1::IEvsEnumerator follow.
+    Return<void>                getCameraList_1_1(getCameraList_1_1_cb _hidl_cb) override;
+    Return<sp<IEvsCamera_1_1>>  openCamera_1_1(const hidl_string& cameraId,
+                                               const Stream& streamCfg) override;
+    Return<bool>                isHardware() override { return false; }
+    Return<void>                getDisplayIdList(getDisplayIdList_cb _list_cb) override;
+    Return<sp<IEvsDisplay_1_1>> openDisplay_1_1(uint8_t id) override;
+    Return<void> getUltrasonicsArrayList(getUltrasonicsArrayList_cb _hidl_cb) override;
+    Return<sp<IEvsUltrasonicsArray>> openUltrasonicsArray(
+            const hidl_string& ultrasonicsArrayId) override;
+    Return<void> closeUltrasonicsArray(
+            const ::android::sp<IEvsUltrasonicsArray>& evsUltrasonicsArray) override;
+
+    // Implementation details
+    bool init(const char* hardwareServiceName);
+
+private:
+    bool inline                     checkPermission();
+    bool                            isLogicalCamera(const camera_metadata_t *metadata);
+    std::unordered_set<std::string> getPhysicalCameraIds(const std::string& id);
+
+    sp<IEvsEnumerator_1_1>            mHwEnumerator;  // Hardware enumerator
+    wp<IEvsDisplay_1_0>               mActiveDisplay; // Display proxy object warpping hw display
+
+    // List of active camera proxy objects that wrap hw cameras
+    std::unordered_map<std::string,
+                       sp<HalCamera>> mActiveCameras;
+
+    // List of camera descriptors of enumerated hw cameras
+    std::unordered_map<std::string,
+                       CameraDesc>    mCameraDevices;
+};
+
+} // namespace implementation
+} // namespace V1_1
+} // namespace evs
+} // namespace automotive
+} // namespace android
+
+#endif  // ANDROID_AUTOMOTIVE_EVS_V1_1_EVSCAMERAENUMERATOR_H
diff --git a/evs/manager/1.1/HalCamera.cpp b/evs/manager/1.1/HalCamera.cpp
new file mode 100644
index 0000000..c252dfd
--- /dev/null
+++ b/evs/manager/1.1/HalCamera.cpp
@@ -0,0 +1,497 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "HalCamera.h"
+#include "VirtualCamera.h"
+#include "Enumerator.h"
+
+#include <ui/GraphicBufferAllocator.h>
+#include <ui/GraphicBufferMapper.h>
+
+
+namespace android {
+namespace automotive {
+namespace evs {
+namespace V1_1 {
+namespace implementation {
+
+
+// TODO(changyeon):
+// We need to hook up death monitoring to detect stream death so we can attempt a reconnect
+
+
+sp<VirtualCamera> HalCamera::makeVirtualCamera() {
+
+    // Create the client camera interface object
+    std::vector<sp<HalCamera>> sourceCameras;
+    sourceCameras.reserve(1);
+    sourceCameras.emplace_back(this);
+    sp<VirtualCamera> client = new VirtualCamera(sourceCameras);
+    if (client == nullptr) {
+        ALOGE("Failed to create client camera object");
+        return nullptr;
+    }
+
+    if (!ownVirtualCamera(client)) {
+        ALOGE("Failed to own a client camera object");
+        client = nullptr;
+    }
+
+    return client;
+}
+
+
+bool HalCamera::ownVirtualCamera(sp<VirtualCamera> virtualCamera) {
+
+    if (virtualCamera == nullptr) {
+        ALOGE("Failed to create virtualCamera camera object");
+        return false;
+    }
+
+    // Make sure we have enough buffers available for all our clients
+    if (!changeFramesInFlight(virtualCamera->getAllowedBuffers())) {
+        // Gah!  We couldn't get enough buffers, so we can't support this virtualCamera
+        // Null the pointer, dropping our reference, thus destroying the virtualCamera object
+        return false;
+    }
+
+    // Create a timeline
+    // TODO(b/146465074): EVS v1.1 client should use v1.0 frame delivery logic
+    //                    when it fails to create a timeline.
+    {
+        std::lock_guard<std::mutex> lock(mFrameMutex);
+        mTimelines[(uint64_t)virtualCamera.get()] = make_unique<UniqueTimeline>(0);
+    }
+
+    // Add this virtualCamera to our ownership list via weak pointer
+    mClients.emplace_back(virtualCamera);
+    return true;
+}
+
+
+void HalCamera::disownVirtualCamera(sp<VirtualCamera> virtualCamera) {
+    // Ignore calls with null pointers
+    if (virtualCamera.get() == nullptr) {
+        ALOGW("Ignoring disownVirtualCamera call with null pointer");
+        return;
+    }
+
+    // Remove the virtual camera from our client list
+    unsigned clientCount = mClients.size();
+    mClients.remove(virtualCamera);
+    if (clientCount != mClients.size() + 1) {
+        ALOGE("Couldn't find camera in our client list to remove it");
+    }
+
+    // Recompute the number of buffers required with the target camera removed from the list
+    if (!changeFramesInFlight(0)) {
+        ALOGE("Error when trying to reduce the in flight buffer count");
+    }
+}
+
+
+bool HalCamera::changeFramesInFlight(int delta) {
+    // Walk all our clients and count their currently required frames
+    unsigned bufferCount = 0;
+    for (auto&& client :  mClients) {
+        sp<VirtualCamera> virtCam = client.promote();
+        if (virtCam != nullptr) {
+            bufferCount += virtCam->getAllowedBuffers();
+        }
+    }
+
+    // Add the requested delta
+    bufferCount += delta;
+
+    // Never drop below 1 buffer -- even if all client cameras get closed
+    if (bufferCount < 1) {
+        bufferCount = 1;
+    }
+
+    // Ask the hardware for the resulting buffer count
+    Return<EvsResult> result = mHwCamera->setMaxFramesInFlight(bufferCount);
+    bool success = (result.isOk() && result == EvsResult::OK);
+
+    // Update the size of our array of outstanding frame records
+    if (success) {
+        std::vector<FrameRecord> newRecords;
+        newRecords.reserve(bufferCount);
+
+        // Copy and compact the old records that are still active
+        for (const auto& rec : mFrames) {
+            if (rec.refCount > 0) {
+                newRecords.emplace_back(rec);
+            }
+        }
+        if (newRecords.size() > (unsigned)bufferCount) {
+            ALOGW("We found more frames in use than requested.");
+        }
+
+        mFrames.swap(newRecords);
+    }
+
+    return success;
+}
+
+
+UniqueFence HalCamera::requestNewFrame(sp<VirtualCamera> client,
+                                       const int64_t lastTimestamp) {
+    FrameRequest req;
+    req.client = client;
+    req.timestamp = lastTimestamp;
+
+    const uint64_t id = (uint64_t)client.get();
+
+    std::lock_guard<std::mutex> lock(mFrameMutex);
+
+    mTimelines[id]->BumpFenceEventCounter();
+    UniqueFence fence = mTimelines[id]->CreateFence("FrameFence");
+
+    mNextRequests->push_back(req);
+
+    return fence.Dup();
+}
+
+
+Return<EvsResult> HalCamera::clientStreamStarting() {
+    Return<EvsResult> result = EvsResult::OK;
+
+    if (mStreamState == STOPPED) {
+        mStreamState = RUNNING;
+        result = mHwCamera->startVideoStream(this);
+    }
+
+    return result;
+}
+
+
+void HalCamera::clientStreamEnding(sp<VirtualCamera> client) {
+    {
+        std::lock_guard<std::mutex> lock(mFrameMutex);
+        auto itReq = mNextRequests->begin();
+        while (itReq != mNextRequests->end()) {
+            if (itReq->client == client) {
+                break;
+            } else {
+                ++itReq;
+            }
+        }
+
+        const uint64_t clientId = reinterpret_cast<const uint64_t>(client.get());
+        if (itReq != mNextRequests->end()) {
+            mNextRequests->erase(itReq);
+
+            // Signal a pending fence and delete associated timeline.
+            mTimelines[clientId]->BumpTimelineEventCounter();
+            mTimelines.erase(clientId);
+        }
+
+        auto itCam = mClients.begin();
+        while (itCam != mClients.end()) {
+            if (itCam->promote() == client.get()) {
+                break;
+            } else {
+                ++itCam;
+            }
+        }
+
+        if (itCam != mClients.end()) {
+            // Remove a client, which requested to stop, from the list.
+            mClients.erase(itCam);
+        }
+    }
+
+    // Do we still have a running client?
+    bool stillRunning = false;
+    for (auto&& client : mClients) {
+        sp<VirtualCamera> virtCam = client.promote();
+        if (virtCam != nullptr) {
+            stillRunning |= virtCam->isStreaming();
+        }
+    }
+
+    // If not, then stop the hardware stream
+    if (!stillRunning) {
+        mStreamState = STOPPING;
+        mHwCamera->stopVideoStream();
+    }
+}
+
+
+Return<void> HalCamera::doneWithFrame(const BufferDesc_1_0& buffer) {
+    // Find this frame in our list of outstanding frames
+    unsigned i;
+    for (i = 0; i < mFrames.size(); i++) {
+        if (mFrames[i].frameId == buffer.bufferId) {
+            break;
+        }
+    }
+    if (i == mFrames.size()) {
+        ALOGE("We got a frame back with an ID we don't recognize!");
+    } else {
+        // Are there still clients using this buffer?
+        mFrames[i].refCount--;
+        if (mFrames[i].refCount <= 0) {
+            // Since all our clients are done with this buffer, return it to the device layer
+            mHwCamera->doneWithFrame(buffer);
+        }
+    }
+
+    return Void();
+}
+
+
+Return<void> HalCamera::doneWithFrame(const BufferDesc_1_1& buffer) {
+    // Find this frame in our list of outstanding frames
+    unsigned i;
+    for (i = 0; i < mFrames.size(); i++) {
+        if (mFrames[i].frameId == buffer.bufferId) {
+            break;
+        }
+    }
+    if (i == mFrames.size()) {
+        ALOGE("We got a frame back with an ID we don't recognize!");
+    } else {
+        // Are there still clients using this buffer?
+        mFrames[i].refCount--;
+        if (mFrames[i].refCount <= 0) {
+            // Since all our clients are done with this buffer, return it to the device layer
+            hardware::hidl_vec<BufferDesc_1_1> returnedBuffers;
+            returnedBuffers.resize(1);
+            returnedBuffers[0] = buffer;
+            mHwCamera->doneWithFrame_1_1(returnedBuffers);
+        }
+    }
+
+    return Void();
+}
+
+
+// Methods from ::android::hardware::automotive::evs::V1_0::IEvsCameraStream follow.
+Return<void> HalCamera::deliverFrame(const BufferDesc_1_0& buffer) {
+    /* Frames are delivered via deliverFrame_1_1 callback for clients that implement
+     * IEvsCameraStream v1.1 interfaces and therefore this method must not be
+     * used.
+     */
+    ALOGI("A delivered frame from EVS v1.0 HW module is rejected.");
+    mHwCamera->doneWithFrame(buffer);
+
+    return Void();
+}
+
+
+// Methods from ::android::hardware::automotive::evs::V1_1::IEvsCameraStream follow.
+Return<void> HalCamera::deliverFrame_1_1(const hardware::hidl_vec<BufferDesc_1_1>& buffer) {
+    ALOGV("Received a frame");
+    // Frames are being forwarded to v1.1 clients only who requested new frame.
+    const auto timestamp = buffer[0].timestamp;
+    // TODO(b/145750636): For now, we are using a approximately half of 1 seconds / 30 frames = 33ms
+    //           but this must be derived from current framerate.
+    constexpr int64_t kThreshold = 16 * 1e+3; // ms
+    unsigned frameDeliveriesV1 = 0;
+    {
+        std::lock_guard<std::mutex> lock(mFrameMutex);
+        std::swap(mCurrentRequests, mNextRequests);
+        while (!mCurrentRequests->empty()) {
+            auto req = mCurrentRequests->front(); mCurrentRequests->pop_front();
+            sp<VirtualCamera> vCam = req.client.promote();
+            if (vCam == nullptr) {
+                // Ignore a client already dead.
+                continue;
+            } else if (timestamp - req.timestamp < kThreshold) {
+                // Skip current frame because it arrives too soon.
+                ALOGD("Skips a frame from %s", getId().c_str());
+                mNextRequests->push_back(req);
+            } else if (vCam != nullptr && vCam->deliverFrame(buffer[0])) {
+                // Forward a frame and move a timeline.
+                ALOGD("%s forwarded the buffer #%d", getId().c_str(), buffer[0].bufferId);
+                mTimelines[(uint64_t)vCam.get()]->BumpTimelineEventCounter();
+                ++frameDeliveriesV1;
+            }
+        }
+    }
+
+    // Frames are being forwarded to v1.0 clients always.
+    unsigned frameDeliveries = 0;
+    for (auto&& client : mClients) {
+        sp<VirtualCamera> vCam = client.promote();
+        if (vCam == nullptr || vCam->getVersion() > 0) {
+            continue;
+        }
+
+        if (vCam->deliverFrame(buffer[0])) {
+            ++frameDeliveries;
+        }
+    }
+
+    frameDeliveries += frameDeliveriesV1;
+    if (frameDeliveries < 1) {
+        // If none of our clients could accept the frame, then return it
+        // right away.
+        ALOGI("Trivially rejecting frame (%d) from %s with no acceptance",
+              buffer[0].bufferId, getId().c_str());
+        mHwCamera->doneWithFrame_1_1(buffer);
+    } else {
+        // Add an entry for this frame in our tracking list.
+        unsigned i;
+        for (i = 0; i < mFrames.size(); ++i) {
+            if (mFrames[i].refCount == 0) {
+                break;
+            }
+        }
+
+        if (i == mFrames.size()) {
+            mFrames.emplace_back(buffer[0].bufferId);
+        } else {
+            mFrames[i].frameId = buffer[0].bufferId;
+        }
+        mFrames[i].refCount = frameDeliveries;
+    }
+
+    return Void();
+}
+
+
+Return<void> HalCamera::notify(const EvsEventDesc& event) {
+    ALOGD("Received an event id: %u", event.aType);
+    if(event.aType == EvsEventType::STREAM_STOPPED) {
+        // This event happens only when there is no more active client.
+        if (mStreamState != STOPPING) {
+            ALOGW("Stream stopped unexpectedly");
+        }
+
+        mStreamState = STOPPED;
+    }
+
+    // Forward all other events to the clients
+    for (auto&& client : mClients) {
+        sp<VirtualCamera> vCam = client.promote();
+        if (vCam != nullptr) {
+            if (!vCam->notify(event)) {
+                ALOGI("Failed to forward an event");
+            }
+        }
+    }
+
+    return Void();
+}
+
+
+Return<EvsResult> HalCamera::setMaster(sp<VirtualCamera> virtualCamera) {
+    if (mMaster == nullptr) {
+        ALOGD("%s: %p becomes a master", __FUNCTION__, virtualCamera.get());
+        mMaster = virtualCamera;
+        return EvsResult::OK;
+    } else {
+        ALOGD("This camera already has a master client.");
+        return EvsResult::OWNERSHIP_LOST;
+    }
+}
+
+
+Return<EvsResult> HalCamera::forceMaster(sp<VirtualCamera> virtualCamera) {
+    sp<VirtualCamera> prevMaster = mMaster.promote();
+    if (prevMaster == virtualCamera) {
+        ALOGD("Client %p is already a master client", virtualCamera.get());
+    } else {
+        mMaster = virtualCamera;
+        if (prevMaster != nullptr) {
+            ALOGD("High priority client %p steals a master role from %p",
+                virtualCamera.get(), prevMaster.get());
+
+            /* Notify a previous master client the loss of a master role */
+            EvsEventDesc event;
+            event.aType = EvsEventType::MASTER_RELEASED;
+            if (!prevMaster->notify(event)) {
+                ALOGE("Fail to deliver a master role lost notification");
+            }
+        }
+    }
+
+    return EvsResult::OK;
+}
+
+
+Return<EvsResult> HalCamera::unsetMaster(sp<VirtualCamera> virtualCamera) {
+    if (mMaster.promote() != virtualCamera) {
+        return EvsResult::INVALID_ARG;
+    } else {
+        ALOGD("Unset a master camera client");
+        mMaster = nullptr;
+
+        /* Notify other clients that a master role becomes available. */
+        EvsEventDesc event;
+        event.aType = EvsEventType::MASTER_RELEASED;
+        auto cbResult = this->notify(event);
+        if (!cbResult.isOk()) {
+            ALOGE("Fail to deliver a parameter change notification");
+        }
+
+        return EvsResult::OK;
+    }
+}
+
+
+Return<EvsResult> HalCamera::setParameter(sp<VirtualCamera> virtualCamera,
+                                          CameraParam id, int32_t& value) {
+    EvsResult result = EvsResult::INVALID_ARG;
+    if (virtualCamera == mMaster.promote()) {
+        mHwCamera->setIntParameter(id, value,
+                                   [&result, &value](auto status, auto readValue) {
+                                       result = status;
+                                       value = readValue[0];
+                                   });
+
+        if (result == EvsResult::OK) {
+            /* Notify a parameter change */
+            EvsEventDesc event;
+            event.aType = EvsEventType::PARAMETER_CHANGED;
+            event.payload[0] = static_cast<uint32_t>(id);
+            event.payload[1] = static_cast<uint32_t>(value);
+            auto cbResult = this->notify(event);
+            if (!cbResult.isOk()) {
+                ALOGE("Fail to deliver a parameter change notification");
+            }
+        }
+    } else {
+        ALOGD("A parameter change request from a non-master client is declined.");
+
+        /* Read a current value of a requested camera parameter */
+        getParameter(id, value);
+    }
+
+    return result;
+}
+
+
+Return<EvsResult> HalCamera::getParameter(CameraParam id, int32_t& value) {
+    EvsResult result = EvsResult::OK;
+    mHwCamera->getIntParameter(id, [&result, &value](auto status, auto readValue) {
+                                       result = status;
+                                       if (result == EvsResult::OK) {
+                                           value = readValue[0];
+                                       }
+    });
+
+    return result;
+}
+
+
+} // namespace implementation
+} // namespace V1_1
+} // namespace evs
+} // namespace automotive
+} // namespace android
diff --git a/evs/manager/1.1/HalCamera.h b/evs/manager/1.1/HalCamera.h
new file mode 100644
index 0000000..e9a25c3
--- /dev/null
+++ b/evs/manager/1.1/HalCamera.h
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_AUTOMOTIVE_EVS_V1_1_HALCAMERA_H
+#define ANDROID_AUTOMOTIVE_EVS_V1_1_HALCAMERA_H
+
+#include <android/hardware/automotive/evs/1.1/types.h>
+#include <android/hardware/automotive/evs/1.1/IEvsCamera.h>
+#include <android/hardware/automotive/evs/1.1/IEvsCameraStream.h>
+#include <ui/GraphicBuffer.h>
+
+#include <thread>
+#include <list>
+#include <deque>
+#include <unordered_map>
+
+#include "sync/unique_fd.h"
+#include "sync/unique_fence.h"
+#include "sync/unique_timeline.h"
+
+using namespace ::android::hardware::automotive::evs::V1_1;
+using ::android::hardware::camera::device::V3_2::Stream;
+using ::android::hardware::Return;
+using ::android::hardware::hidl_handle;
+using ::android::hardware::automotive::evs::V1_0::EvsResult;
+using IEvsCamera_1_0 = ::android::hardware::automotive::evs::V1_0::IEvsCamera;
+using IEvsCamera_1_1 = ::android::hardware::automotive::evs::V1_1::IEvsCamera;
+using BufferDesc_1_0 = ::android::hardware::automotive::evs::V1_0::BufferDesc;
+using BufferDesc_1_1 = ::android::hardware::automotive::evs::V1_1::BufferDesc;
+using IEvsCameraStream_1_0 = ::android::hardware::automotive::evs::V1_0::IEvsCameraStream;
+using IEvsCameraStream_1_1 = ::android::hardware::automotive::evs::V1_1::IEvsCameraStream;
+
+namespace android {
+namespace automotive {
+namespace evs {
+namespace V1_1 {
+namespace implementation {
+
+
+class VirtualCamera;    // From VirtualCamera.h
+
+
+// This class wraps the actual hardware IEvsCamera objects.  There is a one to many
+// relationship between instances of this class and instances of the VirtualCamera class.
+// This class implements the IEvsCameraStream interface so that it can receive the video
+// stream from the hardware camera and distribute it to the associated VirtualCamera objects.
+class HalCamera : public IEvsCameraStream_1_1 {
+public:
+    HalCamera(sp<IEvsCamera_1_1> hwCamera, std::string deviceId = "", Stream cfg = {})
+        : mHwCamera(hwCamera),
+          mId(deviceId),
+          mStreamConfig(cfg){
+        mCurrentRequests = &mFrameRequests[0];
+        mNextRequests    = &mFrameRequests[1];
+    }
+
+    // Factory methods for client VirtualCameras
+    sp<VirtualCamera>     makeVirtualCamera();
+    bool                  ownVirtualCamera(sp<VirtualCamera> virtualCamera);
+    void                  disownVirtualCamera(sp<VirtualCamera> virtualCamera);
+
+    // Implementation details
+    sp<IEvsCamera_1_0>  getHwCamera()       { return mHwCamera; };
+    unsigned            getClientCount()    { return mClients.size(); };
+    std::string         getId()             { return mId; }
+    Stream&             getStreamConfig()   { return mStreamConfig; }
+    bool                changeFramesInFlight(int delta);
+    UniqueFence         requestNewFrame(sp<VirtualCamera> virtualCamera,
+                                        const int64_t timestamp);
+
+    Return<EvsResult>   clientStreamStarting();
+    void                clientStreamEnding(sp<VirtualCamera> client);
+    Return<void>        doneWithFrame(const BufferDesc_1_0& buffer);
+    Return<void>        doneWithFrame(const BufferDesc_1_1& buffer);
+    Return<EvsResult>   setMaster(sp<VirtualCamera> virtualCamera);
+    Return<EvsResult>   forceMaster(sp<VirtualCamera> virtualCamera);
+    Return<EvsResult>   unsetMaster(sp<VirtualCamera> virtualCamera);
+    Return<EvsResult>   setParameter(sp<VirtualCamera> virtualCamera,
+                                     CameraParam id, int32_t& value);
+    Return<EvsResult>   getParameter(CameraParam id, int32_t& value);
+
+    // Methods from ::android::hardware::automotive::evs::V1_0::IEvsCameraStream follow.
+    Return<void> deliverFrame(const BufferDesc_1_0& buffer) override;
+
+    // Methods from ::android::hardware::automotive::evs::V1_1::IEvsCameraStream follow.
+    Return<void> deliverFrame_1_1(const hardware::hidl_vec<BufferDesc_1_1>& buffer) override;
+    Return<void> notify(const EvsEventDesc& event) override;
+
+private:
+    sp<IEvsCamera_1_1>              mHwCamera;
+    std::list<wp<VirtualCamera>>    mClients;   // Weak pointers -> objects destruct if client dies
+
+    enum {
+        STOPPED,
+        RUNNING,
+        STOPPING,
+    }                               mStreamState = STOPPED;
+
+    struct FrameRecord {
+        uint32_t    frameId;
+        uint32_t    refCount;
+        FrameRecord(uint32_t id) : frameId(id), refCount(0) {};
+    };
+    std::vector<FrameRecord>        mFrames;
+    wp<VirtualCamera>               mMaster = nullptr;
+    std::string                     mId;
+    Stream                          mStreamConfig;
+
+    struct FrameRequest {
+        wp<VirtualCamera> client = nullptr;
+        int64_t           timestamp = -1;
+    };
+
+    // synchronization
+    std::mutex                mFrameMutex;
+    std::deque<FrameRequest>  mFrameRequests[2] GUARDED_BY(mFrameMutex);
+    std::deque<FrameRequest>* mCurrentRequests  PT_GUARDED_BY(mFrameMutex);
+    std::deque<FrameRequest>* mNextRequests     PT_GUARDED_BY(mFrameMutex);
+    std::unordered_map<uint64_t,
+                       std::unique_ptr<UniqueTimeline>> mTimelines GUARDED_BY(mFrameMutex);
+};
+
+} // namespace implementation
+} // namespace V1_1
+} // namespace evs
+} // namespace automotive
+} // namespace android
+
+#endif  // ANDROID_AUTOMOTIVE_EVS_V1_1_HALCAMERA_H
diff --git a/evs/manager/1.1/HalDisplay.cpp b/evs/manager/1.1/HalDisplay.cpp
new file mode 100644
index 0000000..7b7abab
--- /dev/null
+++ b/evs/manager/1.1/HalDisplay.cpp
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <log/log.h>
+#include "HalDisplay.h"
+
+namespace android {
+namespace automotive {
+namespace evs {
+namespace V1_1 {
+namespace implementation {
+
+HalDisplay::HalDisplay(sp<IEvsDisplay_1_0> display) :
+  mHwDisplay(display) {
+    // nothing to do.
+}
+
+HalDisplay::~HalDisplay() {
+    shutdown();
+}
+
+void HalDisplay::shutdown() {
+    // simply release a strong pointer to remote display object.
+    mHwDisplay = nullptr;
+}
+
+/**
+ * Returns a strong pointer to remote display object.
+ */
+sp<IEvsDisplay_1_0> HalDisplay::getHwDisplay() {
+    return mHwDisplay;
+}
+
+/**
+ * Gets basic display information from a hardware display object
+ * and returns.
+ */
+Return<void> HalDisplay::getDisplayInfo(getDisplayInfo_cb _hidl_cb) {
+    if (mHwDisplay) {
+        mHwDisplay->getDisplayInfo(_hidl_cb);
+    }
+
+    return Void();
+}
+
+/**
+ * Sets the display state as what the clients wants.
+ */
+Return<EvsResult> HalDisplay::setDisplayState(EvsDisplayState state) {
+    if (mHwDisplay) {
+        return mHwDisplay->setDisplayState(state);
+    } else {
+        return EvsResult::UNDERLYING_SERVICE_ERROR;
+    }
+}
+
+/**
+ * Gets current display state from a hardware display object and return.
+ */
+Return<EvsDisplayState> HalDisplay::getDisplayState() {
+    if (mHwDisplay) {
+        return mHwDisplay->getDisplayState();
+    } else {
+        return EvsDisplayState::DEAD;
+    }
+}
+
+/**
+ * Returns a handle to a frame buffer associated with the display.
+ */
+Return<void> HalDisplay::getTargetBuffer(getTargetBuffer_cb _hidl_cb) {
+    if (mHwDisplay) {
+        mHwDisplay->getTargetBuffer(_hidl_cb);
+    }
+
+    return Void();
+}
+
+/**
+ * Notifies the display that the buffer is ready to be used.
+ */
+Return<EvsResult> HalDisplay::returnTargetBufferForDisplay(const BufferDesc_1_0& buffer) {
+    if (mHwDisplay) {
+        return mHwDisplay->returnTargetBufferForDisplay(buffer);
+    } else {
+        return EvsResult::OWNERSHIP_LOST;
+    }
+}
+
+/**
+ * Gets basic display information from a hardware display object
+ * and returns.
+ */
+Return<void> HalDisplay::getDisplayInfo_1_1(getDisplayInfo_1_1_cb _info_cb) {
+    sp<IEvsDisplay_1_1> display = IEvsDisplay_1_1::castFrom(mHwDisplay)
+                                  .withDefault(nullptr);
+    if (display != nullptr) {
+        display->getDisplayInfo_1_1(_info_cb);
+    }
+
+    return Void();
+}
+
+} // namespace implementation
+} // namespace V1_1
+} // namespace evs
+} // namespace automotive
+} // namespace android
diff --git a/evs/manager/1.1/HalDisplay.h b/evs/manager/1.1/HalDisplay.h
new file mode 100644
index 0000000..de9690b
--- /dev/null
+++ b/evs/manager/1.1/HalDisplay.h
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_AUTOMOTIVE_EVS_V1_1_DISPLAYPROXY_H
+#define ANDROID_AUTOMOTIVE_EVS_V1_1_DISPLAYPROXY_H
+
+#include <android/hardware/automotive/evs/1.1/types.h>
+#include <android/hardware/automotive/evs/1.1/IEvsDisplay.h>
+
+using namespace ::android::hardware::automotive::evs::V1_1;
+using ::android::hardware::Return;
+using ::android::hardware::Void;
+using ::android::hardware::hidl_handle;
+using ::android::hardware::automotive::evs::V1_0::EvsResult;
+using BufferDesc_1_0 = ::android::hardware::automotive::evs::V1_0::BufferDesc;
+using EvsDisplayState = ::android::hardware::automotive::evs::V1_0::DisplayState;
+using IEvsDisplay_1_0 = ::android::hardware::automotive::evs::V1_0::IEvsDisplay;
+using IEvsDisplay_1_1 = ::android::hardware::automotive::evs::V1_1::IEvsDisplay;
+
+namespace android {
+namespace automotive {
+namespace evs {
+namespace V1_1 {
+namespace implementation {
+
+// TODO(129284474): This class has been defined to wrap the IEvsDisplay object the driver
+// returns because of b/129284474 and represents an EVS display to the client
+// application.  With a proper bug fix, we may remove this class and update the
+// manager directly to use the IEvsDisplay object the driver provides.
+class HalDisplay : public IEvsDisplay_1_1 {
+public:
+    explicit HalDisplay(sp<IEvsDisplay_1_0> display);
+    virtual ~HalDisplay() override;
+
+    inline void         shutdown();
+    sp<IEvsDisplay_1_0> getHwDisplay();
+
+    // Methods from ::android::hardware::automotive::evs::V1_0::IEvsDisplay follow.
+    Return<void>            getDisplayInfo(getDisplayInfo_cb _hidl_cb)  override;
+    Return<EvsResult>       setDisplayState(EvsDisplayState state)  override;
+    Return<EvsDisplayState> getDisplayState()  override;
+    Return<void>            getTargetBuffer(getTargetBuffer_cb _hidl_cb)  override;
+    Return<EvsResult>       returnTargetBufferForDisplay(const BufferDesc_1_0& buffer)  override;
+
+    // Methods from ::android::hardware::automotive::evs::V1_1::IEvsDisplay follow.
+    Return<void>            getDisplayInfo_1_1(getDisplayInfo_1_1_cb _info_cb) override;
+
+private:
+    sp<IEvsDisplay_1_0>     mHwDisplay; // The low level display interface that backs this proxy
+};
+
+} // namespace implementation
+} // namespace V1_1
+} // namespace evs
+} // namespace automotive
+} // namespace android
+
+#endif  // ANDROID_AUTOMOTIVE_EVS_V1_1_DISPLAYPROXY_H
diff --git a/evs/manager/1.1/ServiceNames.h b/evs/manager/1.1/ServiceNames.h
new file mode 100644
index 0000000..c2a7a65
--- /dev/null
+++ b/evs/manager/1.1/ServiceNames.h
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+// This is the name as which we'll register ourselves
+const static char kManagedEnumeratorName[] = "default";
+
+// This is the name of the hardware provider to which we'll bind by default
+const static char kHardwareEnumeratorName[]  = "hw/1";
+
+// This is the name of the mock hardware provider selectable via command line.
+// (should match .../hardware/interfaces/automotive/evs/1.1/default/ServiceNames.h)
+const static char kMockEnumeratorName[]  = "EvsEnumeratorHw-Mock";
+
diff --git a/evs/manager/1.1/VirtualCamera.cpp b/evs/manager/1.1/VirtualCamera.cpp
new file mode 100644
index 0000000..eb84446
--- /dev/null
+++ b/evs/manager/1.1/VirtualCamera.cpp
@@ -0,0 +1,835 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "VirtualCamera.h"
+#include "HalCamera.h"
+#include "Enumerator.h"
+
+#include <ui/GraphicBufferAllocator.h>
+#include <ui/GraphicBufferMapper.h>
+
+using ::android::hardware::automotive::evs::V1_0::DisplayState;
+
+
+namespace android {
+namespace automotive {
+namespace evs {
+namespace V1_1 {
+namespace implementation {
+
+
+VirtualCamera::VirtualCamera(const std::vector<sp<HalCamera>>& halCameras) :
+    mStreamState(STOPPED) {
+    for (auto&& cam : halCameras) {
+        mHalCamera.try_emplace(cam->getId(), cam);
+    }
+}
+
+
+VirtualCamera::~VirtualCamera() {
+    shutdown();
+}
+
+
+void VirtualCamera::shutdown() {
+    // In normal operation, the stream should already be stopped by the time we get here
+    if (mStreamState == RUNNING) {
+        // Note that if we hit this case, no terminating frame will be sent to the client,
+        // but they're probably already dead anyway.
+        ALOGW("Virtual camera being shutdown while stream is running");
+
+        // Tell the frame delivery pipeline we don't want any more frames
+        mStreamState = STOPPING;
+
+        for (auto&& [key, hwCamera] : mHalCamera) {
+            auto pHwCamera = hwCamera.promote();
+            if (pHwCamera == nullptr) {
+                ALOGW("Camera device %s is not alive.", key.c_str());
+                continue;
+            }
+
+            if (mFramesHeld[key].size() > 0) {
+                ALOGW("VirtualCamera destructing with frames in flight.");
+
+                // Return to the underlying hardware camera any buffers the client was holding
+                for (auto&& heldBuffer : mFramesHeld[key]) {
+                    // Tell our parent that we're done with this buffer
+                    pHwCamera->doneWithFrame(heldBuffer);
+                }
+                mFramesHeld[key].clear();
+            }
+
+            // Retire from a master client
+            pHwCamera->unsetMaster(this);
+
+            // Give the underlying hardware camera the heads up that it might be time to stop
+            pHwCamera->clientStreamEnding(this);
+        }
+
+        // Join a capture thread
+        if (mCaptureThread.joinable()) {
+            mCaptureThread.join();
+        }
+
+        mFramesHeld.clear();
+    }
+
+    // Drop our reference to our associated hardware camera
+    mHalCamera.clear();
+}
+
+
+std::vector<sp<HalCamera>> VirtualCamera::getHalCameras() {
+    std::vector<sp<HalCamera>> cameras;
+    for (auto&& [key, cam] : mHalCamera) {
+        auto ptr = cam.promote();
+        if (ptr != nullptr) {
+            cameras.emplace_back(ptr);
+        }
+    }
+
+    return cameras;
+}
+
+
+bool VirtualCamera::deliverFrame(const BufferDesc_1_1& bufDesc) {
+    if (mStreamState == STOPPED) {
+        // A stopped stream gets no frames
+        ALOGE("A stopped stream should not get any frames");
+        return false;
+    } else if (mFramesHeld[bufDesc.deviceId].size() >= mFramesAllowed) {
+        // Indicate that we declined to send the frame to the client because they're at quota
+        ALOGI("Skipping new frame as we hold %zu of %u allowed.",
+              mFramesHeld[bufDesc.deviceId].size(), mFramesAllowed);
+
+        if (mStream_1_1 != nullptr) {
+            // Report a frame drop to v1.1 client.
+            EvsEventDesc event;
+            event.deviceId = bufDesc.deviceId;
+            event.aType = EvsEventType::FRAME_DROPPED;
+            auto result = mStream_1_1->notify(event);
+            if (!result.isOk()) {
+                ALOGE("Error delivering end of stream event");
+            }
+        }
+
+        return false;
+    } else {
+        // Keep a record of this frame so we can clean up if we have to in case of client death
+        mFramesHeld[bufDesc.deviceId].emplace_back(bufDesc);
+
+        // v1.0 client uses an old frame-delivery mechanism.
+        if (mStream_1_1 == nullptr) {
+            // Forward a frame to v1.0 client
+            BufferDesc_1_0 frame_1_0 = {};
+            const AHardwareBuffer_Desc* pDesc =
+                reinterpret_cast<const AHardwareBuffer_Desc *>(&bufDesc.buffer.description);
+            frame_1_0.width     = pDesc->width;
+            frame_1_0.height    = pDesc->height;
+            frame_1_0.format    = pDesc->format;
+            frame_1_0.usage     = pDesc->usage;
+            frame_1_0.stride    = pDesc->stride;
+            frame_1_0.memHandle = bufDesc.buffer.nativeHandle;
+            frame_1_0.pixelSize = bufDesc.pixelSize;
+            frame_1_0.bufferId  = bufDesc.bufferId;
+
+            mStream->deliverFrame(frame_1_0);
+        }
+
+        return true;
+    }
+}
+
+
+bool VirtualCamera::notify(const EvsEventDesc& event) {
+    switch(event.aType) {
+        case EvsEventType::STREAM_STOPPED:
+            if (mStreamState != STOPPING) {
+                // Warn if we got an unexpected stream termination
+                ALOGW("Stream unexpectedly stopped, current status 0x%X", mStreamState);
+            }
+
+            // Mark the stream as stopped.
+            mStreamState = STOPPED;
+
+            if (mStream_1_1 == nullptr) {
+                // Send a null frame instead, for v1.0 client
+                BufferDesc_1_0 nullBuff = {};
+                auto result = mStream->deliverFrame(nullBuff);
+                if (!result.isOk()) {
+                    ALOGE("Error delivering end of stream marker");
+                }
+            }
+            break;
+
+        // v1.0 client will ignore all other events.
+        case EvsEventType::PARAMETER_CHANGED:
+            ALOGD("A camera parameter 0x%X is set to 0x%X", event.payload[0], event.payload[1]);
+            break;
+
+        case EvsEventType::MASTER_RELEASED:
+            ALOGD("The master client has been released");
+            break;
+
+        default:
+            ALOGE("Unknown event id 0x%X", event.aType);
+            break;
+    }
+
+    if (mStream_1_1 != nullptr) {
+        // Forward a received event to the v1.1 client
+        auto result = mStream_1_1->notify(event);
+        if (!result.isOk()) {
+            ALOGE("Failed to forward an event");
+            return false;
+        }
+    }
+
+    return true;
+}
+
+
+// Methods from ::android::hardware::automotive::evs::V1_0::IEvsCamera follow.
+Return<void> VirtualCamera::getCameraInfo(getCameraInfo_cb info_cb) {
+    // Straight pass through to hardware layer
+    auto halCamera = mHalCamera.begin()->second.promote();
+    if (halCamera != nullptr) {
+        return halCamera->getHwCamera()->getCameraInfo(info_cb);
+    } else {
+        CameraDesc nullCamera = {};
+        info_cb(nullCamera.v1);
+        return Void();
+    }
+}
+
+
+Return<EvsResult> VirtualCamera::setMaxFramesInFlight(uint32_t bufferCount) {
+    // How many buffers are we trying to add (or remove if negative)
+    int bufferCountChange = bufferCount - mFramesAllowed;
+
+    // Ask our parent for more buffers
+    bool result = true;
+    std::vector<sp<HalCamera>> changedCameras;
+    for (auto&& [key, hwCamera] : mHalCamera) {
+        auto pHwCam = hwCamera.promote();
+        if (pHwCam == nullptr) {
+            continue;
+        }
+
+        result = pHwCam->changeFramesInFlight(bufferCountChange);
+        if (!result) {
+            ALOGE("%s: Failed to change buffer count by %d to %d",
+                  key.c_str(), bufferCountChange, bufferCount);
+            break;
+        }
+
+        changedCameras.emplace_back(pHwCam);
+    }
+
+    // Update our notion of how many frames we're allowed
+    mFramesAllowed = bufferCount;
+
+    if (!result) {
+        // Rollback changes because we failed to update all cameras
+        for (auto&& hwCamera : changedCameras) {
+            ALOGW("Rollback a change on %s", hwCamera->getId().c_str());
+            hwCamera->changeFramesInFlight(-bufferCountChange);
+        }
+
+        // Restore the original buffer count
+        mFramesAllowed -= bufferCountChange;
+        return EvsResult::BUFFER_NOT_AVAILABLE;
+    } else {
+        return EvsResult::OK;
+    }
+}
+
+
+Return<EvsResult> VirtualCamera::startVideoStream(const ::android::sp<IEvsCameraStream_1_0>& stream)  {
+    // We only support a single stream at a time
+    if (mStreamState != STOPPED) {
+        ALOGE("ignoring startVideoStream call when a stream is already running.");
+        return EvsResult::STREAM_ALREADY_RUNNING;
+    }
+
+    // Validate our held frame count is starting out at zero as we expect
+    assert(mFramesHeld.size() == 0);
+
+    // Record the user's callback for use when we have a frame ready
+    mStream = stream;
+    mStream_1_1 = IEvsCameraStream_1_1::castFrom(stream).withDefault(nullptr);
+    if (mStream_1_1 == nullptr) {
+        ALOGI("Start video stream for v1.0 client.");
+    } else {
+        ALOGI("Start video stream for v1.1 client.");
+    }
+
+    mStreamState = RUNNING;
+
+    // Tell the underlying camera hardware that we want to stream
+    auto iter = mHalCamera.begin();
+    while (iter != mHalCamera.end()) {
+        auto pHwCamera = iter->second.promote();
+        if (pHwCamera == nullptr) {
+            ALOGE("Failed to start a video stream on %s", iter->first.c_str());
+            continue;
+        }
+
+        ALOGI("%s starts a video stream on %s", __FUNCTION__, iter->first.c_str());
+        Return<EvsResult> result = pHwCamera->clientStreamStarting();
+        if ((!result.isOk()) || (result != EvsResult::OK)) {
+            // If we failed to start the underlying stream, then we're not actually running
+            mStream = mStream_1_1 = nullptr;
+            mStreamState = STOPPED;
+
+            // Request to stop streams started by this client.
+            auto rb = mHalCamera.begin();
+            while (rb != iter) {
+                auto ptr = rb->second.promote();
+                if (ptr != nullptr) {
+                    ptr->clientStreamEnding(this);
+                }
+                ++rb;
+            }
+            return EvsResult::UNDERLYING_SERVICE_ERROR;
+        }
+        ++iter;
+    }
+
+    // Start a thread that waits on the fence and forwards collected frames
+    // to the v1.1 client.
+    if (mStream_1_1 != nullptr) {
+        mCaptureThread = std::thread([this]() {
+            // TODO(b/145466570): With a proper camera hang handler, we may want
+            // to reduce an amount of timeout.
+            constexpr int kFrameTimeoutMs = 5000; // timeout in ms.
+            int64_t lastFrameTimestamp = -1;
+            while (mStreamState == RUNNING) {
+                UniqueFence fence;
+                unsigned count = 0;
+                for (auto&& [key, hwCamera] : mHalCamera) {
+                    auto pHwCamera = hwCamera.promote();
+                    if (pHwCamera == nullptr) {
+                        ALOGW("Invalid camera %s is ignored.", key.c_str());
+                        continue;
+                    }
+
+                    UniqueFence another = pHwCamera->requestNewFrame(this, lastFrameTimestamp);
+                    if (!another) {
+                        ALOGW("%s returned an invalid fence.", key.c_str());
+                        continue;
+                    }
+
+                    fence = UniqueFence::Merge("MergedFrameFence",
+                                               fence,
+                                               another);
+                    ++count;
+                }
+
+                if (fence.Wait(kFrameTimeoutMs) < 0) {
+                    // TODO(b/145466570): Replace this temporarily camera hang
+                    // handler.
+                    ALOGE("%p: Camera hangs? %s", this, strerror(errno));
+                    break;
+                } else if (mStreamState == RUNNING) {
+                    // Fetch frames and forward to the client
+                    if (mFramesHeld.size() > 0 && mStream_1_1 != nullptr) {
+                        // Pass this buffer through to our client
+                        hardware::hidl_vec<BufferDesc_1_1> frames;
+                        frames.resize(count);
+                        unsigned i = 0;
+                        for (auto&& [key, hwCamera] : mHalCamera) {
+                            auto pHwCamera = hwCamera.promote();
+                            if (pHwCamera == nullptr) {
+                                continue;
+                            }
+
+                            const auto frame = mFramesHeld[key].back();
+                            if (frame.timestamp > lastFrameTimestamp) {
+                                lastFrameTimestamp = frame.timestamp;
+                            }
+                            frames[i++] = frame;
+                        }
+                        mStream_1_1->deliverFrame_1_1(frames);
+                    }
+                }
+            }
+        });
+    }
+
+    // TODO(changyeon):
+    // Detect and exit if we encounter a stalled stream or unresponsive driver?
+    // Consider using a timer and watching for frame arrival?
+
+    return EvsResult::OK;
+}
+
+
+Return<void> VirtualCamera::doneWithFrame(const BufferDesc_1_0& buffer) {
+    if (buffer.memHandle == nullptr) {
+        ALOGE("ignoring doneWithFrame called with invalid handle");
+    } else {
+        // Find this buffer in our "held" list
+        auto& frameQueue = mFramesHeld.begin()->second;
+        auto it = frameQueue.begin();
+        while (it != frameQueue.end()) {
+            if (it->bufferId == buffer.bufferId) {
+                // found it!
+                break;
+            }
+            ++it;
+        }
+        if (it == frameQueue.end()) {
+            // We should always find the frame in our "held" list
+            ALOGE("Ignoring doneWithFrame called with unrecognized frameID %d", buffer.bufferId);
+        } else {
+            // Take this frame out of our "held" list
+            frameQueue.erase(it);
+
+            // Tell our parent that we're done with this buffer
+            auto pHwCamera = mHalCamera.begin()->second.promote();
+            if (pHwCamera != nullptr) {
+                pHwCamera->doneWithFrame(buffer);
+            } else {
+                ALOGW("Possible memory leak because a device %s is not valid.",
+                      mHalCamera.begin()->first.c_str());
+            }
+        }
+    }
+
+    return Void();
+}
+
+
+Return<void> VirtualCamera::stopVideoStream()  {
+    if (mStreamState == RUNNING) {
+        // Tell the frame delivery pipeline we don't want any more frames
+        mStreamState = STOPPING;
+
+        // Deliver an empty frame to close out the frame stream
+        if (mStream_1_1 != nullptr) {
+            // v1.1 client waits for a stream stopped event
+            EvsEventDesc event;
+            event.aType = EvsEventType::STREAM_STOPPED;
+            auto result = mStream_1_1->notify(event);
+            if (!result.isOk()) {
+                ALOGE("Error delivering end of stream event");
+            }
+        } else {
+            // v1.0 client expects a null frame at the end of the stream
+            BufferDesc_1_0 nullBuff = {};
+            auto result = mStream->deliverFrame(nullBuff);
+            if (!result.isOk()) {
+                ALOGE("Error delivering end of stream marker");
+            }
+        }
+
+        // Since we are single threaded, no frame can be delivered while this function is running,
+        // so we can go directly to the STOPPED state here on the server.
+        // Note, however, that there still might be frames already queued that client will see
+        // after returning from the client side of this call.
+        mStreamState = STOPPED;
+
+        // Give the underlying hardware camera the heads up that it might be time to stop
+        for (auto&& [key, hwCamera] : mHalCamera) {
+            auto pHwCamera = hwCamera.promote();
+            if (pHwCamera != nullptr) {
+                pHwCamera->clientStreamEnding(this);
+            }
+        }
+
+        // Join a thread
+        if (mCaptureThread.joinable()) {
+            mCaptureThread.join();
+        }
+
+    }
+
+    return Void();
+}
+
+
+Return<int32_t> VirtualCamera::getExtendedInfo(uint32_t opaqueIdentifier)  {
+    if (mHalCamera.size() > 1) {
+        ALOGW("Logical camera device does not support %s", __FUNCTION__);
+        return 0;
+    }
+
+    // Pass straight through to the hardware device
+    auto pHwCamera = mHalCamera.begin()->second.promote();
+    if (pHwCamera != nullptr) {
+        return pHwCamera->getHwCamera()->getExtendedInfo(opaqueIdentifier);
+    } else {
+        ALOGW("%s is invalid.", mHalCamera.begin()->first.c_str());
+        return 0;
+    }
+}
+
+
+Return<EvsResult> VirtualCamera::setExtendedInfo(uint32_t opaqueIdentifier, int32_t opaqueValue)  {
+    if (mHalCamera.size() > 1) {
+        ALOGW("Logical camera device does not support %s", __FUNCTION__);
+        return EvsResult::INVALID_ARG;
+    }
+
+    // Pass straight through to the hardware device
+    auto pHwCamera = mHalCamera.begin()->second.promote();
+    if (pHwCamera != nullptr) {
+        return pHwCamera->getHwCamera()->setExtendedInfo(opaqueIdentifier, opaqueValue);
+    } else {
+        ALOGW("%s is invalid.", mHalCamera.begin()->first.c_str());
+        return EvsResult::INVALID_ARG;
+    }
+}
+
+
+// Methods from ::android::hardware::automotive::evs::V1_1::IEvsCamera follow.
+Return<void> VirtualCamera::getCameraInfo_1_1(getCameraInfo_1_1_cb info_cb) {
+    if (mHalCamera.size() > 1) {
+        info_cb(*mDesc);
+        return Void();
+    }
+
+    // Straight pass through to hardware layer
+    auto pHwCamera = mHalCamera.begin()->second.promote();
+    if (pHwCamera == nullptr) {
+        // Return an empty list
+        CameraDesc nullCamera = {};
+        info_cb(nullCamera);
+        return Void();
+    }
+
+    auto hwCamera_1_1 =
+        IEvsCamera_1_1::castFrom(pHwCamera->getHwCamera()).withDefault(nullptr);
+    if (hwCamera_1_1 != nullptr) {
+        return hwCamera_1_1->getCameraInfo_1_1(info_cb);
+    } else {
+        // Return an empty list
+        CameraDesc nullCamera = {};
+        info_cb(nullCamera);
+        return Void();
+    }
+}
+
+
+Return<void> VirtualCamera::getPhysicalCameraInfo(const hidl_string& deviceId,
+                                                  getPhysicalCameraInfo_cb info_cb) {
+    auto device = mHalCamera.find(deviceId);
+    if (device != mHalCamera.end()) {
+        // Straight pass through to hardware layer
+        auto pHwCamera = device->second.promote();
+        if (pHwCamera != nullptr) {
+            auto hwCamera_1_1 =
+                IEvsCamera_1_1::castFrom(pHwCamera->getHwCamera()).withDefault(nullptr);
+            if (hwCamera_1_1 != nullptr) {
+                return hwCamera_1_1->getCameraInfo_1_1(info_cb);
+            } else {
+                ALOGW("Failed to promote HW camera to v1.1.");
+            }
+        } else {
+            ALOGW("Camera device %s is not alive.", deviceId.c_str());
+        }
+    } else {
+        ALOGW("Requested device %s does not back this device!", deviceId.c_str());
+    }
+
+    // Return an empty list
+    CameraDesc nullCamera = {};
+    info_cb(nullCamera);
+    return Void();
+}
+
+
+Return<EvsResult> VirtualCamera::doneWithFrame_1_1(
+    const hardware::hidl_vec<BufferDesc_1_1>& buffers) {
+
+    for (auto&& buffer : buffers) {
+        if (buffer.buffer.nativeHandle == nullptr) {
+            ALOGW("ignoring doneWithFrame called with invalid handle");
+        } else {
+            // Find this buffer in our "held" list
+            auto it = mFramesHeld[buffer.deviceId].begin();
+            while (it != mFramesHeld[buffer.deviceId].end()) {
+                if (it->bufferId == buffer.bufferId) {
+                    // found it!
+                    break;
+                }
+                ++it;
+            }
+            if (it == mFramesHeld[buffer.deviceId].end()) {
+                // We should always find the frame in our "held" list
+                ALOGE("Ignoring doneWithFrame called with unrecognized frameID %d",
+                      buffer.bufferId);
+            } else {
+                // Take this frame out of our "held" list
+                mFramesHeld[buffer.deviceId].erase(it);
+
+                // Tell our parent that we're done with this buffer
+                auto pHwCamera = mHalCamera[buffer.deviceId].promote();
+                if (pHwCamera != nullptr) {
+                    pHwCamera->doneWithFrame(buffer);
+                } else {
+                    ALOGW("Possible memory leak; %s is not valid.", buffer.deviceId.c_str());
+                }
+            }
+        }
+    }
+
+    return EvsResult::OK;
+}
+
+
+Return<EvsResult> VirtualCamera::setMaster() {
+    if (mHalCamera.size() > 1) {
+        ALOGW("Logical camera device does not support %s.", __FUNCTION__);
+        return EvsResult::INVALID_ARG;
+    }
+
+    auto pHwCamera = mHalCamera.begin()->second.promote();
+    if (pHwCamera != nullptr) {
+        return pHwCamera->setMaster(this);
+    } else {
+        ALOGW("Camera device %s is not alive.", mHalCamera.begin()->first.c_str());
+        return EvsResult::INVALID_ARG;
+    }
+}
+
+
+Return<EvsResult> VirtualCamera::forceMaster(const sp<IEvsDisplay_1_0>& display) {
+    if (mHalCamera.size() > 1) {
+        ALOGW("Logical camera device does not support %s.", __FUNCTION__);
+        return EvsResult::INVALID_ARG;
+    }
+
+    if (display.get() == nullptr) {
+        ALOGE("%s: Passed display is invalid", __FUNCTION__);
+        return EvsResult::INVALID_ARG;
+    }
+
+    DisplayState state = display->getDisplayState();
+    if (state == DisplayState::NOT_OPEN ||
+        state == DisplayState::DEAD ||
+        state >= DisplayState::NUM_STATES) {
+        ALOGE("%s: Passed display is in invalid state", __FUNCTION__);
+        return EvsResult::INVALID_ARG;
+    }
+
+    auto pHwCamera = mHalCamera.begin()->second.promote();
+    if (pHwCamera != nullptr) {
+        return pHwCamera->forceMaster(this);
+    } else {
+        ALOGW("Camera device %s is not alive.", mHalCamera.begin()->first.c_str());
+        return EvsResult::INVALID_ARG;
+    }
+}
+
+
+Return<EvsResult> VirtualCamera::unsetMaster() {
+    if (mHalCamera.size() > 1) {
+        ALOGW("Logical camera device does not support %s.", __FUNCTION__);
+        return EvsResult::INVALID_ARG;
+    }
+
+    auto pHwCamera = mHalCamera.begin()->second.promote();
+    if (pHwCamera != nullptr) {
+        return pHwCamera->unsetMaster(this);
+    } else {
+        ALOGW("Camera device %s is not alive.", mHalCamera.begin()->first.c_str());
+        return EvsResult::INVALID_ARG;
+    }
+}
+
+
+Return<void> VirtualCamera::getParameterList(getParameterList_cb _hidl_cb) {
+    if (mHalCamera.size() > 1) {
+        ALOGW("Logical camera device does not support %s.", __FUNCTION__);
+
+        // Return an empty list
+        hardware::hidl_vec<CameraParam> emptyList;
+        _hidl_cb(emptyList);
+        return Void();
+    }
+
+    // Straight pass through to hardware layer
+    auto pHwCamera = mHalCamera.begin()->second.promote();
+    if (pHwCamera == nullptr) {
+        ALOGW("Camera device %s is not alive.", mHalCamera.begin()->first.c_str());
+
+        // Return an empty list
+        hardware::hidl_vec<CameraParam> emptyList;
+        _hidl_cb(emptyList);
+        return Void();
+    }
+
+    auto hwCamera_1_1 =
+        IEvsCamera_1_1::castFrom(pHwCamera->getHwCamera()).withDefault(nullptr);
+    if (hwCamera_1_1 != nullptr) {
+        return hwCamera_1_1->getParameterList(_hidl_cb);
+    } else {
+        ALOGW("Camera device %s does not support a parameter programming.",
+              mHalCamera.begin()->first.c_str());
+
+        // Return an empty list
+        hardware::hidl_vec<CameraParam> emptyList;
+        _hidl_cb(emptyList);
+        return Void();
+    }
+}
+
+
+Return<void> VirtualCamera::getIntParameterRange(CameraParam id,
+                                                 getIntParameterRange_cb _hidl_cb) {
+    if (mHalCamera.size() > 1) {
+        ALOGW("Logical camera device does not support %s.", __FUNCTION__);
+
+        // Return [0, 0, 0]
+        _hidl_cb(0, 0, 0);
+        return Void();
+    }
+
+    // Straight pass through to hardware layer
+    auto pHwCamera = mHalCamera.begin()->second.promote();
+    if (pHwCamera == nullptr) {
+        ALOGW("Camera device %s is not alive.", mHalCamera.begin()->first.c_str());
+
+        // Return [0, 0, 0]
+        _hidl_cb(0, 0, 0);
+        return Void();
+    }
+
+    auto hwCamera_1_1 =
+        IEvsCamera_1_1::castFrom(pHwCamera->getHwCamera()).withDefault(nullptr);
+    if (hwCamera_1_1 != nullptr) {
+        return hwCamera_1_1->getIntParameterRange(id, _hidl_cb);
+    } else {
+        ALOGW("Camera device %s does not support a parameter programming.",
+              mHalCamera.begin()->first.c_str());
+
+        // Return [0, 0, 0]
+        _hidl_cb(0, 0, 0);
+        return Void();
+    }
+    return Void();
+}
+
+
+Return<void> VirtualCamera::setIntParameter(CameraParam id,
+                                            int32_t value,
+                                            setIntParameter_cb _hidl_cb) {
+    hardware::hidl_vec<int32_t> values;
+    EvsResult status = EvsResult::INVALID_ARG;
+    if (mHalCamera.size() > 1) {
+        ALOGW("Logical camera device does not support %s.", __FUNCTION__);
+        _hidl_cb(status, values);
+        return Void();
+    }
+
+    auto pHwCamera = mHalCamera.begin()->second.promote();
+    if (pHwCamera == nullptr) {
+        ALOGW("Camera device %s is not alive.", mHalCamera.begin()->first.c_str());
+        _hidl_cb(status, values);
+        return Void();
+    }
+
+    status = pHwCamera->setParameter(this, id, value);
+
+    values.resize(1);
+    values[0] = value;
+    _hidl_cb(status, values);
+
+    return Void();
+}
+
+
+Return<void> VirtualCamera::getIntParameter(CameraParam id,
+                                            getIntParameter_cb _hidl_cb) {
+    hardware::hidl_vec<int32_t> values;
+    EvsResult status = EvsResult::INVALID_ARG;
+    if (mHalCamera.size() > 1) {
+        ALOGW("Logical camera device does not support %s.", __FUNCTION__);
+        _hidl_cb(status, values);
+        return Void();
+    }
+
+    auto pHwCamera = mHalCamera.begin()->second.promote();
+    if (pHwCamera == nullptr) {
+        ALOGW("Camera device %s is not alive.", mHalCamera.begin()->first.c_str());
+        _hidl_cb(status, values);
+        return Void();
+    }
+
+    int32_t value;
+    status = pHwCamera->getParameter(id, value);
+
+    values.resize(1);
+    values[0] = value;
+    _hidl_cb(status, values);
+
+    return Void();
+}
+
+
+Return<EvsResult> VirtualCamera::setExtendedInfo_1_1(uint32_t opaqueIdentifier,
+                                                     const hidl_vec<uint8_t>& opaqueValue) {
+    hardware::hidl_vec<int32_t> values;
+    if (mHalCamera.size() > 1) {
+        ALOGW("Logical camera device does not support %s.", __FUNCTION__);
+        return EvsResult::INVALID_ARG;
+    }
+
+    auto pHwCamera = mHalCamera.begin()->second.promote();
+    if (pHwCamera == nullptr) {
+        ALOGW("Camera device %s is not alive.", mHalCamera.begin()->first.c_str());
+        return EvsResult::INVALID_ARG;
+    } else {
+        auto hwCamera = IEvsCamera_1_1::castFrom(pHwCamera->getHwCamera()).withDefault(nullptr);
+        if (hwCamera != nullptr) {
+            return hwCamera->setExtendedInfo_1_1(opaqueIdentifier, opaqueValue);
+        } else {
+            ALOGE("Underlying hardware camera does not implement v1.1 interfaces.");
+            return EvsResult::INVALID_ARG;
+        }
+    }
+}
+
+
+Return<void> VirtualCamera::getExtendedInfo_1_1(uint32_t opaqueIdentifier,
+                                                getExtendedInfo_1_1_cb _hidl_cb) {
+    hardware::hidl_vec<uint8_t> values;
+    EvsResult status = EvsResult::INVALID_ARG;
+    auto pHwCamera = mHalCamera.begin()->second.promote();
+    if (pHwCamera == nullptr) {
+        ALOGW("Camera device %s is not alive.", mHalCamera.begin()->first.c_str());
+        _hidl_cb(status, values);
+    } else {
+        auto hwCamera = IEvsCamera_1_1::castFrom(pHwCamera->getHwCamera()).withDefault(nullptr);
+        if (hwCamera != nullptr) {
+            hwCamera->getExtendedInfo_1_1(opaqueIdentifier, _hidl_cb);
+        } else {
+            ALOGE("Underlying hardware camera does not implement v1.1 interfaces.");
+            _hidl_cb(status, values);
+        }
+    }
+
+    return Void();
+}
+
+
+} // namespace implementation
+} // namespace V1_1
+} // namespace evs
+} // namespace automotive
+} // namespace android
diff --git a/evs/manager/1.1/VirtualCamera.h b/evs/manager/1.1/VirtualCamera.h
new file mode 100644
index 0000000..63744d9
--- /dev/null
+++ b/evs/manager/1.1/VirtualCamera.h
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_AUTOMOTIVE_EVS_V1_1_CAMERAPROXY_H
+#define ANDROID_AUTOMOTIVE_EVS_V1_1_CAMERAPROXY_H
+
+#include <android/hardware/automotive/evs/1.1/types.h>
+#include <android/hardware/automotive/evs/1.1/IEvsCamera.h>
+#include <android/hardware/automotive/evs/1.1/IEvsCameraStream.h>
+#include <android/hardware/automotive/evs/1.1/IEvsDisplay.h>
+#include <ui/GraphicBuffer.h>
+
+#include <thread>
+#include <deque>
+#include <unordered_map>
+
+
+using namespace std;
+using namespace ::android::hardware::automotive::evs::V1_1;
+using ::android::hardware::Return;
+using ::android::hardware::Void;
+using ::android::hardware::hidl_handle;
+using ::android::hardware::hidl_string;
+using ::android::hardware::hidl_vec;
+using ::android::hardware::automotive::evs::V1_0::EvsResult;
+using ::android::hardware::automotive::evs::V1_0::IEvsDisplay;
+using BufferDesc_1_0 = ::android::hardware::automotive::evs::V1_0::BufferDesc;
+using BufferDesc_1_1 = ::android::hardware::automotive::evs::V1_1::BufferDesc;
+using IEvsCamera_1_0 = ::android::hardware::automotive::evs::V1_0::IEvsCamera;
+using IEvsCamera_1_1 = ::android::hardware::automotive::evs::V1_1::IEvsCamera;
+using IEvsCameraStream_1_0 = ::android::hardware::automotive::evs::V1_0::IEvsCameraStream;
+using IEvsCameraStream_1_1 = ::android::hardware::automotive::evs::V1_1::IEvsCameraStream;
+using IEvsDisplay_1_0 = ::android::hardware::automotive::evs::V1_0::IEvsDisplay;
+using IEvsDisplay_1_1 = ::android::hardware::automotive::evs::V1_1::IEvsDisplay;
+
+namespace android {
+namespace automotive {
+namespace evs {
+namespace V1_1 {
+namespace implementation {
+
+
+class HalCamera;        // From HalCamera.h
+
+
+// This class represents an EVS camera to the client application.  As such it presents
+// the IEvsCamera interface, and also proxies the frame delivery to the client's
+// IEvsCameraStream object.
+class VirtualCamera : public IEvsCamera_1_1 {
+public:
+    explicit          VirtualCamera(const std::vector<sp<HalCamera>>& halCameras);
+    virtual           ~VirtualCamera();
+
+    unsigned          getAllowedBuffers() { return mFramesAllowed; };
+    bool              isStreaming()       { return mStreamState == RUNNING; }
+    bool              getVersion() const  { return (int)(mStream_1_1 != nullptr); }
+    vector<sp<HalCamera>>
+                      getHalCameras();
+    void              setDescriptor(CameraDesc* desc) { mDesc = desc; }
+
+    // Proxy to receive frames and forward them to the client's stream
+    bool              notify(const EvsEventDesc& event);
+    bool              deliverFrame(const BufferDesc& bufDesc);
+
+    // Methods from ::android::hardware::automotive::evs::V1_0::IEvsCamera follow.
+    Return<void>      getCameraInfo(getCameraInfo_cb _hidl_cb)  override;
+    Return<EvsResult> setMaxFramesInFlight(uint32_t bufferCount) override;
+    Return<EvsResult> startVideoStream(const ::android::sp<IEvsCameraStream_1_0>& stream) override;
+    Return<void>      doneWithFrame(const BufferDesc_1_0& buffer) override;
+    Return<void>      stopVideoStream() override;
+    Return<int32_t>   getExtendedInfo(uint32_t opaqueIdentifier) override;
+    Return<EvsResult> setExtendedInfo(uint32_t opaqueIdentifier, int32_t opaqueValue) override;
+
+    // Methods from ::android::hardware::automotive::evs::V1_1::IEvsCamera follow.
+    Return<void>      getCameraInfo_1_1(getCameraInfo_1_1_cb _hidl_cb)  override;
+    Return<void>      getPhysicalCameraInfo(const hidl_string& deviceId,
+                                            getPhysicalCameraInfo_cb _hidl_cb)  override;
+    Return<EvsResult> doneWithFrame_1_1(const hardware::hidl_vec<BufferDesc_1_1>& buffer) override;
+    Return<EvsResult> pauseVideoStream() override { return EvsResult::UNDERLYING_SERVICE_ERROR; }
+    Return<EvsResult> resumeVideoStream() override { return EvsResult::UNDERLYING_SERVICE_ERROR; }
+    Return<EvsResult> setMaster() override;
+    Return<EvsResult> forceMaster(const sp<IEvsDisplay_1_0>& display) override;
+    Return<EvsResult> unsetMaster() override;
+    Return<void>      getParameterList(getParameterList_cb _hidl_cb) override;
+    Return<void>      getIntParameterRange(CameraParam id,
+                                           getIntParameterRange_cb _hidl_cb) override;
+    Return<void>      setIntParameter(CameraParam id, int32_t value,
+                                      setIntParameter_cb _hidl_cb) override;
+    Return<void>      getIntParameter(CameraParam id,
+                                      getIntParameter_cb _hidl_cb) override;
+    Return<EvsResult> setExtendedInfo_1_1(uint32_t opaqueIdentifier,
+                                          const hidl_vec<uint8_t>& opaqueValue) override;
+    Return<void>      getExtendedInfo_1_1(uint32_t opaqueIdentifier,
+                                          getExtendedInfo_1_1_cb _hidl_cb) override;
+
+
+
+private:
+    void shutdown();
+
+    // The low level camera interface that backs this proxy
+    unordered_map<string,
+                 wp<HalCamera>> mHalCamera;
+
+    sp<IEvsCameraStream_1_0>    mStream;
+    sp<IEvsCameraStream_1_1>    mStream_1_1;
+
+    unsigned                    mFramesAllowed  = 1;
+    enum {
+        STOPPED,
+        RUNNING,
+        STOPPING,
+    }                           mStreamState;
+
+    unordered_map<string,
+         deque<BufferDesc_1_1>> mFramesHeld;
+    thread                      mCaptureThread;
+    CameraDesc*                 mDesc;
+};
+
+} // namespace implementation
+} // namespace V1_1
+} // namespace evs
+} // namespace automotive
+} // namespace android
+
+#endif  // ANDROID_AUTOMOTIVE_EVS_V1_1_CAMERAPROXY_H
diff --git a/evs/manager/1.1/android.automotive.evs.manager@1.1.rc b/evs/manager/1.1/android.automotive.evs.manager@1.1.rc
new file mode 100644
index 0000000..41212e2
--- /dev/null
+++ b/evs/manager/1.1/android.automotive.evs.manager@1.1.rc
@@ -0,0 +1,7 @@
+service evs_manager /system/bin/android.automotive.evs.manager@1.1
+    class hal
+    priority -20
+    user automotive_evs
+    group automotive_evs system
+    onrestart restart evs_app
+    disabled # will not automatically start with its class; must be explictly started.
diff --git a/evs/manager/1.1/service.cpp b/evs/manager/1.1/service.cpp
new file mode 100644
index 0000000..ac471e5
--- /dev/null
+++ b/evs/manager/1.1/service.cpp
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <unistd.h>
+
+#include <hidl/HidlTransportSupport.h>
+#include <utils/Errors.h>
+#include <utils/StrongPointer.h>
+#include <utils/Log.h>
+
+#include "ServiceNames.h"
+#include "Enumerator.h"
+
+
+// libhidl:
+using android::hardware::configureRpcThreadpool;
+using android::hardware::joinRpcThreadpool;
+
+// Generated HIDL files
+using android::hardware::automotive::evs::V1_1::IEvsEnumerator;
+using android::hardware::automotive::evs::V1_0::IEvsDisplay;
+
+// The namespace in which all our implementation code lives
+using namespace android::automotive::evs::V1_1::implementation;
+using namespace android;
+
+
+static void startService(const char *hardwareServiceName, const char * managerServiceName) {
+    ALOGI("EVS managed service connecting to hardware service at %s", hardwareServiceName);
+    android::sp<Enumerator> service = new Enumerator();
+    if (!service->init(hardwareServiceName)) {
+        ALOGE("Failed to connect to hardware service - quitting from registrationThread");
+        exit(1);
+    }
+
+    // Register our service -- if somebody is already registered by our name,
+    // they will be killed (their thread pool will throw an exception).
+    ALOGI("EVS managed service is starting as %s", managerServiceName);
+    status_t status = service->registerAsService(managerServiceName);
+    if (status != OK) {
+        ALOGE("Could not register service %s (%d) - quitting from registrationThread",
+              managerServiceName, status);
+        exit(2);
+    }
+
+    ALOGD("Registration complete");
+}
+
+
+int main(int argc, char** argv) {
+    ALOGI("EVS manager starting\n");
+
+    // Set up default behavior, then check for command line options
+    bool printHelp = false;
+    const char* evsHardwareServiceName = kHardwareEnumeratorName;
+    for (int i=1; i< argc; i++) {
+        if (strcmp(argv[i], "--mock") == 0) {
+            evsHardwareServiceName = kMockEnumeratorName;
+        } else if (strcmp(argv[i], "--target") == 0) {
+            i++;
+            if (i >= argc) {
+                ALOGE("--target <service> was not provided with a service name\n");
+            } else {
+                evsHardwareServiceName = argv[i];
+            }
+        } else if (strcmp(argv[i], "--help") == 0) {
+            printHelp = true;
+        } else {
+            printf("Ignoring unrecognized command line arg '%s'\n", argv[i]);
+            printHelp = true;
+        }
+    }
+    if (printHelp) {
+        printf("Options include:\n");
+        printf("  --mock                   Connect to the mock driver at EvsEnumeratorHw-Mock\n");
+        printf("  --target <service_name>  Connect to the named IEvsEnumerator service");
+    }
+
+
+    // Prepare the RPC serving thread pool.  We're configuring it with no additional
+    // threads beyond the main thread which will "join" the pool below.
+    configureRpcThreadpool(1, true /* callerWillJoin */);
+
+    // The connection to the underlying hardware service must happen on a dedicated thread to ensure
+    // that the hwbinder response can be processed by the thread pool without blocking.
+    std::thread registrationThread(startService, evsHardwareServiceName, kManagedEnumeratorName);
+
+    // Send this main thread to become a permanent part of the thread pool.
+    // This is not expected to return.
+    ALOGD("Main thread entering thread pool");
+    joinRpcThreadpool();
+
+    // In normal operation, we don't expect the thread pool to exit
+    ALOGE("EVS Hardware Enumerator is shutting down");
+    return 1;
+}
diff --git a/evs/manager/1.1/sync/unique_fd.cpp b/evs/manager/1.1/sync/unique_fd.cpp
new file mode 100644
index 0000000..49aa876
--- /dev/null
+++ b/evs/manager/1.1/sync/unique_fd.cpp
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "unique_fd.h"
+
+#include <errno.h>
+#include <string.h>
+
+#include <cutils/log.h>
+
+namespace android {
+namespace automotive {
+namespace evs {
+namespace V1_1 {
+namespace implementation {
+
+UniqueFd::UniqueFd() : fd_(-1) {}
+
+UniqueFd::UniqueFd(int fd) : fd_(fd) {
+}
+
+UniqueFd::~UniqueFd() {
+    InternalClose();
+}
+
+UniqueFd::UniqueFd(UniqueFd&& other) : fd_(other.fd_) {
+    other.fd_ = -1;
+}
+
+UniqueFd& UniqueFd::operator=(UniqueFd&& other) {
+    InternalClose();
+    fd_ = other.fd_;
+    other.fd_ = -1;
+    return *this;
+}
+
+void UniqueFd::Reset(int new_fd) {
+    InternalClose();
+    fd_ = new_fd;
+}
+
+UniqueFd UniqueFd::Dup() const {
+    return (fd_ >= 0) ? UniqueFd(InternalDup()) : UniqueFd(fd_);
+}
+
+UniqueFd::operator bool() const {
+    return fd_ >= 0;
+}
+
+int UniqueFd::Get() const {
+    return fd_;
+}
+
+int UniqueFd::GetUnowned() const {
+    return InternalDup();
+}
+
+int UniqueFd::Release() {
+    int ret = fd_;
+    fd_ = -1;
+    return ret;
+}
+
+void UniqueFd::InternalClose() {
+    if (fd_ >= 0) {
+        int err = close(fd_);
+        LOG_ALWAYS_FATAL_IF(err < 0, "Error closing UniqueFd -- %s", strerror(errno));
+    }
+    fd_ = -1;
+}
+
+int UniqueFd::InternalDup() const {
+    int new_fd = fd_ >= 0 ? dup(fd_) : fd_;
+    LOG_ALWAYS_FATAL_IF(new_fd < 0 && fd_ >= 0, "Error duplicating UniqueFd -- %s",
+                        strerror(errno));
+    return new_fd;
+}
+
+}  // namespace implementation
+}  // namespace V1_1
+}  // namespace evs
+}  // namespace automotive
+}  // namespace android
diff --git a/evs/manager/1.1/sync/unique_fd.h b/evs/manager/1.1/sync/unique_fd.h
new file mode 100644
index 0000000..204a5fc
--- /dev/null
+++ b/evs/manager/1.1/sync/unique_fd.h
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+namespace android {
+namespace automotive {
+namespace evs {
+namespace V1_1 {
+namespace implementation {
+
+// This is a simple C++ wrapper around a POSIX file descriptor. It is meant to
+// enforce ownership just like unique_ptr<T>
+//
+// Instances of this type cannot be copied, but they can be moved.
+class UniqueFd {
+public:
+    UniqueFd();
+    explicit UniqueFd(int fd);
+    ~UniqueFd();
+    UniqueFd(UniqueFd&&);
+    UniqueFd& operator=(UniqueFd&&);
+
+    // Destroy the current descriptor, and take ownership of a new one.
+    void Reset(int new_fd = -1);
+
+    // Duplicate the current descriptor.
+    UniqueFd Dup() const;
+
+    // Returns true if the descriptor is valid. False otherwise.
+    explicit operator bool() const;
+
+    // Gets the descriptor
+    int Get() const;
+
+    // Gets a unowned duplicate of the descriptor. The caller is responsible for
+    // closing it.
+    int GetUnowned() const;
+
+    // Gets the descriptor and releases ownership. The caller is responsible for
+    // closing it.
+    int Release();
+
+private:
+    UniqueFd(const UniqueFd&) = delete;
+    UniqueFd& operator=(const UniqueFd&) = delete;
+
+    void InternalClose();
+    int InternalDup() const;
+
+    int fd_;
+};
+
+}  // namespace implementation
+}  // namespace V1_1
+}  // namespace evs
+}  // namespace automotive
+}  // namespace android
+
diff --git a/evs/manager/1.1/sync/unique_fence.cpp b/evs/manager/1.1/sync/unique_fence.cpp
new file mode 100644
index 0000000..467b368
--- /dev/null
+++ b/evs/manager/1.1/sync/unique_fence.cpp
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "unique_fence.h"
+
+#include <errno.h>
+#include <cinttypes>
+#include <cstring>
+#include <memory>
+#include <string>
+
+#include <cutils/log.h>
+#ifdef __clang__
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wzero-length-array"
+#endif  // __clang__
+#include <sync/sync.h>
+#ifdef __clang__
+#pragma clang diagnostic pop
+#endif  // __clang__
+#include <utils/String8.h>
+
+constexpr int kWarningTimeout = 2000;
+
+namespace android {
+namespace automotive {
+namespace evs {
+namespace V1_1 {
+namespace implementation {
+
+namespace {
+
+const char* GetStatusString(int status) {
+    if (status == 0) {
+        return "active";
+    } else if (status == 1) {
+        return "signaled";
+    } else {
+        return "error";
+    }
+}
+
+}  // namespace
+
+UniqueFence::UniqueFence() {}
+
+UniqueFence::UniqueFence(int fd) : fd_(fd) {}
+
+UniqueFence::UniqueFence(UniqueFence&& other) = default;
+UniqueFence& UniqueFence::operator=(UniqueFence&& other) = default;
+
+void UniqueFence::Reset() {
+    fd_.Reset();
+}
+
+UniqueFence UniqueFence::Dup() const {
+    return UniqueFence(fd_.GetUnowned());
+}
+
+int UniqueFence::Get() const {
+    return fd_.Get();
+}
+
+int UniqueFence::GetUnowned() const {
+    return fd_.GetUnowned();
+}
+
+UniqueFence::operator bool() const {
+    return static_cast<bool>(fd_);
+}
+
+void UniqueFence::GetDebugStateDump(String8& result) const {
+    constexpr int INDENT = 8;
+    struct sync_file_info* finfo = sync_file_info(fd_.Get());
+    if (finfo == nullptr) {
+        result.append("no debug info available");
+        return;
+    }
+    result.appendFormat("name: %s status: %d (%s)", finfo->name, finfo->status,
+                        GetStatusString(finfo->status));
+
+    struct sync_fence_info* pinfo = sync_get_fence_info(finfo);
+    for (uint32_t i = 0; i < finfo->num_fences; i++) {
+        result.appendFormat("\n%*spt %u driver: %s obj: %s: status: %d(%s) timestamp: %llu", INDENT,
+                            "", i, pinfo[i].driver_name, pinfo[i].obj_name, pinfo[i].status,
+                            GetStatusString(pinfo[i].status), pinfo[i].timestamp_ns);
+    }
+    sync_file_info_free(finfo);
+}
+
+int UniqueFence::Wait(int wait_time_ms) {
+    if (wait_time_ms == -1) {
+        int err = sync_wait(fd_.Get(), kWarningTimeout);
+        if (err >= 0 || errno != ETIME) return err;
+
+        String8 dump;
+        GetDebugStateDump(dump);
+        ALOGW("Waited on fence %d for %d ms. [%s]", fd_.Get(), kWarningTimeout, dump.string());
+    }
+    return sync_wait(fd_.Get(), wait_time_ms);
+}
+
+UniqueFence UniqueFence::Merge(const char* name, const UniqueFence& fence1,
+                               const UniqueFence& fence2) {
+    UniqueFence merged_fence;
+    if (fence1.fd_ || fence2.fd_) {
+        if (fence1.fd_ && fence2.fd_) {
+            merged_fence.fd_.Reset(sync_merge(name, fence1.fd_.Get(), fence2.fd_.Get()));
+        } else if (fence1.fd_) {
+            // We merge the fence with itself so that we always generate a fence with
+            // a new name.
+            merged_fence.fd_.Reset(sync_merge(name, fence1.fd_.Get(), fence1.fd_.Get()));
+        } else if (fence2.fd_) {
+            // We merge the fence with itself so that we always generate a fence with
+            // a new name.
+            merged_fence.fd_.Reset(sync_merge(name, fence2.fd_.Get(), fence2.fd_.Get()));
+        }
+        ALOGE_IF(!merged_fence.fd_, "merging fences: %s", strerror(errno));
+    }
+    return merged_fence;
+}
+
+}  // namespace implementation
+}  // namespace V1_1
+}  // namespace evs
+}  // namespace automotive
+}  // namespace android
diff --git a/evs/manager/1.1/sync/unique_fence.h b/evs/manager/1.1/sync/unique_fence.h
new file mode 100644
index 0000000..7dd9f91
--- /dev/null
+++ b/evs/manager/1.1/sync/unique_fence.h
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <utils/String8.h>
+
+#include "unique_fd.h"
+
+namespace android {
+namespace automotive {
+namespace evs {
+namespace V1_1 {
+namespace implementation {
+
+// This is a simple C++ wrapper around the sw_sync interface. It is used to
+// create and maintain sync fences created from a timeline.
+class UniqueFence {
+public:
+    UniqueFence();
+    explicit UniqueFence(int fd);
+
+    UniqueFence(UniqueFence&&);
+    UniqueFence& operator=(UniqueFence&&);
+
+    // Destroy the current fence.
+    void Reset();
+
+    // Duplicate the fence.
+    UniqueFence Dup() const;
+
+    // Gets the descriptor
+    int Get() const;
+
+    // Gets an unowned duplicate of the fence descriptor.
+    int GetUnowned() const;
+
+    // Returns true if the fence is set to a valid descriptor. False otherwise.
+    explicit operator bool() const;
+
+    // Waits on the fence for the indicated amount of time in milliseconds. The
+    // default value of -1 means to wait forever.
+    int Wait(int wait_time_ms = -1);
+
+    // Gets a string containing debug information for the fence.
+    void GetDebugStateDump(String8& result) const;
+
+    // Creates a new fence that signals when both input fences are signaled. Note
+    // that it is possible to merge multiple fences this way.
+    static UniqueFence Merge(const char* name, const UniqueFence& fence1,
+                             const UniqueFence& fence2);
+
+private:
+    // The fence file descriptor
+    UniqueFd fd_;
+};
+
+}  // namespace implementation
+}  // namespace V1_1
+}  // namespace evs
+}  // namespace automotive
+}  // namespace android
+
diff --git a/evs/manager/1.1/sync/unique_timeline.cpp b/evs/manager/1.1/sync/unique_timeline.cpp
new file mode 100644
index 0000000..c44ca60
--- /dev/null
+++ b/evs/manager/1.1/sync/unique_timeline.cpp
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "unique_timeline.h"
+
+#include <errno.h>
+#include <string.h>
+#include <limits>
+
+#include <cutils/log.h>
+#include <sw_sync.h>
+
+namespace android {
+namespace automotive {
+namespace evs {
+namespace V1_1 {
+namespace implementation {
+
+UniqueTimeline::UniqueTimeline(unsigned offset)
+      : fd_(sw_sync_timeline_create()), fence_counter_(offset) {
+    LOG_ALWAYS_FATAL_IF(!fd_, "Failed to create a timeline.");
+}
+
+UniqueTimeline::~UniqueTimeline() {
+    // Force any fences waiting on the timeline to be released by incrementing
+    // by the difference between the two counters. The sw_sync driver has
+    // changed behavior several times, and no longer releases fences when the
+    // timeline fd is closed. While at one point adding MAX_UINT worked (by
+    // adding MAX_INT with two separate calls), even that stopped working.
+    // (See b/35115489 for background)
+    BumpTimelineEventCounter(fence_counter_ - timeline_counter_);
+}
+
+bool UniqueTimeline::Supported() {
+    UniqueFd fd{sw_sync_timeline_create()};
+    return !!fd;
+}
+
+UniqueFence UniqueTimeline::CreateFence(const char* name) {
+    UniqueFence fence(sw_sync_fence_create(fd_.Get(), name, fence_counter_));
+    LOG_ALWAYS_FATAL_IF(!fence, "Cannot create fence -- %s", strerror(errno));
+    return fence;
+}
+
+void UniqueTimeline::BumpTimelineEventCounter() {
+    BumpTimelineEventCounter(1);
+}
+
+void UniqueTimeline::BumpTimelineEventCounter(unsigned count) {
+    timeline_counter_ += count;
+    int err = sw_sync_timeline_inc(fd_.Get(), count);
+    LOG_ALWAYS_FATAL_IF(err < 0, "Cannot bump timeline counter -- %s", strerror(errno));
+}
+
+}  // namespace implementation
+}  // namespace V1_1
+}  // namespace evs
+}  // namespace automotive
+}  // namespace android
diff --git a/evs/manager/1.1/sync/unique_timeline.h b/evs/manager/1.1/sync/unique_timeline.h
new file mode 100644
index 0000000..17c2703
--- /dev/null
+++ b/evs/manager/1.1/sync/unique_timeline.h
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include "unique_fd.h"
+#include "unique_fence.h"
+
+namespace android {
+namespace automotive {
+namespace evs {
+namespace V1_1 {
+namespace implementation {
+
+// This is a simple C++ wrapper around the sw_sync interface. It is used to
+// create sync fences using timeline semantics.
+//
+// The timeline has two counters, a fence event counter maintained here in this
+// class, and the timeline counter hidden in the driver. The one in the driver
+// is initialized to zero when creating the timeline, and the one here is
+// initialized to one. The counters are meant to be independently incremented.
+//
+// When the driver counter is incremented, all fences that were created with
+// counts after the previous value of the timeline counter, and before (and
+// including) the new value are signaled by the driver.
+//
+// All fences are signaled if the timeline is also destroyed.
+//
+// The typical uses of these fences is to acquire a fence for some future point
+// on the timeline, and incrementing the local fence event counter to
+// distinguish between separate events. Then later when the event actually
+// occurs you increment the drivers count.
+//
+// Since the fences are file descriptors, they can be easily sent to another
+// process, which can wait for them to signal without needing to define some
+// other IPC mechanism to communicate the event. If the fence is sent well in
+// advance, there should be minimal latency too.
+//
+// Instances of this class cannot be copied, but can be moved.
+class UniqueTimeline {
+public:
+    // Initializes the timeline, using the given initial_fence_couter value.
+    explicit UniqueTimeline(unsigned initial_fence_counter);
+
+    ~UniqueTimeline();
+
+    // Returns true if it is possible to create timelines.
+    static bool Supported();
+
+    // Creates a fence fd using the current value of the fence counter.
+    // A negative value is returned on error.
+    UniqueFence CreateFence(const char* name);
+
+    // Increments the counter used when creating fences
+    void BumpFenceEventCounter() { fence_counter_ += 1; }
+
+    // Increments the drivers version of the counter, signaling any fences in the
+    // range.
+    void BumpTimelineEventCounter();
+
+private:
+    void BumpTimelineEventCounter(unsigned);
+
+    // The timeline file descriptor.
+    UniqueFd fd_{-1};
+
+    // The counter used when creating fences on the timeline.
+    unsigned fence_counter_{0};
+
+    // The effective count for the timeline. The kernel driver has the actual
+    // value, we just track what it should be. If it ever becomes out of sync,
+    // it could be a problem for releasing fences on destruction.
+    unsigned timeline_counter_{0};
+};
+
+}  // namespace implementation
+}  // namespace V1_1
+}  // namespace evs
+}  // namespace automotive
+}  // namespace android
+
diff --git a/evs/manager/Android.mk b/evs/manager/Android.mk
deleted file mode 100644
index 3924562..0000000
--- a/evs/manager/Android.mk
+++ /dev/null
@@ -1,37 +0,0 @@
-LOCAL_PATH:= $(call my-dir)
-
-##################################
-include $(CLEAR_VARS)
-
-LOCAL_SRC_FILES := \
-    service.cpp \
-    Enumerator.cpp \
-    HalCamera.cpp \
-    VirtualCamera.cpp \
-    HalDisplay.cpp
-
-
-LOCAL_SHARED_LIBRARIES := \
-    libcutils \
-    liblog \
-    libutils \
-    libui \
-    libhidlbase \
-    libhidltransport \
-    libhardware \
-    android.hardware.automotive.evs@1.0 \
-    libhwbinder
-
-
-LOCAL_INIT_RC := android.automotive.evs.manager@1.0.rc
-
-LOCAL_MODULE := android.automotive.evs.manager@1.0
-
-LOCAL_MODULE_TAGS := optional
-LOCAL_STRIP_MODULE := keep_symbols
-
-LOCAL_CFLAGS += -DLOG_TAG=\"EvsManager\"
-LOCAL_CFLAGS += -DGL_GLEXT_PROTOTYPES -DEGL_EGLEXT_PROTOTYPES
-LOCAL_CFLAGS += -Wall -Werror -Wunused -Wunreachable-code
-
-include $(BUILD_EXECUTABLE)
diff --git a/evs/manager/ServiceNames.h b/evs/manager/ServiceNames.h
deleted file mode 100644
index fb87536..0000000
--- a/evs/manager/ServiceNames.h
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-
-// This is the name as which we'll register ourselves
-const static char kManagedEnumeratorName[] = "default";
-
-// This is the name of the hardware provider to which we'll bind by default
-const static char kHardwareEnumeratorName[]  = "EvsEnumeratorHw";
-
-// This is the name of the mock hardware provider selectable via command line.
-// (should match .../hardware/interfaces/automotive/evs/1.0/default/ServiceNames.h)
-const static char kMockEnumeratorName[]  = "EvsEnumeratorHw-Mock";
-
diff --git a/evs/manager/android.automotive.evs.manager@1.0.rc b/evs/manager/android.automotive.evs.manager@1.0.rc
deleted file mode 100644
index f1b58ed..0000000
--- a/evs/manager/android.automotive.evs.manager@1.0.rc
+++ /dev/null
@@ -1,7 +0,0 @@
-service evs_manager /system/bin/android.automotive.evs.manager@1.0
-    class hal
-    priority -20
-    user automotive_evs
-    group automotive_evs
-    onrestart restart evs_app
-    disabled # will not automatically start with its class; must be explictly started.
diff --git a/evs/sampleDriver/Android.bp b/evs/sampleDriver/Android.bp
new file mode 100644
index 0000000..b783822
--- /dev/null
+++ b/evs/sampleDriver/Android.bp
@@ -0,0 +1,98 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//
+
+
+//#################################
+cc_binary {
+    name: "android.hardware.automotive.evs@1.1-sample",
+
+    vendor: true,
+
+    srcs: [
+        "service.cpp",
+        "EvsEnumerator.cpp",
+        "EvsV4lCamera.cpp",
+        "EvsGlDisplay.cpp",
+        "GlWrapper.cpp",
+        "VideoCapture.cpp",
+        "bufferCopy.cpp",
+        "ConfigManager.cpp",
+        "ConfigManagerUtil.cpp",
+    ],
+
+    shared_libs: [
+        "android.hardware.automotive.evs@1.0",
+        "android.hardware.automotive.evs@1.1",
+        "android.hardware.camera.device@3.2",
+        "libui",
+        "libEGL",
+        "libGLESv2",
+        "libbase",
+        "libbinder",
+        "libcutils",
+        "libhardware",
+        "libhidlbase",
+        "liblog",
+        "libutils",
+        "libhardware_legacy",
+        "libcamera_metadata",
+        "libtinyxml2",
+        "libbufferqueueconverter",
+        "android.hidl.token@1.0-utils",
+        "android.frameworks.automotive.display@1.0",
+        "android.hardware.graphics.bufferqueue@1.0",
+        "android.hardware.graphics.bufferqueue@2.0",
+    ],
+
+    init_rc: ["android.hardware.automotive.evs@1.1-sample.rc"],
+
+    strip: {
+        keep_symbols: true,
+    },
+
+    cflags: ["-DLOG_TAG=\"EvsSampleDriver\""] + [
+        "-DGL_GLEXT_PROTOTYPES",
+        "-DEGL_EGLEXT_PROTOTYPES",
+    ] + [
+        "-Wall",
+        "-Werror",
+        "-Wunused",
+        "-Wunreachable-code",
+    ],
+
+    required: [
+        "evs_configuration.dtd",
+        "evs_sample_configuration.xml",
+    ],
+
+    include_dirs: [
+        "frameworks/native/include/",
+    ],
+}
+
+prebuilt_etc {
+    name: "evs_configuration.dtd",
+    soc_specific: true,
+    src: "resources/evs_configuration.dtd",
+    sub_dir: "automotive/evs",
+}
+
+prebuilt_etc {
+    name: "evs_sample_configuration.xml",
+    soc_specific: true,
+    src: "resources/evs_sample_configuration.xml",
+    sub_dir: "automotive/evs",
+}
diff --git a/evs/sampleDriver/Android.mk b/evs/sampleDriver/Android.mk
deleted file mode 100644
index 734feea..0000000
--- a/evs/sampleDriver/Android.mk
+++ /dev/null
@@ -1,47 +0,0 @@
-LOCAL_PATH:= $(call my-dir)
-
-##################################
-include $(CLEAR_VARS)
-
-LOCAL_SRC_FILES := \
-    service.cpp \
-    EvsEnumerator.cpp \
-    EvsV4lCamera.cpp \
-    EvsGlDisplay.cpp \
-    GlWrapper.cpp \
-    VideoCapture.cpp \
-    bufferCopy.cpp \
-
-
-LOCAL_SHARED_LIBRARIES := \
-    android.hardware.automotive.evs@1.0 \
-    libui \
-    libgui \
-    libEGL \
-    libGLESv2 \
-    libbase \
-    libbinder \
-    libcutils \
-    libhardware \
-    libhidlbase \
-    libhidltransport \
-    liblog \
-    libutils \
-    libhardware_legacy\
-    libhwbinder
-
-LOCAL_INIT_RC := android.hardware.automotive.evs@1.0-sample.rc
-
-LOCAL_MODULE := android.hardware.automotive.evs@1.0-sample
-
-LOCAL_MODULE_TAGS := optional
-LOCAL_STRIP_MODULE := keep_symbols
-
-LOCAL_CFLAGS += -DLOG_TAG=\"EvsSampleDriver\"
-LOCAL_CFLAGS += -DGL_GLEXT_PROTOTYPES -DEGL_EGLEXT_PROTOTYPES
-LOCAL_CFLAGS += -Wall -Werror -Wunused -Wunreachable-code
-
-# NOTE:  It can be helpful, while debugging, to disable optimizations
-#LOCAL_CFLAGS += -O0 -g
-
-include $(BUILD_EXECUTABLE)
diff --git a/evs/sampleDriver/ConfigManager.cpp b/evs/sampleDriver/ConfigManager.cpp
new file mode 100644
index 0000000..48019db
--- /dev/null
+++ b/evs/sampleDriver/ConfigManager.cpp
@@ -0,0 +1,1070 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <sstream>
+#include <fstream>
+#include <thread>
+
+#include <hardware/gralloc.h>
+#include <utils/SystemClock.h>
+#include <android/hardware/camera/device/3.2/ICameraDevice.h>
+
+#include "ConfigManager.h"
+
+using ::android::hardware::camera::device::V3_2::StreamRotation;
+
+
+ConfigManager::~ConfigManager() {
+    /* Nothing to do */
+}
+
+
+void ConfigManager::printElementNames(const XMLElement *rootElem,
+                                      string prefix) const {
+    const XMLElement *curElem = rootElem;
+
+    while (curElem != nullptr) {
+        ALOGV("[ELEM] %s%s", prefix.c_str(), curElem->Name());
+        const XMLAttribute *curAttr = curElem->FirstAttribute();
+        while (curAttr) {
+            ALOGV("[ATTR] %s%s: %s",
+                  prefix.c_str(), curAttr->Name(), curAttr->Value());
+            curAttr = curAttr->Next();
+        }
+
+        /* recursively go down to descendants */
+        printElementNames(curElem->FirstChildElement(), prefix + "\t");
+
+        curElem = curElem->NextSiblingElement();
+    }
+}
+
+
+void ConfigManager::readCameraInfo(const XMLElement * const aCameraElem) {
+    if (aCameraElem == nullptr) {
+        ALOGW("XML file does not have required camera element");
+        return;
+    }
+
+    const XMLElement *curElem = aCameraElem->FirstChildElement();
+    while (curElem != nullptr) {
+        if (!strcmp(curElem->Name(), "group")) {
+            /* camera group identifier */
+            const char *id = curElem->FindAttribute("id")->Value();
+
+            /* create a camera group to be filled */
+            CameraGroupInfo *aCamera = new CameraGroupInfo();
+
+            /* read camera device information */
+            if (!readCameraDeviceInfo(aCamera, curElem)) {
+                ALOGW("Failed to read a camera information of %s", id);
+                delete aCamera;
+                continue;
+            }
+
+            /* camera group synchronization */
+            const char *sync = curElem->FindAttribute("synchronized")->Value();
+            if (!strcmp(sync, "CALIBRATED")) {
+                aCamera->synchronized =
+                    ANDROID_LOGICAL_MULTI_CAMERA_SENSOR_SYNC_TYPE_CALIBRATED;
+            } else if (!strcmp(sync, "APPROXIMATE")) {
+                aCamera->synchronized =
+                    ANDROID_LOGICAL_MULTI_CAMERA_SENSOR_SYNC_TYPE_APPROXIMATE;
+            } else {
+                aCamera->synchronized = 0; // Not synchronized
+            }
+
+            /* add a group to hash map */
+            mCameraGroups.insert_or_assign(id, unique_ptr<CameraGroupInfo>(aCamera));
+        } else if (!strcmp(curElem->Name(), "device")) {
+            /* camera unique identifier */
+            const char *id = curElem->FindAttribute("id")->Value();
+
+            /* camera mount location */
+            const char *pos = curElem->FindAttribute("position")->Value();
+
+            /* create a camera device to be filled */
+            CameraInfo *aCamera = new CameraInfo();
+
+            /* read camera device information */
+            if (!readCameraDeviceInfo(aCamera, curElem)) {
+                ALOGW("Failed to read a camera information of %s", id);
+                delete aCamera;
+                continue;
+            }
+
+            /* store read camera module information */
+            mCameraInfo.insert_or_assign(id, unique_ptr<CameraInfo>(aCamera));
+
+            /* assign a camera device to a position group */
+            mCameraPosition[pos].emplace(id);
+        } else {
+            /* ignore other device types */
+            ALOGD("Unknown element %s is ignored", curElem->Name());
+        }
+
+        curElem = curElem->NextSiblingElement();
+    }
+}
+
+
+bool
+ConfigManager::readCameraDeviceInfo(CameraInfo *aCamera,
+                                    const XMLElement *aDeviceElem) {
+    if (aCamera == nullptr || aDeviceElem == nullptr) {
+        return false;
+    }
+
+    /* size information to allocate camera_metadata_t */
+    size_t totalEntries = 0;
+    size_t totalDataSize = 0;
+
+    /* read device capabilities */
+    totalEntries +=
+        readCameraCapabilities(aDeviceElem->FirstChildElement("caps"),
+                               aCamera,
+                               totalDataSize);
+
+
+    /* read camera metadata */
+    totalEntries +=
+        readCameraMetadata(aDeviceElem->FirstChildElement("characteristics"),
+                           aCamera,
+                           totalDataSize);
+
+    /* construct camera_metadata_t */
+    if (!constructCameraMetadata(aCamera, totalEntries, totalDataSize)) {
+        ALOGW("Either failed to allocate memory or "
+              "allocated memory was not large enough");
+    }
+
+    return true;
+}
+
+
+size_t
+ConfigManager::readCameraCapabilities(const XMLElement * const aCapElem,
+                                      CameraInfo *aCamera,
+                                      size_t &dataSize) {
+    if (aCapElem == nullptr || aCamera == nullptr) {
+        return 0;
+    }
+
+    string token;
+    const XMLElement *curElem = nullptr;
+
+    /* a list of supported camera parameters/controls */
+    curElem = aCapElem->FirstChildElement("supported_controls");
+    if (curElem != nullptr) {
+        const XMLElement *ctrlElem = curElem->FirstChildElement("control");
+        while (ctrlElem != nullptr) {
+            const char *nameAttr = ctrlElem->FindAttribute("name")->Value();;
+            const int32_t minVal = stoi(ctrlElem->FindAttribute("min")->Value());
+            const int32_t maxVal = stoi(ctrlElem->FindAttribute("max")->Value());
+
+            int32_t stepVal = 1;
+            const XMLAttribute *stepAttr = ctrlElem->FindAttribute("step");
+            if (stepAttr != nullptr) {
+                stepVal = stoi(stepAttr->Value());
+            }
+
+            CameraParam aParam;
+            if (ConfigManagerUtil::convertToEvsCameraParam(nameAttr,
+                                                           aParam)) {
+                aCamera->controls.emplace(
+                    aParam,
+                    make_tuple(minVal, maxVal, stepVal)
+                );
+            }
+
+            ctrlElem = ctrlElem->NextSiblingElement("control");
+        }
+    }
+
+    /* a list of camera stream configurations */
+    curElem = aCapElem->FirstChildElement("stream");
+    while (curElem != nullptr) {
+        /* read 5 attributes */
+        const XMLAttribute *idAttr     = curElem->FindAttribute("id");
+        const XMLAttribute *widthAttr  = curElem->FindAttribute("width");
+        const XMLAttribute *heightAttr = curElem->FindAttribute("height");
+        const XMLAttribute *fmtAttr    = curElem->FindAttribute("format");
+        const XMLAttribute *fpsAttr    = curElem->FindAttribute("framerate");
+
+        const int32_t id = stoi(idAttr->Value());
+        int32_t framerate = 0;
+        if (fpsAttr != nullptr) {
+            framerate = stoi(fpsAttr->Value());
+        }
+
+        int32_t pixFormat;
+        if (ConfigManagerUtil::convertToPixelFormat(fmtAttr->Value(),
+                                                    pixFormat)) {
+            RawStreamConfiguration cfg = {
+                id,
+                stoi(widthAttr->Value()),
+                stoi(heightAttr->Value()),
+                pixFormat,
+                ANDROID_SCALER_AVAILABLE_STREAM_CONFIGURATIONS_OUTPUT,
+                framerate
+            };
+            aCamera->streamConfigurations.insert_or_assign(id, cfg);
+        }
+
+        curElem = curElem->NextSiblingElement("stream");
+    }
+
+    dataSize = calculate_camera_metadata_entry_data_size(
+                   get_camera_metadata_tag_type(
+                       ANDROID_SCALER_AVAILABLE_STREAM_CONFIGURATIONS
+                   ),
+                   aCamera->streamConfigurations.size() * kStreamCfgSz
+               );
+
+    /* a single camera metadata entry contains multiple stream configurations */
+    return dataSize > 0 ? 1 : 0;
+}
+
+
+size_t
+ConfigManager::readCameraMetadata(const XMLElement * const aParamElem,
+                                  CameraInfo *aCamera,
+                                  size_t &dataSize) {
+    if (aParamElem == nullptr || aCamera == nullptr) {
+        return 0;
+    }
+
+    const XMLElement *curElem = aParamElem->FirstChildElement("parameter");
+    size_t numEntries = 0;
+    camera_metadata_tag_t tag;
+    while (curElem != nullptr) {
+        if (ConfigManagerUtil::convertToMetadataTag(curElem->FindAttribute("name")->Value(),
+                                                    tag)) {
+            switch(tag) {
+                case ANDROID_LENS_DISTORTION:
+                case ANDROID_LENS_POSE_ROTATION:
+                case ANDROID_LENS_POSE_TRANSLATION:
+                case ANDROID_LENS_INTRINSIC_CALIBRATION: {
+                    /* float[] */
+                    size_t count = 0;
+                    void   *data = ConfigManagerUtil::convertFloatArray(
+                                        curElem->FindAttribute("size")->Value(),
+                                        curElem->FindAttribute("value")->Value(),
+                                        count
+                                   );
+
+                    aCamera->cameraMetadata.insert_or_assign(
+                        tag, make_pair(data, count)
+                    );
+
+                    ++numEntries;
+                    dataSize += calculate_camera_metadata_entry_data_size(
+                                    get_camera_metadata_tag_type(tag), count
+                                );
+
+                    break;
+                }
+
+                case ANDROID_REQUEST_AVAILABLE_CAPABILITIES: {
+                    camera_metadata_enum_android_request_available_capabilities_t *data =
+                        new camera_metadata_enum_android_request_available_capabilities_t[1];
+                    if (ConfigManagerUtil::convertToCameraCapability(
+                           curElem->FindAttribute("value")->Value(),
+                           *data)
+                       ) {
+                        aCamera->cameraMetadata.insert_or_assign(
+                            tag, make_pair((void *)data, 1)
+                        );
+
+                        ++numEntries;
+                        dataSize += calculate_camera_metadata_entry_data_size(
+                                        get_camera_metadata_tag_type(tag), 1
+                                    );
+                    }
+                    break;
+                }
+
+                case ANDROID_LOGICAL_MULTI_CAMERA_PHYSICAL_IDS: {
+                    /* a comma-separated list of physical camera devices */
+                    size_t len = strlen(curElem->FindAttribute("value")->Value());
+                    char *data = new char[len + 1];
+                    memcpy(data,
+                           curElem->FindAttribute("value")->Value(),
+                           len * sizeof(char));
+
+                    /* replace commas with null char */
+                    char *p = data;
+                    while (*p != '\0') {
+                        if (*p == ',') {
+                            *p = '\0';
+                        }
+                        ++p;
+                    }
+
+                    aCamera->cameraMetadata.insert_or_assign(
+                        tag, make_pair((void *)data, len + 1)
+                    );
+
+                    ++numEntries;
+                    dataSize += calculate_camera_metadata_entry_data_size(
+                                    get_camera_metadata_tag_type(tag), len
+                                );
+                    break;
+                }
+
+
+                /* TODO(b/140416878): add vendor-defined/custom tag support */
+
+                default:
+                    ALOGW("Parameter %s is not supported",
+                          curElem->FindAttribute("name")->Value());
+                    break;
+            }
+        } else {
+            ALOGW("Unsupported metadata tag %s found", curElem->FindAttribute("name")->Value());
+        }
+
+        curElem = curElem->NextSiblingElement("parameter");
+    }
+
+    return numEntries;
+}
+
+
+bool
+ConfigManager::constructCameraMetadata(CameraInfo *aCamera,
+                                       const size_t totalEntries,
+                                       const size_t totalDataSize) {
+    if (aCamera == nullptr || !aCamera->allocate(totalEntries, totalDataSize)) {
+        ALOGE("Failed to allocate memory for camera metadata");
+        return false;
+    }
+
+    const size_t numStreamConfigs = aCamera->streamConfigurations.size();
+    unique_ptr<int32_t[]> data(new int32_t[kStreamCfgSz * numStreamConfigs]);
+    int32_t *ptr = data.get();
+    for (auto &cfg : aCamera->streamConfigurations) {
+        for (auto i = 0; i < kStreamCfgSz; ++i) {
+          *ptr++ = cfg.second[i];
+        }
+    }
+    int32_t err = add_camera_metadata_entry(aCamera->characteristics,
+                                            ANDROID_SCALER_AVAILABLE_STREAM_CONFIGURATIONS,
+                                            data.get(),
+                                            numStreamConfigs * kStreamCfgSz);
+
+    if (err) {
+        ALOGE("Failed to add stream configurations to metadata, ignored");
+        return false;
+    }
+
+    bool success = true;
+    for (auto &[tag, entry] : aCamera->cameraMetadata) {
+        /* try to add new camera metadata entry */
+        int32_t err = add_camera_metadata_entry(aCamera->characteristics,
+                                                tag,
+                                                entry.first,
+                                                entry.second);
+        if (err) {
+            ALOGE("Failed to add an entry with a tag 0x%X", tag);
+
+            /* may exceed preallocated capacity */
+            ALOGE("Camera metadata has %ld / %ld entries and %ld / %ld bytes are filled",
+                  (long)get_camera_metadata_entry_count(aCamera->characteristics),
+                  (long)get_camera_metadata_entry_capacity(aCamera->characteristics),
+                  (long)get_camera_metadata_data_count(aCamera->characteristics),
+                  (long)get_camera_metadata_data_capacity(aCamera->characteristics));
+            ALOGE("\tCurrent metadata entry requires %ld bytes",
+                  (long)calculate_camera_metadata_entry_data_size(tag, entry.second));
+
+            success = false;
+        }
+    }
+
+    ALOGV("Camera metadata has %ld / %ld entries and %ld / %ld bytes are filled",
+          (long)get_camera_metadata_entry_count(aCamera->characteristics),
+          (long)get_camera_metadata_entry_capacity(aCamera->characteristics),
+          (long)get_camera_metadata_data_count(aCamera->characteristics),
+          (long)get_camera_metadata_data_capacity(aCamera->characteristics));
+
+    return success;
+}
+
+
+void ConfigManager::readSystemInfo(const XMLElement * const aSysElem) {
+    if (aSysElem == nullptr) {
+        return;
+    }
+
+    /*
+     * Please note that this function assumes that a given system XML element
+     * and its child elements follow DTD.  If it does not, it will cause a
+     * segmentation fault due to the failure of finding expected attributes.
+     */
+
+    /* read number of cameras available in the system */
+    const XMLElement *xmlElem = aSysElem->FirstChildElement("num_cameras");
+    if (xmlElem != nullptr) {
+        mSystemInfo.numCameras =
+            stoi(xmlElem->FindAttribute("value")->Value());
+    }
+}
+
+
+void ConfigManager::readDisplayInfo(const XMLElement * const aDisplayElem) {
+    if (aDisplayElem == nullptr) {
+        ALOGW("XML file does not have required camera element");
+        return;
+    }
+
+    const XMLElement *curDev = aDisplayElem->FirstChildElement("device");
+    while (curDev != nullptr) {
+        const char *id = curDev->FindAttribute("id")->Value();
+        //const char *pos = curDev->FirstAttribute("position")->Value();
+
+        unique_ptr<DisplayInfo> dpy(new DisplayInfo());
+        if (dpy == nullptr) {
+            ALOGE("Failed to allocate memory for DisplayInfo");
+            return;
+        }
+
+        const XMLElement *cap = curDev->FirstChildElement("caps");
+        if (cap != nullptr) {
+            const XMLElement *curStream = cap->FirstChildElement("stream");
+            while (curStream != nullptr) {
+                /* read 4 attributes */
+                const XMLAttribute *idAttr     = curStream->FindAttribute("id");
+                const XMLAttribute *widthAttr  = curStream->FindAttribute("width");
+                const XMLAttribute *heightAttr = curStream->FindAttribute("height");
+                const XMLAttribute *fmtAttr    = curStream->FindAttribute("format");
+
+                const int32_t id = stoi(idAttr->Value());
+                int32_t pixFormat;
+                if (ConfigManagerUtil::convertToPixelFormat(fmtAttr->Value(),
+                                                            pixFormat)) {
+                    RawStreamConfiguration cfg = {
+                        id,
+                        stoi(widthAttr->Value()),
+                        stoi(heightAttr->Value()),
+                        pixFormat,
+                        ANDROID_SCALER_AVAILABLE_STREAM_CONFIGURATIONS_INPUT,
+                        0   // unused
+                    };
+                    dpy->streamConfigurations.insert_or_assign(id, cfg);
+                }
+
+                curStream = curStream->NextSiblingElement("stream");
+            }
+        }
+
+        mDisplayInfo.insert_or_assign(id, std::move(dpy));
+        curDev = curDev->NextSiblingElement("device");
+    }
+
+    return;
+}
+
+
+bool ConfigManager::readConfigDataFromXML() noexcept {
+    XMLDocument xmlDoc;
+
+    const int64_t parsingStart = android::elapsedRealtimeNano();
+
+    /* load and parse a configuration file */
+    xmlDoc.LoadFile(mConfigFilePath);
+    if (xmlDoc.ErrorID() != XML_SUCCESS) {
+        ALOGE("Failed to load and/or parse a configuration file, %s", xmlDoc.ErrorStr());
+        return false;
+    }
+
+    /* retrieve the root element */
+    const XMLElement *rootElem = xmlDoc.RootElement();
+    if (strcmp(rootElem->Name(), "configuration")) {
+        ALOGE("A configuration file is not in the required format.  "
+              "See /etc/automotive/evs/evs_configuration.dtd");
+        return false;
+    }
+
+    unique_lock<mutex> lock(mConfigLock);
+
+    /*
+     * parse camera information; this needs to be done before reading system
+     * information
+     */
+    readCameraInfo(rootElem->FirstChildElement("camera"));
+
+    /* parse system information */
+    readSystemInfo(rootElem->FirstChildElement("system"));
+
+    /* parse display information */
+    readDisplayInfo(rootElem->FirstChildElement("display"));
+
+    /* configuration data is ready to be consumed */
+    mIsReady = true;
+
+    /* notify that configuration data is ready */
+    lock.unlock();
+    mConfigCond.notify_all();
+
+    const int64_t parsingEnd = android::elapsedRealtimeNano();
+    ALOGI("Parsing configuration file takes %lf (ms)",
+          (double)(parsingEnd - parsingStart) / 1000000.0);
+
+    return true;
+}
+
+
+bool ConfigManager::readConfigDataFromBinary() {
+    /* Temporary buffer to hold configuration data read from a binary file */
+    char mBuffer[1024];
+
+    fstream srcFile;
+    const int64_t readStart = android::elapsedRealtimeNano();
+
+    srcFile.open(mBinaryFilePath, fstream::in | fstream::binary);
+    if (!srcFile) {
+        ALOGE("Failed to open a source binary file, %s", mBinaryFilePath);
+        return false;
+    }
+
+    unique_lock<mutex> lock(mConfigLock);
+    mIsReady = false;
+
+    /* read configuration data into the internal buffer */
+    srcFile.read(mBuffer, sizeof(mBuffer));
+    ALOGV("%s: %ld bytes are read", __FUNCTION__, (long)srcFile.gcount());
+    char *p = mBuffer;
+    size_t sz = 0;
+
+    /* read number of camera group information entries */
+    size_t ngrps = *(reinterpret_cast<size_t *>(p)); p += sizeof(size_t);
+
+    /* read each camera information entry */
+    for (auto cidx = 0; cidx < ngrps; ++cidx) {
+        /* read camera identifier */
+        string cameraId = *(reinterpret_cast<string *>(p)); p += sizeof(string);
+
+        /* size of camera_metadata_t */
+        size_t num_entry = *(reinterpret_cast<size_t *>(p)); p += sizeof(size_t);
+        size_t num_data  = *(reinterpret_cast<size_t *>(p)); p += sizeof(size_t);
+
+        /* create CameraInfo and add it to hash map */
+        unique_ptr<ConfigManager::CameraGroupInfo> aCamera;
+        if (aCamera == nullptr ||
+            !aCamera->allocate(num_entry, num_data))  {
+            ALOGE("Failed to create new CameraInfo object");
+            mCameraInfo.clear();
+            return false;
+        }
+
+        /* controls */
+        typedef struct {
+            CameraParam cid;
+            int32_t min;
+            int32_t max;
+            int32_t step;
+        } CameraCtrl;
+        sz = *(reinterpret_cast<size_t *>(p)); p += sizeof(size_t);
+        CameraCtrl *ptr = reinterpret_cast<CameraCtrl *>(p);
+        for (auto idx = 0; idx < sz; ++idx) {
+            CameraCtrl temp = *ptr++;
+            aCamera->controls.emplace(temp.cid,
+                                      make_tuple(temp.min, temp.max, temp.step));
+        }
+        p = reinterpret_cast<char *>(ptr);
+
+        /* stream configurations */
+        sz = *(reinterpret_cast<size_t *>(p)); p += sizeof(size_t);
+        int32_t *i32_ptr = reinterpret_cast<int32_t *>(p);
+        for (auto idx = 0; idx < sz; ++idx) {
+            const int32_t id = *i32_ptr++;
+
+            std::array<int32_t, kStreamCfgSz> temp;
+            for (auto i = 0; i < kStreamCfgSz; ++i) {
+                temp[i] = *i32_ptr++;
+            }
+            aCamera->streamConfigurations.insert_or_assign(id, temp);
+        }
+        p = reinterpret_cast<char *>(i32_ptr);
+
+        /* synchronization */
+        aCamera->synchronized =
+            *(reinterpret_cast<int32_t *>(p)); p += sizeof(int32_t);
+
+        for (auto idx = 0; idx < num_entry; ++idx) {
+            /* Read camera metadata entries */
+            camera_metadata_tag_t tag =
+                *reinterpret_cast<camera_metadata_tag_t *>(p);
+            p += sizeof(camera_metadata_tag_t);
+            size_t count = *reinterpret_cast<size_t *>(p); p += sizeof(size_t);
+
+            int32_t type = get_camera_metadata_tag_type(tag);
+            switch (type) {
+                case TYPE_BYTE: {
+                    add_camera_metadata_entry(aCamera->characteristics,
+                                              tag,
+                                              p,
+                                              count);
+                    p += count * sizeof(uint8_t);
+                    break;
+                }
+                case TYPE_INT32: {
+                    add_camera_metadata_entry(aCamera->characteristics,
+                                              tag,
+                                              p,
+                                              count);
+                    p += count * sizeof(int32_t);
+                    break;
+                }
+                case TYPE_FLOAT: {
+                    add_camera_metadata_entry(aCamera->characteristics,
+                                              tag,
+                                              p,
+                                              count);
+                    p += count * sizeof(float);
+                    break;
+                }
+                case TYPE_INT64: {
+                    add_camera_metadata_entry(aCamera->characteristics,
+                                              tag,
+                                              p,
+                                              count);
+                    p += count * sizeof(int64_t);
+                    break;
+                }
+                case TYPE_DOUBLE: {
+                    add_camera_metadata_entry(aCamera->characteristics,
+                                              tag,
+                                              p,
+                                              count);
+                    p += count * sizeof(double);
+                    break;
+                }
+                case TYPE_RATIONAL:
+                    p += count * sizeof(camera_metadata_rational_t);
+                    break;
+                default:
+                    ALOGW("Type %d is unknown; data may be corrupted", type);
+                    break;
+            }
+        }
+
+        mCameraInfo.insert_or_assign(cameraId, std::move(aCamera));
+    }
+
+
+
+    /* read number of camera information entries */
+    size_t ncams = *(reinterpret_cast<size_t *>(p)); p += sizeof(size_t);
+
+    /* read each camera information entry */
+    for (auto cidx = 0; cidx < ncams; ++cidx) {
+        /* read camera identifier */
+        string cameraId = *(reinterpret_cast<string *>(p)); p += sizeof(string);
+
+        /* size of camera_metadata_t */
+        size_t num_entry = *(reinterpret_cast<size_t *>(p)); p += sizeof(size_t);
+        size_t num_data  = *(reinterpret_cast<size_t *>(p)); p += sizeof(size_t);
+
+        /* create CameraInfo and add it to hash map */
+        unique_ptr<ConfigManager::CameraInfo> aCamera;
+        if (aCamera == nullptr ||
+            !aCamera->allocate(num_entry, num_data))  {
+            ALOGE("Failed to create new CameraInfo object");
+            mCameraInfo.clear();
+            return false;
+        }
+
+        /* controls */
+        typedef struct {
+            CameraParam cid;
+            int32_t min;
+            int32_t max;
+            int32_t step;
+        } CameraCtrl;
+        sz = *(reinterpret_cast<size_t *>(p)); p += sizeof(size_t);
+        CameraCtrl *ptr = reinterpret_cast<CameraCtrl *>(p);
+        for (auto idx = 0; idx < sz; ++idx) {
+            CameraCtrl temp = *ptr++;
+            aCamera->controls.emplace(temp.cid,
+                                      make_tuple(temp.min, temp.max, temp.step));
+        }
+        p = reinterpret_cast<char *>(ptr);
+
+        /* stream configurations */
+        sz = *(reinterpret_cast<size_t *>(p)); p += sizeof(size_t);
+        int32_t *i32_ptr = reinterpret_cast<int32_t *>(p);
+        for (auto idx = 0; idx < sz; ++idx) {
+            const int32_t id = *i32_ptr++;
+
+            std::array<int32_t, kStreamCfgSz> temp;
+            for (auto i = 0; i < kStreamCfgSz; ++i) {
+                temp[i] = *i32_ptr++;
+            }
+            aCamera->streamConfigurations.insert_or_assign(id, temp);
+        }
+        p = reinterpret_cast<char *>(i32_ptr);
+
+        for (auto idx = 0; idx < num_entry; ++idx) {
+            /* Read camera metadata entries */
+            camera_metadata_tag_t tag =
+                *reinterpret_cast<camera_metadata_tag_t *>(p);
+            p += sizeof(camera_metadata_tag_t);
+            size_t count = *reinterpret_cast<size_t *>(p); p += sizeof(size_t);
+
+            int32_t type = get_camera_metadata_tag_type(tag);
+            switch (type) {
+                case TYPE_BYTE: {
+                    add_camera_metadata_entry(aCamera->characteristics,
+                                              tag,
+                                              p,
+                                              count);
+                    p += count * sizeof(uint8_t);
+                    break;
+                }
+                case TYPE_INT32: {
+                    add_camera_metadata_entry(aCamera->characteristics,
+                                              tag,
+                                              p,
+                                              count);
+                    p += count * sizeof(int32_t);
+                    break;
+                }
+                case TYPE_FLOAT: {
+                    add_camera_metadata_entry(aCamera->characteristics,
+                                              tag,
+                                              p,
+                                              count);
+                    p += count * sizeof(float);
+                    break;
+                }
+                case TYPE_INT64: {
+                    add_camera_metadata_entry(aCamera->characteristics,
+                                              tag,
+                                              p,
+                                              count);
+                    p += count * sizeof(int64_t);
+                    break;
+                }
+                case TYPE_DOUBLE: {
+                    add_camera_metadata_entry(aCamera->characteristics,
+                                              tag,
+                                              p,
+                                              count);
+                    p += count * sizeof(double);
+                    break;
+                }
+                case TYPE_RATIONAL:
+                    p += count * sizeof(camera_metadata_rational_t);
+                    break;
+                default:
+                    ALOGW("Type %d is unknown; data may be corrupted", type);
+                    break;
+            }
+        }
+
+        mCameraInfo.insert_or_assign(cameraId, std::move(aCamera));
+    }
+
+    mIsReady = true;
+
+    /* notify that configuration data is ready */
+    lock.unlock();
+    mConfigCond.notify_all();
+
+    int64_t readEnd = android::elapsedRealtimeNano();
+    ALOGI("%s takes %lf (ms)", __FUNCTION__,
+          (double)(readEnd - readStart) / 1000000.0);
+
+    return true;
+}
+
+
+bool ConfigManager::writeConfigDataToBinary() {
+    fstream outFile;
+
+    int64_t writeStart = android::elapsedRealtimeNano();
+
+    outFile.open(mBinaryFilePath, fstream::out | fstream::binary);
+    if (!outFile) {
+        ALOGE("Failed to open a destination binary file, %s", mBinaryFilePath);
+        return false;
+    }
+
+    /* lock a configuration data while it's being written to the filesystem */
+    lock_guard<mutex> lock(mConfigLock);
+
+    /* write camera group information */
+    size_t sz = mCameraGroups.size();
+    outFile.write(reinterpret_cast<const char *>(&sz),
+                  sizeof(size_t));
+    for (auto&& [camId, camInfo] : mCameraGroups) {
+        ALOGI("Storing camera group %s", camId.c_str());
+
+        /* write a camera identifier string */
+        outFile.write(reinterpret_cast<const char *>(&camId),
+                      sizeof(string));
+
+        /* controls */
+        sz = camInfo->controls.size();
+        outFile.write(reinterpret_cast<const char *>(&sz),
+                      sizeof(size_t));
+        for (auto&& [ctrl, range] : camInfo->controls) {
+            outFile.write(reinterpret_cast<const char *>(&ctrl),
+                          sizeof(CameraParam));
+            outFile.write(reinterpret_cast<const char *>(&get<0>(range)),
+                          sizeof(int32_t));
+            outFile.write(reinterpret_cast<const char *>(&get<1>(range)),
+                          sizeof(int32_t));
+            outFile.write(reinterpret_cast<const char *>(&get<2>(range)),
+                          sizeof(int32_t));
+        }
+
+        /* stream configurations */
+        sz = camInfo->streamConfigurations.size();
+        outFile.write(reinterpret_cast<const char *>(&sz),
+                      sizeof(size_t));
+        for (auto&& [sid, cfg] : camInfo->streamConfigurations) {
+            outFile.write(reinterpret_cast<const char *>(sid),
+                          sizeof(int32_t));
+            for (int idx = 0; idx < kStreamCfgSz; ++idx) {
+                outFile.write(reinterpret_cast<const char *>(&cfg[idx]),
+                              sizeof(int32_t));
+            }
+        }
+
+        /* synchronization */
+        outFile.write(reinterpret_cast<const char *>(&camInfo->synchronized),
+                      sizeof(int32_t));
+
+        /* size of camera_metadata_t */
+        size_t num_entry = 0;
+        size_t num_data  = 0;
+        if (camInfo->characteristics != nullptr) {
+            num_entry = get_camera_metadata_entry_count(camInfo->characteristics);
+            num_data  = get_camera_metadata_data_count(camInfo->characteristics);
+        }
+        outFile.write(reinterpret_cast<const char *>(&num_entry),
+                      sizeof(size_t));
+        outFile.write(reinterpret_cast<const char *>(&num_data),
+                      sizeof(size_t));
+
+        /* write each camera metadata entry */
+        if (num_entry > 0) {
+            camera_metadata_entry_t entry;
+            for (auto idx = 0; idx < num_entry; ++idx) {
+                if (get_camera_metadata_entry(camInfo->characteristics, idx, &entry)) {
+                    ALOGE("Failed to retrieve camera metadata entry %d", idx);
+                    outFile.close();
+                    return false;
+                }
+
+                outFile.write(reinterpret_cast<const char *>(&entry.tag),
+                              sizeof(entry.tag));
+                outFile.write(reinterpret_cast<const char *>(&entry.count),
+                              sizeof(entry.count));
+
+                int32_t type = get_camera_metadata_tag_type(entry.tag);
+                switch (type) {
+                    case TYPE_BYTE:
+                        outFile.write(reinterpret_cast<const char *>(entry.data.u8),
+                                      sizeof(uint8_t) * entry.count);
+                        break;
+                    case TYPE_INT32:
+                        outFile.write(reinterpret_cast<const char *>(entry.data.i32),
+                                      sizeof(int32_t) * entry.count);
+                        break;
+                    case TYPE_FLOAT:
+                        outFile.write(reinterpret_cast<const char *>(entry.data.f),
+                                      sizeof(float) * entry.count);
+                        break;
+                    case TYPE_INT64:
+                        outFile.write(reinterpret_cast<const char *>(entry.data.i64),
+                                      sizeof(int64_t) * entry.count);
+                        break;
+                    case TYPE_DOUBLE:
+                        outFile.write(reinterpret_cast<const char *>(entry.data.d),
+                                      sizeof(double) * entry.count);
+                        break;
+                    case TYPE_RATIONAL:
+                        [[fallthrough]];
+                    default:
+                        ALOGW("Type %d is not supported", type);
+                        break;
+                }
+            }
+        }
+    }
+
+    /* write camera device information */
+    sz = mCameraInfo.size();
+    outFile.write(reinterpret_cast<const char *>(&sz),
+                  sizeof(size_t));
+    for (auto&& [camId, camInfo] : mCameraInfo) {
+        ALOGI("Storing camera %s", camId.c_str());
+
+        /* write a camera identifier string */
+        outFile.write(reinterpret_cast<const char *>(&camId),
+                      sizeof(string));
+
+        /* controls */
+        sz = camInfo->controls.size();
+        outFile.write(reinterpret_cast<const char *>(&sz),
+                      sizeof(size_t));
+        for (auto& [ctrl, range] : camInfo->controls) {
+            outFile.write(reinterpret_cast<const char *>(&ctrl),
+                          sizeof(CameraParam));
+            outFile.write(reinterpret_cast<const char *>(&get<0>(range)),
+                          sizeof(int32_t));
+            outFile.write(reinterpret_cast<const char *>(&get<1>(range)),
+                          sizeof(int32_t));
+            outFile.write(reinterpret_cast<const char *>(&get<2>(range)),
+                          sizeof(int32_t));
+        }
+
+        /* stream configurations */
+        sz = camInfo->streamConfigurations.size();
+        outFile.write(reinterpret_cast<const char *>(&sz),
+                      sizeof(size_t));
+        for (auto&& [sid, cfg] : camInfo->streamConfigurations) {
+            outFile.write(reinterpret_cast<const char *>(sid),
+                          sizeof(int32_t));
+            for (int idx = 0; idx < kStreamCfgSz; ++idx) {
+                outFile.write(reinterpret_cast<const char *>(&cfg[idx]),
+                              sizeof(int32_t));
+            }
+        }
+
+        /* size of camera_metadata_t */
+        size_t num_entry = 0;
+        size_t num_data  = 0;
+        if (camInfo->characteristics != nullptr) {
+            num_entry = get_camera_metadata_entry_count(camInfo->characteristics);
+            num_data  = get_camera_metadata_data_count(camInfo->characteristics);
+        }
+        outFile.write(reinterpret_cast<const char *>(&num_entry),
+                      sizeof(size_t));
+        outFile.write(reinterpret_cast<const char *>(&num_data),
+                      sizeof(size_t));
+
+        /* write each camera metadata entry */
+        if (num_entry > 0) {
+            camera_metadata_entry_t entry;
+            for (auto idx = 0; idx < num_entry; ++idx) {
+                if (get_camera_metadata_entry(camInfo->characteristics, idx, &entry)) {
+                    ALOGE("Failed to retrieve camera metadata entry %d", idx);
+                    outFile.close();
+                    return false;
+                }
+
+                outFile.write(reinterpret_cast<const char *>(&entry.tag),
+                              sizeof(entry.tag));
+                outFile.write(reinterpret_cast<const char *>(&entry.count),
+                              sizeof(entry.count));
+
+                int32_t type = get_camera_metadata_tag_type(entry.tag);
+                switch (type) {
+                    case TYPE_BYTE:
+                        outFile.write(reinterpret_cast<const char *>(entry.data.u8),
+                                      sizeof(uint8_t) * entry.count);
+                        break;
+                    case TYPE_INT32:
+                        outFile.write(reinterpret_cast<const char *>(entry.data.i32),
+                                      sizeof(int32_t) * entry.count);
+                        break;
+                    case TYPE_FLOAT:
+                        outFile.write(reinterpret_cast<const char *>(entry.data.f),
+                                      sizeof(float) * entry.count);
+                        break;
+                    case TYPE_INT64:
+                        outFile.write(reinterpret_cast<const char *>(entry.data.i64),
+                                      sizeof(int64_t) * entry.count);
+                        break;
+                    case TYPE_DOUBLE:
+                        outFile.write(reinterpret_cast<const char *>(entry.data.d),
+                                      sizeof(double) * entry.count);
+                        break;
+                    case TYPE_RATIONAL:
+                        [[fallthrough]];
+                    default:
+                        ALOGW("Type %d is not supported", type);
+                        break;
+                }
+            }
+        }
+    }
+
+    outFile.close();
+    int64_t writeEnd = android::elapsedRealtimeNano();
+    ALOGI("%s takes %lf (ms)", __FUNCTION__,
+          (double)(writeEnd - writeStart) / 1000000.0);
+
+
+    return true;
+}
+
+
+std::unique_ptr<ConfigManager> ConfigManager::Create(const char *path) {
+    unique_ptr<ConfigManager> cfgMgr(new ConfigManager(path));
+
+    /*
+     * Read a configuration from XML file
+     *
+     * If this is too slow, ConfigManager::readConfigDataFromBinary() and
+     * ConfigManager::writeConfigDataToBinary()can serialize CameraInfo object
+     * to the filesystem and construct CameraInfo instead; this was
+     * evaluated as 10x faster.
+     */
+    if (!cfgMgr->readConfigDataFromXML()) {
+        return nullptr;
+    } else {
+        return cfgMgr;
+    }
+}
+
+ConfigManager::CameraInfo::~CameraInfo() {
+    free_camera_metadata(characteristics);
+
+    for (auto&& [tag, val] : cameraMetadata) {
+        switch(tag) {
+            case ANDROID_LENS_DISTORTION:
+            case ANDROID_LENS_POSE_ROTATION:
+            case ANDROID_LENS_POSE_TRANSLATION:
+            case ANDROID_LENS_INTRINSIC_CALIBRATION: {
+                delete[] reinterpret_cast<float *>(val.first);
+                break;
+            }
+
+            case ANDROID_REQUEST_AVAILABLE_CAPABILITIES: {
+                delete[] \
+                    reinterpret_cast<
+                        camera_metadata_enum_android_request_available_capabilities_t *
+                    >(val.first);
+                break;
+            }
+
+            case ANDROID_LOGICAL_MULTI_CAMERA_PHYSICAL_IDS: {
+                delete[] reinterpret_cast<char *>(val.first);
+                break;
+            }
+
+            default:
+                ALOGW("Tag 0x%X is not supported.  Data may be corrupted?", tag);
+                break;
+        }
+    }
+}
+
diff --git a/evs/sampleDriver/ConfigManager.h b/evs/sampleDriver/ConfigManager.h
new file mode 100644
index 0000000..9fa15aa
--- /dev/null
+++ b/evs/sampleDriver/ConfigManager.h
@@ -0,0 +1,399 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#ifndef CONFIG_MANAGER_H
+#define CONFIG_MANAGER_H
+
+#include <vector>
+#include <string>
+#include <unordered_map>
+#include <unordered_set>
+
+#include <tinyxml2.h>
+
+#include <system/camera_metadata.h>
+#include <log/log.h>
+#include <android/hardware/automotive/evs/1.1/types.h>
+
+#include "ConfigManagerUtil.h"
+
+using namespace std;
+using namespace tinyxml2;
+
+using ::android::hardware::hidl_vec;
+using ::android::hardware::camera::device::V3_2::Stream;
+using ::android::hardware::automotive::evs::V1_1::CameraParam;
+
+/*
+ * Plese note that this is different from what is defined in
+ * libhardware/modules/camera/3_4/metadata/types.h; this has one additional
+ * field to store a framerate.
+ */
+const size_t kStreamCfgSz = 6;
+typedef std::array<int32_t, kStreamCfgSz> RawStreamConfiguration;
+
+class ConfigManager {
+public:
+    static std::unique_ptr<ConfigManager> Create(const char *path = "");
+    ConfigManager(const ConfigManager&) = delete;
+    ConfigManager& operator=(const ConfigManager&) = delete;
+
+    virtual ~ConfigManager();
+
+    /* Camera device's capabilities and metadata */
+    class CameraInfo {
+    public:
+        CameraInfo() :
+            characteristics(nullptr) {
+            /* Nothing to do */
+        }
+
+        virtual ~CameraInfo();
+
+        /* Allocate memory for camera_metadata_t */
+        bool allocate(size_t entry_cap, size_t data_cap) {
+            if (characteristics != nullptr) {
+                ALOGE("Camera metadata is already allocated");
+                return false;
+            }
+
+            characteristics = allocate_camera_metadata(entry_cap, data_cap);
+            return characteristics != nullptr;
+        }
+
+        /*
+         * List of supported controls that the master client can program.
+         * Paraemters are stored with its valid range
+         */
+        unordered_map<CameraParam,
+                      tuple<int32_t, int32_t, int32_t>> controls;
+
+        /*
+         * List of supported output stream configurations; each array stores
+         * format, width, height, and direction values in the order.
+         */
+        unordered_map<int32_t, RawStreamConfiguration> streamConfigurations;
+
+        /*
+         * Internal storage for camera metadata.  Each entry holds a pointer to
+         * data and number of elements
+         */
+        unordered_map<camera_metadata_tag_t,
+                      pair<void *, size_t>> cameraMetadata;
+
+        /* Camera module characteristics */
+        camera_metadata_t *characteristics;
+    };
+
+    class CameraGroupInfo : public CameraInfo {
+    public:
+        CameraGroupInfo() {}
+
+        /* ID of member camera devices */
+        unordered_set<string> devices;
+
+        /* The capture operation of member camera devices are synchronized */
+        int32_t synchronized = 0;
+    };
+
+    class SystemInfo {
+    public:
+        /* number of available cameras */
+        int32_t numCameras = 0;
+    };
+
+    class DisplayInfo {
+    public:
+        /*
+         * List of supported input stream configurations; each array stores
+         * format, width, height, and direction values in the order.
+         */
+        unordered_map<int32_t, RawStreamConfiguration> streamConfigurations;
+    };
+
+    /*
+     * Return system information
+     *
+     * @return SystemInfo
+     *         Constant reference of SystemInfo.
+     */
+    const SystemInfo &getSystemInfo() {
+        unique_lock<mutex> lock(mConfigLock);
+        mConfigCond.wait(lock, [this] { return mIsReady; });
+        return mSystemInfo;
+    }
+
+    /*
+     * Return a list of camera identifiers
+     *
+     * This function assumes that it is not being called frequently.
+     *
+     * @return vector<string>
+     *         A vector that contains unique camera device identifiers.
+     */
+    vector<string> getCameraIdList() {
+        unique_lock<mutex> lock(mConfigLock);
+        mConfigCond.wait(lock, [this] { return mIsReady; });
+
+        vector<string> aList;
+        for (auto&& v : mCameraInfo) {
+            aList.emplace_back(v.first);
+        }
+
+        return aList;
+    }
+
+    /*
+     * Return a list of camera group identifiers
+     *
+     * This function assumes that it is not being called frequently.
+     *
+     * @return vector<string>
+     *         A vector that contains unique camera device identifiers.
+     */
+    vector<string> getCameraGroupIdList() {
+        unique_lock<mutex> lock(mConfigLock);
+        mConfigCond.wait(lock, [this] { return mIsReady; });
+
+        vector<string> aList;
+        for (auto&& v : mCameraGroups) {
+            aList.emplace_back(v.first);
+        }
+
+        return aList;
+    }
+
+    /*
+     * Return a pointer to the camera group
+     *
+     * @return CameraGroup
+     *         A pointer to a camera group identified by a given id.
+     */
+    unique_ptr<CameraGroupInfo>& getCameraGroupInfo(const string& gid) {
+        unique_lock<mutex> lock(mConfigLock);
+        mConfigCond.wait(lock, [this] { return mIsReady; });
+
+        return mCameraGroups[gid];
+    }
+
+    /*
+     * Return a camera metadata
+     *
+     * @param  cameraId
+     *         Unique camera node identifier in string
+     *
+     * @return unique_ptr<CameraInfo>
+     *         A pointer to CameraInfo that is associated with a given camera
+     *         ID.  This returns a null pointer if this does not recognize a
+     *         given camera identifier.
+     */
+    unique_ptr<CameraInfo>& getCameraInfo(const string cameraId) noexcept {
+        unique_lock<mutex> lock(mConfigLock);
+        mConfigCond.wait(lock, [this] { return mIsReady; });
+
+        return mCameraInfo[cameraId];
+    }
+
+    /*
+     * Tell whether the configuration data is ready to be used
+     *
+     * @return bool
+     *         True if configuration data is ready to be consumed.
+     */
+    bool isReady() const {
+        return mIsReady;
+    }
+
+private:
+    /* Constructors */
+    ConfigManager(const char *xmlPath) :
+        mConfigFilePath(xmlPath),
+        mBinaryFilePath("") {
+    }
+
+    /* System configuration */
+    SystemInfo mSystemInfo;
+
+    /* Internal data structure for camera device information */
+    unordered_map<string, unique_ptr<CameraInfo>> mCameraInfo;
+
+    /* Internal data structure for camera device information */
+    unordered_map<string, unique_ptr<DisplayInfo>> mDisplayInfo;
+
+    /* Camera groups are stored in <groud id, CameraGroup> hash map */
+    unordered_map<string, unique_ptr<CameraGroupInfo>> mCameraGroups;
+
+    /*
+     * Camera positions are stored in <position, camera id set> hash map.
+     * The position must be one of front, rear, left, and right.
+     */
+    unordered_map<string, unordered_set<string>>  mCameraPosition;
+
+    /* Configuration data lock */
+    mutex mConfigLock;
+
+    /*
+     * This condition is signalled when it completes a configuration data
+     * preparation.
+     */
+    condition_variable mConfigCond;
+
+    /* A path to XML configuration file */
+    const char *mConfigFilePath;
+
+    /* A path to a binary configuration file */
+    const char *mBinaryFilePath;
+
+    /* Configuration data readiness */
+    bool mIsReady = false;
+
+    /*
+     * Parse a given EVS configuration file and store the information
+     * internally.
+     *
+     * @return bool
+     *         True if it completes parsing a file successfully.
+     */
+    bool readConfigDataFromXML() noexcept;
+
+    /*
+     * read the information of the vehicle
+     *
+     * @param  aSysElem
+     *         A pointer to "system" XML element.
+     */
+    void readSystemInfo(const XMLElement * const aSysElem);
+
+    /*
+     * read the information of camera devices
+     *
+     * @param  aCameraElem
+     *         A pointer to "camera" XML element that may contain multiple
+     *         "device" elements.
+     */
+    void readCameraInfo(const XMLElement * const aCameraElem);
+
+    /*
+     * read display device information
+     *
+     * @param  aDisplayElem
+     *         A pointer to "display" XML element that may contain multiple
+     *         "device" elements.
+     */
+    void readDisplayInfo(const XMLElement * const aDisplayElem);
+
+    /*
+     * read camera device information
+     *
+     * @param  aCamera
+     *         A pointer to CameraInfo that will be completed by this
+     *         method.
+     *         aDeviceElem
+     *         A pointer to "device" XML element that contains camera module
+     *         capability info and its characteristics.
+     *
+     * @return bool
+     *         Return false upon any failure in reading and processing camera
+     *         device information.
+     */
+    bool readCameraDeviceInfo(CameraInfo *aCamera,
+                              const XMLElement *aDeviceElem);
+
+    /*
+     * read camera metadata
+     *
+     * @param  aCapElem
+     *         A pointer to "cap" XML element.
+     * @param  aCamera
+     *         A pointer to CameraInfo that is being filled by this method.
+     * @param  dataSize
+     *         Required size of memory to store camera metadata found in this
+     *         method.  This is calculated in this method and returned to the
+     *         caller for camera_metadata allocation.
+     *
+     * @return size_t
+     *         Number of camera metadata entries
+     */
+    size_t readCameraCapabilities(const XMLElement * const aCapElem,
+                                  CameraInfo *aCamera,
+                                  size_t &dataSize);
+
+    /*
+     * read camera metadata
+     *
+     * @param  aParamElem
+     *         A pointer to "characteristics" XML element.
+     * @param  aCamera
+     *         A pointer to CameraInfo that is being filled by this method.
+     * @param  dataSize
+     *         Required size of memory to store camera metadata found in this
+     *         method.
+     *
+     * @return size_t
+     *         Number of camera metadata entries
+     */
+    size_t readCameraMetadata(const XMLElement * const aParamElem,
+                              CameraInfo *aCamera,
+                              size_t &dataSize);
+
+    /*
+     * construct camera_metadata_t from camera capabilities and metadata
+     *
+     * @param  aCamera
+     *         A pointer to CameraInfo that is being filled by this method.
+     * @param  totalEntries
+     *         Number of camera metadata entries to be added.
+     * @param  totalDataSize
+     *         Sum of sizes of camera metadata entries to be added.
+     *
+     * @return bool
+     *         False if either it fails to allocate memory for camera metadata
+     *         or its size is not large enough to add all found camera metadata
+     *         entries.
+     */
+    bool constructCameraMetadata(CameraInfo *aCamera,
+                                 const size_t totalEntries,
+                                 const size_t totalDataSize);
+
+    /*
+     * Read configuration data from the binary file
+     *
+     * @return bool
+     *         True if it succeeds to read configuration data from a binary
+     *         file.
+     */
+    bool readConfigDataFromBinary();
+
+    /*
+     * Store configuration data to the file
+     *
+     * @return bool
+     *         True if it succeeds to serialize mCameraInfo to the file.
+     */
+    bool writeConfigDataToBinary();
+
+    /*
+     * debugging method to print out all XML elements and their attributes in
+     * logcat message.
+     *
+     * @param  aNode
+     *         A pointer to the root XML element to navigate.
+     * @param  prefix
+     *         A prefix to XML string.
+     */
+    void printElementNames(const XMLElement *aNode, string prefix = "") const;
+};
+#endif // CONFIG_MANAGER_H
+
diff --git a/evs/sampleDriver/ConfigManagerUtil.cpp b/evs/sampleDriver/ConfigManagerUtil.cpp
new file mode 100644
index 0000000..d10f236
--- /dev/null
+++ b/evs/sampleDriver/ConfigManagerUtil.cpp
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "ConfigManagerUtil.h"
+
+#include <string>
+#include <sstream>
+#include <linux/videodev2.h>
+
+#include <log/log.h>
+#include <system/graphics-base-v1.0.h>
+
+
+bool ConfigManagerUtil::convertToEvsCameraParam(const string &id,
+                                                CameraParam &camParam) {
+    string trimmed = ConfigManagerUtil::trimString(id);
+    bool success = true;
+
+    if (!trimmed.compare("BRIGHTNESS")) {
+        camParam =  CameraParam::BRIGHTNESS;
+    } else if (!trimmed.compare("CONTRAST")) {
+        camParam =  CameraParam::CONTRAST;
+    } else if (!trimmed.compare("AUTOGAIN")) {
+        camParam =  CameraParam::AUTOGAIN;
+    } else if (!trimmed.compare("GAIN")) {
+        camParam =  CameraParam::GAIN;
+    } else if (!trimmed.compare("AUTO_WHITE_BALANCE")) {
+        camParam =  CameraParam::AUTO_WHITE_BALANCE;
+    } else if (!trimmed.compare("WHITE_BALANCE_TEMPERATURE")) {
+        camParam =  CameraParam::WHITE_BALANCE_TEMPERATURE;
+    } else if (!trimmed.compare("SHARPNESS")) {
+        camParam =  CameraParam::SHARPNESS;
+    } else if (!trimmed.compare("AUTO_EXPOSURE")) {
+        camParam =  CameraParam::AUTO_EXPOSURE;
+    } else if (!trimmed.compare("ABSOLUTE_EXPOSURE")) {
+        camParam =  CameraParam::ABSOLUTE_EXPOSURE;
+    } else if (!trimmed.compare("ABSOLUTE_FOCUS")) {
+        camParam =  CameraParam::ABSOLUTE_FOCUS;
+    } else if (!trimmed.compare("AUTO_FOCUS")) {
+        camParam =  CameraParam::AUTO_FOCUS;
+    } else if (!trimmed.compare("ABSOLUTE_ZOOM")) {
+        camParam =  CameraParam::ABSOLUTE_ZOOM;
+    } else {
+        success = false;
+    }
+
+    return success;
+}
+
+
+bool ConfigManagerUtil::convertToPixelFormat(const string &format,
+                                             int32_t &pixFormat) {
+    string trimmed = ConfigManagerUtil::trimString(format);
+    bool success = true;
+
+    if (!trimmed.compare("RGBA_8888")) {
+        pixFormat =  HAL_PIXEL_FORMAT_RGBA_8888;
+    } else if (!trimmed.compare("YCRCB_420_SP")) {
+        pixFormat =  HAL_PIXEL_FORMAT_YCRCB_420_SP;
+    } else if (!trimmed.compare("YCBCR_422_I")) {
+        pixFormat =  HAL_PIXEL_FORMAT_YCBCR_422_I;
+    } else {
+        success = false;
+    }
+
+    return success;
+}
+
+
+bool ConfigManagerUtil::convertToMetadataTag(const char *name,
+                                             camera_metadata_tag &aTag) {
+    if (!strcmp(name, "LENS_DISTORTION")) {
+        aTag =  ANDROID_LENS_DISTORTION;
+    } else if (!strcmp(name, "LENS_INTRINSIC_CALIBRATION")) {
+        aTag =  ANDROID_LENS_INTRINSIC_CALIBRATION;
+    } else if (!strcmp(name, "LENS_POSE_ROTATION")) {
+        aTag =  ANDROID_LENS_POSE_ROTATION;
+    } else if (!strcmp(name, "LENS_POSE_TRANSLATION")) {
+        aTag =  ANDROID_LENS_POSE_TRANSLATION;
+    } else if (!strcmp(name, "REQUEST_AVAILABLE_CAPABILITIES")) {
+        aTag =  ANDROID_REQUEST_AVAILABLE_CAPABILITIES;
+    } else if (!strcmp(name, "LOGICAL_MULTI_CAMERA_PHYSICAL_IDS")) {
+        aTag =  ANDROID_LOGICAL_MULTI_CAMERA_PHYSICAL_IDS;
+    } else {
+        return false;
+    }
+
+    return true;
+}
+
+
+bool ConfigManagerUtil::convertToCameraCapability(
+    const char *name,
+    camera_metadata_enum_android_request_available_capabilities_t &cap) {
+
+    if (!strcmp(name, "DEPTH_OUTPUT")) {
+        cap = ANDROID_REQUEST_AVAILABLE_CAPABILITIES_DEPTH_OUTPUT;
+    } else if (!strcmp(name, "LOGICAL_MULTI_CAMERA")) {
+        cap = ANDROID_REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA;
+    } else if (!strcmp(name, "MONOCHROME")) {
+        cap = ANDROID_REQUEST_AVAILABLE_CAPABILITIES_MONOCHROME;
+    } else if (!strcmp(name, "SECURE_IMAGE_DATA")) {
+        cap = ANDROID_REQUEST_AVAILABLE_CAPABILITIES_SECURE_IMAGE_DATA;
+    } else {
+        return false;
+    }
+
+    return true;
+}
+
+
+float *ConfigManagerUtil::convertFloatArray(const char *sz, const char *vals,
+                                            size_t &count, const char delimiter) {
+    string size_string(sz);
+    string value_string(vals);
+
+    count = stoi(size_string);
+    float *result = new float[count];
+    stringstream values(value_string);
+
+    int32_t idx = 0;
+    string token;
+    while (getline(values, token, delimiter)) {
+        result[idx++] = stof(token);
+    }
+
+    return result;
+}
+
+
+string ConfigManagerUtil::trimString(const string &src, const string &ws) {
+    const auto s = src.find_first_not_of(ws);
+    if (s == string::npos) {
+        return "";
+    }
+
+    const auto e = src.find_last_not_of(ws);
+    const auto r = e - s + 1;
+
+    return src.substr(s, r);
+}
+
diff --git a/evs/sampleDriver/ConfigManagerUtil.h b/evs/sampleDriver/ConfigManagerUtil.h
new file mode 100644
index 0000000..1710cac
--- /dev/null
+++ b/evs/sampleDriver/ConfigManagerUtil.h
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#ifndef CONFIG_MANAGER_UTIL_H
+#define CONFIG_MANAGER_UTIL_H
+
+#include <utility>
+#include <string>
+#include <system/camera_metadata.h>
+#include <android/hardware/automotive/evs/1.1/types.h>
+
+using namespace std;
+using ::android::hardware::automotive::evs::V1_1::CameraParam;
+
+
+class ConfigManagerUtil {
+public:
+    /**
+     * Convert a given string into V4L2_CID_*
+     */
+    static bool convertToEvsCameraParam(const string &id,
+                                        CameraParam &camParam);
+    /**
+     * Convert a given string into android.hardware.graphics.common.PixelFormat
+     */
+    static bool convertToPixelFormat(const string &format,
+                                     int32_t &pixelFormat);
+    /**
+     * Convert a given string into corresponding camera metadata data tag defined in
+     * system/media/camera/include/system/camera_metadta_tags.h
+     */
+    static bool convertToMetadataTag(const char *name,
+                                     camera_metadata_tag &aTag);
+    /**
+     * Convert a given string into a floating value array
+     */
+    static float *convertFloatArray(const char *sz,
+                                    const char *vals,
+                                    size_t &count,
+                                    const char delimiter = ',');
+    /**
+     * Trim a string
+     */
+    static string trimString(const string &src,
+                             const string &ws = " \n\r\t\f\v");
+
+    /**
+     * Convert a given string to corresponding camera capabilities
+     */
+    static bool convertToCameraCapability(
+        const char *name,
+        camera_metadata_enum_android_request_available_capabilities_t &cap);
+
+};
+
+#endif // CONFIG_MANAGER_UTIL_H
+
diff --git a/evs/sampleDriver/EvsEnumerator.cpp b/evs/sampleDriver/EvsEnumerator.cpp
index 4487dab..113d669 100644
--- a/evs/sampleDriver/EvsEnumerator.cpp
+++ b/evs/sampleDriver/EvsEnumerator.cpp
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2016-2019 The Android Open Source Project
+ * Copyright (C) 2016 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -17,6 +17,7 @@
 #include "EvsEnumerator.h"
 #include "EvsV4lCamera.h"
 #include "EvsGlDisplay.h"
+#include "ConfigManager.h"
 
 #include <dirent.h>
 #include <hardware_legacy/uevent.h>
@@ -25,12 +26,14 @@
 
 
 using namespace std::chrono_literals;
+using CameraDesc_1_0 = ::android::hardware::automotive::evs::V1_0::CameraDesc;
+using CameraDesc_1_1 = ::android::hardware::automotive::evs::V1_1::CameraDesc;
 
 namespace android {
 namespace hardware {
 namespace automotive {
 namespace evs {
-namespace V1_0 {
+namespace V1_1 {
 namespace implementation {
 
 
@@ -41,6 +44,9 @@
 wp<EvsGlDisplay>                                             EvsEnumerator::sActiveDisplay;
 std::mutex                                                   EvsEnumerator::sLock;
 std::condition_variable                                      EvsEnumerator::sCameraSignal;
+std::unique_ptr<ConfigManager>                               EvsEnumerator::sConfigManager;
+sp<IAutomotiveDisplayProxyService>                           EvsEnumerator::sDisplayProxy;
+std::unordered_map<uint8_t, uint64_t>                        EvsEnumerator::sDisplayPortList;
 
 // Constants
 const auto kEnumerationTimeout = 10s;
@@ -48,7 +54,9 @@
 
 bool EvsEnumerator::checkPermission() {
     hardware::IPCThreadState *ipc = hardware::IPCThreadState::self();
-    if (AID_AUTOMOTIVE_EVS != ipc->getCallingUid()) {
+    if (AID_AUTOMOTIVE_EVS != ipc->getCallingUid() &&
+        AID_ROOT != ipc->getCallingUid()) {
+
         ALOGE("EVS access denied: pid = %d, uid = %d", ipc->getCallingPid(), ipc->getCallingUid());
         return false;
     }
@@ -108,7 +116,18 @@
             } else if (cmd_addition) {
                 // NOTE: we are here adding new device without a validation
                 // because it always fails to open, b/132164956.
-                sCameraList.emplace(devpath, devpath.c_str());
+                CameraRecord cam(devpath.c_str());
+                if (sConfigManager != nullptr) {
+                    unique_ptr<ConfigManager::CameraInfo> &camInfo =
+                        sConfigManager->getCameraInfo(devpath);
+                    if (camInfo != nullptr) {
+                        cam.desc.metadata.setToExternal(
+                            (uint8_t *)camInfo->characteristics,
+                             get_camera_metadata_size(camInfo->characteristics)
+                        );
+                    }
+                }
+                sCameraList.emplace(devpath, cam);
                 ALOGI("%s is added", devpath.c_str());
             } else {
                 // Ignore all other actions including "change".
@@ -122,13 +141,25 @@
     return;
 }
 
-EvsEnumerator::EvsEnumerator() {
+EvsEnumerator::EvsEnumerator(sp<IAutomotiveDisplayProxyService> proxyService) {
     ALOGD("EvsEnumerator created");
 
-    enumerateDevices();
+    if (sConfigManager == nullptr) {
+        /* loads and initializes ConfigManager in a separate thread */
+        sConfigManager =
+            ConfigManager::Create("/vendor/etc/automotive/evs/evs_sample_configuration.xml");
+    }
+
+    if (sDisplayProxy == nullptr) {
+        /* sets a car-window service handle */
+        sDisplayProxy = proxyService;
+    }
+
+    enumerateCameras();
+    enumerateDisplays();
 }
 
-void EvsEnumerator::enumerateDevices() {
+void EvsEnumerator::enumerateCameras() {
     // For every video* entry in the dev folder, see if it reports suitable capabilities
     // WARNING:  Depending on the driver implementations this could be slow, especially if
     //           there are timeouts or round trips to hardware required to collect the needed
@@ -168,6 +199,28 @@
     ALOGI("Found %d qualified video capture devices of %d checked\n", captureCount, videoCount);
 }
 
+
+void EvsEnumerator::enumerateDisplays() {
+    ALOGI("%s: Starting display enumeration", __FUNCTION__);
+    if (!sDisplayProxy) {
+        ALOGE("AutomotiveDisplayProxyService is not available!");
+        return;
+    }
+
+    sDisplayProxy->getDisplayIdList(
+        [](const auto& displayIds) {
+            for (const auto& id : displayIds) {
+                const auto port = id & 0xF;
+                ALOGI("Display 0x%lX is detected on the port %ld", (unsigned long)id, (long)port);
+                sDisplayPortList.insert_or_assign(port, id);
+            }
+        }
+    );
+
+    ALOGI("Found %d displays", (int)sDisplayPortList.size());
+}
+
+
 // Methods from ::android::hardware::automotive::evs::V1_0::IEvsEnumerator follow.
 Return<void> EvsEnumerator::getCameraList(getCameraList_cb _hidl_cb)  {
     ALOGD("getCameraList");
@@ -191,11 +244,11 @@
     const unsigned numCameras = sCameraList.size();
 
     // Build up a packed array of CameraDesc for return
-    hidl_vec<CameraDesc> hidlCameras;
+    hidl_vec<CameraDesc_1_0> hidlCameras;
     hidlCameras.resize(numCameras);
     unsigned i = 0;
     for (const auto& [key, cam] : sCameraList) {
-        hidlCameras[i++] = cam.desc;
+        hidlCameras[i++] = cam.desc.v1;
     }
 
     // Send back the results
@@ -207,7 +260,7 @@
 }
 
 
-Return<sp<IEvsCamera>> EvsEnumerator::openCamera(const hidl_string& cameraId) {
+Return<sp<IEvsCamera_1_0>> EvsEnumerator::openCamera(const hidl_string& cameraId) {
     ALOGD("openCamera");
     if (!checkPermission()) {
         return nullptr;
@@ -224,17 +277,23 @@
     }
 
     // Construct a camera instance for the caller
-    pActiveCamera = new EvsV4lCamera(cameraId.c_str());
+    if (sConfigManager == nullptr) {
+        pActiveCamera = EvsV4lCamera::Create(cameraId.c_str());
+    } else {
+        pActiveCamera = EvsV4lCamera::Create(cameraId.c_str(),
+                                             sConfigManager->getCameraInfo(cameraId));
+    }
+
     pRecord->activeInstance = pActiveCamera;
     if (pActiveCamera == nullptr) {
-        ALOGE("Failed to allocate new EvsV4lCamera object for %s\n", cameraId.c_str());
+        ALOGE("Failed to create new EvsV4lCamera object for %s\n", cameraId.c_str());
     }
 
     return pActiveCamera;
 }
 
 
-Return<void> EvsEnumerator::closeCamera(const ::android::sp<IEvsCamera>& pCamera) {
+Return<void> EvsEnumerator::closeCamera(const ::android::sp<IEvsCamera_1_0>& pCamera) {
     ALOGD("closeCamera");
 
     if (pCamera == nullptr) {
@@ -244,37 +303,18 @@
 
     // Get the camera id so we can find it in our list
     std::string cameraId;
-    pCamera->getCameraInfo([&cameraId](CameraDesc desc) {
+    pCamera->getCameraInfo([&cameraId](CameraDesc_1_0 desc) {
                                cameraId = desc.cameraId;
                            }
     );
 
-    // Find the named camera
-    CameraRecord *pRecord = findCameraById(cameraId);
-
-    // Is the display being destroyed actually the one we think is active?
-    if (!pRecord) {
-        ALOGE("Asked to close a camera whose name isn't recognized");
-    } else {
-        sp<EvsV4lCamera> pActiveCamera = pRecord->activeInstance.promote();
-
-        if (pActiveCamera == nullptr) {
-            ALOGE("Somehow a camera is being destroyed when the enumerator didn't know one existed");
-        } else if (pActiveCamera != pCamera) {
-            // This can happen if the camera was aggressively reopened, orphaning this previous instance
-            ALOGW("Ignoring close of previously orphaned camera - why did a client steal?");
-        } else {
-            // Drop the active camera
-            pActiveCamera->shutdown();
-            pRecord->activeInstance = nullptr;
-        }
-    }
+    closeCamera_impl(pCamera, cameraId);
 
     return Void();
 }
 
 
-Return<sp<IEvsDisplay>> EvsEnumerator::openDisplay() {
+Return<sp<IEvsDisplay_1_0>> EvsEnumerator::openDisplay() {
     ALOGD("openDisplay");
     if (!checkPermission()) {
         return nullptr;
@@ -297,7 +337,7 @@
 }
 
 
-Return<void> EvsEnumerator::closeDisplay(const ::android::sp<IEvsDisplay>& pDisplay) {
+Return<void> EvsEnumerator::closeDisplay(const ::android::sp<IEvsDisplay_1_0>& pDisplay) {
     ALOGD("closeDisplay");
 
     // Do we still have a display object we think should be active?
@@ -316,22 +356,202 @@
 }
 
 
-Return<DisplayState> EvsEnumerator::getDisplayState()  {
+Return<EvsDisplayState> EvsEnumerator::getDisplayState()  {
     ALOGD("getDisplayState");
     if (!checkPermission()) {
-        return DisplayState::DEAD;
+        return EvsDisplayState::DEAD;
     }
 
     // Do we still have a display object we think should be active?
-    sp<IEvsDisplay> pActiveDisplay = sActiveDisplay.promote();
+    sp<IEvsDisplay_1_0> pActiveDisplay = sActiveDisplay.promote();
     if (pActiveDisplay != nullptr) {
         return pActiveDisplay->getDisplayState();
     } else {
-        return DisplayState::NOT_OPEN;
+        return EvsDisplayState::NOT_OPEN;
     }
 }
 
 
+// Methods from ::android::hardware::automotive::evs::V1_1::IEvsEnumerator follow.
+Return<void> EvsEnumerator::getCameraList_1_1(getCameraList_1_1_cb _hidl_cb)  {
+    ALOGD("getCameraList_1_1");
+    if (!checkPermission()) {
+        return Void();
+    }
+
+    {
+        std::unique_lock<std::mutex> lock(sLock);
+        if (sCameraList.size() < 1) {
+            // No qualified device has been found.  Wait until new device is ready,
+            if (!sCameraSignal.wait_for(lock,
+                                        kEnumerationTimeout,
+                                        []{ return sCameraList.size() > 0; })) {
+                ALOGD("Timer expired.  No new device has been added.");
+            }
+        }
+    }
+
+    std::vector<CameraDesc_1_1> hidlCameras;
+    if (sConfigManager == nullptr) {
+        auto numCameras = sCameraList.size();
+
+        // Build up a packed array of CameraDesc for return
+        hidlCameras.resize(numCameras);
+        unsigned i = 0;
+        for (auto&& [key, cam] : sCameraList) {
+            hidlCameras[i++] = cam.desc;
+        }
+    } else {
+        // Build up a packed array of CameraDesc for return
+        for (auto&& [key, cam] : sCameraList) {
+            unique_ptr<ConfigManager::CameraInfo> &tempInfo =
+                sConfigManager->getCameraInfo(key);
+            if (tempInfo != nullptr) {
+                cam.desc.metadata.setToExternal(
+                    (uint8_t *)tempInfo->characteristics,
+                     get_camera_metadata_size(tempInfo->characteristics)
+                );
+            }
+
+            hidlCameras.emplace_back(cam.desc);
+        }
+
+        // Adding camera groups that represent logical camera devices
+        auto camGroups = sConfigManager->getCameraGroupIdList();
+        for (auto&& id : camGroups) {
+            if (sCameraList.find(id) != sCameraList.end()) {
+                // Already exists in the list
+                continue;
+            }
+
+            unique_ptr<ConfigManager::CameraGroupInfo> &tempInfo =
+                sConfigManager->getCameraGroupInfo(id);
+            CameraRecord cam(id.c_str());
+            if (tempInfo != nullptr) {
+                cam.desc.metadata.setToExternal(
+                    (uint8_t *)tempInfo->characteristics,
+                     get_camera_metadata_size(tempInfo->characteristics)
+                );
+            }
+
+            sCameraList.emplace(id, cam);
+            hidlCameras.emplace_back(cam.desc);
+        }
+    }
+
+    // Send back the results
+    _hidl_cb(hidlCameras);
+
+    // HIDL convention says we return Void if we sent our result back via callback
+    return Void();
+}
+
+
+Return<sp<IEvsCamera_1_1>> EvsEnumerator::openCamera_1_1(const hidl_string& cameraId,
+                                                         const Stream& streamCfg) {
+    ALOGD("openCamera_1_1");
+    if (!checkPermission()) {
+        return nullptr;
+    }
+
+    // Is this a recognized camera id?
+    CameraRecord *pRecord = findCameraById(cameraId);
+    if (pRecord == nullptr) {
+        ALOGE("%s does not exist!", cameraId.c_str());
+        return nullptr;
+    }
+
+    // Has this camera already been instantiated by another caller?
+    sp<EvsV4lCamera> pActiveCamera = pRecord->activeInstance.promote();
+    if (pActiveCamera != nullptr) {
+        ALOGW("Killing previous camera because of new caller");
+        closeCamera(pActiveCamera);
+    }
+
+    // Construct a camera instance for the caller
+    if (sConfigManager == nullptr) {
+        ALOGW("ConfigManager is not available.  Given stream configuration is ignored.");
+        pActiveCamera = EvsV4lCamera::Create(cameraId.c_str());
+    } else {
+        pActiveCamera = EvsV4lCamera::Create(cameraId.c_str(),
+                                             sConfigManager->getCameraInfo(cameraId),
+                                             &streamCfg);
+    }
+    pRecord->activeInstance = pActiveCamera;
+    if (pActiveCamera == nullptr) {
+        ALOGE("Failed to create new EvsV4lCamera object for %s\n", cameraId.c_str());
+    }
+
+    return pActiveCamera;
+}
+
+
+Return<void> EvsEnumerator::getDisplayIdList(getDisplayIdList_cb _list_cb) {
+    hidl_vec<uint8_t> ids;
+
+    ids.resize(sDisplayPortList.size());
+    unsigned i = 0;
+    for (const auto& [port, id] : sDisplayPortList) {
+        ids[i++] = port;
+    }
+
+    _list_cb(ids);
+    return Void();
+}
+
+
+Return<sp<IEvsDisplay_1_1>> EvsEnumerator::openDisplay_1_1(uint8_t port) {
+    ALOGD("%s", __FUNCTION__);
+    if (!checkPermission()) {
+        return nullptr;
+    }
+
+    // If we already have a display active, then we need to shut it down so we can
+    // give exclusive access to the new caller.
+    sp<EvsGlDisplay> pActiveDisplay = sActiveDisplay.promote();
+    if (pActiveDisplay != nullptr) {
+        ALOGW("Killing previous display because of new caller");
+        closeDisplay(pActiveDisplay);
+    }
+
+    // Create a new display interface and return it
+    pActiveDisplay = new EvsGlDisplay(sDisplayProxy, sDisplayPortList[port]);
+    sActiveDisplay = pActiveDisplay;
+
+    ALOGD("Returning new EvsGlDisplay object %p", pActiveDisplay.get());
+    return pActiveDisplay;
+}
+
+
+void EvsEnumerator::closeCamera_impl(const sp<IEvsCamera_1_0>& pCamera,
+                                     const std::string& cameraId) {
+    // Find the named camera
+    CameraRecord *pRecord = findCameraById(cameraId);
+
+    // Is the display being destroyed actually the one we think is active?
+    if (!pRecord) {
+        ALOGE("Asked to close a camera whose name isn't recognized");
+    } else {
+        sp<EvsV4lCamera> pActiveCamera = pRecord->activeInstance.promote();
+
+        if (pActiveCamera == nullptr) {
+            ALOGE("Somehow a camera is being destroyed "
+                  "when the enumerator didn't know one existed");
+        } else if (pActiveCamera != pCamera) {
+            // This can happen if the camera was aggressively reopened,
+            // orphaning this previous instance
+            ALOGW("Ignoring close of previously orphaned camera - why did a client steal?");
+        } else {
+            // Drop the active camera
+            pActiveCamera->shutdown();
+            pRecord->activeInstance = nullptr;
+        }
+    }
+
+    return;
+}
+
+
 bool EvsEnumerator::qualifyCaptureDevice(const char* deviceName) {
     class FileHandleWrapper {
     public:
@@ -406,8 +626,31 @@
 }
 
 
+// TODO(b/149874793): Add implementation for EVS Manager and Sample driver
+Return<void> EvsEnumerator::getUltrasonicsArrayList(getUltrasonicsArrayList_cb _hidl_cb) {
+    hidl_vec<UltrasonicsArrayDesc> ultrasonicsArrayDesc;
+    _hidl_cb(ultrasonicsArrayDesc);
+    return Void();
+}
+
+
+// TODO(b/149874793): Add implementation for EVS Manager and Sample driver
+Return<sp<IEvsUltrasonicsArray>> EvsEnumerator::openUltrasonicsArray(
+        const hidl_string& ultrasonicsArrayId) {
+    (void)ultrasonicsArrayId;
+    return sp<IEvsUltrasonicsArray>();
+}
+
+
+// TODO(b/149874793): Add implementation for EVS Manager and Sample driver
+Return<void> EvsEnumerator::closeUltrasonicsArray(
+        const ::android::sp<IEvsUltrasonicsArray>& evsUltrasonicsArray)  {
+    (void)evsUltrasonicsArray;
+    return Void();
+}
+
 } // namespace implementation
-} // namespace V1_0
+} // namespace V1_1
 } // namespace evs
 } // namespace automotive
 } // namespace hardware
diff --git a/evs/sampleDriver/EvsEnumerator.h b/evs/sampleDriver/EvsEnumerator.h
index 1d36886..b767a3b 100644
--- a/evs/sampleDriver/EvsEnumerator.h
+++ b/evs/sampleDriver/EvsEnumerator.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2016-2019 The Android Open Source Project
+ * Copyright (C) 2016 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,21 +14,34 @@
  * limitations under the License.
  */
 
-#ifndef ANDROID_HARDWARE_AUTOMOTIVE_EVS_V1_0_EVSCAMERAENUMERATOR_H
-#define ANDROID_HARDWARE_AUTOMOTIVE_EVS_V1_0_EVSCAMERAENUMERATOR_H
+#ifndef ANDROID_HARDWARE_AUTOMOTIVE_EVS_V1_1_EVSCAMERAENUMERATOR_H
+#define ANDROID_HARDWARE_AUTOMOTIVE_EVS_V1_1_EVSCAMERAENUMERATOR_H
 
-#include <android/hardware/automotive/evs/1.0/IEvsEnumerator.h>
-#include <android/hardware/automotive/evs/1.0/IEvsCamera.h>
+#include <android/hardware/automotive/evs/1.1/IEvsEnumerator.h>
+#include <android/hardware/automotive/evs/1.1/IEvsCamera.h>
+#include <android/hardware/camera/device/3.2/ICameraDevice.h>
+#include <android/frameworks/automotive/display/1.0/IAutomotiveDisplayProxyService.h>
+
 
 #include <unordered_map>
 #include <thread>
 #include <atomic>
 
+#include "ConfigManager.h"
+
+using ::android::hardware::camera::device::V3_2::Stream;
+using EvsDisplayState = ::android::hardware::automotive::evs::V1_0::DisplayState;
+using IEvsCamera_1_0  = ::android::hardware::automotive::evs::V1_0::IEvsCamera;
+using IEvsCamera_1_1  = ::android::hardware::automotive::evs::V1_1::IEvsCamera;
+using IEvsDisplay_1_0  = ::android::hardware::automotive::evs::V1_0::IEvsDisplay;
+using IEvsDisplay_1_1  = ::android::hardware::automotive::evs::V1_1::IEvsDisplay;
+using android::frameworks::automotive::display::V1_0::IAutomotiveDisplayProxyService;
+
 namespace android {
 namespace hardware {
 namespace automotive {
 namespace evs {
-namespace V1_0 {
+namespace V1_1 {
 namespace implementation {
 
 
@@ -38,15 +51,28 @@
 class EvsEnumerator : public IEvsEnumerator {
 public:
     // Methods from ::android::hardware::automotive::evs::V1_0::IEvsEnumerator follow.
-    Return<void> getCameraList(getCameraList_cb _hidl_cb)  override;
-    Return<sp<IEvsCamera>> openCamera(const hidl_string& cameraId) override;
-    Return<void> closeCamera(const ::android::sp<IEvsCamera>& carCamera)  override;
-    Return<sp<IEvsDisplay>> openDisplay()  override;
-    Return<void> closeDisplay(const ::android::sp<IEvsDisplay>& display)  override;
-    Return<DisplayState> getDisplayState()  override;
+    Return<void>                getCameraList(getCameraList_cb _hidl_cb)  override;
+    Return<sp<IEvsCamera_1_0>>  openCamera(const hidl_string& cameraId) override;
+    Return<void>                closeCamera(const ::android::sp<IEvsCamera_1_0>& pCamera)  override;
+    Return<sp<IEvsDisplay_1_0>> openDisplay()  override;
+    Return<void>                closeDisplay(const ::android::sp<IEvsDisplay_1_0>& display)  override;
+    Return<EvsDisplayState>     getDisplayState()  override;
+
+    // Methods from ::android::hardware::automotive::evs::V1_1::IEvsEnumerator follow.
+    Return<void>                getCameraList_1_1(getCameraList_1_1_cb _hidl_cb) override;
+    Return<sp<IEvsCamera_1_1>>  openCamera_1_1(const hidl_string& cameraId,
+                                               const Stream& streamCfg) override;
+    Return<bool>                isHardware() override { return true; }
+    Return<void>                getDisplayIdList(getDisplayIdList_cb _list_cb) override;
+    Return<sp<IEvsDisplay_1_1>> openDisplay_1_1(uint8_t port) override;
+    Return<void> getUltrasonicsArrayList(getUltrasonicsArrayList_cb _hidl_cb) override;
+    Return<sp<IEvsUltrasonicsArray>> openUltrasonicsArray(
+            const hidl_string& ultrasonicsArrayId) override;
+    Return<void> closeUltrasonicsArray(
+            const ::android::sp<IEvsUltrasonicsArray>& evsUltrasonicsArray) override;
 
     // Implementation details
-    EvsEnumerator();
+    EvsEnumerator(sp<IAutomotiveDisplayProxyService> proxyService = nullptr);
 
     // Listen to video device uevents
     static void EvsUeventThread(std::atomic<bool>& running);
@@ -56,14 +82,17 @@
         CameraDesc          desc;
         wp<EvsV4lCamera>    activeInstance;
 
-        CameraRecord(const char *cameraId) : desc() { desc.cameraId = cameraId; }
+        CameraRecord(const char *cameraId) : desc() { desc.v1.cameraId = cameraId; }
     };
 
     bool checkPermission();
 
     static bool qualifyCaptureDevice(const char* deviceName);
     static CameraRecord* findCameraById(const std::string& cameraId);
-    static void enumerateDevices();
+    static void enumerateCameras();
+    static void enumerateDisplays();
+
+    void closeCamera_impl(const sp<IEvsCamera_1_0>& pCamera, const std::string& cameraId);
 
     // NOTE:  All members values are static so that all clients operate on the same state
     //        That is to say, this is effectively a singleton despite the fact that HIDL
@@ -79,13 +108,19 @@
 
     static std::mutex                       sLock;          // Mutex on shared camera device list.
     static std::condition_variable          sCameraSignal;  // Signal on camera device addition.
+
+    static std::unique_ptr<ConfigManager>   sConfigManager; // ConfigManager
+
+    static sp<IAutomotiveDisplayProxyService> sDisplayProxy;
+    static std::unordered_map<uint8_t,
+                              uint64_t>       sDisplayPortList;
 };
 
 } // namespace implementation
-} // namespace V1_0
+} // namespace V1_1
 } // namespace evs
 } // namespace automotive
 } // namespace hardware
 } // namespace android
 
-#endif  // ANDROID_HARDWARE_AUTOMOTIVE_EVS_V1_0_EVSCAMERAENUMERATOR_H
+#endif  // ANDROID_HARDWARE_AUTOMOTIVE_EVS_V1_1_EVSCAMERAENUMERATOR_H
diff --git a/evs/sampleDriver/EvsGlDisplay.cpp b/evs/sampleDriver/EvsGlDisplay.cpp
index 89bfcf9..48522df 100644
--- a/evs/sampleDriver/EvsGlDisplay.cpp
+++ b/evs/sampleDriver/EvsGlDisplay.cpp
@@ -20,17 +20,26 @@
 #include <ui/GraphicBufferMapper.h>
 #include <utils/SystemClock.h>
 
+using ::android::frameworks::automotive::display::V1_0::HwDisplayConfig;
+using ::android::frameworks::automotive::display::V1_0::HwDisplayState;
 
 namespace android {
 namespace hardware {
 namespace automotive {
 namespace evs {
-namespace V1_0 {
+namespace V1_1 {
 namespace implementation {
 
 static bool sDebugFirstFrameDisplayed = false;
 
 EvsGlDisplay::EvsGlDisplay() {
+    EvsGlDisplay(nullptr, 0);
+}
+
+
+EvsGlDisplay::EvsGlDisplay(sp<IAutomotiveDisplayProxyService> pDisplayProxy, uint64_t displayId)
+    : mDisplayProxy(pDisplayProxy),
+      mDisplayId(displayId) {
     ALOGD("EvsGlDisplay instantiated");
 
     // Set up our self description
@@ -42,7 +51,7 @@
 
 EvsGlDisplay::~EvsGlDisplay() {
     ALOGD("EvsGlDisplay being destroyed");
-	forceShutdown();
+    forceShutdown();
 }
 
 
@@ -73,7 +82,7 @@
 
     // Put this object into an unrecoverable error state since somebody else
     // is going to own the display now.
-    mRequestedState = DisplayState::DEAD;
+    mRequestedState = EvsDisplayState::DEAD;
 }
 
 
@@ -99,26 +108,26 @@
  * then begin providing video.  When the display is no longer required, the client
  * is expected to request the NOT_VISIBLE state after passing the last video frame.
  */
-Return<EvsResult> EvsGlDisplay::setDisplayState(DisplayState state) {
+Return<EvsResult> EvsGlDisplay::setDisplayState(EvsDisplayState state) {
     ALOGD("setDisplayState");
     std::lock_guard<std::mutex> lock(mAccessLock);
 
-    if (mRequestedState == DisplayState::DEAD) {
+    if (mRequestedState == EvsDisplayState::DEAD) {
         // This object no longer owns the display -- it's been superceeded!
         return EvsResult::OWNERSHIP_LOST;
     }
 
     // Ensure we recognize the requested state so we don't go off the rails
-    if (state >= DisplayState::NUM_STATES) {
+    if (state >= EvsDisplayState::NUM_STATES) {
         return EvsResult::INVALID_ARG;
     }
 
     switch (state) {
-    case DisplayState::NOT_VISIBLE:
-        mGlWrapper.hideWindow();
+    case EvsDisplayState::NOT_VISIBLE:
+        mGlWrapper.hideWindow(mDisplayProxy, mDisplayId);
         break;
-    case DisplayState::VISIBLE:
-        mGlWrapper.showWindow();
+    case EvsDisplayState::VISIBLE:
+        mGlWrapper.showWindow(mDisplayProxy, mDisplayId);
         break;
     default:
         break;
@@ -138,7 +147,7 @@
  * the device layer, making it undesirable for the HAL implementation to
  * spontaneously change display states.
  */
-Return<DisplayState> EvsGlDisplay::getDisplayState()  {
+Return<EvsDisplayState> EvsGlDisplay::getDisplayState()  {
     ALOGD("getDisplayState");
     std::lock_guard<std::mutex> lock(mAccessLock);
 
@@ -156,9 +165,9 @@
     ALOGV("getTargetBuffer");
     std::lock_guard<std::mutex> lock(mAccessLock);
 
-    if (mRequestedState == DisplayState::DEAD) {
+    if (mRequestedState == EvsDisplayState::DEAD) {
         ALOGE("Rejecting buffer request from object that lost ownership of the display.");
-        BufferDesc nullBuff = {};
+        BufferDesc_1_0 nullBuff = {};
         _hidl_cb(nullBuff);
         return Void();
     }
@@ -169,10 +178,10 @@
         // NOTE:  This will cause the display to become "VISIBLE" before a frame is actually
         // returned, which is contrary to the spec and will likely result in a black frame being
         // (briefly) shown.
-        if (!mGlWrapper.initialize()) {
+        if (!mGlWrapper.initialize(mDisplayProxy, mDisplayId)) {
             // Report the failure
             ALOGE("Failed to initialize GL display");
-            BufferDesc nullBuff = {};
+            BufferDesc_1_0 nullBuff = {};
             _hidl_cb(nullBuff);
             return Void();
         }
@@ -196,14 +205,14 @@
         if (result != NO_ERROR) {
             ALOGE("Error %d allocating %d x %d graphics buffer",
                   result, mBuffer.width, mBuffer.height);
-            BufferDesc nullBuff = {};
+            BufferDesc_1_0 nullBuff = {};
             _hidl_cb(nullBuff);
             mGlWrapper.shutdown();
             return Void();
         }
         if (!handle) {
             ALOGE("We didn't get a buffer handle back from the allocator");
-            BufferDesc nullBuff = {};
+            BufferDesc_1_0 nullBuff = {};
             _hidl_cb(nullBuff);
             mGlWrapper.shutdown();
             return Void();
@@ -222,7 +231,7 @@
         // a previously issued buffer yet (they're behaving badly).
         // NOTE:  We have to make the callback even if we have nothing to provide
         ALOGE("getTargetBuffer called while no buffers available.");
-        BufferDesc nullBuff = {};
+        BufferDesc_1_0 nullBuff = {};
         _hidl_cb(nullBuff);
         return Void();
     } else {
@@ -242,7 +251,7 @@
  * This call tells the display that the buffer is ready for display.
  * The buffer is no longer valid for use by the client after this call.
  */
-Return<EvsResult> EvsGlDisplay::returnTargetBufferForDisplay(const BufferDesc& buffer)  {
+Return<EvsResult> EvsGlDisplay::returnTargetBufferForDisplay(const BufferDesc_1_0& buffer)  {
     ALOGV("returnTargetBufferForDisplay %p", buffer.memHandle.getNativeHandle());
     std::lock_guard<std::mutex> lock(mAccessLock);
 
@@ -263,18 +272,18 @@
     mFrameBusy = false;
 
     // If we've been displaced by another owner of the display, then we can't do anything else
-    if (mRequestedState == DisplayState::DEAD) {
+    if (mRequestedState == EvsDisplayState::DEAD) {
         return EvsResult::OWNERSHIP_LOST;
     }
 
     // If we were waiting for a new frame, this is it!
-    if (mRequestedState == DisplayState::VISIBLE_ON_NEXT_FRAME) {
-        mRequestedState = DisplayState::VISIBLE;
-        mGlWrapper.showWindow();
+    if (mRequestedState == EvsDisplayState::VISIBLE_ON_NEXT_FRAME) {
+        mRequestedState = EvsDisplayState::VISIBLE;
+        mGlWrapper.showWindow(mDisplayProxy, mDisplayId);
     }
 
     // Validate we're in an expected state
-    if (mRequestedState != DisplayState::VISIBLE) {
+    if (mRequestedState != EvsDisplayState::VISIBLE) {
         // Not sure why a client would send frames back when we're not visible.
         ALOGW ("Got a frame returned while not visible - ignoring.\n");
     } else {
@@ -297,8 +306,21 @@
     return EvsResult::OK;
 }
 
+
+Return<void> EvsGlDisplay::getDisplayInfo_1_1(getDisplayInfo_1_1_cb _info_cb) {
+    if (mDisplayProxy != nullptr) {
+        return mDisplayProxy->getDisplayInfo(mDisplayId, _info_cb);
+    } else {
+        HwDisplayConfig nullConfig;
+        HwDisplayState  nullState;
+        _info_cb(nullConfig, nullState);
+        return Void();
+    }
+}
+
+
 } // namespace implementation
-} // namespace V1_0
+} // namespace V1_1
 } // namespace evs
 } // namespace automotive
 } // namespace hardware
diff --git a/evs/sampleDriver/EvsGlDisplay.h b/evs/sampleDriver/EvsGlDisplay.h
index 7adbac9..3e411f9 100644
--- a/evs/sampleDriver/EvsGlDisplay.h
+++ b/evs/sampleDriver/EvsGlDisplay.h
@@ -14,48 +14,64 @@
  * limitations under the License.
  */
 
-#ifndef ANDROID_HARDWARE_AUTOMOTIVE_EVS_V1_0_EVSGLDISPLAY_H
-#define ANDROID_HARDWARE_AUTOMOTIVE_EVS_V1_0_EVSGLDISPLAY_H
+#ifndef ANDROID_HARDWARE_AUTOMOTIVE_EVS_V1_1_EVSGLDISPLAY_H
+#define ANDROID_HARDWARE_AUTOMOTIVE_EVS_V1_1_EVSGLDISPLAY_H
 
-#include <android/hardware/automotive/evs/1.0/IEvsDisplay.h>
+#include <android/hardware/automotive/evs/1.1/IEvsDisplay.h>
+#include <android/frameworks/automotive/display/1.0/IAutomotiveDisplayProxyService.h>
 #include <ui/GraphicBuffer.h>
 
 #include "GlWrapper.h"
 
+using ::android::hardware::automotive::evs::V1_0::EvsResult;
+using ::android::hardware::automotive::evs::V1_0::DisplayDesc;
+using ::android::hardware::automotive::evs::V1_0::DisplayState;
+using ::android::hardware::automotive::evs::V1_1::IEvsDisplay;
+using EvsResult   = ::android::hardware::automotive::evs::V1_0::EvsResult;
+using BufferDesc_1_0  = ::android::hardware::automotive::evs::V1_0::BufferDesc;
+using EvsDisplayState = ::android::hardware::automotive::evs::V1_0::DisplayState;
+using android::frameworks::automotive::display::V1_0::IAutomotiveDisplayProxyService;
 
 namespace android {
 namespace hardware {
 namespace automotive {
 namespace evs {
-namespace V1_0 {
+namespace V1_1 {
 namespace implementation {
 
 
 class EvsGlDisplay : public IEvsDisplay {
 public:
     // Methods from ::android::hardware::automotive::evs::V1_0::IEvsDisplay follow.
-    Return<void> getDisplayInfo(getDisplayInfo_cb _hidl_cb)  override;
-    Return<EvsResult> setDisplayState(DisplayState state)  override;
-    Return<DisplayState> getDisplayState()  override;
-    Return<void> getTargetBuffer(getTargetBuffer_cb _hidl_cb)  override;
-    Return<EvsResult> returnTargetBufferForDisplay(const BufferDesc& buffer)  override;
+    Return<void>            getDisplayInfo(getDisplayInfo_cb _hidl_cb)  override;
+    Return<EvsResult>       setDisplayState(EvsDisplayState state)  override;
+    Return<EvsDisplayState> getDisplayState()  override;
+    Return<void>            getTargetBuffer(getTargetBuffer_cb _hidl_cb)  override;
+    Return<EvsResult>       returnTargetBufferForDisplay(const BufferDesc_1_0& buffer)  override;
+
+    // Methods from ::android::hardware::automotive::evs::V1_1::IEvsDisplay follow.
+    Return<void>            getDisplayInfo_1_1(getDisplayInfo_1_1_cb _info_cb) override;
 
     // Implementation details
     EvsGlDisplay();
+    EvsGlDisplay(sp<IAutomotiveDisplayProxyService> pWindowService, uint64_t displayId);
     virtual ~EvsGlDisplay() override;
 
     void forceShutdown();   // This gets called if another caller "steals" ownership of the display
 
 private:
     DisplayDesc     mInfo           = {};
-    BufferDesc      mBuffer         = {};       // A graphics buffer into which we'll store images
+    BufferDesc_1_0  mBuffer         = {};       // A graphics buffer into which we'll store images
 
     bool            mFrameBusy      = false;    // A flag telling us our buffer is in use
-    DisplayState    mRequestedState = DisplayState::NOT_VISIBLE;
+    EvsDisplayState mRequestedState = EvsDisplayState::NOT_VISIBLE;
 
     GlWrapper       mGlWrapper;
 
     std::mutex      mAccessLock;
+
+    sp<IAutomotiveDisplayProxyService> mDisplayProxy;
+    uint64_t                           mDisplayId;
 };
 
 } // namespace implementation
diff --git a/evs/sampleDriver/EvsV4lCamera.cpp b/evs/sampleDriver/EvsV4lCamera.cpp
index 79a0b2e..8d6bcd3 100644
--- a/evs/sampleDriver/EvsV4lCamera.cpp
+++ b/evs/sampleDriver/EvsV4lCamera.cpp
@@ -20,35 +20,38 @@
 
 #include <ui/GraphicBufferAllocator.h>
 #include <ui/GraphicBufferMapper.h>
+#include <android/hardware_buffer.h>
+#include <utils/SystemClock.h>
 
 
 namespace android {
 namespace hardware {
 namespace automotive {
 namespace evs {
-namespace V1_0 {
+namespace V1_1 {
 namespace implementation {
 
+// Default camera output image resolution
+const std::array<int32_t, 2> kDefaultResolution = {640, 480};
 
 // Arbitrary limit on number of graphics buffers allowed to be allocated
 // Safeguards against unreasonable resource consumption and provides a testable limit
 static const unsigned MAX_BUFFERS_IN_FLIGHT = 100;
 
-
-EvsV4lCamera::EvsV4lCamera(const char *deviceName) :
+EvsV4lCamera::EvsV4lCamera(const char *deviceName,
+                           unique_ptr<ConfigManager::CameraInfo> &camInfo) :
         mFramesAllowed(0),
-        mFramesInUse(0) {
+        mFramesInUse(0),
+        mCameraInfo(camInfo) {
     ALOGD("EvsV4lCamera instantiated");
 
-    mDescription.cameraId = deviceName;
-
-    // Initialize the video device
-    if (!mVideo.open(deviceName)) {
-        ALOGE("Failed to open v4l device %s\n", deviceName);
+    mDescription.v1.cameraId = deviceName;
+    if (camInfo != nullptr) {
+        mDescription.metadata.setToExternal((uint8_t *)camInfo->characteristics,
+                                            get_camera_metadata_size(camInfo->characteristics));
     }
 
-    // Output buffer format.
-    // TODO: Does this need to be configurable?
+    // Default output buffer format.
     mFormat = HAL_PIXEL_FORMAT_RGBA_8888;
 
     // How we expect to use the gralloc buffers we'll exchange with our client
@@ -100,7 +103,7 @@
     ALOGD("getCameraInfo");
 
     // Send back our self description
-    _hidl_cb(mDescription);
+    _hidl_cb(mDescription.v1);
     return Void();
 }
 
@@ -130,7 +133,7 @@
 }
 
 
-Return<EvsResult> EvsV4lCamera::startVideoStream(const ::android::sp<IEvsCameraStream>& stream)  {
+Return<EvsResult> EvsV4lCamera::startVideoStream(const sp<IEvsCameraStream_1_0>& stream)  {
     ALOGD("startVideoStream");
     std::lock_guard<std::mutex> lock(mAccessLock);
 
@@ -194,13 +197,16 @@
 
     // Record the user's callback for use when we have a frame ready
     mStream = stream;
+    mStream_1_1 = IEvsCameraStream_1_1::castFrom(mStream).withDefault(nullptr);
 
     // Set up the video stream with a callback to our member function forwardFrame()
     if (!mVideo.startStream([this](VideoCapture*, imageBuffer* tgt, void* data) {
                                 this->forwardFrame(tgt, data);
                             })
     ) {
-        mStream = nullptr;  // No need to hold onto this if we failed to start
+        // No need to hold onto this if we failed to start
+        mStream = nullptr;
+        mStream_1_1 = nullptr;
         ALOGE("underlying camera start stream failed");
         return EvsResult::UNDERLYING_SERVICE_ERROR;
     }
@@ -209,41 +215,9 @@
 }
 
 
-Return<void> EvsV4lCamera::doneWithFrame(const BufferDesc& buffer)  {
+Return<void> EvsV4lCamera::doneWithFrame(const BufferDesc_1_0& buffer)  {
     ALOGD("doneWithFrame");
-    std::lock_guard <std::mutex> lock(mAccessLock);
-
-    // If we've been displaced by another owner of the camera, then we can't do anything else
-    if (!mVideo.isOpen()) {
-        ALOGW("ignoring doneWithFrame call when camera has been lost.");
-    } else {
-        if (buffer.memHandle == nullptr) {
-            ALOGE("ignoring doneWithFrame called with null handle");
-        } else if (buffer.bufferId >= mBuffers.size()) {
-            ALOGE("ignoring doneWithFrame called with invalid bufferId %d (max is %zu)",
-                  buffer.bufferId, mBuffers.size()-1);
-        } else if (!mBuffers[buffer.bufferId].inUse) {
-            ALOGE("ignoring doneWithFrame called on frame %d which is already free",
-                  buffer.bufferId);
-        } else {
-            // Mark the frame as available
-            mBuffers[buffer.bufferId].inUse = false;
-            mFramesInUse--;
-
-            // If this frame's index is high in the array, try to move it down
-            // to improve locality after mFramesAllowed has been reduced.
-            if (buffer.bufferId >= mFramesAllowed) {
-                // Find an empty slot lower in the array (which should always exist in this case)
-                for (auto&& rec : mBuffers) {
-                    if (rec.handle == nullptr) {
-                        rec.handle = mBuffers[buffer.bufferId].handle;
-                        mBuffers[buffer.bufferId].handle = nullptr;
-                        break;
-                    }
-                }
-            }
-        }
-    }
+    doneWithFrame_impl(buffer.bufferId, buffer.memHandle);
 
     return Void();
 }
@@ -255,11 +229,25 @@
     // Tell the capture device to stop (and block until it does)
     mVideo.stopStream();
 
-    if (mStream != nullptr) {
+    if (mStream_1_1 != nullptr) {
+        // V1.1 client is waiting on STREAM_STOPPED event.
+        std::unique_lock <std::mutex> lock(mAccessLock);
+
+        EvsEventDesc event;
+        event.aType = EvsEventType::STREAM_STOPPED;
+        auto result = mStream_1_1->notify(event);
+        if (!result.isOk()) {
+            ALOGE("Error delivering end of stream event");
+        }
+
+        // Drop our reference to the client's stream receiver
+        mStream_1_1 = nullptr;
+        mStream     = nullptr;
+    } else if (mStream != nullptr) {
         std::unique_lock <std::mutex> lock(mAccessLock);
 
         // Send one last NULL frame to signal the actual end of stream
-        BufferDesc nullBuff = {};
+        BufferDesc_1_0 nullBuff = {};
         auto result = mStream->deliverFrame(nullBuff);
         if (!result.isOk()) {
             ALOGE("Error delivering end of stream marker");
@@ -281,7 +269,7 @@
 
 
 Return<EvsResult> EvsV4lCamera::setExtendedInfo(uint32_t /*opaqueIdentifier*/,
-                                                int32_t /*opaqueValue*/)  {
+                                                int32_t  /*opaqueValue*/)  {
     ALOGD("setExtendedInfo");
     std::lock_guard<std::mutex> lock(mAccessLock);
 
@@ -296,6 +284,210 @@
 }
 
 
+// Methods from ::android::hardware::automotive::evs::V1_1::IEvsCamera follow.
+Return<void> EvsV4lCamera::getCameraInfo_1_1(getCameraInfo_1_1_cb _hidl_cb) {
+    ALOGD("getCameraInfo_1_1");
+
+    // Send back our self description
+    _hidl_cb(mDescription);
+    return Void();
+}
+
+
+Return<void> EvsV4lCamera::getPhysicalCameraInfo(const hidl_string& id,
+                                                 getPhysicalCameraInfo_cb _hidl_cb) {
+    ALOGD("%s", __FUNCTION__);
+
+    // This method works exactly same as getCameraInfo_1_1() in EVS HW module.
+    (void)id;
+    _hidl_cb(mDescription);
+    return Void();
+}
+
+
+Return<EvsResult> EvsV4lCamera::doneWithFrame_1_1(const hidl_vec<BufferDesc_1_1>& buffers)  {
+    ALOGD(__FUNCTION__);
+
+    for (auto&& buffer : buffers) {
+        doneWithFrame_impl(buffer.bufferId, buffer.buffer.nativeHandle);
+    }
+
+    return EvsResult::OK;
+}
+
+
+Return<EvsResult> EvsV4lCamera::pauseVideoStream() {
+    return EvsResult::UNDERLYING_SERVICE_ERROR;
+}
+
+
+Return<EvsResult> EvsV4lCamera::resumeVideoStream() {
+    return EvsResult::UNDERLYING_SERVICE_ERROR;
+}
+
+
+Return<EvsResult> EvsV4lCamera::setMaster() {
+    /* Because EVS HW module reference implementation expects a single client at
+     * a time, this returns a success code always.
+     */
+    return EvsResult::OK;
+}
+
+
+Return<EvsResult> EvsV4lCamera::forceMaster(const sp<IEvsDisplay_1_0>&) {
+    /* Because EVS HW module reference implementation expects a single client at
+     * a time, this returns a success code always.
+     */
+    return EvsResult::OK;
+}
+
+
+Return<EvsResult> EvsV4lCamera::unsetMaster() {
+    /* Because EVS HW module reference implementation expects a single client at
+     * a time, there is no chance that this is called by a non-master client and
+     * therefore returns a success code always.
+     */
+    return EvsResult::OK;
+}
+
+
+Return<void> EvsV4lCamera::getParameterList(getParameterList_cb _hidl_cb) {
+    hidl_vec<CameraParam> hidlCtrls;
+    if (mCameraInfo != nullptr) {
+        hidlCtrls.resize(mCameraInfo->controls.size());
+        unsigned idx = 0;
+        for (auto& [cid, range]: mCameraInfo->controls) {
+            hidlCtrls[idx++] = cid;
+        }
+    }
+
+    _hidl_cb(hidlCtrls);
+    return Void();
+}
+
+
+Return<void> EvsV4lCamera::getIntParameterRange(CameraParam id,
+                                                getIntParameterRange_cb _hidl_cb) {
+    if (mCameraInfo != nullptr) {
+        auto range = mCameraInfo->controls[id];
+        _hidl_cb(get<0>(range), get<1>(range), get<2>(range));
+    } else {
+        _hidl_cb(0, 0, 0);
+    }
+
+    return Void();
+}
+
+
+Return<void> EvsV4lCamera::setIntParameter(CameraParam id, int32_t value,
+                                           setIntParameter_cb _hidl_cb) {
+    uint32_t v4l2cid = V4L2_CID_BASE;
+    hidl_vec<int32_t> values;
+    values.resize(1);
+    if (!convertToV4l2CID(id, v4l2cid)) {
+        _hidl_cb(EvsResult::INVALID_ARG, values);
+    } else {
+        EvsResult result = EvsResult::OK;
+        v4l2_control control = {v4l2cid, value};
+        if (mVideo.setParameter(control) < 0 ||
+            mVideo.getParameter(control) < 0) {
+            result = EvsResult::UNDERLYING_SERVICE_ERROR;
+        }
+
+        values[0] = control.value;
+        _hidl_cb(result, values);
+    }
+
+    return Void();
+}
+
+
+Return<void> EvsV4lCamera::getIntParameter(CameraParam id,
+                                           getIntParameter_cb _hidl_cb) {
+    uint32_t v4l2cid = V4L2_CID_BASE;
+    hidl_vec<int32_t> values;
+    values.resize(1);
+    if (!convertToV4l2CID(id, v4l2cid)) {
+        _hidl_cb(EvsResult::INVALID_ARG, values);
+    } else {
+        EvsResult result = EvsResult::OK;
+        v4l2_control control = {v4l2cid, 0};
+        if (mVideo.getParameter(control) < 0) {
+            result = EvsResult::INVALID_ARG;
+        }
+
+        // Report a result
+        values[0] = control.value;
+        _hidl_cb(result, values);
+    }
+
+    return Void();
+}
+
+
+Return<EvsResult> EvsV4lCamera::setExtendedInfo_1_1(uint32_t opaqueIdentifier,
+                                                    const hidl_vec<uint8_t>& opaqueValue) {
+    mExtInfo.insert_or_assign(opaqueIdentifier, opaqueValue);
+    return EvsResult::OK;
+}
+
+
+Return<void> EvsV4lCamera::getExtendedInfo_1_1(uint32_t opaqueIdentifier,
+                                               getExtendedInfo_1_1_cb _hidl_cb) {
+    const auto it = mExtInfo.find(opaqueIdentifier);
+    hidl_vec<uint8_t> value;
+    auto status = EvsResult::OK;
+    if (it == mExtInfo.end()) {
+        status = EvsResult::INVALID_ARG;
+    } else {
+        value = mExtInfo[opaqueIdentifier];
+    }
+
+    _hidl_cb(status, value);
+    return Void();
+}
+
+
+EvsResult EvsV4lCamera::doneWithFrame_impl(const uint32_t bufferId,
+                                           const buffer_handle_t memHandle) {
+    std::lock_guard <std::mutex> lock(mAccessLock);
+
+    // If we've been displaced by another owner of the camera, then we can't do anything else
+    if (!mVideo.isOpen()) {
+        ALOGW("ignoring doneWithFrame call when camera has been lost.");
+    } else {
+        if (memHandle == nullptr) {
+            ALOGE("ignoring doneWithFrame called with null handle");
+        } else if (bufferId >= mBuffers.size()) {
+            ALOGE("ignoring doneWithFrame called with invalid bufferId %d (max is %zu)",
+                  bufferId, mBuffers.size()-1);
+        } else if (!mBuffers[bufferId].inUse) {
+            ALOGE("ignoring doneWithFrame called on frame %d which is already free",
+                  bufferId);
+        } else {
+            // Mark the frame as available
+            mBuffers[bufferId].inUse = false;
+            mFramesInUse--;
+
+            // If this frame's index is high in the array, try to move it down
+            // to improve locality after mFramesAllowed has been reduced.
+            if (bufferId >= mFramesAllowed) {
+                // Find an empty slot lower in the array (which should always exist in this case)
+                for (auto&& rec : mBuffers) {
+                    if (rec.handle == nullptr) {
+                        rec.handle = mBuffers[bufferId].handle;
+                        mBuffers[bufferId].handle = nullptr;
+                        break;
+                    }
+                }
+            }
+        }
+    }
+
+    return EvsResult::OK;
+}
+
+
 bool EvsV4lCamera::setAvailableFrames_Locked(unsigned bufferCount) {
     if (bufferCount < 1) {
         ALOGE("Ignoring request to set buffer count to zero");
@@ -422,7 +614,7 @@
 
 
 // This is the async callback from the video camera that tells us a frame is ready
-void EvsV4lCamera::forwardFrame(imageBuffer* /*pV4lBuff*/, void* pData) {
+void EvsV4lCamera::forwardFrame(imageBuffer* pV4lBuff, void* pData) {
     bool readyForFrame = false;
     size_t idx = 0;
 
@@ -461,45 +653,81 @@
         mVideo.markFrameConsumed();
     } else {
         // Assemble the buffer description we'll transmit below
-        BufferDesc buff = {};
-        buff.width      = mVideo.getWidth();
-        buff.height     = mVideo.getHeight();
-        buff.stride     = mStride;
-        buff.format     = mFormat;
-        buff.usage      = mUsage;
-        buff.bufferId   = idx;
-        buff.memHandle  = mBuffers[idx].handle;
+        BufferDesc_1_1 bufDesc_1_1 = {};
+        AHardwareBuffer_Desc* pDesc =
+            reinterpret_cast<AHardwareBuffer_Desc *>(&bufDesc_1_1.buffer.description);
+        pDesc->width  = mVideo.getWidth();
+        pDesc->height = mVideo.getHeight();
+        pDesc->layers = 1;
+        pDesc->format = mFormat;
+        pDesc->usage  = mUsage;
+        pDesc->stride = mStride;
+        bufDesc_1_1.buffer.nativeHandle = mBuffers[idx].handle;
+        bufDesc_1_1.bufferId = idx;
+        bufDesc_1_1.deviceId = mDescription.v1.cameraId;
+        // timestamp in microseconds.
+        bufDesc_1_1.timestamp =
+            pV4lBuff->timestamp.tv_sec * 1e+6 + pV4lBuff->timestamp.tv_usec;
 
         // Lock our output buffer for writing
+        // TODO(b/145459970): Sometimes, physical camera device maps a buffer
+        // into the address that is about to be unmapped by another device; this
+        // causes SEGV_MAPPER.
         void *targetPixels = nullptr;
         GraphicBufferMapper &mapper = GraphicBufferMapper::get();
-        mapper.lock(buff.memHandle,
+        status_t result = mapper.lock(bufDesc_1_1.buffer.nativeHandle,
                     GRALLOC_USAGE_SW_WRITE_OFTEN | GRALLOC_USAGE_SW_READ_NEVER,
-                    android::Rect(buff.width, buff.height),
-                    (void **) &targetPixels);
+                    android::Rect(pDesc->width, pDesc->height),
+                    (void **)&targetPixels);
 
         // If we failed to lock the pixel buffer, we're about to crash, but log it first
         if (!targetPixels) {
-            ALOGE("Camera failed to gain access to image buffer for writing");
+            // TODO(b/145457727): When EvsHidlTest::CameraToDisplayRoundTrip
+            // test case was repeatedly executed, EVS occasionally fails to map
+            // a buffer.
+            ALOGE("Camera failed to gain access to image buffer for writing - "
+                  "status: %s, error: %s", statusToString(result).c_str(), strerror(errno));
         }
 
         // Transfer the video image into the output buffer, making any needed
         // format conversion along the way
-        mFillBufferFromVideo(buff, (uint8_t*)targetPixels, pData, mVideo.getStride());
+        mFillBufferFromVideo(bufDesc_1_1, (uint8_t *)targetPixels, pData, mVideo.getStride());
 
         // Unlock the output buffer
-        mapper.unlock(buff.memHandle);
-
+        mapper.unlock(bufDesc_1_1.buffer.nativeHandle);
 
         // Give the video frame back to the underlying device for reuse
-        // Note that we do this before making the client callback to give the underlying
-        // camera more time to capture the next frame.
+        // Note that we do this before making the client callback to give the
+        // underlying camera more time to capture the next frame
         mVideo.markFrameConsumed();
 
-        // Issue the (asynchronous) callback to the client -- can't be holding the lock
-        auto result = mStream->deliverFrame(buff);
-        if (result.isOk()) {
-            ALOGD("Delivered %p as id %d", buff.memHandle.getNativeHandle(), buff.bufferId);
+        // Issue the (asynchronous) callback to the client -- can't be holding
+        // the lock
+        bool flag = false;
+        if (mStream_1_1 != nullptr) {
+            hidl_vec<BufferDesc_1_1> frames;
+            frames.resize(1);
+            frames[0] = bufDesc_1_1;
+            auto result = mStream_1_1->deliverFrame_1_1(frames);
+            flag = result.isOk();
+        } else {
+            BufferDesc_1_0 bufDesc_1_0 = {
+                pDesc->width,
+                pDesc->height,
+                pDesc->stride,
+                bufDesc_1_1.pixelSize,
+                static_cast<uint32_t>(pDesc->format),
+                static_cast<uint32_t>(pDesc->usage),
+                bufDesc_1_1.bufferId,
+                bufDesc_1_1.buffer.nativeHandle
+            };
+
+            auto result = mStream->deliverFrame(bufDesc_1_0);
+            flag = result.isOk();
+        }
+
+        if (flag) {
+            ALOGD("Delivered %p as id %d", bufDesc_1_1.buffer.nativeHandle.getNativeHandle(), bufDesc_1_1.bufferId);
         } else {
             // This can happen if the client dies and is likely unrecoverable.
             // To avoid consuming resources generating failing calls, we stop sending
@@ -515,8 +743,132 @@
     }
 }
 
+
+bool EvsV4lCamera::convertToV4l2CID(CameraParam id, uint32_t& v4l2cid) {
+    switch (id) {
+        case CameraParam::BRIGHTNESS:
+            v4l2cid = V4L2_CID_BRIGHTNESS;
+            break;
+        case CameraParam::CONTRAST:
+            v4l2cid = V4L2_CID_CONTRAST;
+            break;
+        case CameraParam::AUTO_WHITE_BALANCE:
+            v4l2cid = V4L2_CID_AUTO_WHITE_BALANCE;
+            break;
+        case CameraParam::WHITE_BALANCE_TEMPERATURE:
+            v4l2cid = V4L2_CID_WHITE_BALANCE_TEMPERATURE;
+            break;
+        case CameraParam::SHARPNESS:
+            v4l2cid = V4L2_CID_SHARPNESS;
+            break;
+        case CameraParam::AUTO_EXPOSURE:
+            v4l2cid = V4L2_CID_EXPOSURE_AUTO;
+            break;
+        case CameraParam::ABSOLUTE_EXPOSURE:
+            v4l2cid = V4L2_CID_EXPOSURE_ABSOLUTE;
+            break;
+        case CameraParam::AUTO_FOCUS:
+            v4l2cid = V4L2_CID_FOCUS_AUTO;
+            break;
+        case CameraParam::ABSOLUTE_FOCUS:
+            v4l2cid = V4L2_CID_FOCUS_ABSOLUTE;
+            break;
+        case CameraParam::ABSOLUTE_ZOOM:
+            v4l2cid = V4L2_CID_ZOOM_ABSOLUTE;
+            break;
+        default:
+            ALOGE("Camera parameter %u is unknown.", id);
+            return false;
+    }
+
+    if (mCameraInfo != nullptr) {
+        return mCameraInfo->controls.find(id) != mCameraInfo->controls.end();
+    } else {
+        return false;
+    }
+}
+
+
+sp<EvsV4lCamera> EvsV4lCamera::Create(const char *deviceName) {
+    unique_ptr<ConfigManager::CameraInfo> nullCamInfo = nullptr;
+
+    return Create(deviceName, nullCamInfo);
+}
+
+
+sp<EvsV4lCamera> EvsV4lCamera::Create(const char *deviceName,
+                                      unique_ptr<ConfigManager::CameraInfo> &camInfo,
+                                      const Stream *requestedStreamCfg) {
+    ALOGI("Create %s", deviceName);
+    sp<EvsV4lCamera> evsCamera = new EvsV4lCamera(deviceName, camInfo);
+    if (evsCamera == nullptr) {
+        return nullptr;
+    }
+
+    // Initialize the video device
+    bool success = false;
+    if (camInfo != nullptr && requestedStreamCfg != nullptr) {
+        // Validate a given stream configuration.  If there is no exact match,
+        // this will try to find the best match based on:
+        // 1) same output format
+        // 2) the largest resolution that is smaller that a given configuration.
+        int32_t streamId = -1, area = INT_MIN;
+        for (auto& [id, cfg] : camInfo->streamConfigurations) {
+            // RawConfiguration has id, width, height, format, direction, and
+            // fps.
+            if (cfg[3] == static_cast<uint32_t>(requestedStreamCfg->format)) {
+                if (cfg[1] == requestedStreamCfg->width &&
+                    cfg[2] == requestedStreamCfg->height) {
+                    // Find exact match.
+                    streamId = id;
+                    break;
+                } else if (requestedStreamCfg->width  > cfg[1] &&
+                           requestedStreamCfg->height > cfg[2] &&
+                           cfg[1] * cfg[2] > area) {
+                    streamId = id;
+                    area = cfg[1] * cfg[2];
+                }
+            }
+
+        }
+
+        if (streamId >= 0) {
+            ALOGI("Try to open a video with width: %d, height: %d, format: %d",
+                   camInfo->streamConfigurations[streamId][1],
+                   camInfo->streamConfigurations[streamId][2],
+                   camInfo->streamConfigurations[streamId][3]);
+            success =
+                evsCamera->mVideo.open(deviceName,
+                                       camInfo->streamConfigurations[streamId][1],
+                                       camInfo->streamConfigurations[streamId][2]);
+            evsCamera->mFormat = static_cast<uint32_t>(camInfo->streamConfigurations[streamId][3]);
+        }
+    }
+
+    if (!success) {
+        // Create a camera object with the default resolution and format
+        // , HAL_PIXEL_FORMAT_RGBA_8888.
+        ALOGI("Open a video with default parameters");
+        success =
+            evsCamera->mVideo.open(deviceName, kDefaultResolution[0], kDefaultResolution[1]);
+        if (!success) {
+            ALOGE("Failed to open a video stream");
+            return nullptr;
+        }
+    }
+
+    // Please note that the buffer usage flag does not come from a given stream
+    // configuration.
+    evsCamera->mUsage  = GRALLOC_USAGE_HW_TEXTURE     |
+                         GRALLOC_USAGE_SW_READ_RARELY |
+                         GRALLOC_USAGE_SW_WRITE_OFTEN;
+
+    return evsCamera;
+}
+
+
 } // namespace implementation
-} // namespace V1_0
+} // namespace V1_1
 } // namespace evs
 } // namespace automotive
 } // namespace hardware
diff --git a/evs/sampleDriver/EvsV4lCamera.h b/evs/sampleDriver/EvsV4lCamera.h
index 3d351b9..3dc9f06 100644
--- a/evs/sampleDriver/EvsV4lCamera.h
+++ b/evs/sampleDriver/EvsV4lCamera.h
@@ -14,24 +14,38 @@
  * limitations under the License.
  */
 
-#ifndef ANDROID_HARDWARE_AUTOMOTIVE_EVS_V1_0_EVSV4LCAMERA_H
-#define ANDROID_HARDWARE_AUTOMOTIVE_EVS_V1_0_EVSV4LCAMERA_H
+#ifndef ANDROID_HARDWARE_AUTOMOTIVE_EVS_V1_1_EVSV4LCAMERA_H
+#define ANDROID_HARDWARE_AUTOMOTIVE_EVS_V1_1_EVSV4LCAMERA_H
 
-#include <android/hardware/automotive/evs/1.0/types.h>
-#include <android/hardware/automotive/evs/1.0/IEvsCamera.h>
+#include <android/hardware/automotive/evs/1.1/types.h>
+#include <android/hardware/automotive/evs/1.1/IEvsCamera.h>
+#include <android/hardware/automotive/evs/1.1/IEvsCameraStream.h>
+#include <android/hardware/automotive/evs/1.1/IEvsDisplay.h>
+#include <android/hardware/camera/device/3.2/ICameraDevice.h>
 #include <ui/GraphicBuffer.h>
 
 #include <thread>
 #include <functional>
 
 #include "VideoCapture.h"
+#include "ConfigManager.h"
 
+using ::android::hardware::hidl_string;
+using ::android::hardware::camera::device::V3_2::Stream;
+using ::android::hardware::automotive::evs::V1_0::EvsResult;
+using ::android::hardware::automotive::evs::V1_0::CameraDesc;
+using IEvsDisplay_1_0      = ::android::hardware::automotive::evs::V1_0::IEvsDisplay;
+using IEvsDisplay_1_1      = ::android::hardware::automotive::evs::V1_1::IEvsDisplay;
+using BufferDesc_1_0       = ::android::hardware::automotive::evs::V1_0::BufferDesc;
+using BufferDesc_1_1       = ::android::hardware::automotive::evs::V1_1::BufferDesc;
+using IEvsCameraStream_1_0 = ::android::hardware::automotive::evs::V1_0::IEvsCameraStream;
+using IEvsCameraStream_1_1 = ::android::hardware::automotive::evs::V1_1::IEvsCameraStream;
 
 namespace android {
 namespace hardware {
 namespace automotive {
 namespace evs {
-namespace V1_0 {
+namespace V1_1 {
 namespace implementation {
 
 
@@ -42,34 +56,67 @@
 class EvsV4lCamera : public IEvsCamera {
 public:
     // Methods from ::android::hardware::automotive::evs::V1_0::IEvsCamera follow.
-    Return<void> getCameraInfo(getCameraInfo_cb _hidl_cb)  override;
-    Return <EvsResult> setMaxFramesInFlight(uint32_t bufferCount) override;
-    Return <EvsResult> startVideoStream(const ::android::sp<IEvsCameraStream>& stream) override;
-    Return<void> doneWithFrame(const BufferDesc& buffer) override;
-    Return<void> stopVideoStream() override;
-    Return <int32_t> getExtendedInfo(uint32_t opaqueIdentifier) override;
-    Return <EvsResult> setExtendedInfo(uint32_t opaqueIdentifier, int32_t opaqueValue) override;
+    Return<void>      getCameraInfo(getCameraInfo_cb _hidl_cb)  override;
+    Return<EvsResult> setMaxFramesInFlight(uint32_t bufferCount) override;
+    Return<EvsResult> startVideoStream(const ::android::sp<IEvsCameraStream_1_0>& stream) override;
+    Return<void>      doneWithFrame(const BufferDesc_1_0& buffer) override;
+    Return<void>      stopVideoStream() override;
+    Return<int32_t>   getExtendedInfo(uint32_t opaqueIdentifier) override;
+    Return<EvsResult> setExtendedInfo(uint32_t opaqueIdentifier, int32_t opaqueValue) override;
 
-    // Implementation details
-    EvsV4lCamera(const char *deviceName);
+    // Methods from ::android::hardware::automotive::evs::V1_1::IEvsCamera follow.
+    Return<void>      getCameraInfo_1_1(getCameraInfo_1_1_cb _hidl_cb)  override;
+    Return<void>      getPhysicalCameraInfo(const hidl_string& deviceId,
+                                            getPhysicalCameraInfo_cb _hidl_cb)  override;
+    Return<EvsResult> pauseVideoStream() override;
+    Return<EvsResult> resumeVideoStream() override;
+    Return<EvsResult> doneWithFrame_1_1(const hidl_vec<BufferDesc_1_1>& buffer) override;
+    Return<EvsResult> setMaster() override;
+    Return<EvsResult> forceMaster(const sp<IEvsDisplay_1_0>&) override;
+    Return<EvsResult> unsetMaster() override;
+    Return<void>      getParameterList(getParameterList_cb _hidl_cb) override;
+    Return<void>      getIntParameterRange(CameraParam id,
+                                           getIntParameterRange_cb _hidl_cb) override;
+    Return<void>      setIntParameter(CameraParam id, int32_t value,
+                                      setIntParameter_cb _hidl_cb) override;
+    Return<void>      getIntParameter(CameraParam id,
+                                      getIntParameter_cb _hidl_cb) override;
+    Return<EvsResult> setExtendedInfo_1_1(uint32_t opaqueIdentifier,
+                                          const hidl_vec<uint8_t>& opaqueValue) override;
+    Return<void>      getExtendedInfo_1_1(uint32_t opaqueIdentifier,
+                                          getExtendedInfo_1_1_cb _hidl_cb) override;
+
+    static sp<EvsV4lCamera> Create(const char *deviceName);
+    static sp<EvsV4lCamera> Create(const char *deviceName,
+                                   unique_ptr<ConfigManager::CameraInfo> &camInfo,
+                                   const Stream *streamCfg = nullptr);
+    EvsV4lCamera(const EvsV4lCamera&) = delete;
+    EvsV4lCamera& operator=(const EvsV4lCamera&) = delete;
+
     virtual ~EvsV4lCamera() override;
     void shutdown();
 
     const CameraDesc& getDesc() { return mDescription; };
 
 private:
+    // Constructors
+    EvsV4lCamera(const char *deviceName,
+                 unique_ptr<ConfigManager::CameraInfo> &camInfo);
+
     // These three functions are expected to be called while mAccessLock is held
     bool setAvailableFrames_Locked(unsigned bufferCount);
     unsigned increaseAvailableFrames_Locked(unsigned numToAdd);
     unsigned decreaseAvailableFrames_Locked(unsigned numToRemove);
 
     void forwardFrame(imageBuffer* tgt, void* data);
+    inline bool convertToV4l2CID(CameraParam id, uint32_t& v4l2cid);
 
-    sp <IEvsCameraStream> mStream = nullptr;  // The callback used to deliver each frame
+    sp <IEvsCameraStream_1_0> mStream     = nullptr;  // The callback used to deliver each frame
+    sp <IEvsCameraStream_1_1> mStream_1_1 = nullptr;  // The callback used to deliver each frame
 
-    VideoCapture          mVideo;   // Interface to the v4l device
+    VideoCapture              mVideo;                 // Interface to the v4l device
+    CameraDesc                mDescription = {};      // The properties of this camera
 
-    CameraDesc mDescription = {};   // The properties of this camera
     uint32_t mFormat = 0;           // Values from android_pixel_format_t
     uint32_t mUsage  = 0;           // Values from from Gralloc.h
     uint32_t mStride = 0;           // Pixels per row (may be greater than image width)
@@ -89,16 +136,25 @@
     void(*mFillBufferFromVideo)(const BufferDesc& tgtBuff, uint8_t* tgt,
                                 void* imgData, unsigned imgStride);
 
+
+    EvsResult doneWithFrame_impl(const uint32_t id, const buffer_handle_t handle);
+
     // Synchronization necessary to deconflict the capture thread from the main service thread
     // Note that the service interface remains single threaded (ie: not reentrant)
     std::mutex mAccessLock;
+
+    // Static camera module information
+    unique_ptr<ConfigManager::CameraInfo> &mCameraInfo;
+
+    // Extended information
+    std::unordered_map<uint32_t, std::vector<uint8_t>> mExtInfo;
 };
 
 } // namespace implementation
-} // namespace V1_0
+} // namespace V1_1
 } // namespace evs
 } // namespace automotive
 } // namespace hardware
 } // namespace android
 
-#endif  // ANDROID_HARDWARE_AUTOMOTIVE_EVS_V1_0_EVSV4LCAMERA_H
+#endif  // ANDROID_HARDWARE_AUTOMOTIVE_EVS_V1_1_EVSV4LCAMERA_H
diff --git a/evs/sampleDriver/GlWrapper.cpp b/evs/sampleDriver/GlWrapper.cpp
index cb55a5e..a8f7d2a 100644
--- a/evs/sampleDriver/GlWrapper.cpp
+++ b/evs/sampleDriver/GlWrapper.cpp
@@ -20,7 +20,10 @@
 #include <fcntl.h>
 #include <sys/ioctl.h>
 
-#include <ui/DisplayInfo.h>
+#include <utility>
+
+#include <ui/DisplayConfig.h>
+#include <ui/DisplayState.h>
 #include <ui/GraphicBuffer.h>
 
 
@@ -184,55 +187,48 @@
 
 
 // Main entry point
-bool GlWrapper::initialize() {
-    //
-    //  Create the native full screen window and get a suitable configuration to match it
-    //
-    status_t err;
+bool GlWrapper::initialize(sp<IAutomotiveDisplayProxyService> pWindowProxy,
+                           uint64_t displayId) {
+    ALOGD("%s", __FUNCTION__);
 
-    mFlinger = new SurfaceComposerClient();
-    if (mFlinger == nullptr) {
-        ALOGE("SurfaceComposerClient couldn't be allocated");
-        return false;
-    }
-    err = mFlinger->initCheck();
-    if (err != NO_ERROR) {
-        ALOGE("SurfaceComposerClient::initCheck error: %#x", err);
+    if (pWindowProxy == nullptr) {
+        ALOGE("Could not get IAutomotiveDisplayProxyService.");
         return false;
     }
 
-    // Get main display parameters.
-    sp<IBinder> mainDpy = SurfaceComposerClient::getInternalDisplayToken();
-    if (mainDpy == nullptr) {
-        ALOGE("ERROR: no internal display");
+    // We will use the first display in the list as the primary.
+    pWindowProxy->getDisplayInfo(displayId, [this](auto dpyConfig, auto dpyState) {
+        DisplayConfig *pConfig = (DisplayConfig*)dpyConfig.data();
+        mWidth = pConfig->resolution.getWidth();
+        mHeight = pConfig->resolution.getHeight();
+
+        ui::DisplayState* pState = (ui::DisplayState*)dpyState.data();
+        if (pState->orientation != ui::ROTATION_0 &&
+            pState->orientation != ui::ROTATION_180) {
+            // rotate
+            std::swap(mWidth, mHeight);
+        }
+
+        ALOGD("Display resolution is %d x %d", mWidth, mHeight);
+    });
+
+    mGfxBufferProducer = pWindowProxy->getIGraphicBufferProducer(displayId);
+    if (mGfxBufferProducer == nullptr) {
+        ALOGE("Failed to get IGraphicBufferProducer from IAutomotiveDisplayProxyService.");
         return false;
     }
 
-    DisplayInfo mainDpyInfo;
-    err = SurfaceComposerClient::getDisplayInfo(mainDpy, &mainDpyInfo);
-    if (err != NO_ERROR) {
-        ALOGE("ERROR: unable to get display characteristics");
+    mSurfaceHolder = getSurfaceFromHGBP(mGfxBufferProducer);
+    if (mSurfaceHolder == nullptr) {
+        ALOGE("Failed to get a Surface from HGBP.");
         return false;
     }
 
-    if (mainDpyInfo.orientation != DISPLAY_ORIENTATION_0 &&
-        mainDpyInfo.orientation != DISPLAY_ORIENTATION_180) {
-        // rotated
-        mWidth = mainDpyInfo.h;
-        mHeight = mainDpyInfo.w;
-    } else {
-        mWidth = mainDpyInfo.w;
-        mHeight = mainDpyInfo.h;
-    }
-
-    mFlingerSurfaceControl = mFlinger->createSurface(
-            String8("Evs Display"), mWidth, mHeight,
-            PIXEL_FORMAT_RGBX_8888, ISurfaceComposerClient::eOpaque);
-    if (mFlingerSurfaceControl == nullptr || !mFlingerSurfaceControl->isValid()) {
-        ALOGE("Failed to create SurfaceControl");
+    mWindow = getNativeWindow(mSurfaceHolder.get());
+    if (mWindow == nullptr) {
+        ALOGE("Failed to get a native window from Surface.");
         return false;
     }
-    mFlingerSurface = mFlingerSurfaceControl->getSurface();
 
 
     // Set up our OpenGL ES context associated with the default display
@@ -269,9 +265,9 @@
     }
 
     // Create the EGL render target surface
-    mSurface = eglCreateWindowSurface(mDisplay, egl_config, mFlingerSurface.get(), nullptr);
+    mSurface = eglCreateWindowSurface(mDisplay, egl_config, mWindow, nullptr);
     if (mSurface == EGL_NO_SURFACE) {
-        ALOGE("gelCreateWindowSurface failed.");
+        ALOGE("eglCreateWindowSurface failed.");
         return false;
     }
 
@@ -334,66 +330,77 @@
     mContext = EGL_NO_CONTEXT;
     mDisplay = EGL_NO_DISPLAY;
 
-    // Let go of our SurfaceComposer resources
-    mFlingerSurface.clear();
-    mFlingerSurfaceControl.clear();
-    mFlinger.clear();
+    // Release the window
+    mSurfaceHolder = nullptr;
 }
 
 
-void GlWrapper::showWindow() {
-    if (mFlingerSurfaceControl != nullptr) {
-        SurfaceComposerClient::Transaction{}
-                .setLayer(mFlingerSurfaceControl, 0x7FFFFFFF)     // always on top
-                .show(mFlingerSurfaceControl)
-                .apply();
+void GlWrapper::showWindow(sp<IAutomotiveDisplayProxyService>& pWindowProxy, uint64_t id) {
+    if (pWindowProxy != nullptr) {
+        pWindowProxy->showWindow(id);
+    } else {
+        ALOGE("IAutomotiveDisplayProxyService is not available.");
     }
 }
 
 
-void GlWrapper::hideWindow() {
-    if (mFlingerSurfaceControl != nullptr) {
-        SurfaceComposerClient::Transaction{}
-                .hide(mFlingerSurfaceControl)
-                .apply();
+void GlWrapper::hideWindow(sp<IAutomotiveDisplayProxyService>& pWindowProxy, uint64_t id) {
+    if (pWindowProxy != nullptr) {
+        pWindowProxy->hideWindow(id);
+    } else {
+        ALOGE("IAutomotiveDisplayProxyService is not available.");
     }
 }
 
 
-bool GlWrapper::updateImageTexture(const BufferDesc& buffer) {
+bool GlWrapper::updateImageTexture(const BufferDesc_1_0& buffer) {
+    BufferDesc_1_1 newBuffer = {};
+    AHardwareBuffer_Desc* pDesc =
+        reinterpret_cast<AHardwareBuffer_Desc *>(&newBuffer.buffer.description);
+    pDesc->width = buffer.width;
+    pDesc->height = buffer.height;
+    pDesc->layers = 1;
+    pDesc->format = buffer.format;
+    pDesc->usage = buffer.usage;
+    pDesc->stride = buffer.stride;
+    newBuffer.buffer.nativeHandle = buffer.memHandle;
+    newBuffer.pixelSize = buffer.pixelSize;
+    newBuffer.bufferId = buffer.bufferId;
+
+    return updateImageTexture(newBuffer);
+}
+
+
+bool GlWrapper::updateImageTexture(const BufferDesc_1_1& aFrame) {
 
     // If we haven't done it yet, create an "image" object to wrap the gralloc buffer
     if (mKHRimage == EGL_NO_IMAGE_KHR) {
         // create a temporary GraphicBuffer to wrap the provided handle
+        const AHardwareBuffer_Desc* pDesc =
+            reinterpret_cast<const AHardwareBuffer_Desc *>(&aFrame.buffer.description);
         sp<GraphicBuffer> pGfxBuffer = new GraphicBuffer(
-                buffer.width,
-                buffer.height,
-                buffer.format,
-                1,      /* layer count */
-                buffer.usage,
-                buffer.stride,
-                const_cast<native_handle_t*>(buffer.memHandle.getNativeHandle()),
+                pDesc->width,
+                pDesc->height,
+                pDesc->format,
+                pDesc->layers,
+                pDesc->usage,
+                pDesc->stride,
+                const_cast<native_handle_t*>(aFrame.buffer.nativeHandle.getNativeHandle()),
                 false   /* keep ownership */
         );
         if (pGfxBuffer.get() == nullptr) {
-            ALOGE("Failed to allocate GraphicsBuffer to wrap our native handle");
+            ALOGE("Failed to allocate GraphicBuffer to wrap our native handle");
             return false;
         }
 
-
         // Get a GL compatible reference to the graphics buffer we've been given
         EGLint eglImageAttributes[] = {EGL_IMAGE_PRESERVED_KHR, EGL_TRUE, EGL_NONE};
         EGLClientBuffer cbuf = static_cast<EGLClientBuffer>(pGfxBuffer->getNativeBuffer());
-// TODO:  If we pass in a context, we get "bad context" back
-#if 0
-        mKHRimage = eglCreateImageKHR(mDisplay, mContext,
-                                      EGL_NATIVE_BUFFER_ANDROID, cbuf,
+        mKHRimage = eglCreateImageKHR(mDisplay,
+                                      EGL_NO_CONTEXT,
+                                      EGL_NATIVE_BUFFER_ANDROID,
+                                      cbuf,
                                       eglImageAttributes);
-#else
-        mKHRimage = eglCreateImageKHR(mDisplay, EGL_NO_CONTEXT,
-                                      EGL_NATIVE_BUFFER_ANDROID, cbuf,
-                                      eglImageAttributes);
-#endif
         if (mKHRimage == EGL_NO_IMAGE_KHR) {
             ALOGE("error creating EGLImage: %s", getEGLError());
             return false;
diff --git a/evs/sampleDriver/GlWrapper.h b/evs/sampleDriver/GlWrapper.h
index 07b5525..2d99540 100644
--- a/evs/sampleDriver/GlWrapper.h
+++ b/evs/sampleDriver/GlWrapper.h
@@ -14,8 +14,8 @@
  * limitations under the License.
  */
 
-#ifndef ANDROID_HARDWARE_AUTOMOTIVE_EVS_V1_0_DISPLAY_GLWRAPPER_H
-#define ANDROID_HARDWARE_AUTOMOTIVE_EVS_V1_0_DISPLAY_GLWRAPPER_H
+#ifndef ANDROID_HARDWARE_AUTOMOTIVE_EVS_V1_1_DISPLAY_GLWRAPPER_H
+#define ANDROID_HARDWARE_AUTOMOTIVE_EVS_V1_1_DISPLAY_GLWRAPPER_H
 
 #include <EGL/egl.h>
 #include <EGL/eglext.h>
@@ -24,38 +24,40 @@
 #include <GLES3/gl3.h>
 #include <GLES3/gl3ext.h>
 
-#include <gui/ISurfaceComposer.h>
-#include <gui/Surface.h>
-#include <gui/SurfaceComposerClient.h>
-
-#include <android/hardware/automotive/evs/1.0/types.h>
+#include <android/frameworks/automotive/display/1.0/IAutomotiveDisplayProxyService.h>
+#include <android/hardware/automotive/evs/1.1/types.h>
+#include <bufferqueueconverter/BufferQueueConverter.h>
 
 
 using ::android::sp;
-using ::android::SurfaceComposerClient;
-using ::android::SurfaceControl;
-using ::android::Surface;
-using ::android::hardware::automotive::evs::V1_0::BufferDesc;
+using ::android::SurfaceHolder;
+using BufferDesc_1_0 = ::android::hardware::automotive::evs::V1_0::BufferDesc;
+using BufferDesc_1_1 = ::android::hardware::automotive::evs::V1_1::BufferDesc;
+using ::android::frameworks::automotive::display::V1_0::IAutomotiveDisplayProxyService;
+using ::android::hardware::graphics::bufferqueue::V2_0::IGraphicBufferProducer;
 
 
 class GlWrapper {
 public:
-    bool initialize();
+    GlWrapper()
+        : mSurfaceHolder(android::SurfaceHolderUniquePtr(nullptr, nullptr)) {}
+    bool initialize(sp<IAutomotiveDisplayProxyService> pWindowService, uint64_t displayId);
     void shutdown();
 
-    bool updateImageTexture(const BufferDesc& buffer);
+    bool updateImageTexture(const BufferDesc_1_0& buffer);
+    bool updateImageTexture(const BufferDesc_1_1& buffer);
     void renderImageToScreen();
 
-    void showWindow();
-    void hideWindow();
+    void showWindow(sp<IAutomotiveDisplayProxyService>& pWindowService, uint64_t id);
+    void hideWindow(sp<IAutomotiveDisplayProxyService>& pWindowService, uint64_t id);
 
     unsigned getWidth()     { return mWidth; };
     unsigned getHeight()    { return mHeight; };
 
 private:
-    sp<SurfaceComposerClient>   mFlinger;
-    sp<SurfaceControl>          mFlingerSurfaceControl;
-    sp<Surface>                 mFlingerSurface;
+    sp<IAutomotiveDisplayProxyService> mAutomotiveDisplayProxyService;
+    sp<IGraphicBufferProducer>         mGfxBufferProducer;
+
     EGLDisplay                  mDisplay;
     EGLSurface                  mSurface;
     EGLContext                  mContext;
@@ -67,6 +69,13 @@
 
     GLuint mTextureMap    = 0;
     GLuint mShaderProgram = 0;
+
+    // Opaque handle for a native hardware buffer defined in
+    // frameworks/native/opengl/include/EGL/eglplatform.h
+    ANativeWindow*                  mWindow;
+
+    // Pointer to a Surface wrapper.
+    android::SurfaceHolderUniquePtr mSurfaceHolder;
 };
 
-#endif // ANDROID_HARDWARE_AUTOMOTIVE_EVS_V1_0_DISPLAY_GLWRAPPER_H
+#endif // ANDROID_HARDWARE_AUTOMOTIVE_EVS_V1_1_DISPLAY_GLWRAPPER_H
diff --git a/evs/sampleDriver/ServiceNames.h b/evs/sampleDriver/ServiceNames.h
index 6458b1b..4fb7225 100644
--- a/evs/sampleDriver/ServiceNames.h
+++ b/evs/sampleDriver/ServiceNames.h
@@ -13,9 +13,9 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-#ifndef ANDROID_HARDWARE_AUTOMOTIVE_EVS_V1_0_SERVICENAMES_H
-#define ANDROID_HARDWARE_AUTOMOTIVE_EVS_V1_0_SERVICENAMES_H
+#ifndef ANDROID_HARDWARE_AUTOMOTIVE_EVS_V1_1_SERVICENAMES_H
+#define ANDROID_HARDWARE_AUTOMOTIVE_EVS_V1_1_SERVICENAMES_H
 
-const static char kEnumeratorServiceName[] = "EvsEnumeratorHw";
+const static char kEnumeratorServiceName[] = "hw/1";
 
-#endif // ANDROID_HARDWARE_AUTOMOTIVE_EVS_V1_0_SERVICENAMES_H
+#endif // ANDROID_HARDWARE_AUTOMOTIVE_EVS_V1_1_SERVICENAMES_H
diff --git a/evs/sampleDriver/VideoCapture.cpp b/evs/sampleDriver/VideoCapture.cpp
index 5ef996d..9be55cc 100644
--- a/evs/sampleDriver/VideoCapture.cpp
+++ b/evs/sampleDriver/VideoCapture.cpp
@@ -22,7 +22,7 @@
 #include <unistd.h>
 #include <sys/ioctl.h>
 #include <sys/mman.h>
-#include <cutils/log.h>
+#include <log/log.h>
 
 #include "assert.h"
 
@@ -33,7 +33,7 @@
 //        during the resource setup phase.  Of particular note is the potential to leak
 //        the file descriptor.  This must be fixed before using this code for anything but
 //        experimentation.
-bool VideoCapture::open(const char* deviceName) {
+bool VideoCapture::open(const char* deviceName, const int32_t width, const int32_t height) {
     // If we want a polling interface for getting frames, we would use O_NONBLOCK
 //    int mDeviceFd = open(deviceName, O_RDWR | O_NONBLOCK, 0);
     mDeviceFd = ::open(deviceName, O_RDWR, 0);
@@ -93,8 +93,8 @@
     v4l2_format format;
     format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
     format.fmt.pix.pixelformat = V4L2_PIX_FMT_UYVY;
-    format.fmt.pix.width = 720;
-    format.fmt.pix.height = 240;
+    format.fmt.pix.width = width;
+    format.fmt.pix.height = height;
     ALOGI("Requesting format %c%c%c%c (0x%08X)",
           ((char*)&format.fmt.pix.pixelformat)[0],
           ((char*)&format.fmt.pix.pixelformat)[1],
@@ -140,7 +140,7 @@
     assert(mRunMode == STOPPED);
 
     if (isOpen()) {
-        ALOGD("closing video device file handled %d", mDeviceFd);
+        ALOGD("closing video device file handle %d", mDeviceFd);
         ::close(mDeviceFd);
         mDeviceFd = -1;
     }
@@ -179,6 +179,7 @@
     ALOGI("Buffer description:");
     ALOGI("  offset: %d", mBufferInfo.m.offset);
     ALOGI("  length: %d", mBufferInfo.length);
+    ALOGI("  flags : 0x%X", mBufferInfo.flags);
 
     // Get a pointer to the buffer contents by mapping into our address space
     mPixelBuffer = mmap(
@@ -300,3 +301,25 @@
     ALOGD("VideoCapture thread ending");
     mRunMode = STOPPED;
 }
+
+
+int VideoCapture::setParameter(v4l2_control& control) {
+    int status = ioctl(mDeviceFd, VIDIOC_S_CTRL, &control);
+    if (status < 0) {
+        ALOGE("Failed to program a parameter value (id: 0x%X): %s",
+              control.id, strerror(errno));
+    }
+
+    return status;
+}
+
+
+int VideoCapture::getParameter(v4l2_control& control) {
+    int status = ioctl(mDeviceFd, VIDIOC_G_CTRL, &control);
+    if (status < 0) {
+        ALOGE("Failed to read a parameter value (fd: 0x%X, id: 0x%X): %s",
+              mDeviceFd, control.id, strerror(errno));
+    }
+
+    return status;
+}
diff --git a/evs/sampleDriver/VideoCapture.h b/evs/sampleDriver/VideoCapture.h
index 63305b9..e205c36 100644
--- a/evs/sampleDriver/VideoCapture.h
+++ b/evs/sampleDriver/VideoCapture.h
@@ -13,8 +13,8 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-#ifndef ANDROID_HARDWARE_AUTOMOTIVE_EVS_V1_0_VIDEOCAPTURE_H
-#define ANDROID_HARDWARE_AUTOMOTIVE_EVS_V1_0_VIDEOCAPTURE_H
+#ifndef ANDROID_HARDWARE_AUTOMOTIVE_EVS_V1_1_VIDEOCAPTURE_H
+#define ANDROID_HARDWARE_AUTOMOTIVE_EVS_V1_1_VIDEOCAPTURE_H
 
 #include <atomic>
 #include <thread>
@@ -27,7 +27,7 @@
 
 class VideoCapture {
 public:
-    bool open(const char* deviceName);
+    bool open(const char* deviceName, const int32_t width = 0, const int32_t height = 0);
     void close();
 
     bool startStream(std::function<void(VideoCapture*, imageBuffer*, void*)> callback = nullptr);
@@ -47,6 +47,9 @@
 
     bool isOpen()               { return mDeviceFd >= 0; };
 
+    int setParameter(struct v4l2_control& control);
+    int getParameter(struct v4l2_control& control);
+
 private:
     void collectFrames();
     void markFrameReady();
diff --git a/evs/sampleDriver/android.hardware.automotive.evs@1.0-sample.rc b/evs/sampleDriver/android.hardware.automotive.evs@1.0-sample.rc
deleted file mode 100644
index 445a3a0..0000000
--- a/evs/sampleDriver/android.hardware.automotive.evs@1.0-sample.rc
+++ /dev/null
@@ -1,7 +0,0 @@
-service evs_driver /system/bin/android.hardware.automotive.evs@1.0-sample
-    class hal
-    priority -20
-    user graphics
-    group automotive_evs camera
-    onrestart restart evs_manager
-    disabled # will not automatically start with its class; must be explictly started.
diff --git a/evs/sampleDriver/android.hardware.automotive.evs@1.1-sample.rc b/evs/sampleDriver/android.hardware.automotive.evs@1.1-sample.rc
new file mode 100644
index 0000000..324a949
--- /dev/null
+++ b/evs/sampleDriver/android.hardware.automotive.evs@1.1-sample.rc
@@ -0,0 +1,7 @@
+service evs_driver /vendor/bin/android.hardware.automotive.evs@1.1-sample
+    class hal
+    priority -20
+    user graphics
+    group automotive_evs camera
+    onrestart restart evs_manager
+    disabled # will not automatically start with its class; must be explictly started.
diff --git a/evs/sampleDriver/bufferCopy.cpp b/evs/sampleDriver/bufferCopy.cpp
index 6c8c3ef..1c07478 100644
--- a/evs/sampleDriver/bufferCopy.cpp
+++ b/evs/sampleDriver/bufferCopy.cpp
@@ -21,7 +21,7 @@
 namespace hardware {
 namespace automotive {
 namespace evs {
-namespace V1_0 {
+namespace V1_1 {
 namespace implementation {
 
 
@@ -71,10 +71,12 @@
     // an even multiple of 16 bytes for both the Y and UV arrays.
 
     // Target  and source image layout properties (They match since the formats match!)
-    const unsigned strideLum = align<16>(tgtBuff.width);
-    const unsigned sizeY = strideLum * tgtBuff.height;
+    const AHardwareBuffer_Desc* pDesc =
+        reinterpret_cast<const AHardwareBuffer_Desc*>(&tgtBuff.buffer.description);
+    const unsigned strideLum = align<16>(pDesc->width);
+    const unsigned sizeY = strideLum * pDesc->height;
     const unsigned strideColor = strideLum;   // 1/2 the samples, but two interleaved channels
-    const unsigned sizeColor = strideColor * tgtBuff.height/2;
+    const unsigned sizeColor = strideColor * pDesc->height/2;
     const unsigned totalBytes = sizeY + sizeColor;
 
     // Simply copy the data byte for byte
@@ -100,8 +102,10 @@
     };
 
     // Target image layout properties
-    const unsigned strideLum = align<16>(tgtBuff.width);
-    const unsigned sizeY = strideLum * tgtBuff.height;
+    const AHardwareBuffer_Desc* pDesc =
+        reinterpret_cast<const AHardwareBuffer_Desc*>(&tgtBuff.buffer.description);
+    const unsigned strideLum = align<16>(pDesc->width);
+    const unsigned sizeY = strideLum * pDesc->height;
     const unsigned strideColor = strideLum;   // 1/2 the samples, but two interleaved channels
 
     // Source image layout properties
@@ -111,14 +115,14 @@
     uint32_t* botSrcRow =  srcDataYUYV + srcRowPixels;
 
     // We're going to work on one 2x2 cell in the output image at at time
-    for (unsigned cellRow = 0; cellRow < tgtBuff.height/2; cellRow++) {
+    for (unsigned cellRow = 0; cellRow < pDesc->height/2; cellRow++) {
 
         // Set up the output pointers
         uint8_t* yTopRow = tgt + (cellRow*2) * strideLum;
         uint8_t* yBotRow = yTopRow + strideLum;
         uint8_t* uvRow   = (tgt + sizeY) + cellRow * strideColor;
 
-        for (unsigned cellCol = 0; cellCol < tgtBuff.width/2; cellCol++) {
+        for (unsigned cellCol = 0; cellCol < pDesc->width/2; cellCol++) {
             // Collect the values from the YUYV interleaved data
             const YUYVpixel* pTopMacroPixel = (YUYVpixel*)&topSrcRow[cellCol];
             const YUYVpixel* pBotMacroPixel = (YUYVpixel*)&botSrcRow[cellCol];
@@ -144,12 +148,14 @@
 
 
 void fillRGBAFromYUYV(const BufferDesc& tgtBuff, uint8_t* tgt, void* imgData, unsigned imgStride) {
-    unsigned width = tgtBuff.width;
-    unsigned height = tgtBuff.height;
+    const AHardwareBuffer_Desc* pDesc =
+        reinterpret_cast<const AHardwareBuffer_Desc*>(&tgtBuff.buffer.description);
+    unsigned width = pDesc->width;
+    unsigned height = pDesc->height;
     uint32_t* src = (uint32_t*)imgData;
     uint32_t* dst = (uint32_t*)tgt;
     unsigned srcStridePixels = imgStride / 2;
-    unsigned dstStridePixels = tgtBuff.stride;
+    unsigned dstStridePixels = pDesc->stride;
 
     const int srcRowPadding32 = srcStridePixels/2 - width/2;  // 2 bytes per pixel, 4 bytes per word
     const int dstRowPadding32 = dstStridePixels   - width;    // 4 bytes per pixel, 4 bytes per word
@@ -178,12 +184,14 @@
 
 
 void fillYUYVFromYUYV(const BufferDesc& tgtBuff, uint8_t* tgt, void* imgData, unsigned imgStride) {
-    unsigned width = tgtBuff.width;
-    unsigned height = tgtBuff.height;
+    const AHardwareBuffer_Desc* pDesc =
+        reinterpret_cast<const AHardwareBuffer_Desc*>(&tgtBuff.buffer.description);
+    unsigned width = pDesc->width;
+    unsigned height = pDesc->height;
     uint8_t* src = (uint8_t*)imgData;
     uint8_t* dst = (uint8_t*)tgt;
     unsigned srcStrideBytes = imgStride;
-    unsigned dstStrideBytes = tgtBuff.stride * 2;
+    unsigned dstStrideBytes = pDesc->stride * 2;
 
     for (unsigned r=0; r<height; r++) {
         // Copy a pixel row at a time (2 bytes per pixel, averaged over a YUYV macro pixel)
@@ -193,12 +201,14 @@
 
 
 void fillYUYVFromUYVY(const BufferDesc& tgtBuff, uint8_t* tgt, void* imgData, unsigned imgStride) {
-    unsigned width = tgtBuff.width;
-    unsigned height = tgtBuff.height;
+    const AHardwareBuffer_Desc* pDesc =
+        reinterpret_cast<const AHardwareBuffer_Desc*>(&tgtBuff.buffer.description);
+    unsigned width = pDesc->width;
+    unsigned height = pDesc->height;
     uint32_t* src = (uint32_t*)imgData;
     uint32_t* dst = (uint32_t*)tgt;
     unsigned srcStridePixels = imgStride / 2;
-    unsigned dstStridePixels = tgtBuff.stride;
+    unsigned dstStridePixels = pDesc->stride;
 
     const int srcRowPadding32 = srcStridePixels/2 - width/2;  // 2 bytes per pixel, 4 bytes per word
     const int dstRowPadding32 = dstStridePixels/2 - width/2;  // 2 bytes per pixel, 4 bytes per word
@@ -228,7 +238,7 @@
 
 
 } // namespace implementation
-} // namespace V1_0
+} // namespace V1_1
 } // namespace evs
 } // namespace automotive
 } // namespace hardware
diff --git a/evs/sampleDriver/bufferCopy.h b/evs/sampleDriver/bufferCopy.h
index 9f68f2b..b07a619 100644
--- a/evs/sampleDriver/bufferCopy.h
+++ b/evs/sampleDriver/bufferCopy.h
@@ -14,17 +14,18 @@
  * limitations under the License.
  */
 
-#ifndef ANDROID_HARDWARE_AUTOMOTIVE_EVS_V1_0_BUFFERCOPY_H
-#define ANDROID_HARDWARE_AUTOMOTIVE_EVS_V1_0_BUFFERCOPY_H
+#ifndef ANDROID_HARDWARE_AUTOMOTIVE_EVS_V1_1_BUFFERCOPY_H
+#define ANDROID_HARDWARE_AUTOMOTIVE_EVS_V1_1_BUFFERCOPY_H
 
-#include <android/hardware/automotive/evs/1.0/types.h>
+#include <android/hardware_buffer.h>
+#include <android/hardware/automotive/evs/1.1/types.h>
 
 
 namespace android {
 namespace hardware {
 namespace automotive {
 namespace evs {
-namespace V1_0 {
+namespace V1_1 {
 namespace implementation {
 
 
@@ -44,10 +45,10 @@
                       void* imgData, unsigned imgStride);
 
 } // namespace implementation
-} // namespace V1_0
+} // namespace V1_1
 } // namespace evs
 } // namespace automotive
 } // namespace hardware
 } // namespace android
 
-#endif  // ANDROID_HARDWARE_AUTOMOTIVE_EVS_V1_0_BUFFERCOPY_H
+#endif  // ANDROID_HARDWARE_AUTOMOTIVE_EVS_V1_1_BUFFERCOPY_H
diff --git a/evs/sampleDriver/resources/evs_configuration.dtd b/evs/sampleDriver/resources/evs_configuration.dtd
new file mode 100644
index 0000000..d6be018
--- /dev/null
+++ b/evs/sampleDriver/resources/evs_configuration.dtd
@@ -0,0 +1,108 @@
+<?xml version='1.0' encoding='utf-8'?>
+<!-- Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<!-- Author : changyeon@google.com
+     Version: 1.0
+-->
+
+<!ELEMENT configuration (system,camera,display)>
+
+<!-- System Configuration that contains below informations:
+     - Number of cameras available to EVS.
+-->
+<!ELEMENT system (num_cameras)>
+    <!-- The number of cameras that are available to EVS on the vehicle.
+         This must be equal to number of camera elements of device element.
+    -->
+    <!ELEMENT num_cameras EMPTY>
+    <!ATTLIST num_cameras
+        value CDATA #REQUIRED
+    >
+
+<!-- Device descriptions -->
+<!ELEMENT camera (group|device)+>
+    <!-- Camera group descriptor
+         @attr group_id    : Unique logical camera group identifier.  Camera device use this to be
+                             a member of the group.
+         @attr device_id   : Comma-separated list of unique camera identifiers of member camera
+                             devices.
+         @attr synchronized: NONE if cameras are not synchronized.
+                             CALIBRATED if cameras are synchronized by hardware.
+                             APPROXIMATE if cameras are synchronized by other means.
+    -->
+    <!ELEMENT group (caps)>
+    <!ATTLIST group
+        group_id        CDATA #REQUIRED
+        device_id       CDATA #REQUIRED
+        synchronized    CDATA #REQUIRED
+    >
+    <!-- Please note that a camera group may have stream configurations.  If it has, all stream
+         configurations must be supported by each camera device in the group.
+    -->
+
+    <!-- Camera device descriptor
+         @attr id          : Unique camera identifier.
+         @attr position    : Must be one of front, rear, left, or right.
+    -->
+    <!ELEMENT device (caps,characteristics*)>
+    <!ATTLIST device
+        id              CDATA #REQUIRED
+        position        CDATA #REQUIRED
+    >
+        <!-- Camera metadata that contains:
+             - A list of supported controls.
+             - A list of supported stream configurations.
+        -->
+        <!ELEMENT caps (supported_controls|stream)*>
+            <!-- A list of supported controls.
+                 This must be a subset of android.hardware.automotive.evs@1.1::CameraParam.
+            -->
+            <!ELEMENT supported_controls (control)+>
+                <!-- A camera control parameter with its valid value range -->
+                <!ELEMENT control EMPTY>
+                <!ATTLIST control
+                    name    CDATA #REQUIRED
+                    min     CDATA #REQUIRED
+                    max     CDATA #REQUIRED
+                    step    CDATA '1'
+                >
+
+            <!-- A list of supported output sizes. -->
+            <!ELEMENT stream EMPTY>
+            <!ATTLIST stream
+                id        CDATA #REQUIRED
+                width     CDATA #REQUIRED
+                height    CDATA #REQUIRED
+                format    CDATA #REQUIRED
+                framerate CDATA #REQUIRED
+            >
+
+        <!-- Camera module characteristics including its optics and imaging sensor. -->
+        <!ELEMENT characteristics (parameter)*>
+                <!ELEMENT parameter EMPTY>
+                <!-- A name of camera characteristic.  This must be a subset of
+                     android.hardware.automotive.evs@1.1::CameraCharacteristics.
+                -->
+                <!ATTLIST parameter
+                    name  CDATA #REQUIRED
+                    type  CDATA #REQUIRED
+                    size  CDATA #REQUIRED
+                    value CDATA #REQUIRED
+                >
+
+<!-- Available display devices -->
+<!ELEMENT display (device)+>
+
diff --git a/evs/sampleDriver/resources/evs_sample_configuration.xml b/evs/sampleDriver/resources/evs_sample_configuration.xml
new file mode 100644
index 0000000..fa3ada3
--- /dev/null
+++ b/evs/sampleDriver/resources/evs_sample_configuration.xml
@@ -0,0 +1,212 @@
+<?xml version='1.0' encoding='utf-8'?>
+<!-- Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<!-- Exterior View System Example Configuration
+
+     Android Automotive axes are used to define coordinates.
+     See https://source.android.com/devices/sensors/sensor-types#auto_axes
+
+     Use evs_configuration.dtd with xmllint tool, to validate XML configuration file
+-->
+
+<configuration>
+    <!-- system configuration -->
+    <system>
+        <!-- number of cameras available to EVS -->
+        <num_cameras value='2'/>
+    </system>
+
+    <!-- camera device information -->
+    <camera>
+        <!-- camera group 0 -->
+        <group id='group0' synchronized='CALIBRATED'>
+            <caps>
+                <!-- list of supported controls supported by all physical devices -->
+                <supported_controls>
+                    <control name='BRIGHTNESS' min='0' max='255'/>
+                    <control name='CONTRAST' min='0' max='255'/>
+                </supported_controls>
+
+                <!-- list of stream configuration supported by all physical devices -->
+                <stream id='0' width='640' height='480' format='RGBA_8888' framerate='30'/>
+            </caps>
+
+            <!-- list of parameters -->
+            <characteristics>
+                <parameter
+                    name='REQUEST_AVAILABLE_CAPABILITIES'
+                    type='enum'
+                    size='1'
+                    value='LOGICAL_MULTI_CAMERA'
+                />
+                <parameter
+                    name='LOGICAL_MULTI_CAMERA_PHYSICAL_IDS'
+                    type='byte[]'
+                    size='2'
+                    value='/dev/video3,/dev/video4'
+                />
+            </characteristics>
+        </group>
+
+        <!-- camera device starts -->
+        <device id='/dev/video3' position='rear'>
+            <caps>
+                <!-- list of supported controls -->
+                <supported_controls>
+                    <control name='BRIGHTNESS' min='0' max='255'/>
+                    <control name='CONTRAST' min='0' max='255'/>
+                    <control name='AUTO_WHITE_BALANCE' min='0' max='1'/>
+                    <control name='WHITE_BALANCE_TEMPERATURE' min='2000' max='7500'/>
+                    <control name='SHARPNESS' min='0' max='255'/>
+                    <control name='AUTO_FOCUS' min='0' max='1'/>
+                    <control name='ABSOLUTE_FOCUS' min='0' max='255' step='5'/>
+                    <control name='ABSOLUTE_ZOOM' min='100' max='400'/>
+                </supported_controls>
+
+                <!-- list of supported stream configurations -->
+                <!-- below configurations were taken from v4l2-ctrl query on Logitech Webcam C930e device -->
+                <stream id='0' width='1920' height='1080' format='RGBA_8888' framerate='5'/>
+                <stream id='1' width='2304' height='1296' format='RGBA_8888' framerate='2'/>
+                <stream id='2' width='2304' height='1536' format='RGBA_8888' framerate='2'/>
+                <stream id='4' width='1280' height='720'  format='RGBA_8888' framerate='10'/>
+                <stream id='4' width='1024' height='576'  format='RGBA_8888' framerate='15'/>
+                <stream id='5' width='960'  height='540'  format='RGBA_8888' framerate='15'/>
+                <stream id='6' width='848'  height='480'  format='RGBA_8888' framerate='30'/>
+                <stream id='7' width='640'  height='360'  format='RGBA_8888' framerate='30'/>
+                <stream id='8' width='480'  height='270'  format='RGBA_8888' framerate='30'/>
+                <stream id='9' width='160'  height='120'  format='RGBA_8888' framerate='30'/>
+            </caps>
+
+            <!-- list of parameters -->
+            <characteristics>
+                <!-- Lens distortion information. See
+                     https://developer.android.com/reference/android/hardware/camera2/CameraCharacteristics.html#LENS_DISTORTION
+                -->
+                <parameter
+                    name='LENS_DISTORTION'
+                    type='float'
+                    size='5'
+                    value='0.0,0.0,0.0,0.0,0.0'
+                />
+
+                <!-- Camera intrinsic calibration matrix. See
+                     https://developer.android.com/reference/android/hardware/camera2/CameraCharacteristics.html#LENS_INTRINSIC_CALIBRATION
+                -->
+                <parameter
+                    name='LENS_INTRINSIC_CALIBRATION'
+                    type='float'
+                    size='5'
+                    value='0.0,0.0,0.0,0.0,0.0'
+                />
+
+                <!-- Camera pose translation and rotation.  See
+                     https://developer.android.com/reference/android/hardware/camera2/CameraCharacteristics.html#LENS_POSE_TRANSLATION
+                     https://developer.android.com/reference/android/hardware/camera2/CameraCharacteristics.html#LENS_POSE_ROTATION
+                -->
+                <parameter
+                    name='LENS_POSE_TRANSLATION'
+                    type='float'
+                    size='3'
+                    value='0.0,0.0,0.0'
+                />
+                <parameter
+                    name='LENS_POSE_ROTATION'
+                    type='float'
+                    size='4'
+                    value='0.0,0.0,0.0,0.0'
+                />
+            </characteristics>
+        </device>
+        <device id='/dev/video4' position='front'>
+            <caps>
+                <!-- list of supported controls -->
+                <supported_controls>
+                    <control name='BRIGHTNESS' min='0' max='255'/>
+                    <control name='CONTRAST' min='0' max='255'/>
+                    <control name='AUTO_WHITE_BALANCE' min='0' max='1'/>
+                    <control name='WHITE_BALANCE_TEMPERATURE' min='2000' max='7500'/>
+                    <control name='SHARPNESS' min='0' max='255'/>
+                    <control name='AUTO_FOCUS' min='0' max='1'/>
+                    <control name='ABSOLUTE_FOCUS' min='0' max='255' step='5'/>
+                    <control name='ABSOLUTE_ZOOM' min='100' max='400'/>
+                </supported_controls>
+
+                <!-- list of supported stream configurations -->
+                <!-- below configurations were taken from v4l2-ctrl query on Logitech Webcam C930e device -->
+                <stream id='0' width='1920' height='1080' format='RGBA_8888' framerate='5'/>
+                <stream id='1' width='2304' height='1296' format='RGBA_8888' framerate='2'/>
+                <stream id='2' width='2304' height='1536' format='RGBA_8888' framerate='2'/>
+                <stream id='4' width='1280' height='720'  format='RGBA_8888' framerate='10'/>
+                <stream id='4' width='1024' height='576'  format='RGBA_8888' framerate='15'/>
+                <stream id='5' width='960'  height='540'  format='RGBA_8888' framerate='15'/>
+                <stream id='6' width='848'  height='480'  format='RGBA_8888' framerate='30'/>
+                <stream id='7' width='640'  height='360'  format='RGBA_8888' framerate='30'/>
+                <stream id='8' width='480'  height='270'  format='RGBA_8888' framerate='30'/>
+                <stream id='9' width='160'  height='120'  format='RGBA_8888' framerate='30'/>
+            </caps>
+
+            <!-- list of parameters -->
+            <characteristics>
+                <!-- Lens distortion information. See
+                     https://developer.android.com/reference/android/hardware/camera2/CameraCharacteristics.html#LENS_DISTORTION
+                -->
+                <parameter
+                    name='LENS_DISTORTION'
+                    type='float'
+                    size='5'
+                    value='0.0,0.0,0.0,0.0,0.0'
+                />
+
+                <!-- Camera intrinsic calibration matrix. See
+                     https://developer.android.com/reference/android/hardware/camera2/CameraCharacteristics.html#LENS_INTRINSIC_CALIBRATION
+                -->
+                <parameter
+                    name='LENS_INTRINSIC_CALIBRATION'
+                    type='float'
+                    size='5'
+                    value='0.0,0.0,0.0,0.0,0.0'
+                />
+
+                <!-- Camera pose translation and rotation.  See
+                     https://developer.android.com/reference/android/hardware/camera2/CameraCharacteristics.html#LENS_POSE_TRANSLATION
+                     https://developer.android.com/reference/android/hardware/camera2/CameraCharacteristics.html#LENS_POSE_ROTATION
+                -->
+                <parameter
+                    name='LENS_POSE_TRANSLATION'
+                    type='float'
+                    size='3'
+                    value='0.0,0.0,0.0'
+                />
+                <parameter
+                    name='LENS_POSE_ROTATION'
+                    type='float'
+                    size='4'
+                    value='0.0,0.0,0.0,0.0'
+                />
+            </characteristics>
+        </device>
+    </camera>
+
+    <!-- display device starts -->
+    <display>
+        <device id='display0' position='driver'>
+            <caps>
+                <!-- list of supported inpu stream configurations -->
+                <stream id='0' width='1280' height='720' format='RGBA_8888' framerate='30'/>
+            </caps>
+        </device>
+    </display>
+</configuration>
+
diff --git a/evs/sampleDriver/service.cpp b/evs/sampleDriver/service.cpp
index d8fb316..fef417e 100644
--- a/evs/sampleDriver/service.cpp
+++ b/evs/sampleDriver/service.cpp
@@ -32,22 +32,29 @@
 using android::hardware::joinRpcThreadpool;
 
 // Generated HIDL files
-using android::hardware::automotive::evs::V1_0::IEvsEnumerator;
-using android::hardware::automotive::evs::V1_0::IEvsDisplay;
+using android::hardware::automotive::evs::V1_1::IEvsEnumerator;
+using android::hardware::automotive::evs::V1_1::IEvsDisplay;
+using android::frameworks::automotive::display::V1_0::IAutomotiveDisplayProxyService;
 
 // The namespace in which all our implementation code lives
-using namespace android::hardware::automotive::evs::V1_0::implementation;
+using namespace android::hardware::automotive::evs::V1_1::implementation;
 using namespace android;
 
 
 int main() {
     ALOGI("EVS Hardware Enumerator service is starting");
 
+    android::sp<IAutomotiveDisplayProxyService> carWindowService = IAutomotiveDisplayProxyService::getService("default");
+    if (carWindowService == nullptr) {
+        ALOGE("Cannot use AutomotiveDisplayProxyService.  Exiting.");
+        return 1;
+    }
+
     // Start a thread to listen video device addition events.
     std::atomic<bool> running { true };
     std::thread ueventHandler(EvsEnumerator::EvsUeventThread, std::ref(running));
 
-    android::sp<IEvsEnumerator> service = new EvsEnumerator();
+    android::sp<IEvsEnumerator> service = new EvsEnumerator(carWindowService);
 
     configureRpcThreadpool(1, true /* callerWillJoin */);
 
diff --git a/evs/sepolicy/evs_driver.te b/evs/sepolicy/evs_driver.te
index f1f31e9..179623d 100644
--- a/evs/sepolicy/evs_driver.te
+++ b/evs/sepolicy/evs_driver.te
@@ -1,10 +1,10 @@
 # evs_mock mock hardware driver service
-type hal_evs_driver, domain, coredomain;
+type hal_evs_driver, domain;
 hal_server_domain(hal_evs_driver, hal_evs)
 hal_client_domain(hal_evs_driver, hal_evs)
 
 # allow init to launch processes in this context
-type hal_evs_driver_exec, exec_type, file_type, system_file_type;
+type hal_evs_driver_exec, exec_type, file_type, vendor_file_type;
 init_daemon_domain(hal_evs_driver)
 binder_use(hal_evs_driver)
 
@@ -22,3 +22,4 @@
 
 # Allow the driver to access kobject uevents
 allow hal_evs_driver self:netlink_kobject_uevent_socket create_socket_perms_no_ioctl;
+
diff --git a/evs/sepolicy/evs_manager.te b/evs/sepolicy/evs_manager.te
index 51acac1..6a4173a 100644
--- a/evs/sepolicy/evs_manager.te
+++ b/evs/sepolicy/evs_manager.te
@@ -9,3 +9,4 @@
 
 # allow use of hwservices
 allow evs_manager hal_graphics_allocator_default:fd use;
+
diff --git a/evs/sepolicy/file_contexts b/evs/sepolicy/file_contexts
index 0358e16..7b8ec19 100644
--- a/evs/sepolicy/file_contexts
+++ b/evs/sepolicy/file_contexts
@@ -3,9 +3,10 @@
 # Binaries associated with the default EVS stack, plus
 # the directory which contains the configuration for the evs_app
 #
-/system/bin/android\.hardware\.automotive\.evs@1\.0-sample   u:object_r:hal_evs_driver_exec:s0
-/system/bin/android\.automotive\.evs\.manager@1\.0           u:object_r:evs_manager_exec:s0
-/system/bin/evs_app                                          u:object_r:evs_app_exec:s0
-/system/etc/automotive/evs(/.*)?                             u:object_r:evs_app_files:s0
+/system/bin/android\.automotive\.evs\.manager@1\.[0-9]+         u:object_r:evs_manager_exec:s0
+/system/bin/evs_app                                             u:object_r:evs_app_exec:s0
+/system/bin/evs_app_support_lib                                 u:object_r:evs_app_exec:s0
+/system/etc/automotive/evs(/.*)?                                u:object_r:evs_app_files:s0
+/vendor/bin/android\.hardware\.automotive\.evs@1\.[0-9]+-sample u:object_r:hal_evs_driver_exec:s0
 
 ###################################
diff --git a/evs/sepolicy/servicemanager.te b/evs/sepolicy/servicemanager.te
new file mode 100644
index 0000000..006e3e3
--- /dev/null
+++ b/evs/sepolicy/servicemanager.te
@@ -0,0 +1,3 @@
+allow servicemanager evs_app:dir search;
+allow servicemanager evs_app:file r_file_perms;
+allow servicemanager evs_app:process getattr;
diff --git a/evs/support_library/AnalyzeUseCase.cpp b/evs/support_library/AnalyzeUseCase.cpp
new file mode 100644
index 0000000..d79f93e
--- /dev/null
+++ b/evs/support_library/AnalyzeUseCase.cpp
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include <hidl/HidlTransportSupport.h>
+#include <log/log.h>
+#include <utils/SystemClock.h>
+
+#include "AnalyzeUseCase.h"
+#include "ConfigManager.h"
+
+using android::hardware::configureRpcThreadpool;
+using android::hardware::joinRpcThreadpool;
+
+namespace android {
+namespace automotive {
+namespace evs {
+namespace support {
+
+AnalyzeUseCase::AnalyzeUseCase(string cameraId, BaseAnalyzeCallback* callback)
+              : BaseUseCase(vector<string>(1, cameraId)),
+                mAnalyzeCallback(callback) {}
+
+AnalyzeUseCase::~AnalyzeUseCase() {}
+
+bool AnalyzeUseCase::initialize() {
+    // TODO(b/130246434): Move the following ConfigManager and thread pool
+    // logic into ResourceManager, for both display and analyze use case.
+
+    ConfigManager config;
+    if (!config.initialize("/system/etc/automotive/evs_support_lib/camera_config.json")) {
+        ALOGE("Missing or improper configuration for the EVS application.  Exiting.");
+        return false;
+    }
+
+    // Set thread pool size to one to avoid concurrent events from the HAL.
+    // This pool will handle the EvsCameraStream callbacks.
+    // Note:  This _will_ run in parallel with the EvsListener run() loop below which
+    // runs the application logic that reacts to the async events.
+    configureRpcThreadpool(1, false /* callerWillJoin */);
+
+    mResourceManager = ResourceManager::getInstance();
+
+    ALOGD("Requesting camera list");
+    for (auto&& info : config.getCameras()) {
+        // This use case is currently a single camera use case.
+        // Only one element is available in the camera id list.
+        string cameraId = mCameraIds[0];
+        if (cameraId == info.cameraId) {
+            mStreamHandler =
+                mResourceManager->obtainStreamHandler(cameraId);
+            if (mStreamHandler.get() == nullptr) {
+                ALOGE("Failed to get a valid StreamHandler for %s",
+                      cameraId.c_str());
+                return false;
+            }
+
+            mIsInitialized = true;
+            return true;
+        }
+    }
+
+    ALOGE("Cannot find a match camera. Exiting");
+    return false;
+}
+
+bool AnalyzeUseCase::startVideoStream() {
+    ALOGD("AnalyzeUseCase::startVideoStream");
+
+    // Initialize the use case.
+    if (!mIsInitialized && !initialize()) {
+        ALOGE("There is an error while initializing the use case. Exiting");
+        return false;
+    }
+
+    ALOGD("Attach callback to StreamHandler");
+    if (mAnalyzeCallback != nullptr) {
+        mStreamHandler->attachAnalyzeCallback(mAnalyzeCallback);
+    }
+
+    mStreamHandler->startStream();
+
+    return true;
+}
+
+void AnalyzeUseCase::stopVideoStream() {
+    ALOGD("AnalyzeUseCase::stopVideoStream");
+
+    if (mStreamHandler == nullptr) {
+        ALOGE("Failed to detach render callback since stream handler is null");
+
+        // Something may go wrong. Instead of to return this method right away,
+        // we want to finish the remaining logic of this method to try to
+        // release other resources.
+    } else {
+        mStreamHandler->detachAnalyzeCallback();
+    }
+
+    if (mResourceManager == nullptr) {
+        ALOGE("Failed to release resources since resource manager is null");
+    } else {
+        mResourceManager->releaseStreamHandler(mCameraIds[0]);
+    }
+
+    mStreamHandler = nullptr;
+
+    // TODO(b/130246434): with the current logic, the initialize method will
+    // be triggered every time when a pair of
+    // stopVideoStream/startVideoStream is called. We might want to move
+    // some heavy work away from initialize method so increase the
+    // performance.
+
+    // Sets mIsInitialzed to false so the initialize method will be
+    // triggered when startVideoStream is called again.
+    mIsInitialized = false;
+}
+
+// TODO(b/130246434): For both Analyze use case and Display use case, return a
+// pointer instead of an object.
+AnalyzeUseCase AnalyzeUseCase::createDefaultUseCase(
+    string cameraId, BaseAnalyzeCallback* callback) {
+    return AnalyzeUseCase(cameraId, callback);
+}
+
+}  // namespace support
+}  // namespace evs
+}  // namespace automotive
+}  // namespace android
+
diff --git a/evs/support_library/AnalyzeUseCase.h b/evs/support_library/AnalyzeUseCase.h
new file mode 100644
index 0000000..837aaf1
--- /dev/null
+++ b/evs/support_library/AnalyzeUseCase.h
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#ifndef CAR_LIB_EVS_SUPPORT_ANALYZE_USECASE_H
+#define CAR_LIB_EVS_SUPPORT_ANALYZE_USECASE_H
+
+#include <thread>
+
+#include "BaseUseCase.h"
+#include "StreamHandler.h"
+#include "BaseAnalyzeCallback.h"
+#include "ResourceManager.h"
+
+using ::android::sp;
+using ::android::hardware::Return;
+using ::std::string;
+
+namespace android {
+namespace automotive {
+namespace evs {
+namespace support {
+
+class AnalyzeUseCase : public BaseUseCase {
+public:
+    AnalyzeUseCase(string cameraId, BaseAnalyzeCallback* analyzeCallback);
+    virtual ~AnalyzeUseCase();
+    virtual bool startVideoStream() override;
+    virtual void stopVideoStream() override;
+
+    static AnalyzeUseCase createDefaultUseCase(string cameraId,
+                                               BaseAnalyzeCallback* cb = nullptr);
+
+private:
+    bool initialize();
+
+    bool mIsInitialized = false;
+    BaseAnalyzeCallback* mAnalyzeCallback = nullptr;
+
+    sp<StreamHandler>           mStreamHandler;
+    sp<ResourceManager>         mResourceManager;
+};
+
+}  // namespace support
+}  // namespace evs
+}  // namespace automotive
+}  // namespace android
+
+#endif // CAR_LIB_EVS_SUPPORT_ANALYZE_USECASE_H
diff --git a/evs/support_library/Android.bp b/evs/support_library/Android.bp
new file mode 100644
index 0000000..6556404
--- /dev/null
+++ b/evs/support_library/Android.bp
@@ -0,0 +1,80 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//
+
+//#################################
+cc_library_shared {
+    name: "libevssupport",
+
+    srcs: [
+        "RenderBase.cpp",
+        "RenderDirectView.cpp",
+        "ConfigManager.cpp",
+        "glError.cpp",
+        "shader.cpp",
+        "TexWrapper.cpp",
+        "VideoTex.cpp",
+        "StreamHandler.cpp",
+        "ResourceManager.cpp",
+        "FormatConvert.cpp",
+        "DisplayUseCase.cpp",
+        "AnalyzeUseCase.cpp",
+        "Utils.cpp",
+    ],
+
+    shared_libs: [
+        "libcutils",
+        "liblog",
+        "libutils",
+        "libui",
+        "libhidlbase",
+        "libEGL",
+        "libGLESv2",
+        "libhardware",
+        "libpng",
+        "android.hardware.automotive.evs@1.0",
+    ],
+
+    static_libs: [
+        "libmath",
+        "libjsoncpp",
+    ],
+
+    required: [
+        "camera_config.json",
+    ],
+
+    strip: {
+        keep_symbols: true,
+    },
+
+    cflags: ["-DLOG_TAG=\"libevssupport\""] + [
+        "-DGL_GLEXT_PROTOTYPES",
+        "-DEGL_EGLEXT_PROTOTYPES",
+    ] + [
+        "-Wall",
+        "-Werror",
+        "-Wunused",
+        "-Wunreachable-code",
+    ],
+}
+
+prebuilt_etc {
+    name: "camera_config.json",
+
+    src: "config.json",
+    sub_dir: "automotive/evs_support_lib",
+}
+
diff --git a/evs/support_library/BaseAnalyzeCallback.h b/evs/support_library/BaseAnalyzeCallback.h
new file mode 100644
index 0000000..e67c791
--- /dev/null
+++ b/evs/support_library/BaseAnalyzeCallback.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef CAR_LIB_EVS_SUPPORT_BASE_ANALYZE_CALLBACK_H
+#define CAR_LIB_EVS_SUPPORT_BASE_ANALYZE_CALLBACK_H
+
+#include "Frame.h"
+
+namespace android {
+namespace automotive {
+namespace evs {
+namespace support {
+
+class BaseAnalyzeCallback{
+    public:
+        virtual void analyze(const Frame&) = 0;
+        virtual ~BaseAnalyzeCallback() {};
+};
+
+}  // namespace support
+}  // namespace evs
+}  // namespace automotive
+}  // namespace android
+
+#endif // CAR_LIB_EVS_SUPPORT_BASE_ANALYZE_CALLBACK_H
diff --git a/evs/support_library/BaseRenderCallback.h b/evs/support_library/BaseRenderCallback.h
new file mode 100644
index 0000000..cdcf6a4
--- /dev/null
+++ b/evs/support_library/BaseRenderCallback.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#ifndef EVS_SUPPORT_LIBRARY_BASERENDERCALLBACK_H_
+#define EVS_SUPPORT_LIBRARY_BASERENDERCALLBACK_H_
+
+#include "Frame.h"
+
+namespace android {
+namespace automotive {
+namespace evs {
+namespace support {
+
+class BaseRenderCallback {
+  public:
+    // TODO(b/130246434): Rename the callback to a more accurate name since
+    // the callback itself is about image inline processing. Also avoid
+    // passing in two frames, since the two frames are almost identical except
+    // for the data pointer. Instead, pass in one input frame and one output
+    // data pointer.
+    virtual void render(const Frame& in, const Frame& out) = 0;
+    virtual ~BaseRenderCallback() {
+    }
+};
+
+}  // namespace support
+}  // namespace evs
+}  // namespace automotive
+}  // namespace android
+
+#endif  // EVS_SUPPORT_LIBRARY_BASERENDERCALLBACK_H_
diff --git a/evs/support_library/BaseUseCase.h b/evs/support_library/BaseUseCase.h
new file mode 100644
index 0000000..a76174d
--- /dev/null
+++ b/evs/support_library/BaseUseCase.h
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#ifndef EVS_SUPPORT_LIBRARY_BASEUSECASE_H_
+#define EVS_SUPPORT_LIBRARY_BASEUSECASE_H_
+
+#include <string>
+#include <vector>
+
+namespace android {
+namespace automotive {
+namespace evs {
+namespace support {
+
+using ::std::string;
+using ::std::vector;
+
+/**
+ * Base class for all the use cases in the EVS support library.
+ */
+class BaseUseCase {
+public:
+    /**
+     * Requests delivery of camera frames from the desired EVS camera(s). The
+     * use case begins receiving periodic calls from EVS camera with new image
+     * frames until stopVideoStream is called.
+     *
+     * If the same EVS camera has already been started by other use cases,
+     * the frame delivery to this use case starts without affecting the status
+     * of the EVS camera.
+     *
+     * @return Returns true if the video stream is started successfully.
+     * Otherwise returns false.
+     *
+     * @see stopVideoStream()
+     */
+    virtual bool startVideoStream() = 0;
+
+    /**
+     * Stops the delivery of EVS camera frames, and tries to close the EVS
+     * camera. Because delivery is asynchronous, frames may continue to
+     * arrive for some time after this call returns.
+     *
+     * If other use cases are using the camera at the same time, the EVS
+     * camera will not be closed, until all the other use cases using the
+     * camera are stopped.
+     *
+     * @see startVideoStream()
+     */
+    virtual void stopVideoStream() = 0;
+
+    /**
+     * Default constructor for BaseUseCase.
+     *
+     * @param The ids for the desired EVS cameras.
+     */
+    BaseUseCase(vector<string> cameraIds) : mCameraIds(cameraIds) {};
+
+    virtual ~BaseUseCase() {}
+
+protected:
+    vector<string> mCameraIds;
+};
+
+}  // namespace support
+}  // namespace evs
+}  // namespace automotive
+}  // namespace android
+
+#endif  // EVS_SUPPORT_LIBRARY_BASEUSECASE_H_
diff --git a/evs/support_library/ConfigManager.cpp b/evs/support_library/ConfigManager.cpp
new file mode 100644
index 0000000..f686526
--- /dev/null
+++ b/evs/support_library/ConfigManager.cpp
@@ -0,0 +1,197 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "ConfigManager.h"
+
+#include "json/json.h"
+
+#include <fstream>
+#include <math.h>
+#include <assert.h>
+
+namespace android {
+namespace automotive {
+namespace evs {
+namespace support {
+
+static const float kDegreesToRadians = M_PI / 180.0f;
+
+
+static float normalizeToPlusMinus180degrees(float theta) {
+    const float wraps = floor((theta+180.0f) / 360.0f);
+    return theta - wraps*360.0f;
+}
+
+
+static bool readChildNodeAsFloat(const char* groupName,
+                                 const Json::Value& parentNode,
+                                 const char* childName,
+                                 float* value) {
+    // Must have a place to put the value!
+    assert(value);
+
+    Json::Value childNode = parentNode[childName];
+    if (!childNode.isNumeric()) {
+        printf("Missing or invalid field %s in record %s", childName, groupName);
+        return false;
+    }
+
+    *value = childNode.asFloat();
+    return true;
+}
+
+
+bool ConfigManager::initialize(const char* configFileName)
+{
+    bool complete = true;
+
+    // Set up a stream to read in the input file
+    std::ifstream configStream(configFileName);
+
+    // Parse the stream into JSON objects
+    Json::Reader reader;
+    Json::Value rootNode;
+    bool parseOk = reader.parse(configStream, rootNode, false /* don't need comments */);
+    if (!parseOk) {
+        printf("Failed to read configuration file %s\n", configFileName);
+        printf("%s\n", reader.getFormatedErrorMessages().c_str());
+        return false;
+    }
+
+
+    //
+    // Read car information
+    //
+    {
+        Json::Value car = rootNode["car"];
+        if (!car.isObject()) {
+            printf("Invalid configuration format -- we expect a car description\n");
+            return false;
+        }
+        complete &= readChildNodeAsFloat("car", car, "width",       &mCarWidth);
+        complete &= readChildNodeAsFloat("car", car, "wheelBase",   &mWheelBase);
+        complete &= readChildNodeAsFloat("car", car, "frontExtent", &mFrontExtent);
+        complete &= readChildNodeAsFloat("car", car, "rearExtent",  &mRearExtent);
+    }
+
+
+    //
+    // Read display layout information
+    //
+    {
+        Json::Value displayNode = rootNode["display"];
+        if (!displayNode.isObject()) {
+            printf("Invalid configuration format -- we expect a display description\n");
+            return false;
+        }
+        complete &= readChildNodeAsFloat("display", displayNode, "frontRange", &mFrontRangeInCarSpace);
+        complete &= readChildNodeAsFloat("display", displayNode, "rearRange",  &mRearRangeInCarSpace);
+    }
+
+
+    //
+    // Car top view texture properties for top down view
+    //
+    {
+        Json::Value graphicNode = rootNode["graphic"];
+        if (!graphicNode.isObject()) {
+            printf("Invalid configuration format -- we expect a graphic description\n");
+            return false;
+        }
+        complete &= readChildNodeAsFloat("graphic", graphicNode, "frontPixel", &mCarGraphicFrontPixel);
+        complete &= readChildNodeAsFloat("display", graphicNode, "rearPixel",  &mCarGraphicRearPixel);
+    }
+
+
+    //
+    // Read camera information
+    // NOTE:  Missing positions and angles are not reported, but instead default to zero
+    //
+    {
+        Json::Value cameraArray = rootNode["cameras"];
+        if (!cameraArray.isArray()) {
+            printf("Invalid configuration format -- we expect an array of cameras\n");
+            return false;
+        }
+
+        mCameras.reserve(cameraArray.size());
+        for (auto&& node: cameraArray) {
+            // Get data from the configuration file
+            Json::Value nameNode = node.get("cameraId", "MISSING");
+            const char *cameraId = nameNode.asCString();
+
+            Json::Value usageNode = node.get("function", "");
+            const char *function = usageNode.asCString();
+
+            float yaw   = node.get("yaw", 0).asFloat();
+            float pitch = node.get("pitch", 0).asFloat();
+            float hfov  = node.get("hfov", 0).asFloat();
+            float vfov  = node.get("vfov", 0).asFloat();
+
+            // Wrap the direction angles to be in the 180deg to -180deg range
+            // Rotate 180 in yaw if necessary to flip the pitch into the +/-90degree range
+            pitch = normalizeToPlusMinus180degrees(pitch);
+            if (pitch > 90.0f) {
+                yaw += 180.0f;
+                pitch = 180.0f - pitch;
+            }
+            if (pitch < -90.0f) {
+                yaw += 180.0f;
+                pitch = -180.0f + pitch;
+            }
+            yaw = normalizeToPlusMinus180degrees(yaw);
+
+            // Range check the FOV values to ensure they are positive and less than 180degrees
+            if (hfov > 179.0f) {
+                printf("Pathological horizontal field of view %f clamped to 179 degrees\n", hfov);
+                hfov = 179.0f;
+            }
+            if (hfov < 1.0f) {
+                printf("Pathological horizontal field of view %f clamped to 1 degree\n", hfov);
+                hfov = 1.0f;
+            }
+            if (vfov > 179.0f) {
+                printf("Pathological horizontal field of view %f clamped to 179 degrees\n", vfov);
+                vfov = 179.0f;
+            }
+            if (vfov < 1.0f) {
+                printf("Pathological horizontal field of view %f clamped to 1 degree\n", vfov);
+                vfov = 1.0f;
+            }
+
+            // Store the camera info (converting degrees to radians in the process)
+            CameraInfo info;
+            info.position[0] = node.get("x", 0).asFloat();
+            info.position[1] = node.get("y", 0).asFloat();
+            info.position[2] = node.get("z", 0).asFloat();
+            info.yaw         = yaw   * kDegreesToRadians;
+            info.pitch       = pitch * kDegreesToRadians;
+            info.hfov        = hfov  * kDegreesToRadians;
+            info.vfov        = vfov  * kDegreesToRadians;
+            info.cameraId    = cameraId;
+            info.function    = function;
+
+            mCameras.push_back(info);
+        }
+    }
+
+    // If we got this far, we were successful as long as we found all our child fields
+    return complete;
+}
+
+}  // namespace support
+}  // namespace evs
+}  // namespace automotive
+}  // namespace android
diff --git a/evs/support_library/ConfigManager.h b/evs/support_library/ConfigManager.h
new file mode 100644
index 0000000..6777938
--- /dev/null
+++ b/evs/support_library/ConfigManager.h
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#ifndef CONFIG_MANAGER_H
+#define CONFIG_MANAGER_H
+
+#include <vector>
+#include <string>
+
+namespace android {
+namespace automotive {
+namespace evs {
+namespace support {
+
+class ConfigManager {
+public:
+    struct CameraInfo {
+        std::string cameraId = "";  // The name of the camera from the point of view of the HAL
+        std::string function = "";  // The expected use for this camera ("reverse", "left", "right")
+        float position[3] = {0};    // x, y, z -> right, fwd, up in the units of car space
+        float yaw   = 0;    // radians positive to the left (right hand rule about global z axis)
+        float pitch = 0;    // positive upward (ie: right hand rule about local x axis)
+        float hfov  = 0;    // radians
+        float vfov  = 0;    // radians
+    };
+
+    bool initialize(const char* configFileName);
+
+    // World space dimensions of the car
+    float getCarWidth() const   { return mCarWidth; };
+    float getCarLength() const  { return mWheelBase + mFrontExtent + mRearExtent; };
+    float getWheelBase() const  { return mWheelBase; };
+
+    // Car space (world space centered on the rear axel) edges of the car
+    float getFrontLocation() const  { return mWheelBase + mFrontExtent; };
+    float getRearLocation() const   { return -mRearExtent; };
+    float getRightLocation() const  { return mCarWidth*0.5f; };
+    float getLeftLocation() const   { return -mCarWidth*0.5f; };
+
+    // Where are the edges of the top down display in car space?
+    float getDisplayTopLocation() const {
+        // From the rear axel (origin) to the front bumper, and then beyond by the front range
+        return mWheelBase + mFrontExtent + mFrontRangeInCarSpace;
+    };
+    float getDisplayBottomLocation() const {
+        // From the rear axel (origin) to the back bumper, and then beyond by the back range
+        return -mRearExtent - mRearRangeInCarSpace;
+    };
+    float getDisplayRightLocation(float aspectRatio) const   {
+        // Given the display aspect ratio (width over height), how far can we see to the right?
+        return (getDisplayTopLocation() - getDisplayBottomLocation()) * 0.5f * aspectRatio;
+    };
+    float getDisplayLeftLocation(float aspectRatio) const {
+        // Given the display aspect ratio (width over height), how far can we see to the left?
+        return -getDisplayRightLocation(aspectRatio);
+    };
+
+    // At which texel (vertically in the image) are the front and rear bumpers of the car?
+    float carGraphicFrontPixel() const      { return mCarGraphicFrontPixel; };
+    float carGraphicRearPixel() const       { return mCarGraphicRearPixel; };
+
+    const std::vector<CameraInfo>& getCameras() const   { return mCameras; };
+
+private:
+    // Camera information
+    std::vector<CameraInfo> mCameras;
+
+    // Car body information (assumes front wheel steering and origin at center of rear axel)
+    // Note that units aren't specified and don't matter as long as all length units are consistent
+    // within the JSON file from which we parse.  That is, if everything is in meters, that's fine.
+    // Everything in mm?  That's fine too.
+    float mCarWidth;
+    float mWheelBase;
+    float mFrontExtent;
+    float mRearExtent;
+
+    // Display information
+    float    mFrontRangeInCarSpace;     // How far the display extends in front of the car
+    float    mRearRangeInCarSpace;      // How far the display extends behind the car
+
+    // Top view car image information
+    float mCarGraphicFrontPixel;    // How many pixels from the top of the image does the car start
+    float mCarGraphicRearPixel;     // How many pixels from the top of the image does the car end
+};
+
+}  // namespace support
+}  // namespace evs
+}  // namespace automotive
+}  // namespace android
+
+#endif  // CONFIG_MANAGER_H
diff --git a/evs/support_library/DisplayUseCase.cpp b/evs/support_library/DisplayUseCase.cpp
new file mode 100644
index 0000000..cf50387
--- /dev/null
+++ b/evs/support_library/DisplayUseCase.cpp
@@ -0,0 +1,254 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include <hidl/HidlTransportSupport.h>
+#include <log/log.h>
+#include <utils/SystemClock.h>
+
+#include "DisplayUseCase.h"
+#include "RenderDirectView.h"
+#include "Utils.h"
+
+namespace android {
+namespace automotive {
+namespace evs {
+namespace support {
+
+using android::hardware::configureRpcThreadpool;
+using android::hardware::joinRpcThreadpool;
+
+// TODO(b/130246434): since we don't support multi-display use case, there
+// should only be one DisplayUseCase. Add the logic to prevent more than
+// one DisplayUseCases running at the same time.
+DisplayUseCase::DisplayUseCase(string cameraId, BaseRenderCallback* callback)
+              : BaseUseCase(vector<string>(1, cameraId)) {
+    mRenderCallback = callback;
+}
+
+DisplayUseCase::~DisplayUseCase() {
+    if (mCurrentRenderer != nullptr) {
+        mCurrentRenderer->deactivate();
+        mCurrentRenderer = nullptr;  // It's a smart pointer, so destructs on assignment to null
+    }
+
+    mIsReadyToRun = false;
+    if (mWorkerThread.joinable()) {
+        mWorkerThread.join();
+    }
+}
+
+bool DisplayUseCase::initialize() {
+    // Load our configuration information
+    ConfigManager config;
+    if (!config.initialize("/system/etc/automotive/evs_support_lib/camera_config.json")) {
+        ALOGE("Missing or improper configuration for the EVS application.  Exiting.");
+        return false;
+    }
+
+    // Set thread pool size to one to avoid concurrent events from the HAL.
+    // This pool will handle the EvsCameraStream callbacks.
+    // Note:  This _will_ run in parallel with the EvsListener run() loop below which
+    // runs the application logic that reacts to the async events.
+    configureRpcThreadpool(1, false /* callerWillJoin */);
+
+    mResourceManager = ResourceManager::getInstance();
+    if (mResourceManager == nullptr) {
+        ALOGE("Failed to get resource manager instance. Initialization failed.");
+        return false;
+    }
+
+    // Request exclusive access to the EVS display
+    ALOGI("Acquiring EVS Display");
+
+    mDisplay = mResourceManager->openDisplay();
+    if (mDisplay.get() == nullptr) {
+        ALOGE("EVS Display unavailable.  Exiting.");
+        return false;
+    }
+
+    ALOGD("Requesting camera list");
+    for (auto&& info : config.getCameras()) {
+        // This use case is currently a single camera use case.
+        // Only one element is available in the camera id list.
+        string cameraId = mCameraIds[0];
+        if (cameraId == info.cameraId) {
+            mStreamHandler = mResourceManager->obtainStreamHandler(cameraId);
+            if (mStreamHandler.get() == nullptr) {
+                ALOGE("Failed to get a valid StreamHandler for %s",
+                      cameraId.c_str());
+                return false;
+            }
+
+            mIsInitialized = true;
+            return true;
+        }
+    }
+
+    ALOGE("Cannot find a match camera. Exiting");
+    return false;
+}
+
+// TODO(b/130246434): if user accidentally call this function twice, there is
+// no logic to handle that and it will causes issues. For example, the
+// mWorkerThread will be assigned twice and cause unexpected behavior.
+// We need to fix this issue.
+bool DisplayUseCase::startVideoStream() {
+    // Initialize the use case.
+    if (!mIsInitialized && !initialize()) {
+        ALOGE("There is an error while initializing the use case. Exiting");
+        return false;
+    }
+
+    ALOGD("Attach use case to StreamHandler");
+    if (mRenderCallback != nullptr) {
+        mStreamHandler->attachRenderCallback(mRenderCallback);
+    }
+
+    ALOGD("Start video streaming using worker thread");
+    mIsReadyToRun = true;
+    mWorkerThread = std::thread([this]() {
+        // We have a camera assigned to this state for direct view
+        mCurrentRenderer = std::make_unique<RenderDirectView>();
+        if (!mCurrentRenderer) {
+            ALOGE("Failed to construct direct renderer. Exiting.");
+            mIsReadyToRun = false;
+            return;
+        }
+
+        // Now set the display state based on whether we have a video feed to show
+        // Start the camera stream
+        ALOGD("EvsStartCameraStreamTiming start time: %" PRId64 "ms", android::elapsedRealtime());
+        if (!mCurrentRenderer->activate()) {
+            ALOGE("New renderer failed to activate. Exiting");
+            mIsReadyToRun = false;
+            return;
+        }
+
+        // Activate the display
+        ALOGD("EvsActivateDisplayTiming start time: %" PRId64 "ms", android::elapsedRealtime());
+        Return<EvsResult> result = mDisplay->setDisplayState(DisplayState::VISIBLE_ON_NEXT_FRAME);
+        if (result != EvsResult::OK) {
+            ALOGE("setDisplayState returned an error (%d). Exiting.", (EvsResult)result);
+            mIsReadyToRun = false;
+            return;
+        }
+
+        if (!mStreamHandler->startStream()) {
+            ALOGE("failed to start stream handler");
+            mIsReadyToRun = false;
+            return;
+        }
+
+        while (mIsReadyToRun && streamFrame());
+
+        ALOGD("Worker thread stops.");
+    });
+
+    return true;
+}
+
+void DisplayUseCase::stopVideoStream() {
+    ALOGD("Stop video streaming in worker thread.");
+    mIsReadyToRun = false;
+
+    if (mStreamHandler == nullptr) {
+        ALOGE("Failed to detach render callback since stream handler is null");
+
+        // Something may go wrong. Instead of to return this method right away,
+        // we want to finish the remaining logic of this method to try to
+        // release other resources.
+    } else {
+        mStreamHandler->detachRenderCallback();
+    }
+
+    if (mResourceManager == nullptr) {
+        ALOGE("Failed to release resources since resource manager is null");
+    } else {
+        mResourceManager->releaseStreamHandler(mCameraIds[0]);
+        mStreamHandler = nullptr;
+
+        mResourceManager->closeDisplay(mDisplay);
+        mDisplay = nullptr;
+
+        // TODO(b/130246434): with the current logic, the initialize method will
+        // be triggered every time when a pair of
+        // stopVideoStream/startVideoStream is called. We might want to move
+        // some heavy work away from initialize method so increase the
+        // performance.
+
+        // Sets mIsInitialzed to false so the initialize method will be
+        // triggered when startVideoStream is called again.
+        mIsInitialized = false;
+    }
+    return;
+}
+
+bool DisplayUseCase::streamFrame() {
+    // Get the output buffer we'll use to display the imagery
+    BufferDesc tgtBuffer = {};
+    mDisplay->getTargetBuffer([&tgtBuffer](const BufferDesc& buff) { tgtBuffer = buff; });
+
+    // TODO(b/130246434): if there is no new display frame available, shall we
+    // still get display buffer? Shall we just skip and keep the display
+    // un-refreshed?
+    // We should explore this option.
+
+    // If there is no display buffer available, skip it.
+    if (tgtBuffer.memHandle == nullptr) {
+        ALOGW("Didn't get requested output buffer -- skipping this frame.");
+
+        // Return true since it won't affect next call.
+        return true;
+    } else {
+        // If there is no new display frame available, re-use the old (held)
+        // frame for display.
+        // Otherwise, return the old (held) frame, fetch the newly available
+        // frame from stream handler, and use the new frame for display
+        // purposes.
+        if (!mStreamHandler->newDisplayFrameAvailable()) {
+            ALOGD("No new display frame is available. Re-use the old frame.");
+        } else {
+            ALOGD("Get new display frame, refreshing");
+
+            // If we already hold a camera image for display purposes, it's
+            // time to return it to evs camera driver.
+            if (mImageBuffer.memHandle.getNativeHandle() != nullptr) {
+                mStreamHandler->doneWithFrame(mImageBuffer);
+            }
+
+            // Get the new image we want to use as our display content
+            mImageBuffer = mStreamHandler->getNewDisplayFrame();
+        }
+
+        // Render the image buffer to the display buffer
+        bool result = mCurrentRenderer->drawFrame(tgtBuffer, mImageBuffer);
+
+        // Send the finished display buffer back to display driver
+        // Even if the rendering fails, we still want to return the display
+        // buffer.
+        mDisplay->returnTargetBufferForDisplay(tgtBuffer);
+
+        return result;
+    }
+}
+
+DisplayUseCase DisplayUseCase::createDefaultUseCase(string cameraId, BaseRenderCallback* callback) {
+    return DisplayUseCase(cameraId, callback);
+}
+
+}  // namespace support
+}  // namespace evs
+}  // namespace automotive
+}  // namespace android
diff --git a/evs/support_library/DisplayUseCase.h b/evs/support_library/DisplayUseCase.h
new file mode 100644
index 0000000..0eeac64
--- /dev/null
+++ b/evs/support_library/DisplayUseCase.h
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#ifndef CAR_LIB_EVS_SUPPORT_DISPLAY_USECASE_H
+#define CAR_LIB_EVS_SUPPORT_DISPLAY_USECASE_H
+
+#include <android/hardware/automotive/evs/1.0/IEvsDisplay.h>
+
+#include <string>
+#include <thread>
+
+#include "BaseRenderCallback.h"
+#include "BaseUseCase.h"
+#include "ConfigManager.h"
+#include "RenderBase.h"
+#include "StreamHandler.h"
+#include "ResourceManager.h"
+
+namespace android {
+namespace automotive {
+namespace evs {
+namespace support {
+
+using namespace ::android::hardware::automotive::evs::V1_0;
+using ::android::sp;
+using ::android::hardware::Return;
+using ::std::string;
+
+// TODO(b/130246434): Think about multi-camera situation.
+class DisplayUseCase : public BaseUseCase {
+  public:
+    ~DisplayUseCase();
+    bool startVideoStream() override;
+    void stopVideoStream() override;
+
+    // TODO(b/130246434): Add configuration class to create more use case.
+    static DisplayUseCase createDefaultUseCase(string cameraId,
+                                               BaseRenderCallback* cb = nullptr);
+
+  private:
+    DisplayUseCase(string cameraId, BaseRenderCallback* renderCallback);
+
+    // TODO(b/130246434): Think about whether we should make init public so
+    // users can call it.
+    bool initialize();
+    bool streamFrame();
+
+    bool mIsInitialized = false;
+    BaseRenderCallback* mRenderCallback = nullptr;
+    std::unique_ptr<RenderBase> mCurrentRenderer;
+
+    sp<IEvsDisplay> mDisplay;
+    sp<StreamHandler> mStreamHandler;
+    sp<ResourceManager> mResourceManager;
+    bool mIsReadyToRun;
+    std::thread mWorkerThread;
+    BufferDesc mImageBuffer;
+};
+
+}  // namespace support
+}  // namespace evs
+}  // namespace automotive
+}  // namespace android
+
+#endif  // CAR_LIB_EVS_SUPPORT_DISPLAY_USECASE_H
diff --git a/evs/support_library/FormatConvert.cpp b/evs/support_library/FormatConvert.cpp
new file mode 100644
index 0000000..f823179
--- /dev/null
+++ b/evs/support_library/FormatConvert.cpp
@@ -0,0 +1,178 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "FormatConvert.h"
+
+namespace android {
+namespace automotive {
+namespace evs {
+namespace support {
+
+// Round up to the nearest multiple of the given alignment value
+template<unsigned alignment>
+int align(int value) {
+    static_assert((alignment && !(alignment & (alignment - 1))),
+                  "alignment must be a power of 2");
+
+    unsigned mask = alignment - 1;
+    return (value + mask) & ~mask;
+}
+
+
+// Limit the given value to the provided range.  :)
+static inline float clamp(float v, float min, float max) {
+    if (v < min) return min;
+    if (v > max) return max;
+    return v;
+}
+
+
+static uint32_t yuvToRgbx(const unsigned char Y, const unsigned char Uin, const unsigned char Vin) {
+    // Don't use this if you want to see the best performance.  :)
+    // Better to do this in a pixel shader if we really have to, but on actual
+    // embedded hardware we expect to be able to texture directly from the YUV data
+    float U = Uin - 128.0f;
+    float V = Vin - 128.0f;
+
+    float Rf = Y + 1.140f*V;
+    float Gf = Y - 0.395f*U - 0.581f*V;
+    float Bf = Y + 2.032f*U;
+    unsigned char R = (unsigned char)clamp(Rf, 0.0f, 255.0f);
+    unsigned char G = (unsigned char)clamp(Gf, 0.0f, 255.0f);
+    unsigned char B = (unsigned char)clamp(Bf, 0.0f, 255.0f);
+
+    return (R      ) |
+           (G <<  8) |
+           (B << 16) |
+           0xFF000000;  // Fill the alpha channel with ones
+}
+
+
+void copyNV21toRGB32(unsigned width, unsigned height,
+                     uint8_t* src,
+                     uint32_t* dst, unsigned dstStridePixels)
+{
+    // The NV21 format provides a Y array of 8bit values, followed by a 1/2 x 1/2 interleaved
+    // U/V array.  It assumes an even width and height for the overall image, and a horizontal
+    // stride that is an even multiple of 16 bytes for both the Y and UV arrays.
+    unsigned strideLum = align<16>(width);
+    unsigned sizeY = strideLum * height;
+    unsigned strideColor = strideLum;   // 1/2 the samples, but two interleaved channels
+    unsigned offsetUV = sizeY;
+
+    uint8_t* srcY = src;
+    uint8_t* srcUV = src+offsetUV;
+
+    for (unsigned r = 0; r < height; r++) {
+        // Note that we're walking the same UV row twice for even/odd luminance rows
+        uint8_t* rowY  = srcY  + r*strideLum;
+        uint8_t* rowUV = srcUV + (r/2 * strideColor);
+
+        uint32_t* rowDest = dst + r*dstStridePixels;
+
+        for (unsigned c = 0; c < width; c++) {
+            unsigned uCol = (c & ~1);   // uCol is always even and repeats 1:2 with Y values
+            unsigned vCol = uCol | 1;   // vCol is always odd
+            rowDest[c] = yuvToRgbx(rowY[c], rowUV[uCol], rowUV[vCol]);
+        }
+    }
+}
+
+
+void copyYV12toRGB32(unsigned width, unsigned height,
+                     uint8_t* src,
+                     uint32_t* dst, unsigned dstStridePixels)
+{
+    // The YV12 format provides a Y array of 8bit values, followed by a 1/2 x 1/2 U array, followed
+    // by another 1/2 x 1/2 V array.  It assumes an even width and height for the overall image,
+    // and a horizontal stride that is an even multiple of 16 bytes for each of the Y, U,
+    // and V arrays.
+    unsigned strideLum = align<16>(width);
+    unsigned sizeY = strideLum * height;
+    unsigned strideColor = align<16>(strideLum/2);
+    unsigned sizeColor = strideColor * height/2;
+    unsigned offsetU = sizeY;
+    unsigned offsetV = sizeY + sizeColor;
+
+    uint8_t* srcY = src;
+    uint8_t* srcU = src+offsetU;
+    uint8_t* srcV = src+offsetV;
+
+    for (unsigned r = 0; r < height; r++) {
+        // Note that we're walking the same U and V rows twice for even/odd luminance rows
+        uint8_t* rowY = srcY + r*strideLum;
+        uint8_t* rowU = srcU + (r/2 * strideColor);
+        uint8_t* rowV = srcV + (r/2 * strideColor);
+
+        uint32_t* rowDest = dst + r*dstStridePixels;
+
+        for (unsigned c = 0; c < width; c++) {
+            rowDest[c] = yuvToRgbx(rowY[c], rowU[c], rowV[c]);
+        }
+    }
+}
+
+
+void copyYUYVtoRGB32(unsigned width, unsigned height,
+                     uint8_t* src, unsigned srcStridePixels,
+                     uint32_t* dst, unsigned dstStridePixels)
+{
+    uint32_t* srcWords = (uint32_t*)src;
+
+    const int srcRowPadding32 = srcStridePixels/2 - width/2;  // 2 bytes per pixel, 4 bytes per word
+    const int dstRowPadding32 = dstStridePixels   - width;    // 4 bytes per pixel, 4 bytes per word
+
+    for (unsigned r = 0; r < height; r++) {
+        for (unsigned c = 0; c < width/2; c++) {
+            // Note:  we're walking two pixels at a time here (even/odd)
+            uint32_t srcPixel = *srcWords++;
+
+            uint8_t Y1 = (srcPixel)       & 0xFF;
+            uint8_t U  = (srcPixel >> 8)  & 0xFF;
+            uint8_t Y2 = (srcPixel >> 16) & 0xFF;
+            uint8_t V  = (srcPixel >> 24) & 0xFF;
+
+            // On the RGB output, we're writing one pixel at a time
+            *(dst+0) = yuvToRgbx(Y1, U, V);
+            *(dst+1) = yuvToRgbx(Y2, U, V);
+            dst += 2;
+        }
+
+        // Skip over any extra data or end of row alignment padding
+        srcWords += srcRowPadding32;
+        dst += dstRowPadding32;
+    }
+}
+
+
+void copyMatchedInterleavedFormats(unsigned width, unsigned height,
+                                   void* src, unsigned srcStridePixels,
+                                   void* dst, unsigned dstStridePixels,
+                                   unsigned pixelSize) {
+    for (unsigned row = 0; row < height; row++) {
+        // Copy the entire row of pixel data
+        memcpy(dst, src, width * pixelSize);
+
+        // Advance to the next row (keeping in mind that stride here is in units of pixels)
+        src = (uint8_t*)src + srcStridePixels * pixelSize;
+        dst = (uint8_t*)dst + dstStridePixels * pixelSize;
+    }
+}
+
+}  // namespace support
+}  // namespace evs
+}  // namespace automotive
+}  // namespace android
diff --git a/evs/support_library/FormatConvert.h b/evs/support_library/FormatConvert.h
new file mode 100644
index 0000000..2513dfe
--- /dev/null
+++ b/evs/support_library/FormatConvert.h
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef EVS_VTS_FORMATCONVERT_H
+#define EVS_VTS_FORMATCONVERT_H
+
+#include <queue>
+#include <stdint.h>
+
+namespace android {
+namespace automotive {
+namespace evs {
+namespace support {
+
+// Given an image buffer in NV21 format (HAL_PIXEL_FORMAT_YCRCB_420_SP), output 32bit RGBx values.
+// The NV21 format provides a Y array of 8bit values, followed by a 1/2 x 1/2 interleaved
+// U/V array.  It assumes an even width and height for the overall image, and a horizontal
+// stride that is an even multiple of 16 bytes for both the Y and UV arrays.
+void copyNV21toRGB32(unsigned width, unsigned height,
+                     uint8_t* src,
+                     uint32_t* dst, unsigned dstStridePixels);
+
+
+// Given an image buffer in YV12 format (HAL_PIXEL_FORMAT_YV12), output 32bit RGBx values.
+// The YV12 format provides a Y array of 8bit values, followed by a 1/2 x 1/2 U array, followed
+// by another 1/2 x 1/2 V array.  It assumes an even width and height for the overall image,
+// and a horizontal stride that is an even multiple of 16 bytes for each of the Y, U,
+// and V arrays.
+void copyYV12toRGB32(unsigned width, unsigned height,
+                     uint8_t* src,
+                     uint32_t* dst, unsigned dstStridePixels);
+
+
+// Given an image buffer in YUYV format (HAL_PIXEL_FORMAT_YCBCR_422_I), output 32bit RGBx values.
+// The NV21 format provides a Y array of 8bit values, followed by a 1/2 x 1/2 interleaved
+// U/V array.  It assumes an even width and height for the overall image, and a horizontal
+// stride that is an even multiple of 16 bytes for both the Y and UV arrays.
+void copyYUYVtoRGB32(unsigned width, unsigned height,
+                     uint8_t* src, unsigned srcStrideBytes,
+                     uint32_t* dst, unsigned dstStrideBytes);
+
+
+// Given an simple rectangular image buffer with an integer number of bytes per pixel,
+// copy the pixel values into a new rectangular buffer (potentially with a different stride).
+// This is typically used to copy RGBx data into an RGBx output buffer.
+void copyMatchedInterleavedFormats(unsigned width, unsigned height,
+                                   void* src, unsigned srcStridePixels,
+                                   void* dst, unsigned dstStridePixels,
+                                   unsigned pixelSize);
+
+}  // namespace support
+}  // namespace evs
+}  // namespace automotive
+}  // namespace android
+
+#endif // EVS_VTS_FORMATCONVERT_H
diff --git a/evs/support_library/Frame.h b/evs/support_library/Frame.h
new file mode 100644
index 0000000..92af203
--- /dev/null
+++ b/evs/support_library/Frame.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#ifndef CAR_LIB_EVS_SUPPORT_FRAME_H
+#define CAR_LIB_EVS_SUPPORT_FRAME_H
+
+namespace android {
+namespace automotive {
+namespace evs {
+namespace support {
+
+struct Frame {
+    unsigned width;
+    unsigned height;
+    unsigned stride;
+    uint8_t* data;
+};
+
+}  // namespace support
+}  // namespace evs
+}  // namespace automotive
+}  // namespace android
+
+#endif  // CAR_LIB_EVS_SUPPORT_FRAME_H
diff --git a/evs/support_library/RenderBase.cpp b/evs/support_library/RenderBase.cpp
new file mode 100644
index 0000000..3dab978
--- /dev/null
+++ b/evs/support_library/RenderBase.cpp
@@ -0,0 +1,227 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "RenderBase.h"
+#include "glError.h"
+
+#include <log/log.h>
+#include <ui/GraphicBuffer.h>
+
+namespace android {
+namespace automotive {
+namespace evs {
+namespace support {
+
+// Eventually we shouldn't need this dependency, but for now the
+// graphics allocator interface isn't fully supported on all platforms
+// and this is our work around.
+using ::android::GraphicBuffer;
+
+
+// OpenGL state shared among all renderers
+EGLDisplay   RenderBase::sDisplay = EGL_NO_DISPLAY;
+EGLContext   RenderBase::sContext = EGL_NO_CONTEXT;
+EGLSurface   RenderBase::sDummySurface = EGL_NO_SURFACE;
+GLuint       RenderBase::sFrameBuffer = -1;
+GLuint       RenderBase::sColorBuffer = -1;
+GLuint       RenderBase::sDepthBuffer = -1;
+EGLImageKHR  RenderBase::sKHRimage = EGL_NO_IMAGE_KHR;
+unsigned     RenderBase::sWidth  = 0;
+unsigned     RenderBase::sHeight = 0;
+float        RenderBase::sAspectRatio = 0.0f;
+
+
+bool RenderBase::prepareGL() {
+    // Just trivially return success if we're already prepared
+    if (sDisplay != EGL_NO_DISPLAY) {
+        return true;
+    }
+
+    // Hardcoded to RGBx output display
+    const EGLint config_attribs[] = {
+        // Tag                  Value
+        EGL_RENDERABLE_TYPE,    EGL_OPENGL_ES2_BIT,
+        EGL_RED_SIZE,           8,
+        EGL_GREEN_SIZE,         8,
+        EGL_BLUE_SIZE,          8,
+        EGL_NONE
+    };
+
+    // Select OpenGL ES v 3
+    const EGLint context_attribs[] = {EGL_CONTEXT_CLIENT_VERSION, 3, EGL_NONE};
+
+
+    // Set up our OpenGL ES context associated with the default display (though we won't be visible)
+    EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
+    if (display == EGL_NO_DISPLAY) {
+        ALOGE("Failed to get egl display");
+        return false;
+    }
+
+    EGLint major = 0;
+    EGLint minor = 0;
+    if (!eglInitialize(display, &major, &minor)) {
+        ALOGE("Failed to initialize EGL: %s", getEGLError());
+        return false;
+    } else {
+        ALOGI("Intiialized EGL at %d.%d", major, minor);
+    }
+
+
+    // Select the configuration that "best" matches our desired characteristics
+    EGLConfig egl_config;
+    EGLint num_configs;
+    if (!eglChooseConfig(display, config_attribs, &egl_config, 1, &num_configs)) {
+        ALOGE("eglChooseConfig() failed with error: %s", getEGLError());
+        return false;
+    }
+
+
+    // Create a dummy pbuffer so we have a surface to bind -- we never intend to draw to this
+    // because attachRenderTarget will be called first.
+    EGLint surface_attribs[] = { EGL_WIDTH, 1, EGL_HEIGHT, 1, EGL_NONE };
+    sDummySurface = eglCreatePbufferSurface(display, egl_config, surface_attribs);
+    if (sDummySurface == EGL_NO_SURFACE) {
+        ALOGE("Failed to create OpenGL ES Dummy surface: %s", getEGLError());
+        return false;
+    } else {
+        ALOGI("Dummy surface looks good!  :)");
+    }
+
+
+    //
+    // Create the EGL context
+    //
+    EGLContext context = eglCreateContext(display, egl_config, EGL_NO_CONTEXT, context_attribs);
+    if (context == EGL_NO_CONTEXT) {
+        ALOGE("Failed to create OpenGL ES Context: %s", getEGLError());
+        return false;
+    }
+
+
+    // Activate our render target for drawing
+    if (!eglMakeCurrent(display, sDummySurface, sDummySurface, context)) {
+        ALOGE("Failed to make the OpenGL ES Context current: %s", getEGLError());
+        return false;
+    } else {
+        ALOGI("We made our context current!  :)");
+    }
+
+
+    // Report the extensions available on this implementation
+    const char* gl_extensions = (const char*) glGetString(GL_EXTENSIONS);
+    ALOGI("GL EXTENSIONS:\n  %s", gl_extensions);
+
+
+    // Reserve handles for the color and depth targets we'll be setting up
+    glGenRenderbuffers(1, &sColorBuffer);
+    glGenRenderbuffers(1, &sDepthBuffer);
+
+    // Set up the frame buffer object we can modify and use for off screen rendering
+    glGenFramebuffers(1, &sFrameBuffer);
+    glBindFramebuffer(GL_FRAMEBUFFER, sFrameBuffer);
+
+
+    // Now that we're assured success, store object handles we constructed
+    sDisplay = display;
+    sContext = context;
+
+    return true;
+}
+
+
+bool RenderBase::attachRenderTarget(const BufferDesc& tgtBuffer) {
+    // Hardcoded to RGBx for now
+    if (tgtBuffer.format != HAL_PIXEL_FORMAT_RGBA_8888) {
+        ALOGE("Unsupported target buffer format");
+        return false;
+    }
+
+    // create a GraphicBuffer from the existing handle
+    sp<GraphicBuffer> pGfxBuffer = new GraphicBuffer(tgtBuffer.memHandle,
+                                                     GraphicBuffer::CLONE_HANDLE,
+                                                     tgtBuffer.width, tgtBuffer.height,
+                                                     tgtBuffer.format, 1, // layer count
+                                                     GRALLOC_USAGE_HW_RENDER,
+                                                     tgtBuffer.stride);
+    if (pGfxBuffer.get() == nullptr) {
+        ALOGE("Failed to allocate GraphicBuffer to wrap image handle");
+        return false;
+    }
+
+    // Get a GL compatible reference to the graphics buffer we've been given
+    EGLint eglImageAttributes[] = {EGL_IMAGE_PRESERVED_KHR, EGL_TRUE, EGL_NONE};
+    EGLClientBuffer clientBuf = static_cast<EGLClientBuffer>(pGfxBuffer->getNativeBuffer());
+    sKHRimage = eglCreateImageKHR(sDisplay, EGL_NO_CONTEXT,
+                                  EGL_NATIVE_BUFFER_ANDROID, clientBuf,
+                                  eglImageAttributes);
+    if (sKHRimage == EGL_NO_IMAGE_KHR) {
+        ALOGE("error creating EGLImage for target buffer: %s", getEGLError());
+        return false;
+    }
+
+    // Construct a render buffer around the external buffer
+    glBindRenderbuffer(GL_RENDERBUFFER, sColorBuffer);
+    glEGLImageTargetRenderbufferStorageOES(GL_RENDERBUFFER, static_cast<GLeglImageOES>(sKHRimage));
+    if (eglGetError() != EGL_SUCCESS) {
+        ALOGI("glEGLImageTargetRenderbufferStorageOES => %s", getEGLError());
+        return false;
+    }
+
+    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, sColorBuffer);
+    if (eglGetError() != EGL_SUCCESS) {
+        ALOGE("glFramebufferRenderbuffer => %s", getEGLError());
+        return false;
+    }
+
+    GLenum checkResult = glCheckFramebufferStatus(GL_FRAMEBUFFER);
+    if (checkResult != GL_FRAMEBUFFER_COMPLETE) {
+        ALOGE("Offscreen framebuffer not configured successfully (%d: %s)",
+              checkResult, getGLFramebufferError());
+        return false;
+    }
+
+    // Store the size of our target buffer
+    sWidth = tgtBuffer.width;
+    sHeight = tgtBuffer.height;
+    sAspectRatio = (float)sWidth / sHeight;
+
+    // Set the viewport
+    glViewport(0, 0, sWidth, sHeight);
+
+#if 1   // We don't actually need the clear if we're going to cover the whole screen anyway
+    // Clear the color buffer
+    glClearColor(0.8f, 0.1f, 0.2f, 1.0f);
+    glClear(GL_COLOR_BUFFER_BIT);
+#endif
+
+
+    return true;
+}
+
+
+void RenderBase::detachRenderTarget() {
+    // Drop our external render target
+    if (sKHRimage != EGL_NO_IMAGE_KHR) {
+        eglDestroyImageKHR(sDisplay, sKHRimage);
+        sKHRimage = EGL_NO_IMAGE_KHR;
+    }
+}
+
+}  // namespace support
+}  // namespace evs
+}  // namespace automotive
+}  // namespace android
diff --git a/evs/support_library/RenderBase.h b/evs/support_library/RenderBase.h
new file mode 100644
index 0000000..0a9be51
--- /dev/null
+++ b/evs/support_library/RenderBase.h
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef CAR_EVS_APP_RENDERBASE_H
+#define CAR_EVS_APP_RENDERBASE_H
+
+#include <EGL/egl.h>
+#include <EGL/eglext.h>
+#include <GLES2/gl2.h>
+#include <GLES2/gl2ext.h>
+#include <GLES3/gl3.h>
+#include <GLES3/gl3ext.h>
+
+#include <android/hardware/automotive/evs/1.0/IEvsEnumerator.h>
+
+#include <BaseRenderCallback.h>
+
+namespace android {
+namespace automotive {
+namespace evs {
+namespace support {
+
+using namespace ::android::hardware::automotive::evs::V1_0;
+using ::android::sp;
+
+
+/*
+ * Abstract base class for the workhorse classes that handle the user interaction and display for
+ * each mode of the EVS application.
+ */
+class RenderBase {
+public:
+    virtual ~RenderBase() {};
+
+    virtual bool activate() = 0;
+    virtual void deactivate() = 0;
+
+    virtual bool drawFrame(const BufferDesc& tgtBuffer, const BufferDesc& imageBuffer) = 0;
+
+protected:
+    static bool prepareGL();
+
+    static bool attachRenderTarget(const BufferDesc& tgtBuffer);
+    static void detachRenderTarget();
+
+    // OpenGL state shared among all renderers
+    static EGLDisplay   sDisplay;
+    static EGLContext   sContext;
+    static EGLSurface   sDummySurface;
+    static GLuint       sFrameBuffer;
+    static GLuint       sColorBuffer;
+    static GLuint       sDepthBuffer;
+
+    static EGLImageKHR  sKHRimage;
+
+    static unsigned     sWidth;
+    static unsigned     sHeight;
+    static float        sAspectRatio;
+};
+
+}  // namespace support
+}  // namespace evs
+}  // namespace automotive
+}  // namespace android
+
+#endif //CAR_EVS_APP_RENDERBASE_H
diff --git a/evs/support_library/RenderDirectView.cpp b/evs/support_library/RenderDirectView.cpp
new file mode 100644
index 0000000..f727002
--- /dev/null
+++ b/evs/support_library/RenderDirectView.cpp
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "RenderDirectView.h"
+#include "VideoTex.h"
+#include "glError.h"
+#include "shader.h"
+#include "shader_simpleTex.h"
+
+#include <log/log.h>
+#include <math/mat4.h>
+
+namespace android {
+namespace automotive {
+namespace evs {
+namespace support {
+
+bool RenderDirectView::activate() {
+    // Ensure GL is ready to go...
+    if (!prepareGL()) {
+        ALOGE("Error initializing GL");
+        return false;
+    }
+
+    // Load our shader program if we don't have it already
+    if (!mShaderProgram) {
+        mShaderProgram = buildShaderProgram(vtxShader_simpleTexture,
+                                            pixShader_simpleTexture,
+                                            "simpleTexture");
+        if (!mShaderProgram) {
+            ALOGE("Error building shader program");
+            return false;
+        }
+    }
+
+    // Construct our video texture
+    mTexture.reset(new VideoTex(sDisplay));
+    if (!mTexture) {
+        ALOGE("Failed to set up video texture");
+// TODO:  For production use, we may actually want to fail in this case, but not yet...
+//       return false;
+    }
+
+    return true;
+}
+
+
+void RenderDirectView::deactivate() {
+    // Release our video texture
+    // We can't hold onto it because some other Render object might need the same camera
+    // TODO:  If start/stop costs become a problem, we could share video textures
+    mTexture = nullptr;
+}
+
+
+bool RenderDirectView::drawFrame(const BufferDesc& tgtBuffer,
+                                 const BufferDesc& imageBuffer) {
+    // Tell GL to render to the given buffer
+    if (!attachRenderTarget(tgtBuffer)) {
+        ALOGE("Failed to attached render target");
+        return false;
+    }
+
+    // Select our screen space simple texture shader
+    glUseProgram(mShaderProgram);
+
+    // Set up the model to clip space transform (identity matrix if we're modeling in screen space)
+    GLint loc = glGetUniformLocation(mShaderProgram, "cameraMat");
+    if (loc < 0) {
+        ALOGE("Couldn't set shader parameter 'cameraMat'");
+        return false;
+    } else {
+        const android::mat4 identityMatrix;
+        glUniformMatrix4fv(loc, 1, false, identityMatrix.asArray());
+    }
+
+
+    // Bind the texture and assign it to the shader's sampler
+    mTexture->refresh(imageBuffer);
+    glActiveTexture(GL_TEXTURE0);
+    glBindTexture(GL_TEXTURE_2D, mTexture->glId());
+
+
+    GLint sampler = glGetUniformLocation(mShaderProgram, "tex");
+    if (sampler < 0) {
+        ALOGE("Couldn't set shader parameter 'tex'");
+        return false;
+    } else {
+        // Tell the sampler we looked up from the shader to use texture slot 0 as its source
+        glUniform1i(sampler, 0);
+    }
+
+    // We want our image to show up opaque regardless of alpha values
+    glDisable(GL_BLEND);
+
+
+    // Draw a rectangle on the screen
+    GLfloat vertsCarPos[] = { -1.0,  1.0, 0.0f,   // left top in window space
+                               1.0,  1.0, 0.0f,   // right top
+                              -1.0, -1.0, 0.0f,   // left bottom
+                               1.0, -1.0, 0.0f    // right bottom
+    };
+    // TODO:  We're flipping horizontally here, but should do it only for specified cameras!
+    GLfloat vertsCarTex[] = { 1.0f, 1.0f,   // left top
+                              0.0f, 1.0f,   // right top
+                              1.0f, 0.0f,   // left bottom
+                              0.0f, 0.0f    // right bottom
+    };
+    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, vertsCarPos);
+    glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 0, vertsCarTex);
+    glEnableVertexAttribArray(0);
+    glEnableVertexAttribArray(1);
+
+    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
+
+    glDisableVertexAttribArray(0);
+    glDisableVertexAttribArray(1);
+
+
+    // Now that everything is submitted, release our hold on the texture resource
+    detachRenderTarget();
+
+    // Wait for the rendering to finish
+    glFinish();
+    detachRenderTarget();
+    return true;
+}
+
+}  // namespace support
+}  // namespace evs
+}  // namespace automotive
+}  // namespace android
diff --git a/evs/support_library/RenderDirectView.h b/evs/support_library/RenderDirectView.h
new file mode 100644
index 0000000..25bf990
--- /dev/null
+++ b/evs/support_library/RenderDirectView.h
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef CAR_EVS_APP_RENDERDIRECTVIEW_H
+#define CAR_EVS_APP_RENDERDIRECTVIEW_H
+
+
+#include "RenderBase.h"
+
+#include <android/hardware/automotive/evs/1.0/IEvsEnumerator.h>
+#include "ConfigManager.h"
+#include "VideoTex.h"
+
+namespace android {
+namespace automotive {
+namespace evs {
+namespace support {
+
+using namespace ::android::hardware::automotive::evs::V1_0;
+
+
+/*
+ * Renders the view from a single specified camera directly to the full display.
+ */
+class RenderDirectView: public RenderBase {
+public:
+    virtual bool activate() override;
+    virtual void deactivate() override;
+
+    virtual bool drawFrame(const BufferDesc& tgtBuffer,
+                           const BufferDesc& imageBuffer) override;
+
+protected:
+    std::unique_ptr<VideoTex>       mTexture;
+
+    GLuint                          mShaderProgram = 0;
+};
+
+}  // namespace support
+}  // namespace evs
+}  // namespace automotive
+}  // namespace android
+
+#endif //CAR_EVS_APP_RENDERDIRECTVIEW_H
diff --git a/evs/support_library/ResourceManager.cpp b/evs/support_library/ResourceManager.cpp
new file mode 100644
index 0000000..aebd15a
--- /dev/null
+++ b/evs/support_library/ResourceManager.cpp
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "ResourceManager.h"
+
+namespace android {
+namespace automotive {
+namespace evs {
+namespace support {
+
+using ::std::lock_guard;
+
+const string ResourceManager::kDefaultServiceName = "default";
+sp<ResourceManager> ResourceManager::sInstance;
+mutex ResourceManager::sLockSingleton;
+mutex ResourceManager::sLockEvs;
+sp<IEvsEnumerator> ResourceManager::sEvs;
+
+sp<IEvsEnumerator> ResourceManager::getEvsEnumerator(string serviceName) {
+    lock_guard<mutex> lock(sLockEvs);
+    if (sEvs.get() == nullptr) {
+        sEvs = IEvsEnumerator::getService(serviceName);
+    }
+    return sEvs;
+}
+
+sp<ResourceManager> ResourceManager::getInstance() {
+    lock_guard<mutex> lock(sLockSingleton);
+    if (sInstance == nullptr) {
+        ALOGD("Creating new ResourceManager instance");
+        sInstance = new ResourceManager();
+    }
+    return sInstance;
+}
+
+sp<StreamHandler> ResourceManager::obtainStreamHandler(string pCameraId) {
+    ALOGD("ResourceManager::obtainStreamHandler");
+
+    // Lock for stream handler related methods.
+    lock_guard<mutex> lock(mLockStreamHandler);
+
+    auto result = mCameraInstances.find(pCameraId);
+    if (result == mCameraInstances.end()) {
+        sp<CameraInstance> instance = new CameraInstance();
+
+        // CameraInstance::useCaseCount
+        instance->useCaseCount++;
+
+        // CameraInstance::cameraId
+        instance->cameraId = pCameraId;
+
+        // CameraInstance::camera
+        instance->camera = getEvsEnumerator()->openCamera(pCameraId);
+        if (instance->camera.get() == nullptr) {
+            ALOGE("Failed to allocate new EVS Camera interface for %s",
+                  pCameraId.c_str());
+            return nullptr;
+        }
+
+        // CameraInstance::handler
+        instance->handler = new StreamHandler(instance->camera);
+        if (instance->handler == nullptr) {
+            ALOGE("Failed to create stream handler for %s",
+                  pCameraId.c_str());
+        }
+
+        // Move the newly-created instance into vector, and the vector takes
+        // ownership of the instance.
+        mCameraInstances.emplace(pCameraId, instance);
+
+        return instance->handler;
+    } else {
+        auto instance = result->second;
+        instance->useCaseCount++;
+
+        return instance->handler;
+    }
+}
+
+void ResourceManager::releaseStreamHandler(string pCameraId) {
+    ALOGD("ResourceManager::releaseStreamHandler");
+
+    // Lock for stream handler related methods.
+    lock_guard<mutex> lock(mLockStreamHandler);
+
+    auto result = mCameraInstances.find(pCameraId);
+    if (result == mCameraInstances.end()) {
+        ALOGW("No stream handler is active with camera id %s", pCameraId.c_str());
+    } else {
+        auto instance = result->second;
+        instance->useCaseCount--;
+
+        if (instance->useCaseCount <= 0) {
+            // The vector keeps the only strong reference to the camera
+            // instance. Once the instance is erased from the vector, the
+            // override onLastStrongRef method for CameraInstance class will
+            // be called and clean up the resources.
+            mCameraInstances.erase(result);
+        }
+    }
+}
+
+// TODO(b/130246434): have further discussion about how the display resource
+// should be managed.
+sp<IEvsDisplay> ResourceManager::openDisplay() {
+    // Lock for display related methods.
+    lock_guard<mutex> lock(mLockDisplay);
+
+    if (mDisplay.get() == nullptr) {
+        mDisplay = getEvsEnumerator()->openDisplay();
+        if (mDisplay.get() != nullptr) {
+            ALOGD("Evs display is opened");
+        } else {
+            ALOGE("Failed to open evs display.");
+        }
+    }
+
+    return mDisplay;
+}
+
+void ResourceManager::closeDisplay(sp<IEvsDisplay> pDisplay) {
+    // Lock for display related methods.
+    lock_guard<mutex> lock(mLockDisplay);
+
+    // Even though there are logics in evs manager to prevent errors from
+    // unrecognized IEvsDisplay object, we still want to check whether the
+    // incoming pDisplay is the one we opened earlier in resource manager. So
+    // when developer make mistakes by passing in incorrect IEvsDisplay object,
+    // we know that we should not proceed and the active display is still
+    // opened.
+    if (mDisplay.get() == pDisplay.get()) {
+        getEvsEnumerator()->closeDisplay(mDisplay);
+        mDisplay = nullptr;
+        ALOGD("Evs display is closed");
+    } else {
+        ALOGW("Ignored! Unrecognized display object for closeDisplay method");
+    }
+}
+
+bool ResourceManager::isDisplayOpened() {
+    // Lock for display related methods.
+    lock_guard<mutex> lock(mLockDisplay);
+
+    return mDisplay.get() != nullptr;
+}
+
+}  // namespace support
+}  // namespace evs
+}  // namespace automotive
+}  // namespace android
diff --git a/evs/support_library/ResourceManager.h b/evs/support_library/ResourceManager.h
new file mode 100644
index 0000000..2bf2e27
--- /dev/null
+++ b/evs/support_library/ResourceManager.h
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef CAR_LIB_EVS_SUPPORT_RESOURCEMANAGER_H
+#define CAR_LIB_EVS_SUPPORT_RESOURCEMANAGER_H
+
+#include <utils/RefBase.h>
+#include <unordered_map>
+#include <mutex>
+
+#include <android/hardware/automotive/evs/1.0/IEvsEnumerator.h>
+
+#include "StreamHandler.h"
+
+namespace android {
+namespace automotive {
+namespace evs {
+namespace support {
+
+using ::android::sp;
+using ::std::string;
+using ::std::mutex;
+using ::std::unordered_map;
+
+/*
+ * Manages EVS related resources. E.g. evs camera, stream handler, and display.
+ *
+ * The methods in the class are guaranteed to be thread-safe.
+ */
+class ResourceManager : public android::RefBase {
+public:
+    /*
+     * Gets the singleton instance of the class.
+     */
+    static sp<ResourceManager> getInstance();
+
+    /*
+     * Obtains a StreamHandler instance to receive evs camera imagery from the
+     * given camera.
+     *
+     * When this function is called with a new camera id the first time, an evs
+     * camera instance will be opened. An internal reference count will be
+     * incremented by one every time when this method is called with the same
+     * camera id. The count will be decreased by one when releaseStreamHandler
+     * method is called, and when the reference count for the camera is
+     * decreased to zero, the stream handler will be shut down and the evs
+     * camera instance will be closed.
+     *
+     * The method will block other stream handler related calls. For example,
+     * method releaseStreamHandler.
+     *
+     * @see releaseStreamHandler()
+     */
+    sp<StreamHandler> obtainStreamHandler(string pCameraId);
+
+    /*
+     * Releases the StreamHandler associated with the given camera.
+     *
+     * An internal reference count will be decreased when this method is
+     * called. When the count is down to zero, the stream handler will be shut
+     * down and the evs camera instance will be closed.
+     *
+     * The method will block other stream handler related calls. For example,
+     * method obtainStreamHandler.
+     *
+     * @see obtainStreamHandler()
+     */
+    void releaseStreamHandler(string pCameraId);
+
+    /*
+     * Obtains an interface object used to exclusively interact with the
+     * system's evs display.
+     *
+     * @see closeDisplay()
+     */
+    sp<IEvsDisplay> openDisplay();
+
+    /*
+     * Releases the evs display interface.
+     *
+     * @see openDisplay()
+     */
+    void closeDisplay(sp<IEvsDisplay>);
+
+    /**
+     * Returns true if display is opened by openDisplay method; returns false
+     * if display is never opened, or closed by closeDisplay method.
+     *
+     * @see openDisplay()
+     * @see closeDisplay()
+     */
+    bool isDisplayOpened();
+
+private:
+    static sp<IEvsEnumerator> getEvsEnumerator(string serviceName = kDefaultServiceName);
+
+    static const string kDefaultServiceName;
+
+    static sp<ResourceManager> sInstance;
+    static sp<IEvsEnumerator> sEvs;
+    static mutex sLockSingleton, sLockEvs;
+
+    class CameraInstance : public RefBase {
+    public:
+        int useCaseCount = 0;
+        string cameraId;
+        sp<IEvsCamera> camera;
+        sp<StreamHandler> handler;
+
+    private:
+        void onLastStrongRef(const void* /*id*/) {
+            ALOGD("StreamHandler::onLastStrongRef");
+
+            handler->shutdown();
+            ALOGD("Stream handler for camera id (%s) has been shutdown",
+                  cameraId.c_str());
+
+            getEvsEnumerator()->closeCamera(camera);
+            ALOGD("Camera with id (%s) has been closed", cameraId.c_str());
+        }
+    };
+
+    sp<IEvsDisplay> mDisplay;
+    unordered_map<string, sp<CameraInstance>> mCameraInstances;
+    mutex mLockStreamHandler, mLockDisplay;
+};
+
+}  // namespace support
+}  // namespace evs
+}  // namespace automotive
+}  // namespace android
+
+#endif //CAR_LIB_EVS_SUPPORT_RESOURCEMANAGER_H
diff --git a/evs/support_library/StreamHandler.cpp b/evs/support_library/StreamHandler.cpp
new file mode 100644
index 0000000..5b962cc
--- /dev/null
+++ b/evs/support_library/StreamHandler.cpp
@@ -0,0 +1,469 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "StreamHandler.h"
+
+#include <stdio.h>
+#include <string.h>
+
+#include <log/log.h>
+#include <cutils/native_handle.h>
+
+#include <ui/GraphicBufferAllocator.h>
+#include <ui/GraphicBufferMapper.h>
+
+#include "Frame.h"
+#include "ResourceManager.h"
+
+namespace android {
+namespace automotive {
+namespace evs {
+namespace support {
+
+using ::std::lock_guard;
+using ::std::unique_lock;
+
+StreamHandler::StreamHandler(android::sp <IEvsCamera> pCamera) :
+    mCamera(pCamera)
+{
+    // We rely on the camera having at least two buffers available since we'll hold one and
+    // expect the camera to be able to capture a new image in the background.
+    pCamera->setMaxFramesInFlight(2);
+}
+
+// TODO(b/130246343): investigate further to make sure the resources are cleaned
+// up properly in the shutdown logic.
+void StreamHandler::shutdown()
+{
+    // Tell the camera to stop streaming.
+    // This will result in a null frame being delivered when the stream actually stops.
+    mCamera->stopVideoStream();
+
+    // Wait until the stream has actually stopped
+    unique_lock<mutex> lock(mLock);
+    if (mRunning) {
+        mSignal.wait(lock, [this]() { return !mRunning; });
+    }
+
+    // At this point, the receiver thread is no longer running, so we can safely drop
+    // our remote object references so they can be freed
+    mCamera = nullptr;
+}
+
+
+bool StreamHandler::startStream() {
+    lock_guard<mutex> lock(mLock);
+
+    if (!mRunning) {
+        // Tell the camera to start streaming
+        Return <EvsResult> result = mCamera->startVideoStream(this);
+        if (result != EvsResult::OK) {
+            return false;
+        }
+
+        // Mark ourselves as running
+        mRunning = true;
+    }
+
+    return true;
+}
+
+
+bool StreamHandler::newDisplayFrameAvailable() {
+    lock_guard<mutex> lock(mLock);
+    return (mReadyBuffer >= 0);
+}
+
+
+const BufferDesc& StreamHandler::getNewDisplayFrame() {
+    lock_guard<mutex> lock(mLock);
+
+    if (mHeldBuffer >= 0) {
+        ALOGE("Ignored call for new frame while still holding the old one.");
+    } else {
+        if (mReadyBuffer < 0) {
+            ALOGE("Returning invalid buffer because we don't have any. "
+                  " Call newDisplayFrameAvailable first?");
+            mReadyBuffer = 0;   // This is a lie!
+        }
+
+        // Move the ready buffer into the held position, and clear the ready position
+        mHeldBuffer = mReadyBuffer;
+        mReadyBuffer = -1;
+    }
+
+    if (mRenderCallback == nullptr) {
+        return mOriginalBuffers[mHeldBuffer];
+    } else {
+        return mProcessedBuffers[mHeldBuffer];
+    }
+}
+
+
+void StreamHandler::doneWithFrame(const BufferDesc& buffer) {
+    lock_guard<mutex> lock(mLock);
+
+    // We better be getting back the buffer we original delivered!
+    if ((mHeldBuffer < 0)
+        || (buffer.bufferId != mOriginalBuffers[mHeldBuffer].bufferId)) {
+        ALOGE("StreamHandler::doneWithFrame got an unexpected buffer!");
+        ALOGD("Held buffer id: %d, input buffer id: %d",
+              mOriginalBuffers[mHeldBuffer].bufferId, buffer.bufferId);
+        return;
+    }
+
+    // Send the buffer back to the underlying camera
+    mCamera->doneWithFrame(mOriginalBuffers[mHeldBuffer]);
+
+    // Clear the held position
+    mHeldBuffer = -1;
+}
+
+
+Return<void> StreamHandler::deliverFrame(const BufferDesc& buffer) {
+    ALOGD("Received a frame from the camera. NativeHandle:%p, buffer id:%d",
+          buffer.memHandle.getNativeHandle(), buffer.bufferId);
+
+    // Take the lock to protect our frame slots and running state variable
+    {
+        lock_guard <mutex> lock(mLock);
+
+        if (buffer.memHandle.getNativeHandle() == nullptr) {
+            // Signal that the last frame has been received and the stream is stopped
+            mRunning = false;
+        } else {
+            // Do we already have a "ready" frame?
+            if (mReadyBuffer >= 0) {
+                // Send the previously saved buffer back to the camera unused
+                mCamera->doneWithFrame(mOriginalBuffers[mReadyBuffer]);
+
+                // We'll reuse the same ready buffer index
+            } else if (mHeldBuffer >= 0) {
+                // The client is holding a buffer, so use the other slot for "on deck"
+                mReadyBuffer = 1 - mHeldBuffer;
+            } else {
+                // This is our first buffer, so just pick a slot
+                mReadyBuffer = 0;
+            }
+
+            // Save this frame until our client is interested in it
+            mOriginalBuffers[mReadyBuffer] = buffer;
+
+            // If render callback is not null, process the frame with render
+            // callback.
+            if (mRenderCallback != nullptr) {
+                processFrame(mOriginalBuffers[mReadyBuffer],
+                             mProcessedBuffers[mReadyBuffer]);
+            } else {
+                ALOGI("Render callback is null in deliverFrame.");
+            }
+
+            // If analyze callback is not null and the analyze thread is
+            // available, copy the frame and run the analyze callback in
+            // analyze thread.
+            if (mAnalyzeCallback != nullptr && !mAnalyzerRunning) {
+                copyAndAnalyzeFrame(mOriginalBuffers[mReadyBuffer]);
+            }
+        }
+    }
+
+    // Notify anybody who cares that things have changed
+    mSignal.notify_all();
+
+    return Void();
+}
+
+void StreamHandler::attachRenderCallback(BaseRenderCallback* callback) {
+    ALOGD("StreamHandler::attachRenderCallback");
+
+    lock_guard<mutex> lock(mLock);
+
+    if (mRenderCallback != nullptr) {
+        ALOGW("Ignored! There should only be one render callback");
+        return;
+    }
+    mRenderCallback = callback;
+}
+
+void StreamHandler::detachRenderCallback() {
+    ALOGD("StreamHandler::detachRenderCallback");
+
+    lock_guard<mutex> lock(mLock);
+
+    mRenderCallback = nullptr;
+}
+
+void StreamHandler::attachAnalyzeCallback(BaseAnalyzeCallback* callback) {
+    ALOGD("StreamHandler::attachAnalyzeCallback");
+
+    lock_guard<mutex> lock(mLock);
+
+    if (mAnalyzeCallback != nullptr) {
+        ALOGW("Ignored! There should only be one analyze callcack");
+        return;
+    }
+    mAnalyzeCallback = callback;
+}
+
+void StreamHandler::detachAnalyzeCallback() {
+    ALOGD("StreamHandler::detachAnalyzeCallback");
+    lock_guard<mutex> lock(mLock);
+
+    mAnalyzeCallback = nullptr;
+}
+
+bool isSameFormat(const BufferDesc& input, const BufferDesc& output) {
+    return input.width == output.width
+        && input.height == output.height
+        && input.format == output.format
+        && input.usage == output.usage
+        && input.stride == output.stride
+        && input.pixelSize == output.pixelSize;
+}
+
+bool allocate(BufferDesc& buffer) {
+    ALOGD("StreamHandler::allocate");
+    buffer_handle_t handle;
+    android::GraphicBufferAllocator& alloc(android::GraphicBufferAllocator::get());
+    android::status_t result = alloc.allocate(
+        buffer.width, buffer.height, buffer.format, 1, buffer.usage,
+        &handle, &buffer.stride, 0, "EvsDisplay");
+    if (result != android::NO_ERROR) {
+        ALOGE("Error %d allocating %d x %d graphics buffer", result, buffer.width,
+              buffer.height);
+        return false;
+    }
+
+    // The reason that we have to check null for "handle" is because that the
+    // above "result" might not cover all the failure scenarios.
+    // By looking into Gralloc4.cpp (and 3, 2, as well), it turned out that if
+    // there is anything that goes wrong in the process of buffer importing (see
+    // Ln 385 in Gralloc4.cpp), the error won't be covered by the above "result"
+    // we got from "allocate" method. In other words, it means that there is
+    // still a chance that the "result" is "NO_ERROR" but the handle is nullptr
+    // (that means buffer importing failed).
+    if (!handle) {
+        ALOGE("We didn't get a buffer handle back from the allocator");
+        return false;
+    }
+
+    buffer.memHandle = hidl_handle(handle);
+    return true;
+}
+
+bool StreamHandler::processFrame(const BufferDesc& input,
+                                 BufferDesc& output) {
+    ALOGD("StreamHandler::processFrame");
+    if (!isSameFormat(input, output)
+        || output.memHandle.getNativeHandle() == nullptr) {
+        output.width = input.width;
+        output.height = input.height;
+        output.format = input.format;
+        output.usage = input.usage;
+        output.stride = input.stride;
+        output.pixelSize = input.pixelSize;
+
+        // free the allocated output frame handle if it is not null
+        if (output.memHandle.getNativeHandle() != nullptr) {
+            GraphicBufferAllocator::get().free(output.memHandle);
+        }
+
+        if (!allocate(output)) {
+            ALOGE("Error allocating buffer");
+            return false;
+        }
+    }
+    output.bufferId = input.bufferId;
+
+    // Create a GraphicBuffer from the existing handle
+    sp<GraphicBuffer> inputBuffer = new GraphicBuffer(
+        input.memHandle, GraphicBuffer::CLONE_HANDLE, input.width,
+        input.height, input.format, 1,  // layer count
+        GRALLOC_USAGE_HW_TEXTURE, input.stride);
+
+    if (inputBuffer.get() == nullptr) {
+        ALOGE("Failed to allocate GraphicBuffer to wrap image handle");
+        // Returning "true" in this error condition because we already released
+        // the previous image (if any) and so the texture may change in
+        // unpredictable ways now!
+        return false;
+    }
+
+    // Lock the input GraphicBuffer and map it to a pointer. If we failed to
+    // lock, return false.
+    void* inputDataPtr;
+    inputBuffer->lock(
+        GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_NEVER,
+        &inputDataPtr);
+
+    // Unlock the buffer and return if lock did not succeed.
+    if (!inputDataPtr) {
+        ALOGE("Failed to gain read access to image buffer");
+
+        // The program reaches at here when it fails to lock the buffer. But
+        // it is still safer to unlock it. The reason is as described in "lock"
+        // method in  Gralloc.h: "The ownership of acquireFence is always
+        // transferred to the callee, even on errors."
+        // And even if the buffer was not locked, it does not harm anything
+        // given the comment for "unlock" method in IMapper.hal:
+        // "`BAD_BUFFER` if the buffer is invalid or not locked."
+        inputBuffer->unlock();
+        return false;
+    }
+
+    // Lock the allocated buffer in output BufferDesc and map it to a pointer
+    void* outputDataPtr = nullptr;
+    android::GraphicBufferMapper& mapper = android::GraphicBufferMapper::get();
+    mapper.lock(output.memHandle,
+                GRALLOC_USAGE_SW_WRITE_OFTEN | GRALLOC_USAGE_SW_READ_NEVER,
+                android::Rect(output.width, output.height),
+                (void**)&outputDataPtr);
+
+    // If we failed to lock the pixel buffer, return false, and unlock both
+    // input and output buffers.
+    if (!outputDataPtr) {
+        ALOGE("Failed to gain write access to image buffer");
+
+        // Please refer to the previous "if" block for why we want to unlock
+        // the buffers even if the buffer locking fails.
+        inputBuffer->unlock();
+        mapper.unlock(output.memHandle);
+        return false;
+    }
+
+    // Wrap the raw data and copied data, and pass them to the callback.
+    Frame inputFrame = {
+        .width = input.width,
+        .height = input.height,
+        .stride = input.stride,
+        .data = (uint8_t*)inputDataPtr
+    };
+
+    Frame outputFrame = {
+        .width = output.width,
+        .height = output.height,
+        .stride = output.stride,
+        .data = (uint8_t*)outputDataPtr
+    };
+
+    mRenderCallback->render(inputFrame, outputFrame);
+
+    // Unlock the buffers after all changes to the buffer are completed.
+    inputBuffer->unlock();
+    mapper.unlock(output.memHandle);
+
+    return true;
+}
+
+bool StreamHandler::copyAndAnalyzeFrame(const BufferDesc& input) {
+    ALOGD("StreamHandler::copyAndAnalyzeFrame");
+
+    // TODO(b/130246434): make the following into a method. Some lines are
+    // duplicated with processFrame, move them into new methods as well.
+    if (!isSameFormat(input, mAnalyzeBuffer)
+        || mAnalyzeBuffer.memHandle.getNativeHandle() == nullptr) {
+        mAnalyzeBuffer.width = input.width;
+        mAnalyzeBuffer.height = input.height;
+        mAnalyzeBuffer.format = input.format;
+        mAnalyzeBuffer.usage = input.usage;
+        mAnalyzeBuffer.stride = input.stride;
+        mAnalyzeBuffer.pixelSize = input.pixelSize;
+        mAnalyzeBuffer.bufferId = input.bufferId;
+
+        // free the allocated output frame handle if it is not null
+        if (mAnalyzeBuffer.memHandle.getNativeHandle() != nullptr) {
+            GraphicBufferAllocator::get().free(mAnalyzeBuffer.memHandle);
+        }
+
+        if (!allocate(mAnalyzeBuffer)) {
+            ALOGE("Error allocating buffer");
+            return false;
+        }
+    }
+
+    // create a GraphicBuffer from the existing handle
+    sp<GraphicBuffer> inputBuffer = new GraphicBuffer(
+        input.memHandle, GraphicBuffer::CLONE_HANDLE, input.width,
+        input.height, input.format, 1,  // layer count
+        GRALLOC_USAGE_HW_TEXTURE, input.stride);
+
+    if (inputBuffer.get() == nullptr) {
+        ALOGE("Failed to allocate GraphicBuffer to wrap image handle");
+        // Returning "true" in this error condition because we already released the
+        // previous image (if any) and so the texture may change in unpredictable
+        // ways now!
+        return false;
+    }
+
+    // Lock the input GraphicBuffer and map it to a pointer.  If we failed to
+    // lock, return false.
+    void* inputDataPtr;
+    inputBuffer->lock(
+        GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_NEVER,
+        &inputDataPtr);
+    if (!inputDataPtr) {
+        ALOGE("Failed to gain read access to imageGraphicBuffer");
+        inputBuffer->unlock();
+        return false;
+    }
+
+    // Lock the allocated buffer in output BufferDesc and map it to a pointer
+    void* analyzeDataPtr = nullptr;
+    android::GraphicBufferMapper::get().lock(
+        mAnalyzeBuffer.memHandle,
+        GRALLOC_USAGE_SW_WRITE_OFTEN | GRALLOC_USAGE_SW_READ_NEVER,
+        android::Rect(mAnalyzeBuffer.width, mAnalyzeBuffer.height),
+        (void**)&analyzeDataPtr);
+
+    // If we failed to lock the pixel buffer, return false, and unlock both
+    // input and output buffers.
+    if (!analyzeDataPtr) {
+        ALOGE("Camera failed to gain access to image buffer for analyzing");
+        return false;
+    }
+
+    // Wrap the raw data and copied data, and pass them to the callback.
+    Frame analyzeFrame = {
+        .width = mAnalyzeBuffer.width,
+        .height = mAnalyzeBuffer.height,
+        .stride = mAnalyzeBuffer.stride,
+        .data = (uint8_t*)analyzeDataPtr,
+    };
+
+    memcpy(analyzeDataPtr, inputDataPtr, mAnalyzeBuffer.stride * mAnalyzeBuffer.height * 4);
+
+    // Unlock the buffers after all changes to the buffer are completed.
+    inputBuffer->unlock();
+
+    mAnalyzerRunning = true;
+
+    mAnalyzeThread = std::thread([this, analyzeFrame]() {
+        ALOGD("StreamHandler: Analyze Thread starts");
+
+        this->mAnalyzeCallback->analyze(analyzeFrame);
+        android::GraphicBufferMapper::get().unlock(this->mAnalyzeBuffer.memHandle);
+        this->mAnalyzerRunning = false;
+        ALOGD("StreamHandler: Analyze Thread ends");
+    });
+    mAnalyzeThread.detach();
+
+    return true;
+}
+
+}  // namespace support
+}  // namespace evs
+}  // namespace automotive
+}  // namespace android
diff --git a/evs/support_library/StreamHandler.h b/evs/support_library/StreamHandler.h
new file mode 100644
index 0000000..4899809
--- /dev/null
+++ b/evs/support_library/StreamHandler.h
@@ -0,0 +1,166 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef EVS_VTS_STREAMHANDLER_H
+#define EVS_VTS_STREAMHANDLER_H
+
+#include <queue>
+#include <thread>
+#include <ui/GraphicBuffer.h>
+#include <android/hardware/automotive/evs/1.0/IEvsCameraStream.h>
+#include <android/hardware/automotive/evs/1.0/IEvsCamera.h>
+#include <android/hardware/automotive/evs/1.0/IEvsDisplay.h>
+
+#include "BaseRenderCallback.h"
+#include "BaseAnalyzeCallback.h"
+
+namespace android {
+namespace automotive {
+namespace evs {
+namespace support {
+
+using namespace ::android::hardware::automotive::evs::V1_0;
+using ::android::hardware::Return;
+using ::android::hardware::Void;
+using ::android::hardware::hidl_vec;
+using ::android::hardware::hidl_handle;
+using ::android::sp;
+
+
+/*
+ * StreamHandler:
+ * This class can be used to receive camera imagery from an IEvsCamera implementation.  It will
+ * hold onto the most recent image buffer, returning older ones.
+ * Note that the video frames are delivered on a background thread, while the control interface
+ * is actuated from the applications foreground thread.
+ */
+class StreamHandler : public IEvsCameraStream {
+public:
+    virtual ~StreamHandler() {
+        // The shutdown logic is supposed to be handled by ResourceManager
+        // class. But if something goes wrong, we want to make sure that the
+        // related resources are still released properly.
+        if (mCamera != nullptr) {
+            shutdown();
+        }
+    };
+
+    StreamHandler(android::sp <IEvsCamera> pCamera);
+    void shutdown();
+
+    bool startStream();
+
+    bool newDisplayFrameAvailable();
+    const BufferDesc& getNewDisplayFrame();
+    void doneWithFrame(const BufferDesc& buffer);
+
+    /*
+     * Attaches a render callback to the StreamHandler.
+     *
+     * Every frame will be processed by the attached render callback before it
+     * is delivered to the client by method getNewDisplayFrame().
+     *
+     * Since there is only one DisplayUseCase allowed at the same time, at most
+     * only one render callback can be attached. The current render callback
+     * needs to be detached first (by method detachRenderCallback()), before a
+     * new callback can be attached. In other words, the call will be ignored
+     * if the current render callback is not null.
+     *
+     * @see detachRenderCallback()
+     * @see getNewDisplayFrame()
+     */
+    void attachRenderCallback(BaseRenderCallback*);
+
+    /*
+     * Detaches the current render callback.
+     *
+     * If no render callback is attached, this call will be ignored.
+     *
+     * @see attachRenderCallback(BaseRenderCallback*)
+     */
+    void detachRenderCallback();
+
+    /*
+     * Attaches an analyze callback to the StreamHandler.
+     *
+     * When there is a valid analyze callback attached, a thread dedicated for
+     * the analyze callback will be allocated. When the thread is not busy, the
+     * next available evs frame will be copied (now happens in binder thread).
+     * And the copy will be passed into the analyze thread, and be processed by
+     * the analyze callback.
+     *
+     * Since there is only one AnalyzeUseCase allowed at the same time, at most
+     * only one analyze callback can be attached. The current analyze callback
+     * needs to be detached first (by method detachAnalyzeCallback()), before a
+     * new callback can be attached. In other words, the call will be ignored
+     * if the current analyze callback is not null.
+     *
+     * @see detachAnalyzeCallback()
+     */
+    // TODO(b/130246434): now only one analyze use case is supported, so one
+    // analyze thread id good enough. But we should be able to support several
+    // analyze use cases running at the same time, so we should probably use a
+    // thread pool to handle the cases.
+    void attachAnalyzeCallback(BaseAnalyzeCallback*);
+
+    /*
+     * Detaches the current analyze callback.
+     *
+     * If no analyze callback is attached, this call will be ignored.
+     *
+     * @see attachAnalyzeCallback(BaseAnalyzeCallback*)
+     */
+    void detachAnalyzeCallback();
+
+private:
+    // Implementation for ::android::hardware::automotive::evs::V1_0::ICarCameraStream
+    Return<void> deliverFrame(const BufferDesc& buffer)  override;
+
+    bool processFrame(const BufferDesc&, BufferDesc&);
+    bool copyAndAnalyzeFrame(const BufferDesc&);
+
+    // Values initialized as startup
+    android::sp <IEvsCamera>    mCamera;
+
+    // Since we get frames delivered to us asnchronously via the ICarCameraStream interface,
+    // we need to protect all member variables that may be modified while we're streaming
+    // (ie: those below)
+    std::mutex                  mLock;
+    std::condition_variable     mSignal;
+
+    bool                        mRunning = false;
+
+    BufferDesc                  mOriginalBuffers[2];
+    int                         mHeldBuffer = -1;   // Index of the one currently held by the client
+    int                         mReadyBuffer = -1;  // Index of the newest available buffer
+
+    BufferDesc                  mProcessedBuffers[2];
+    BufferDesc                  mAnalyzeBuffer;
+
+    BaseRenderCallback*         mRenderCallback = nullptr;
+
+    BaseAnalyzeCallback*        mAnalyzeCallback = nullptr;
+    bool                        mAnalyzerRunning = false;
+    std::thread                 mAnalyzeThread;
+};
+
+}  // namespace support
+}  // namespace evs
+}  // namespace automotive
+}  // namespace android
+
+#endif //EVS_VTS_STREAMHANDLER_H
+
diff --git a/evs/support_library/TexWrapper.cpp b/evs/support_library/TexWrapper.cpp
new file mode 100644
index 0000000..eb29a24
--- /dev/null
+++ b/evs/support_library/TexWrapper.cpp
@@ -0,0 +1,207 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "TexWrapper.h"
+#include "glError.h"
+
+#include "log/log.h"
+
+#include <fcntl.h>
+#include <malloc.h>
+#include <png.h>
+
+namespace android {
+namespace automotive {
+namespace evs {
+namespace support {
+
+/* Create an new empty GL texture that will be filled later */
+TexWrapper::TexWrapper() {
+    GLuint textureId;
+    glGenTextures(1, &textureId);
+    if (textureId <= 0) {
+        ALOGE("Didn't get a texture handle allocated: %s", getEGLError());
+    } else {
+        // Store the basic texture properties
+        id = textureId;
+        w  = 0;
+        h  = 0;
+    }
+}
+
+
+/* Wrap a texture that already allocated.  The wrapper takes ownership. */
+TexWrapper::TexWrapper(GLuint textureId, unsigned width, unsigned height) {
+    // Store the basic texture properties
+    id = textureId;
+    w  = width;
+    h  = height;
+}
+
+
+TexWrapper::~TexWrapper() {
+    // Give the texture ID back
+    if (id > 0) {
+        glDeleteTextures(1, &id);
+    }
+    id = -1;
+}
+
+
+/* Factory to build TexWrapper objects from a given PNG file */
+TexWrapper* createTextureFromPng(const char * filename)
+{
+    // Open the PNG file
+    FILE *inputFile = fopen(filename, "rb");
+    if (inputFile == 0)
+    {
+        perror(filename);
+        return nullptr;
+    }
+
+    // Read the file header and validate that it is a PNG
+    static const int kSigSize = 8;
+    png_byte header[kSigSize] = {0};
+    fread(header, 1, kSigSize, inputFile);
+    if (png_sig_cmp(header, 0, kSigSize)) {
+        printf("%s is not a PNG.\n", filename);
+        fclose(inputFile);
+        return nullptr;
+    }
+
+    // Set up our control structure
+    png_structp pngControl = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
+    if (!pngControl)
+    {
+        printf("png_create_read_struct failed.\n");
+        fclose(inputFile);
+        return nullptr;
+    }
+
+    // Set up our image info structure
+    png_infop pngInfo = png_create_info_struct(pngControl);
+    if (!pngInfo)
+    {
+        printf("error: png_create_info_struct returned 0.\n");
+        png_destroy_read_struct(&pngControl, nullptr, nullptr);
+        fclose(inputFile);
+        return nullptr;
+    }
+
+    // Install an error handler
+    if (setjmp(png_jmpbuf(pngControl))) {
+        printf("libpng reported an error\n");
+        png_destroy_read_struct(&pngControl, &pngInfo, nullptr);
+        fclose(inputFile);
+        return nullptr;
+    }
+
+    // Set up the png reader and fetch the remaining bits of the header
+    png_init_io(pngControl, inputFile);
+    png_set_sig_bytes(pngControl, kSigSize);
+    png_read_info(pngControl, pngInfo);
+
+    // Get basic information about the PNG we're reading
+    int bitDepth;
+    int colorFormat;
+    png_uint_32 width;
+    png_uint_32 height;
+    png_get_IHDR(pngControl, pngInfo,
+                 &width, &height,
+                 &bitDepth, &colorFormat,
+                 NULL, NULL, NULL);
+
+    GLint format;
+    switch(colorFormat)
+    {
+        case PNG_COLOR_TYPE_RGB:
+            format = GL_RGB;
+            break;
+        case PNG_COLOR_TYPE_RGB_ALPHA:
+            format = GL_RGBA;
+            break;
+        default:
+            printf("%s: Unknown libpng color format %d.\n", filename, colorFormat);
+            return nullptr;
+    }
+
+    // Refresh the values in the png info struct in case any transformation shave been applied.
+    png_read_update_info(pngControl, pngInfo);
+    int stride = png_get_rowbytes(pngControl, pngInfo);
+    stride += 3 - ((stride-1) % 4);   // glTexImage2d requires rows to be 4-byte aligned
+
+    // Allocate storage for the pixel data
+    png_byte * buffer = (png_byte*)malloc(stride * height);
+    if (buffer == NULL)
+    {
+        printf("error: could not allocate memory for PNG image data\n");
+        png_destroy_read_struct(&pngControl, &pngInfo, nullptr);
+        fclose(inputFile);
+        return nullptr;
+    }
+
+    // libpng needs an array of pointers into the image data for each row
+    png_byte ** rowPointers = (png_byte**)malloc(height * sizeof(png_byte*));
+    if (rowPointers == NULL)
+    {
+        printf("Failed to allocate temporary row pointers\n");
+        png_destroy_read_struct(&pngControl, &pngInfo, nullptr);
+        free(buffer);
+        fclose(inputFile);
+        return nullptr;
+    }
+    for (unsigned int r = 0; r < height; r++)
+    {
+        rowPointers[r] = buffer + r*stride;
+    }
+
+
+    // Read in the actual image bytes
+    png_read_image(pngControl, rowPointers);
+    png_read_end(pngControl, nullptr);
+
+
+    // Set up the OpenGL texture to contain this image
+    GLuint textureId;
+    glGenTextures(1, &textureId);
+    glBindTexture(GL_TEXTURE_2D, textureId);
+
+    // Send the image data to GL
+    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, buffer);
+
+    // Initialize the sampling properties (it seems the sample may not work if this isn't done)
+    // The user of this texture may very well want to set their own filtering, but we're going
+    // to pay the (minor) price of setting this up for them to avoid the dreaded "black image" if
+    // they forget.
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+
+    // clean up
+    png_destroy_read_struct(&pngControl, &pngInfo, nullptr);
+    free(buffer);
+    free(rowPointers);
+    fclose(inputFile);
+
+    glBindTexture(GL_TEXTURE_2D, 0);
+
+
+    // Return the texture
+    return new TexWrapper(textureId, width, height);
+}
+
+}  // namespace support
+}  // namespace evs
+}  // namespace automotive
+}  // namespace android
diff --git a/evs/support_library/TexWrapper.h b/evs/support_library/TexWrapper.h
new file mode 100644
index 0000000..b26f443
--- /dev/null
+++ b/evs/support_library/TexWrapper.h
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#ifndef TEXWRAPPER_H
+#define TEXWRAPPER_H
+
+#include <GLES2/gl2.h>
+
+namespace android {
+namespace automotive {
+namespace evs {
+namespace support {
+
+class TexWrapper {
+public:
+    TexWrapper(GLuint textureId, unsigned width, unsigned height);
+    virtual ~TexWrapper();
+
+    GLuint glId()       { return id; };
+    unsigned width()    { return w; };
+    unsigned height()   { return h; };
+
+protected:
+    TexWrapper();
+
+    GLuint id;
+    unsigned w;
+    unsigned h;
+};
+
+
+TexWrapper* createTextureFromPng(const char* filename);
+
+}  // namespace support
+}  // namespace evs
+}  // namespace automotive
+}  // namespace android
+
+#endif  // TEXWRAPPER_H
diff --git a/evs/support_library/Utils.cpp b/evs/support_library/Utils.cpp
new file mode 100644
index 0000000..ae6e792
--- /dev/null
+++ b/evs/support_library/Utils.cpp
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include <android/hardware/automotive/evs/1.0/IEvsEnumerator.h>
+#include <hidl/HidlTransportSupport.h>
+#include <log/log.h>
+
+#include "ConfigManager.h"
+#include "Utils.h"
+
+namespace android {
+namespace automotive {
+namespace evs {
+namespace support {
+
+using namespace ::android::hardware::automotive::evs::V1_0;
+using ::android::hardware::hidl_vec;
+
+vector<string> Utils::sCameraIds;
+
+vector<string> Utils::getRearViewCameraIds() {
+    // If we already get the camera list, re-use it.
+    if (!sCameraIds.empty()) {
+        return sCameraIds;
+    }
+
+    const char* evsServiceName = "default";
+
+    // Load our configuration information
+    ConfigManager config;
+    if (!config.initialize("/system/etc/automotive/evs_support_lib/camera_config.json")) {
+        ALOGE("Missing or improper configuration for the EVS application.  Exiting.");
+        return vector<string>();
+    }
+
+    ALOGI("Acquiring EVS Enumerator");
+    sp<IEvsEnumerator> evs = IEvsEnumerator::getService(evsServiceName);
+    if (evs.get() == nullptr) {
+        ALOGE("getService(%s) returned NULL.  Exiting.", evsServiceName);
+        return vector<string>();
+    }
+
+    // static variable cannot be passed into capture, so we create a local
+    // variable instead.
+    vector<string> cameraIds;
+    ALOGD("Requesting camera list");
+    evs->getCameraList([&config, &cameraIds](hidl_vec<CameraDesc> cameraList) {
+        ALOGI("Camera list callback received %zu cameras", cameraList.size());
+        for (auto&& cam : cameraList) {
+            ALOGD("Found camera %s", cam.cameraId.c_str());
+
+            // If there are more than one rear-view cameras, return the first
+            // one.
+            for (auto&& info : config.getCameras()) {
+                if (cam.cameraId == info.cameraId) {
+                    // We found a match!
+                    if (info.function.find("reverse") != std::string::npos) {
+                        ALOGD("Camera %s is matched with reverse state",
+                              cam.cameraId.c_str());
+                        cameraIds.emplace_back(cam.cameraId);
+                    }
+                }
+            }
+        }
+    });
+    sCameraIds = cameraIds;
+    return sCameraIds;
+}
+
+string Utils::getDefaultRearViewCameraId() {
+    auto list = getRearViewCameraIds();
+    if (!list.empty()) {
+        return list[0];
+    } else {
+        return string();
+    }
+}
+
+}  // namespace support
+}  // namespace evs
+}  // namespace automotive
+}  // namespace android
diff --git a/evs/support_library/Utils.h b/evs/support_library/Utils.h
new file mode 100644
index 0000000..a059175
--- /dev/null
+++ b/evs/support_library/Utils.h
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#ifndef CAR_LIB_EVS_SUPPORT_UTILS_H
+#define CAR_LIB_EVS_SUPPORT_UTILS_H
+
+#include <string>
+#include <vector>
+
+namespace android {
+namespace automotive {
+namespace evs {
+namespace support {
+
+using std::vector;
+using std::string;
+
+class Utils {
+public:
+    /**
+     * Gets camera ids for all the available rear view cameras. For
+     * now, we don't support dynamically adding/removing camera. In
+     * other words, the camera list won't be updated after the first
+     * time the camera list is obtained.
+     *
+     * An empty vector is returned if no rear view camera is found.
+     */
+    static vector<string> getRearViewCameraIds();
+
+    /**
+     * Gets camera id for the default rear view camera. For now, we
+     * always assume that the first element in rear view camera list
+     * is the default one.
+     *
+     * An empty string is returned if no rear view camera is found.
+     */
+    static string getDefaultRearViewCameraId();
+
+private:
+    static vector<string> sCameraIds;
+};
+
+}  // namespace support
+}  // namespace evs
+}  // namespace automotive
+}  // namespace android
+
+#endif  // CAR_LIB_EVS_SUPPORT_UTILS_H
diff --git a/evs/support_library/VideoTex.cpp b/evs/support_library/VideoTex.cpp
new file mode 100644
index 0000000..0f0397a
--- /dev/null
+++ b/evs/support_library/VideoTex.cpp
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include <vector>
+#include <stdio.h>
+#include <fcntl.h>
+#include <alloca.h>
+#include <unistd.h>
+#include <sys/ioctl.h>
+#include <malloc.h>
+#include <png.h>
+
+#include "VideoTex.h"
+#include "glError.h"
+
+#include <ui/GraphicBuffer.h>
+#include <ui/GraphicBufferAllocator.h>
+#include <ui/GraphicBufferMapper.h>
+
+namespace android {
+namespace automotive {
+namespace evs {
+namespace support {
+
+// Eventually we shouldn't need this dependency, but for now the
+// graphics allocator interface isn't fully supported on all platforms
+// and this is our work around.
+using ::android::GraphicBuffer;
+
+
+VideoTex::VideoTex(EGLDisplay glDisplay)
+    : TexWrapper()
+    , mDisplay(glDisplay) {
+    // Nothing but initialization here...
+}
+
+VideoTex::~VideoTex() {
+    // Drop our device texture image
+    if (mKHRimage != EGL_NO_IMAGE_KHR) {
+        eglDestroyImageKHR(mDisplay, mKHRimage);
+        mKHRimage = EGL_NO_IMAGE_KHR;
+    }
+}
+
+
+// Return true if the texture contents are changed
+bool VideoTex::refresh(const BufferDesc& imageBuffer) {
+    // No new image has been delivered, so there's nothing to do here
+    if (imageBuffer.memHandle.getNativeHandle() == nullptr) {
+        return false;
+    }
+
+    // Drop our device texture image
+    if (mKHRimage != EGL_NO_IMAGE_KHR) {
+        eglDestroyImageKHR(mDisplay, mKHRimage);
+        mKHRimage = EGL_NO_IMAGE_KHR;
+    }
+
+    // create a GraphicBuffer from the existing handle
+    sp<GraphicBuffer> imageGraphicBuffer = new GraphicBuffer(
+        imageBuffer.memHandle, GraphicBuffer::CLONE_HANDLE, imageBuffer.width,
+        imageBuffer.height, imageBuffer.format, 1, // layer count
+        GRALLOC_USAGE_HW_TEXTURE, imageBuffer.stride);
+
+    if (imageGraphicBuffer.get() == nullptr) {
+        ALOGE("Failed to allocate GraphicBuffer to wrap image handle");
+        // Returning "true" in this error condition because we already released the
+        // previous image (if any) and so the texture may change in unpredictable ways now!
+        return true;
+    }
+
+
+    // Get a GL compatible reference to the graphics buffer we've been given
+    EGLint eglImageAttributes[] = {EGL_IMAGE_PRESERVED_KHR, EGL_TRUE, EGL_NONE};
+    EGLClientBuffer clientBuf = static_cast<EGLClientBuffer>(imageGraphicBuffer->getNativeBuffer());
+    mKHRimage = eglCreateImageKHR(mDisplay, EGL_NO_CONTEXT,
+                                  EGL_NATIVE_BUFFER_ANDROID, clientBuf,
+                                  eglImageAttributes);
+    if (mKHRimage == EGL_NO_IMAGE_KHR) {
+        const char *msg = getEGLError();
+        ALOGE("error creating EGLImage: %s", msg);
+        return false;
+    } else {
+        // Update the texture handle we already created to refer to this gralloc buffer
+        glActiveTexture(GL_TEXTURE0);
+        glBindTexture(GL_TEXTURE_2D, glId());
+        glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, static_cast<GLeglImageOES>(mKHRimage));
+
+        // Initialize the sampling properties (it seems the sample may not work if this isn't done)
+        // The user of this texture may very well want to set their own filtering, but we're going
+        // to pay the (minor) price of setting this up for them to avoid the dreaded "black image"
+        // if they forget.
+        // TODO:  Can we do this once for the texture ID rather than ever refresh?
+        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+    }
+
+    return true;
+}
+}  // namespace support
+}  // namespace evs
+}  // namespace automotive
+}  // namespace android
diff --git a/evs/support_library/VideoTex.h b/evs/support_library/VideoTex.h
new file mode 100644
index 0000000..58a1882
--- /dev/null
+++ b/evs/support_library/VideoTex.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#ifndef VIDEOTEX_H
+#define VIDEOTEX_H
+
+#include <EGL/egl.h>
+#include <EGL/eglext.h>
+#include <GLES2/gl2.h>
+#include <GLES2/gl2ext.h>
+#include <GLES3/gl3.h>
+#include <GLES3/gl3ext.h>
+
+#include <android/hardware/automotive/evs/1.0/IEvsEnumerator.h>
+
+#include "BaseRenderCallback.h"
+#include "StreamHandler.h"
+#include "TexWrapper.h"
+
+namespace android {
+namespace automotive {
+namespace evs {
+namespace support {
+
+using namespace ::android::hardware::automotive::evs::V1_0;
+
+
+class VideoTex: public TexWrapper {
+public:
+    VideoTex() = delete;
+    VideoTex(EGLDisplay glDisplay);
+    virtual ~VideoTex();
+
+    // returns true if the texture contents were updated
+    bool refresh(const BufferDesc& imageBuffer);
+
+private:
+    EGLDisplay          mDisplay;
+    EGLImageKHR mKHRimage = EGL_NO_IMAGE_KHR;
+};
+}  // namespace support
+}  // namespace evs
+}  // namespace automotive
+}  // namespace android
+
+#endif  // VIDEOTEX_H
diff --git a/evs/app/config.json b/evs/support_library/config.json
similarity index 100%
rename from evs/app/config.json
rename to evs/support_library/config.json
diff --git a/evs/support_library/glError.cpp b/evs/support_library/glError.cpp
new file mode 100644
index 0000000..6c96455
--- /dev/null
+++ b/evs/support_library/glError.cpp
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stdio.h>
+#include <EGL/egl.h>
+#include <GLES3/gl3.h>
+
+namespace android {
+namespace automotive {
+namespace evs {
+namespace support {
+
+const char *getEGLError(void) {
+    switch (eglGetError()) {
+        case EGL_SUCCESS:
+            return "EGL_SUCCESS";
+        case EGL_NOT_INITIALIZED:
+            return "EGL_NOT_INITIALIZED";
+        case EGL_BAD_ACCESS:
+            return "EGL_BAD_ACCESS";
+        case EGL_BAD_ALLOC:
+            return "EGL_BAD_ALLOC";
+        case EGL_BAD_ATTRIBUTE:
+            return "EGL_BAD_ATTRIBUTE";
+        case EGL_BAD_CONTEXT:
+            return "EGL_BAD_CONTEXT";
+        case EGL_BAD_CONFIG:
+            return "EGL_BAD_CONFIG";
+        case EGL_BAD_CURRENT_SURFACE:
+            return "EGL_BAD_CURRENT_SURFACE";
+        case EGL_BAD_DISPLAY:
+            return "EGL_BAD_DISPLAY";
+        case EGL_BAD_SURFACE:
+            return "EGL_BAD_SURFACE";
+        case EGL_BAD_MATCH:
+            return "EGL_BAD_MATCH";
+        case EGL_BAD_PARAMETER:
+            return "EGL_BAD_PARAMETER";
+        case EGL_BAD_NATIVE_PIXMAP:
+            return "EGL_BAD_NATIVE_PIXMAP";
+        case EGL_BAD_NATIVE_WINDOW:
+            return "EGL_BAD_NATIVE_WINDOW";
+        case EGL_CONTEXT_LOST:
+            return "EGL_CONTEXT_LOST";
+        default:
+            return "Unknown error";
+    }
+}
+
+
+const char *getGLFramebufferError(void) {
+    switch (glCheckFramebufferStatus(GL_FRAMEBUFFER)) {
+    case GL_FRAMEBUFFER_COMPLETE:
+        return "GL_FRAMEBUFFER_COMPLETE";
+    case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT:
+        return "GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT";
+    case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT:
+        return "GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT";
+    case GL_FRAMEBUFFER_UNSUPPORTED:
+        return "GL_FRAMEBUFFER_UNSUPPORTED";
+    case GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS:
+        return "GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS";
+    default:
+        return "Unknown error";
+    }
+}
+
+}  // namespace support
+}  // namespace evs
+}  // namespace automotive
+}  // namespace android
diff --git a/evs/support_library/glError.h b/evs/support_library/glError.h
new file mode 100644
index 0000000..636860a
--- /dev/null
+++ b/evs/support_library/glError.h
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef GLERROR_H
+#define GLERROR_H
+
+namespace android {
+namespace automotive {
+namespace evs {
+namespace support {
+
+const char *getEGLError(void);
+
+const char *getGLFramebufferError(void);
+
+}  // namespace support
+}  // namespace evs
+}  // namespace automotive
+}  // namespace android
+
+#endif  // GLERROR_H
diff --git a/evs/app/shader.cpp b/evs/support_library/shader.cpp
similarity index 100%
copy from evs/app/shader.cpp
copy to evs/support_library/shader.cpp
diff --git a/evs/app/shader.h b/evs/support_library/shader.h
similarity index 100%
copy from evs/app/shader.h
copy to evs/support_library/shader.h
diff --git a/evs/app/shader_projectedTex.h b/evs/support_library/shader_projectedTex.h
similarity index 100%
copy from evs/app/shader_projectedTex.h
copy to evs/support_library/shader_projectedTex.h
diff --git a/evs/app/shader_simpleTex.h b/evs/support_library/shader_simpleTex.h
similarity index 100%
copy from evs/app/shader_simpleTex.h
copy to evs/support_library/shader_simpleTex.h
diff --git a/experimental/experimental_api/Android.bp b/experimental/experimental_api/Android.bp
new file mode 100644
index 0000000..24e9b99
--- /dev/null
+++ b/experimental/experimental_api/Android.bp
@@ -0,0 +1,37 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//
+
+// Experimental API backed by Experimental Car service.
+
+java_library {
+
+    name: "car-experimental-api-static-lib",
+
+    srcs: [
+            "src/**/*.java",
+            "src/**/*.aidl"
+        ],
+
+    libs: ["android.car"],
+
+    platform_apis: true,
+
+    product_variables: {
+            pdk: {
+                enabled: false,
+            },
+    },
+}
diff --git a/experimental/experimental_api/src/android/car/experimental/CarDriverDistractionManager.java b/experimental/experimental_api/src/android/car/experimental/CarDriverDistractionManager.java
new file mode 100644
index 0000000..1a1e1cc
--- /dev/null
+++ b/experimental/experimental_api/src/android/car/experimental/CarDriverDistractionManager.java
@@ -0,0 +1,194 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.car.experimental;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.car.Car;
+import android.car.CarManagerBase;
+import android.car.annotation.RequiredFeature;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.lang.ref.WeakReference;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+/**
+ * Experimental APIs that allow clients to consume information about a driver's current distraction
+ * level.
+ *
+ * @hide
+ */
+@RequiredFeature(ExperimentalCar.TEST_EXPERIMENTAL_FEATURE_SERVICE)
+public final class CarDriverDistractionManager extends CarManagerBase {
+
+    private static final String TAG = "CarDriverDistractionManager";
+    private static final int MSG_HANDLE_DISTRACTION_CHANGE = 0;
+
+    private final IDriverDistractionManager mService;
+    private final EventHandler mEventHandler;
+
+    private final IDriverDistractionChangeListenerImpl mBinderCallback;
+
+    /**
+     * Listener to monitor any changes to the driver's distraction level.
+     */
+    public interface OnDriverDistractionChangeListener {
+        /**
+         * Called when the driver's distraction level has changed.
+         *
+         * @param event the new driver distraction information.
+         */
+        void onDriverDistractionChange(DriverDistractionChangeEvent event);
+    }
+
+    private final CopyOnWriteArrayList<OnDriverDistractionChangeListener> mListeners =
+            new CopyOnWriteArrayList<>();
+
+    /**
+     * Constructor parameters should remain this way for Car library to construct this.
+     */
+    public CarDriverDistractionManager(Car car, IBinder service) {
+        super(car);
+        mService = IDriverDistractionManager.Stub.asInterface(service);
+        mEventHandler = new EventHandler(this, getEventHandler().getLooper());
+        mBinderCallback = new IDriverDistractionChangeListenerImpl(this);
+    }
+
+    /**
+     * Gets the current driver distraction level based on the last change event that is not stale.
+     *
+     * Return {@code null} if there is an internal error.
+     */
+    @RequiresPermission(value = ExperimentalCar.PERMISSION_READ_CAR_DRIVER_DISTRACTION)
+    @Nullable
+    public DriverDistractionChangeEvent getLastDistractionEvent() {
+        try {
+            return mService.getLastDistractionEvent();
+        } catch (RemoteException e) {
+            return handleRemoteExceptionFromCarService(e, null);
+        }
+    }
+
+    /**
+     * Adds a {@link OnDriverDistractionChangeListener} to be notified for changes to the driver's
+     * distraction level.
+     *
+     * <p>The provided listener will immediately receive a callback for the most recent distraction
+     * event.
+     *
+     * @param listener to be added
+     */
+    @RequiresPermission(value = ExperimentalCar.PERMISSION_READ_CAR_DRIVER_DISTRACTION)
+    public void addDriverDistractionChangeListener(
+            @NonNull OnDriverDistractionChangeListener listener) {
+        if (mListeners.addIfAbsent(listener)) {
+            if (mListeners.size() == 1) {
+                try {
+                    mService.addDriverDistractionChangeListener(mBinderCallback);
+                } catch (RemoteException e) {
+                    handleRemoteExceptionFromCarService(e);
+                }
+            }
+        }
+    }
+
+    /**
+     * Removes the listener. Listeners not added before will be ignored.
+     */
+    @RequiresPermission(value = ExperimentalCar.PERMISSION_READ_CAR_DRIVER_DISTRACTION)
+    public void removeDriverDistractionChangeListener(
+            @NonNull OnDriverDistractionChangeListener listener) {
+        if (mListeners.remove(listener)) {
+            if (mListeners.size() == 0) {
+                try {
+                    mService.removeDriverDistractionChangeListener(mBinderCallback);
+                } catch (RemoteException ignored) {
+                    // ignore for unregistering
+                }
+            }
+        }
+    }
+
+    @Override
+    protected void onCarDisconnected() {
+        // nothing to do
+    }
+
+    private void dispatchDistractionChangeToClient(
+            DriverDistractionChangeEvent distractionChangeEvent) {
+        if (distractionChangeEvent == null) {
+            return;
+        }
+        for (OnDriverDistractionChangeListener listener : mListeners) {
+            listener.onDriverDistractionChange(distractionChangeEvent);
+        }
+    }
+
+    private static final class EventHandler extends Handler {
+        private final WeakReference<CarDriverDistractionManager> mDistractionManager;
+
+        EventHandler(CarDriverDistractionManager manager, Looper looper) {
+            super(looper);
+            mDistractionManager = new WeakReference<>(manager);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            CarDriverDistractionManager mgr = mDistractionManager.get();
+            if (mgr == null) {
+                Log.e(TAG,
+                        "handleMessage: null reference to distraction manager when handling "
+                                + "message for " + msg.what);
+                return;
+            }
+            switch (msg.what) {
+                case MSG_HANDLE_DISTRACTION_CHANGE:
+                    mgr.dispatchDistractionChangeToClient((DriverDistractionChangeEvent) msg.obj);
+                    break;
+                default:
+                    Log.e(TAG, "Unknown msg not handled:" + msg.what);
+            }
+        }
+
+        private void dispatchOnDistractionChangedEvent(DriverDistractionChangeEvent event) {
+            sendMessage(obtainMessage(MSG_HANDLE_DISTRACTION_CHANGE, event));
+        }
+    }
+
+    private static class IDriverDistractionChangeListenerImpl extends
+            IDriverDistractionChangeListener.Stub {
+        private final WeakReference<CarDriverDistractionManager> mDistractionManager;
+
+        IDriverDistractionChangeListenerImpl(CarDriverDistractionManager manager) {
+            mDistractionManager = new WeakReference<>(manager);
+        }
+
+        @Override
+        public void onDriverDistractionChange(DriverDistractionChangeEvent event) {
+            CarDriverDistractionManager manager = mDistractionManager.get();
+            if (manager != null) {
+                manager.mEventHandler.dispatchOnDistractionChangedEvent(event);
+            }
+        }
+    }
+}
diff --git a/experimental/experimental_api/src/android/car/experimental/CarTestDemoExperimentalFeatureManager.java b/experimental/experimental_api/src/android/car/experimental/CarTestDemoExperimentalFeatureManager.java
new file mode 100644
index 0000000..517165e
--- /dev/null
+++ b/experimental/experimental_api/src/android/car/experimental/CarTestDemoExperimentalFeatureManager.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.car.experimental;
+
+import android.car.Car;
+import android.car.CarManagerBase;
+import android.car.annotation.RequiredFeature;
+import android.os.IBinder;
+import android.os.RemoteException;
+
+/**
+ * Demo CarManager API for demonstrating experimental feature / API usage. This will never go to
+ * production.
+ *
+ * @hide
+ */
+@RequiredFeature(ExperimentalCar.TEST_EXPERIMENTAL_FEATURE_SERVICE)
+public final class CarTestDemoExperimentalFeatureManager extends CarManagerBase {
+
+    private final ITestDemoExperimental mService;
+
+    /**
+     * Constructor parameters should remain this way for Car library to construct this.
+     */
+    public CarTestDemoExperimentalFeatureManager(Car car, IBinder service) {
+        super(car);
+        mService = ITestDemoExperimental.Stub.asInterface(service);
+    }
+
+    /**
+     * Send ping msg service. It will replay back with the same message.
+     */
+    public String ping(String msg) {
+        try {
+            return mService.ping(msg);
+        } catch (RemoteException e) {
+            // For experimental API, we just crash client.
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    protected void onCarDisconnected() {
+        // Nothing to do
+    }
+}
diff --git a/experimental/experimental_api/src/android/car/experimental/DriverAwarenessEvent.aidl b/experimental/experimental_api/src/android/car/experimental/DriverAwarenessEvent.aidl
new file mode 100644
index 0000000..e9f38b5
--- /dev/null
+++ b/experimental/experimental_api/src/android/car/experimental/DriverAwarenessEvent.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.car.experimental;
+
+parcelable DriverAwarenessEvent;
\ No newline at end of file
diff --git a/experimental/experimental_api/src/android/car/experimental/DriverAwarenessEvent.java b/experimental/experimental_api/src/android/car/experimental/DriverAwarenessEvent.java
new file mode 100644
index 0000000..19134ed
--- /dev/null
+++ b/experimental/experimental_api/src/android/car/experimental/DriverAwarenessEvent.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.car.experimental;
+
+import android.annotation.FloatRange;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Objects;
+
+/**
+ * Event about a driver's awareness level at a certain point in time.
+ *
+ * <p>Driver Awareness is an abstract concept based on a driver's cognitive situational awareness
+ * of the environment around them. This metric can be approximated based on signals about the
+ * driver's behavior, such as where they are looking or how much their interact with the headunit
+ * in the car.
+ *
+ * <p>Furthermore, what constitutes the boundaries of no awareness and full awareness must be based
+ * on the UX Research through real-world studies and driving simulation. It is the responsibility
+ * of {@link DriverAwarenessSupplier}s to understand how their sensor input fits with current
+ * research in order to determine the appropriate awareness value.
+ *
+ * @hide
+ */
+public final class DriverAwarenessEvent implements Parcelable {
+
+    private final long mTimeStamp;
+
+    @FloatRange(from = 0.0f, to = 1.0f)
+    private final float mAwarenessValue;
+
+    /**
+     * Creator for {@link Parcelable}.
+     */
+    public static final Parcelable.Creator<DriverAwarenessEvent> CREATOR =
+            new Parcelable.Creator<DriverAwarenessEvent>() {
+                public DriverAwarenessEvent createFromParcel(Parcel in) {
+                    return new DriverAwarenessEvent(in);
+                }
+
+                public DriverAwarenessEvent[] newArray(int size) {
+                    return new DriverAwarenessEvent[size];
+                }
+            };
+
+    /**
+     * Creates an instance of a {@link DriverAwarenessEvent}.
+     *
+     * @param timeStamp      the time that the awareness value was sampled, in milliseconds elapsed
+     *                       since system boot
+     * @param awarenessValue the driver's awareness level at this point in time, ranging from 0, no
+     *                       awareness, to 1, full awareness
+     */
+    public DriverAwarenessEvent(long timeStamp,
+            @FloatRange(from = 0.0f, to = 1.0f) float awarenessValue) {
+        mTimeStamp = timeStamp;
+        mAwarenessValue = awarenessValue;
+    }
+
+    /**
+     * Parcelable constructor.
+     */
+    private DriverAwarenessEvent(Parcel in) {
+        mTimeStamp = in.readLong();
+        mAwarenessValue = in.readFloat();
+    }
+
+    /**
+     * Returns the time at which this driver awareness value was inferred based on the car's
+     * sensors. It is the elapsed time in milliseconds since system boot.
+     */
+    public long getTimeStamp() {
+        return mTimeStamp;
+    }
+
+    /**
+     * The current driver awareness value, where 0 is no awareness and 1 is full awareness.
+     */
+    @FloatRange(from = 0.0f, to = 1.0f)
+    public float getAwarenessValue() {
+        return mAwarenessValue;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeLong(mTimeStamp);
+        dest.writeFloat(mAwarenessValue);
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (!(o instanceof DriverAwarenessEvent)) {
+            return false;
+        }
+        DriverAwarenessEvent that = (DriverAwarenessEvent) o;
+        return mTimeStamp == that.mTimeStamp
+                && Float.compare(that.mAwarenessValue, mAwarenessValue) == 0;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mTimeStamp, mAwarenessValue);
+    }
+
+    @Override
+    public String toString() {
+        return String.format("DriverAwarenessEvent{timeStamp=%s, awarenessValue=%s}",
+                mTimeStamp, mAwarenessValue);
+    }
+}
diff --git a/experimental/experimental_api/src/android/car/experimental/DriverAwarenessSupplierConfig.aidl b/experimental/experimental_api/src/android/car/experimental/DriverAwarenessSupplierConfig.aidl
new file mode 100644
index 0000000..f7b1398
--- /dev/null
+++ b/experimental/experimental_api/src/android/car/experimental/DriverAwarenessSupplierConfig.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.car.experimental;
+
+parcelable DriverAwarenessSupplierConfig;
\ No newline at end of file
diff --git a/experimental/experimental_api/src/android/car/experimental/DriverAwarenessSupplierConfig.java b/experimental/experimental_api/src/android/car/experimental/DriverAwarenessSupplierConfig.java
new file mode 100644
index 0000000..1fd867a
--- /dev/null
+++ b/experimental/experimental_api/src/android/car/experimental/DriverAwarenessSupplierConfig.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.car.experimental;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Configuration for an instance of {@link android.car.experimental.IDriverAwarenessSupplier}.
+ */
+public class DriverAwarenessSupplierConfig implements Parcelable {
+
+    private final long mMaxStalenessMillis;
+
+    /**
+     * Creator for {@link Parcelable}.
+     */
+    public static final Parcelable.Creator<DriverAwarenessSupplierConfig> CREATOR =
+            new Parcelable.Creator<DriverAwarenessSupplierConfig>() {
+                public DriverAwarenessSupplierConfig createFromParcel(Parcel in) {
+                    return new DriverAwarenessSupplierConfig(in);
+                }
+
+                public DriverAwarenessSupplierConfig[] newArray(int size) {
+                    return new DriverAwarenessSupplierConfig[size];
+                }
+            };
+
+    /**
+     * Creates an instance of a {@link DriverAwarenessSupplierConfig}.
+     */
+    public DriverAwarenessSupplierConfig(long maxStalenessMillis) {
+        mMaxStalenessMillis = maxStalenessMillis;
+    }
+
+    /**
+     * Parcelable constructor.
+     */
+    private DriverAwarenessSupplierConfig(Parcel in) {
+        mMaxStalenessMillis = in.readLong();
+    }
+
+    /**
+     * Returns the duration in milliseconds after which the input from this supplier should be
+     * considered stale.
+     *
+     * <p>Data should be sent more frequently than the staleness rate defined here.
+     */
+    public long getMaxStalenessMillis() {
+        return mMaxStalenessMillis;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeLong(mMaxStalenessMillis);
+    }
+
+
+    @Override
+    public String toString() {
+        return String.format("DriverAwarenessEvent{mMaxStalenessMillis=%s}", mMaxStalenessMillis);
+    }
+}
diff --git a/experimental/experimental_api/src/android/car/experimental/DriverAwarenessSupplierService.java b/experimental/experimental_api/src/android/car/experimental/DriverAwarenessSupplierService.java
new file mode 100644
index 0000000..df48d10
--- /dev/null
+++ b/experimental/experimental_api/src/android/car/experimental/DriverAwarenessSupplierService.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.car.experimental;
+
+
+import android.annotation.CallSuper;
+import android.app.Service;
+import android.content.Intent;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+
+import com.android.internal.annotations.GuardedBy;
+
+/**
+ * The supplier for providing a stream of the driver's current situational awareness.
+ *
+ * <p>A perfect understanding of driver awareness requires years of extensive research and signals
+ * that suggest the cognitive situational awareness of the driver. Implementations of this class
+ * attempt to approximate driver awareness using concrete, but less accurate signals, such as gaze
+ * or touch.
+ *
+ * <p>Suppliers should notify of updates to the driver awareness level through {@link
+ * #onDriverAwarenessUpdated(DriverAwarenessEvent)}.
+ *
+ * <p>Suppliers define the amount of time until their data should be considered stale through
+ * {@link #getMaxStalenessMillis()}. After that amount of time data from this supplier will no
+ * longer be considered fresh. {@link #NO_STALENESS} is meant to be used by change-based suppliers
+ * such as a touch supplier - it is not appropriate for data signals that change continuous over
+ * time.
+ *
+ * <p>If this supplier has its own internal configuration, that configuration must be configurable
+ * by locale.
+ *
+ * <p>It is the attention supplier's responsibility to make sure that it only sends high-quality
+ * data events.
+ */
+public abstract class DriverAwarenessSupplierService extends Service {
+    private static final String TAG = "DriverAwarenessSupplierService";
+
+    /**
+     * Value that can be returned by {@link #getMaxStalenessMillis()} to indicate that an attention
+     * supplier sends change-events instead of push events on a regular interval. Should only be
+     * used for a supplier that is guaranteed to always be running (e.g. it won't crash or have
+     * periods of poor data).
+     */
+    public static final long NO_STALENESS = -1;
+
+    private final Object mLock = new Object();
+
+    private SupplierBinder mSupplierBinder;
+
+    @GuardedBy("mLock")
+    private IDriverAwarenessSupplierCallback mDriverAwarenessSupplierCallback;
+
+    /**
+     * Returns the duration in milliseconds after which the input from this supplier should be
+     * considered stale. This method should return a positive value greater than 0. There is no
+     * technical limit on the value returned here, but a value of 1000ms (1 second) would likely be
+     * considered too high since the driving environment can change drastically in that amount of
+     * time.
+     *
+     * <p>This can also return {@link #NO_STALENESS} if the supplier only emits change events and
+     * has no risk of failing to emit those change events within a reasonable amount of time.
+     *
+     * <p>Data should be sent more frequently than the staleness period defined here.
+     */
+    public abstract long getMaxStalenessMillis();
+
+    /**
+     * The distraction service is ready to start receiving events via {@link
+     * #onDriverAwarenessUpdated(DriverAwarenessEvent)}.
+     */
+    protected abstract void onReady();
+
+    @Override
+    @CallSuper
+    public IBinder onBind(Intent intent) {
+        logd("onBind, intent: " + intent);
+        if (mSupplierBinder == null) {
+            mSupplierBinder = new SupplierBinder();
+        }
+        return mSupplierBinder;
+    }
+
+    /**
+     * The driver awareness has changed - emit that update to the {@link
+     * com.android.experimentalcar.DriverDistractionExperimentalFeatureService}.
+     */
+    protected void onDriverAwarenessUpdated(DriverAwarenessEvent event) {
+        logd("onDriverAwarenessUpdated: " + event);
+        synchronized (mLock) {
+            if (mDriverAwarenessSupplierCallback != null) {
+                try {
+                    mDriverAwarenessSupplierCallback.onDriverAwarenessUpdated(event);
+                } catch (RemoteException e) {
+                    Log.e(TAG, "Remote exception", e);
+                }
+            }
+        }
+    }
+
+    private void handleReady() {
+        synchronized (mLock) {
+            try {
+                mDriverAwarenessSupplierCallback.onConfigLoaded(
+                        new DriverAwarenessSupplierConfig(getMaxStalenessMillis()));
+            } catch (RemoteException e) {
+                Log.e(TAG, "Unable to send config - abandoning ready process", e);
+                return;
+            }
+        }
+        onReady();
+    }
+
+    /**
+     * The binder between this service and
+     * {@link com.android.experimentalcar.DriverDistractionExperimentalFeatureService}.
+     */
+    private class SupplierBinder extends IDriverAwarenessSupplier.Stub {
+
+        @Override
+        public void onReady() {
+            handleReady();
+        }
+
+        @Override
+        public void setCallback(IDriverAwarenessSupplierCallback callback) {
+            synchronized (mLock) {
+                mDriverAwarenessSupplierCallback = callback;
+            }
+        }
+    }
+
+    private static void logd(String message) {
+        if (Log.isLoggable(TAG, Log.DEBUG)) {
+            Log.d(TAG, message);
+        }
+    }
+}
diff --git a/experimental/experimental_api/src/android/car/experimental/DriverDistractionChangeEvent.aidl b/experimental/experimental_api/src/android/car/experimental/DriverDistractionChangeEvent.aidl
new file mode 100644
index 0000000..dfd44ac
--- /dev/null
+++ b/experimental/experimental_api/src/android/car/experimental/DriverDistractionChangeEvent.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.car.experimental;
+
+parcelable DriverDistractionChangeEvent;
diff --git a/experimental/experimental_api/src/android/car/experimental/DriverDistractionChangeEvent.java b/experimental/experimental_api/src/android/car/experimental/DriverDistractionChangeEvent.java
new file mode 100644
index 0000000..0f619bd
--- /dev/null
+++ b/experimental/experimental_api/src/android/car/experimental/DriverDistractionChangeEvent.java
@@ -0,0 +1,190 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.car.experimental;
+
+import android.annotation.FloatRange;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.Objects;
+
+/**
+ * Event about a change in the driver's distraction.
+ *
+ * @hide
+ */
+public class DriverDistractionChangeEvent implements Parcelable {
+
+    private final long mElapsedRealtimeTimestamp;
+
+    @FloatRange(from = 0.0f, to = 1.0f)
+    private final float mAwarenessPercentage;
+
+    /**
+     * Creator for {@link Parcelable}.
+     */
+    public static final Parcelable.Creator<DriverDistractionChangeEvent> CREATOR =
+            new Parcelable.Creator<DriverDistractionChangeEvent>() {
+                public DriverDistractionChangeEvent createFromParcel(Parcel in) {
+                    return new DriverDistractionChangeEvent(in);
+                }
+
+                public DriverDistractionChangeEvent[] newArray(int size) {
+                    return new DriverDistractionChangeEvent[size];
+                }
+            };
+
+
+    private DriverDistractionChangeEvent(Builder builder) {
+        mElapsedRealtimeTimestamp = builder.mElapsedRealtimeTimestamp;
+        mAwarenessPercentage = builder.mAwarenessPercentage;
+    }
+
+    /**
+     * Parcelable constructor.
+     */
+    private DriverDistractionChangeEvent(Parcel in) {
+        mElapsedRealtimeTimestamp = in.readLong();
+        mAwarenessPercentage = in.readFloat();
+    }
+
+    /**
+     * Returns the timestamp for the change event, in milliseconds since boot, including time spent
+     * in sleep.
+     */
+    public long getElapsedRealtimeTimestamp() {
+        return mElapsedRealtimeTimestamp;
+    }
+
+    /**
+     * Returns the current driver driver awareness value as a percentage of the required awareness
+     * of the situation.
+     * <ul>
+     * <li>0% indicates that the driver has no situational awareness of the surrounding environment.
+     * <li>100% indicates that the driver has the target amount of situational awareness
+     * necessary for the current driving environment.
+     * </ul>
+     *
+     * <p>If the required awareness of the current driving environment is 0, such as when the car
+     * is in park, then the awareness percentage will be 100%.
+     *
+     * <p>Since this is a percentage, 0% will always correspond with a 0.0 driver awareness level.
+     * However, the rest of the range will get squeezed or compressed in time depending on the
+     * required awareness of the situation.
+     *
+     * <p>For example, suppose that driver awareness can go from 1.0 to 0.0 with 6 seconds of
+     * eyes-off-road time and that the driver has just started looking off-road. Now consider
+     * these two scenarios:
+     * <ol>
+     * <li>Scenario 1: The required awareness of the driving environment is 1.0:
+     * After 0 seconds: 100% awareness
+     * After 3 seconds: 50% awareness
+     * After 4.5 seconds: 25% awareness
+     * After 6 seconds: 0% awareness
+     * <li>Scenario 2: The required awareness of the driving environment is 0.5:
+     * After 0 seconds: 100% awareness
+     * After 3 seconds: 100% awareness
+     * After 4.5 seconds: 50% awareness
+     * After 6 seconds: 0% awareness
+     * </ol>
+     */
+    @FloatRange(from = 0.0f, to = 1.0f)
+    public float getAwarenessPercentage() {
+        return mAwarenessPercentage;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeLong(mElapsedRealtimeTimestamp);
+        dest.writeFloat(mAwarenessPercentage);
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (!(o instanceof DriverDistractionChangeEvent)) {
+            return false;
+        }
+        DriverDistractionChangeEvent that = (DriverDistractionChangeEvent) o;
+        return mElapsedRealtimeTimestamp == that.mElapsedRealtimeTimestamp
+                && Float.compare(that.mAwarenessPercentage, mAwarenessPercentage) == 0;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mElapsedRealtimeTimestamp, mAwarenessPercentage);
+    }
+
+    @Override
+    public String toString() {
+        return String.format(
+                "DriverDistractionChangeEvent{mElapsedRealtimeTimestamp=%s, "
+                        + "mAwarenessPercentage=%s}",
+                mElapsedRealtimeTimestamp, mAwarenessPercentage);
+    }
+
+
+    /**
+     * Builder for {@link DriverDistractionChangeEvent}.
+     */
+    public static class Builder {
+        private long mElapsedRealtimeTimestamp;
+
+        @FloatRange(from = 0.0f, to = 1.0f)
+        private float mAwarenessPercentage;
+
+        /**
+         * Set the timestamp for the change event, in milliseconds since boot, including time spent
+         * in sleep.
+         */
+        public Builder setElapsedRealtimeTimestamp(long timestamp) {
+            mElapsedRealtimeTimestamp = timestamp;
+            return this;
+        }
+
+        /**
+         * Set the current driver driver awareness value as a percentage of the required awareness
+         * of the situation.
+         */
+        public Builder setAwarenessPercentage(
+                @FloatRange(from = 0.0f, to = 1.0f) float awarenessPercentage) {
+            Preconditions.checkArgument(
+                    awarenessPercentage >= 0 && awarenessPercentage <= 1,
+                    "awarenessPercentage must be between 0 and 1");
+            mAwarenessPercentage = awarenessPercentage;
+            return this;
+        }
+
+        /**
+         * Build and return the {@link DriverDistractionChangeEvent} object.
+         */
+        public DriverDistractionChangeEvent build() {
+            return new DriverDistractionChangeEvent(this);
+        }
+
+    }
+
+}
diff --git a/experimental/experimental_api/src/android/car/experimental/ExperimentalCar.java b/experimental/experimental_api/src/android/car/experimental/ExperimentalCar.java
new file mode 100644
index 0000000..0f4c43f
--- /dev/null
+++ b/experimental/experimental_api/src/android/car/experimental/ExperimentalCar.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.car.experimental;
+
+import android.car.annotation.ExperimentalFeature;
+
+/**
+ * Top level class for experimental Car features
+ *
+ * @hide
+ */
+public final class ExperimentalCar {
+    /**
+     * Service for testing experimental feature
+     *
+     * @hide
+     */
+    @ExperimentalFeature
+    public static final String TEST_EXPERIMENTAL_FEATURE_SERVICE =
+            "android.car.experimental.test_demo_experimental_feature_service";
+
+    /**
+     * Service for monitoring the driver distraction level.
+     *
+     * @hide
+     */
+    @ExperimentalFeature
+    public static final String DRIVER_DISTRACTION_EXPERIMENTAL_FEATURE_SERVICE =
+            "android.car.experimental.driver_distraction_experimental_feature_service";
+
+
+    /**
+     * Permission necessary to observe the driver distraction level through {@link
+     * CarDriverDistractionManager}.
+     *
+     * @hide
+     */
+    public static final String PERMISSION_READ_CAR_DRIVER_DISTRACTION =
+            "android.car.permission.CAR_DRIVER_DISTRACTION";
+}
diff --git a/experimental/experimental_api/src/android/car/experimental/IDriverAwarenessSupplier.aidl b/experimental/experimental_api/src/android/car/experimental/IDriverAwarenessSupplier.aidl
new file mode 100644
index 0000000..6eaadeb
--- /dev/null
+++ b/experimental/experimental_api/src/android/car/experimental/IDriverAwarenessSupplier.aidl
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.car.experimental;
+
+import android.car.experimental.IDriverAwarenessSupplierCallback;
+
+/**
+ * Binder API for a driver awareness supplier.
+ *
+ * @hide
+ */
+oneway interface IDriverAwarenessSupplier {
+
+    /** Called once the distraction service is ready to receive events */
+    void onReady() = 0;
+
+    /** Set the callback to be used for this supplier */
+    void setCallback(IDriverAwarenessSupplierCallback callback) = 1;
+}
\ No newline at end of file
diff --git a/experimental/experimental_api/src/android/car/experimental/IDriverAwarenessSupplierCallback.aidl b/experimental/experimental_api/src/android/car/experimental/IDriverAwarenessSupplierCallback.aidl
new file mode 100644
index 0000000..7b6ec1f
--- /dev/null
+++ b/experimental/experimental_api/src/android/car/experimental/IDriverAwarenessSupplierCallback.aidl
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.car.experimental;
+
+import android.car.experimental.DriverAwarenessEvent;
+import android.car.experimental.DriverAwarenessSupplierConfig;
+
+/**
+ * Binder callback for IDriverAwarenessSupplier.
+ *
+ * @hide
+ */
+interface IDriverAwarenessSupplierCallback {
+   /**
+    * Sets the awareness level for the driver. Determining sufficient data quality is the
+    * responsibility of the AwarenessSupplier and events with insufficient data quality should not
+    * be sent.
+    *
+    * <p>Suppliers could crash in the background or fail to send continuously high data and
+    * therefore should push events, even if the Awareness level hasn't changed, with a frequency
+    * greater than their specified AwarenessSupplier#getMaxStalenessMillis().
+    *
+    * <p>Should be called once when first registered.
+    *
+    * @param event a snapshot of the driver's awareness at a certain point in time.
+    */
+   void onDriverAwarenessUpdated(in DriverAwarenessEvent event) = 0;
+
+   /**
+    * Sends the configuration for IDriverAwarenessSupplier configuration that this is a callback
+    * for.
+    *
+    * @param config for the IDriverAwarenessSupplier
+    */
+   void onConfigLoaded(in DriverAwarenessSupplierConfig config) = 1;
+}
diff --git a/experimental/experimental_api/src/android/car/experimental/IDriverDistractionChangeListener.aidl b/experimental/experimental_api/src/android/car/experimental/IDriverDistractionChangeListener.aidl
new file mode 100644
index 0000000..10fbb7a
--- /dev/null
+++ b/experimental/experimental_api/src/android/car/experimental/IDriverDistractionChangeListener.aidl
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.car.experimental;
+
+import android.car.experimental.DriverDistractionChangeEvent;
+
+/**
+ * Binder callback for changes to the driver's distraction level.
+ * @hide
+ */
+oneway interface IDriverDistractionChangeListener {
+   /**
+    * Called when the driver's distraction level has changed.
+    *
+    * Should be called once when first registered.
+    */
+   void onDriverDistractionChange(in DriverDistractionChangeEvent event) = 0;
+}
\ No newline at end of file
diff --git a/experimental/experimental_api/src/android/car/experimental/IDriverDistractionManager.aidl b/experimental/experimental_api/src/android/car/experimental/IDriverDistractionManager.aidl
new file mode 100644
index 0000000..00d5268
--- /dev/null
+++ b/experimental/experimental_api/src/android/car/experimental/IDriverDistractionManager.aidl
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.car.experimental;
+
+import android.car.experimental.DriverDistractionChangeEvent;
+import android.car.experimental.IDriverDistractionChangeListener;
+
+/** @hide */
+interface IDriverDistractionManager {
+    /**
+     * Gets the current driver distraction level based on the last change event that is not stale.
+     */
+    DriverDistractionChangeEvent getLastDistractionEvent() = 0;
+   /**
+    * Registers a listener to be called when the driver's distraction level has changed.
+    *
+    * Listener immediately receives a callback.
+    */
+    void addDriverDistractionChangeListener(in IDriverDistractionChangeListener listener) = 1;
+    /**
+     * Removes the provided listener from receiving callbacks.
+     */
+    void removeDriverDistractionChangeListener(in IDriverDistractionChangeListener listener) = 2;
+}
\ No newline at end of file
diff --git a/experimental/experimental_api/src/android/car/experimental/ITestDemoExperimental.aidl b/experimental/experimental_api/src/android/car/experimental/ITestDemoExperimental.aidl
new file mode 100644
index 0000000..b5ce7e1
--- /dev/null
+++ b/experimental/experimental_api/src/android/car/experimental/ITestDemoExperimental.aidl
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.car.experimental;
+
+/** @hide */
+interface ITestDemoExperimental {
+    String ping(in String msg);
+}
\ No newline at end of file
diff --git a/experimental/service/Android.bp b/experimental/service/Android.bp
new file mode 100644
index 0000000..b0c59c4
--- /dev/null
+++ b/experimental/service/Android.bp
@@ -0,0 +1,83 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//
+
+// Build the Experimental Car service.
+
+experimentalcar_service_sources = ["src/**/*.java"]
+
+android_app {
+    name: "ExperimentalCarService",
+
+    srcs: experimentalcar_service_sources,
+
+    resource_dirs: ["res"],
+
+    platform_apis: true,
+
+    // Each update should be signed by OEMs
+    certificate: "platform",
+    privileged: true,
+
+    optimize: {
+        proguard_flags_files: ["proguard.flags"],
+        enabled: false,
+    },
+
+    libs: ["android.car"],
+
+    static_libs: [
+      "car-service-common-util-static-lib",
+      "car-experimental-api-static-lib",
+    ],
+
+    required: ["privapp_whitelist_com.android.experimentalcar"],
+
+    // Disable build in PDK, missing aidl import breaks build
+    product_variables: {
+        pdk: {
+            enabled: false,
+        },
+    },
+}
+
+//####################################################################################
+// Build a static library to help mocking various car services in testing. This is meant to be used
+// for internal unit tests around the car service.
+//####################################################################################
+android_library {
+    name: "experimentalcar-service-test-static-lib",
+
+    srcs: experimentalcar_service_sources,
+
+    resource_dirs: ["res"],
+
+    libs: [
+        "android.car",
+    ],
+
+    static_libs: [
+      "car-service-common-util-static-lib",
+      "car-experimental-api-static-lib",
+    ],
+
+    min_sdk_version: "25",
+
+    product_variables: {
+        pdk: {
+            enabled: false,
+        },
+    },
+}
diff --git a/experimental/service/AndroidManifest.xml b/experimental/service/AndroidManifest.xml
new file mode 100644
index 0000000..09e50b1
--- /dev/null
+++ b/experimental/service/AndroidManifest.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="com.android.experimentalcar"
+          coreApp="true"
+          android:sharedUserId="android.uid.system">
+
+    <original-package android:name="com.android.experimentalcar"/>
+
+    <!-- Allows an application to get the driver's current distraction level.
+         <p>Protection level: signature|privileged
+    -->
+    <permission
+        android:name="android.car.permission.CAR_DRIVER_DISTRACTION"
+        android:protectionLevel="signature|privileged"
+        android:label="@string/car_permission_label_driver_distraction"
+        android:description="@string/car_permission_desc_driver_distraction"/>
+
+    <application android:label="@string/app_title"
+                 android:directBootAware="true"
+                 android:allowBackup="false"
+                 android:persistent="false">
+        <service android:name=".ExperimentalCarService"
+                 android:singleUser="true">
+        </service>
+        <service android:name=".TouchDriverAwarenessSupplier" android:exported="false"/>
+        <service android:name=".GazeDriverAwarenessSupplier" android:exported="false"/>
+        <service android:name=".SampleExternalDriverAwarenessSupplier" android:exported="false"/>
+    </application>
+</manifest>
diff --git a/experimental/service/proguard.flags b/experimental/service/proguard.flags
new file mode 100644
index 0000000..22cc22d
--- /dev/null
+++ b/experimental/service/proguard.flags
@@ -0,0 +1,3 @@
+-verbose
+-keep @com.android.internal.annotations.VisibleForTesting class *
+
diff --git a/experimental/service/res/values-af/strings.xml b/experimental/service/res/values-af/strings.xml
new file mode 100644
index 0000000..bdec8e5
--- /dev/null
+++ b/experimental/service/res/values-af/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="car_permission_label_driver_distraction" msgid="4864958399001183048">"Bestuurderafleiding"</string>
+    <string name="car_permission_desc_driver_distraction" msgid="2506435411900299264">"Lees bestuurder se afleidingsvlak."</string>
+</resources>
diff --git a/experimental/service/res/values-am/strings.xml b/experimental/service/res/values-am/strings.xml
new file mode 100644
index 0000000..a0ff5f8
--- /dev/null
+++ b/experimental/service/res/values-am/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="car_permission_label_driver_distraction" msgid="4864958399001183048">"የነጂ መዘናጋት"</string>
+    <string name="car_permission_desc_driver_distraction" msgid="2506435411900299264">"የነጂ መዘናጋት ደረጃን ያንብቡ።"</string>
+</resources>
diff --git a/experimental/service/res/values-ar/strings.xml b/experimental/service/res/values-ar/strings.xml
new file mode 100644
index 0000000..a22a28b
--- /dev/null
+++ b/experimental/service/res/values-ar/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="car_permission_label_driver_distraction" msgid="4864958399001183048">"تشتت السائق"</string>
+    <string name="car_permission_desc_driver_distraction" msgid="2506435411900299264">"قراءة مستوى تشتت السائق"</string>
+</resources>
diff --git a/experimental/service/res/values-as/strings.xml b/experimental/service/res/values-as/strings.xml
new file mode 100644
index 0000000..ebd84c4
--- /dev/null
+++ b/experimental/service/res/values-as/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="car_permission_label_driver_distraction" msgid="4864958399001183048">"চালকৰ অন্যমনস্কতা"</string>
+    <string name="car_permission_desc_driver_distraction" msgid="2506435411900299264">"চালকৰ অন্যমনস্কতাৰ স্তৰ পঢ়ক।"</string>
+</resources>
diff --git a/experimental/service/res/values-az/strings.xml b/experimental/service/res/values-az/strings.xml
new file mode 100644
index 0000000..9551ba1
--- /dev/null
+++ b/experimental/service/res/values-az/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="car_permission_label_driver_distraction" msgid="4864958399001183048">"Sürücünün yayınması"</string>
+    <string name="car_permission_desc_driver_distraction" msgid="2506435411900299264">"Sürücünün yayınma səviyyəsini oxuyun."</string>
+</resources>
diff --git a/experimental/service/res/values-b+sr+Latn/strings.xml b/experimental/service/res/values-b+sr+Latn/strings.xml
new file mode 100644
index 0000000..be3cf46
--- /dev/null
+++ b/experimental/service/res/values-b+sr+Latn/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="car_permission_label_driver_distraction" msgid="4864958399001183048">"Ometanje vozača"</string>
+    <string name="car_permission_desc_driver_distraction" msgid="2506435411900299264">"Očitavaju nivo ometanja vozača."</string>
+</resources>
diff --git a/experimental/service/res/values-be/strings.xml b/experimental/service/res/values-be/strings.xml
new file mode 100644
index 0000000..60b007c
--- /dev/null
+++ b/experimental/service/res/values-be/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="car_permission_label_driver_distraction" msgid="4864958399001183048">"Рассеянасць вадзіцеля"</string>
+    <string name="car_permission_desc_driver_distraction" msgid="2506435411900299264">"Вызначаць узровень рассеянасці вадзіцеля."</string>
+</resources>
diff --git a/experimental/service/res/values-bg/strings.xml b/experimental/service/res/values-bg/strings.xml
new file mode 100644
index 0000000..958eb4b
--- /dev/null
+++ b/experimental/service/res/values-bg/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="car_permission_label_driver_distraction" msgid="4864958399001183048">"Разсейване на шофьора"</string>
+    <string name="car_permission_desc_driver_distraction" msgid="2506435411900299264">"Четене на нивото на разсейване на шофьора."</string>
+</resources>
diff --git a/experimental/service/res/values-bn/strings.xml b/experimental/service/res/values-bn/strings.xml
new file mode 100644
index 0000000..30c8e62
--- /dev/null
+++ b/experimental/service/res/values-bn/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="car_permission_label_driver_distraction" msgid="4864958399001183048">"ড্রাইভারের মনোযোগে ব্যাঘাত ঘটা"</string>
+    <string name="car_permission_desc_driver_distraction" msgid="2506435411900299264">"ড্রাইভারের মনোযোগে ব্যাঘাত ঘটার লেভেল সম্পর্কে জানুন।"</string>
+</resources>
diff --git a/experimental/service/res/values-bs/strings.xml b/experimental/service/res/values-bs/strings.xml
new file mode 100644
index 0000000..966cba9
--- /dev/null
+++ b/experimental/service/res/values-bs/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="car_permission_label_driver_distraction" msgid="4864958399001183048">"Ometanje vozača"</string>
+    <string name="car_permission_desc_driver_distraction" msgid="2506435411900299264">"Očitavanje nivoa ometanja vozača."</string>
+</resources>
diff --git a/experimental/service/res/values-ca/strings.xml b/experimental/service/res/values-ca/strings.xml
new file mode 100644
index 0000000..75a62b7
--- /dev/null
+++ b/experimental/service/res/values-ca/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="car_permission_label_driver_distraction" msgid="4864958399001183048">"Distracció del conductor"</string>
+    <string name="car_permission_desc_driver_distraction" msgid="2506435411900299264">"Consultar el nivell de distracció del conductor."</string>
+</resources>
diff --git a/experimental/service/res/values-cs/strings.xml b/experimental/service/res/values-cs/strings.xml
new file mode 100644
index 0000000..39c5880
--- /dev/null
+++ b/experimental/service/res/values-cs/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="car_permission_label_driver_distraction" msgid="4864958399001183048">"Vyrušení pozornosti řidiče"</string>
+    <string name="car_permission_desc_driver_distraction" msgid="2506435411900299264">"Čtení míry vyrušení pozornosti řidiče."</string>
+</resources>
diff --git a/experimental/service/res/values-da/strings.xml b/experimental/service/res/values-da/strings.xml
new file mode 100644
index 0000000..ef79a77
--- /dev/null
+++ b/experimental/service/res/values-da/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="car_permission_label_driver_distraction" msgid="4864958399001183048">"Distrahering af fører"</string>
+    <string name="car_permission_desc_driver_distraction" msgid="2506435411900299264">"Tjek, hvor distraheret føreren er."</string>
+</resources>
diff --git a/experimental/service/res/values-de/strings.xml b/experimental/service/res/values-de/strings.xml
new file mode 100644
index 0000000..6773ba2
--- /dev/null
+++ b/experimental/service/res/values-de/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="car_permission_label_driver_distraction" msgid="4864958399001183048">"Ablenkungsgrad des Fahrers"</string>
+    <string name="car_permission_desc_driver_distraction" msgid="2506435411900299264">"Ablenkungsgrad des Fahrers erkennen."</string>
+</resources>
diff --git a/experimental/service/res/values-el/strings.xml b/experimental/service/res/values-el/strings.xml
new file mode 100644
index 0000000..e3d9845
--- /dev/null
+++ b/experimental/service/res/values-el/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="car_permission_label_driver_distraction" msgid="4864958399001183048">"Απόσπαση προσοχής οδηγού"</string>
+    <string name="car_permission_desc_driver_distraction" msgid="2506435411900299264">"Ανάγνωση επιπέδου απόσπασης προσοχής οδηγού."</string>
+</resources>
diff --git a/experimental/service/res/values-en-rAU/strings.xml b/experimental/service/res/values-en-rAU/strings.xml
new file mode 100644
index 0000000..f5f5ff0
--- /dev/null
+++ b/experimental/service/res/values-en-rAU/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="car_permission_label_driver_distraction" msgid="4864958399001183048">"Driver distraction"</string>
+    <string name="car_permission_desc_driver_distraction" msgid="2506435411900299264">"Read driver distraction level."</string>
+</resources>
diff --git a/experimental/service/res/values-en-rCA/strings.xml b/experimental/service/res/values-en-rCA/strings.xml
new file mode 100644
index 0000000..f5f5ff0
--- /dev/null
+++ b/experimental/service/res/values-en-rCA/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="car_permission_label_driver_distraction" msgid="4864958399001183048">"Driver distraction"</string>
+    <string name="car_permission_desc_driver_distraction" msgid="2506435411900299264">"Read driver distraction level."</string>
+</resources>
diff --git a/experimental/service/res/values-en-rGB/strings.xml b/experimental/service/res/values-en-rGB/strings.xml
new file mode 100644
index 0000000..f5f5ff0
--- /dev/null
+++ b/experimental/service/res/values-en-rGB/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="car_permission_label_driver_distraction" msgid="4864958399001183048">"Driver distraction"</string>
+    <string name="car_permission_desc_driver_distraction" msgid="2506435411900299264">"Read driver distraction level."</string>
+</resources>
diff --git a/experimental/service/res/values-en-rIN/strings.xml b/experimental/service/res/values-en-rIN/strings.xml
new file mode 100644
index 0000000..f5f5ff0
--- /dev/null
+++ b/experimental/service/res/values-en-rIN/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="car_permission_label_driver_distraction" msgid="4864958399001183048">"Driver distraction"</string>
+    <string name="car_permission_desc_driver_distraction" msgid="2506435411900299264">"Read driver distraction level."</string>
+</resources>
diff --git a/experimental/service/res/values-en-rXC/strings.xml b/experimental/service/res/values-en-rXC/strings.xml
new file mode 100644
index 0000000..19bc3a8
--- /dev/null
+++ b/experimental/service/res/values-en-rXC/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="car_permission_label_driver_distraction" msgid="4864958399001183048">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‎‎‎‎‏‏‏‎‎‎‎‎‏‏‏‏‎‎‏‏‎‏‏‏‏‎‎‎‏‏‎‏‏‎‎‎‏‏‏‎‎‎‎‏‎‎‏‏‎‎‎‎‏‏‎‏‎‎‏‎‎‎‎Driver Distraction‎‏‎‎‏‎"</string>
+    <string name="car_permission_desc_driver_distraction" msgid="2506435411900299264">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‎‏‎‎‎‏‎‏‏‎‎‏‎‎‎‏‎‏‎‎‏‎‏‏‎‏‏‏‎‏‎‎‎‏‏‏‎‏‎‏‏‎‎‎‏‏‎‏‏‏‏‏‎‎‎‎‎‎‎‎‎‎‎‎Read driver distraction level.‎‏‎‎‏‎"</string>
+</resources>
diff --git a/experimental/service/res/values-es-rUS/strings.xml b/experimental/service/res/values-es-rUS/strings.xml
new file mode 100644
index 0000000..e8fbad5
--- /dev/null
+++ b/experimental/service/res/values-es-rUS/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="car_permission_label_driver_distraction" msgid="4864958399001183048">"Distracción del conductor"</string>
+    <string name="car_permission_desc_driver_distraction" msgid="2506435411900299264">"Leer el nivel de distracción del conductor."</string>
+</resources>
diff --git a/experimental/service/res/values-es/strings.xml b/experimental/service/res/values-es/strings.xml
new file mode 100644
index 0000000..e8fbad5
--- /dev/null
+++ b/experimental/service/res/values-es/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="car_permission_label_driver_distraction" msgid="4864958399001183048">"Distracción del conductor"</string>
+    <string name="car_permission_desc_driver_distraction" msgid="2506435411900299264">"Leer el nivel de distracción del conductor."</string>
+</resources>
diff --git a/experimental/service/res/values-et/strings.xml b/experimental/service/res/values-et/strings.xml
new file mode 100644
index 0000000..a5e67ef
--- /dev/null
+++ b/experimental/service/res/values-et/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="car_permission_label_driver_distraction" msgid="4864958399001183048">"Juhi häiritus"</string>
+    <string name="car_permission_desc_driver_distraction" msgid="2506435411900299264">"Juhi häirituse taseme lugemine."</string>
+</resources>
diff --git a/experimental/service/res/values-eu/strings.xml b/experimental/service/res/values-eu/strings.xml
new file mode 100644
index 0000000..b24ac35
--- /dev/null
+++ b/experimental/service/res/values-eu/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="car_permission_label_driver_distraction" msgid="4864958399001183048">"Gidariaren distrakzioa"</string>
+    <string name="car_permission_desc_driver_distraction" msgid="2506435411900299264">"Irakurri gidariaren distrakzio-maila."</string>
+</resources>
diff --git a/experimental/service/res/values-fa/strings.xml b/experimental/service/res/values-fa/strings.xml
new file mode 100644
index 0000000..3931b80
--- /dev/null
+++ b/experimental/service/res/values-fa/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="car_permission_label_driver_distraction" msgid="4864958399001183048">"حواس‌پرتی راننده"</string>
+    <string name="car_permission_desc_driver_distraction" msgid="2506435411900299264">"خواندن سطح حواس‌پرتی راننده."</string>
+</resources>
diff --git a/experimental/service/res/values-fi/strings.xml b/experimental/service/res/values-fi/strings.xml
new file mode 100644
index 0000000..ab43067
--- /dev/null
+++ b/experimental/service/res/values-fi/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="car_permission_label_driver_distraction" msgid="4864958399001183048">"Kuljettajan keskittyminen"</string>
+    <string name="car_permission_desc_driver_distraction" msgid="2506435411900299264">"Lue kuljettajan keskittymisen taso."</string>
+</resources>
diff --git a/experimental/service/res/values-fr-rCA/strings.xml b/experimental/service/res/values-fr-rCA/strings.xml
new file mode 100644
index 0000000..497534b
--- /dev/null
+++ b/experimental/service/res/values-fr-rCA/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="car_permission_label_driver_distraction" msgid="4864958399001183048">"Distraction du conducteur"</string>
+    <string name="car_permission_desc_driver_distraction" msgid="2506435411900299264">"Accédez au niveau de distraction du conducteur."</string>
+</resources>
diff --git a/experimental/service/res/values-fr/strings.xml b/experimental/service/res/values-fr/strings.xml
new file mode 100644
index 0000000..eaa7572
--- /dev/null
+++ b/experimental/service/res/values-fr/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="car_permission_label_driver_distraction" msgid="4864958399001183048">"Niveau de distraction du conducteur"</string>
+    <string name="car_permission_desc_driver_distraction" msgid="2506435411900299264">"Accéder au niveau de distraction du conducteur."</string>
+</resources>
diff --git a/experimental/service/res/values-gl/strings.xml b/experimental/service/res/values-gl/strings.xml
new file mode 100644
index 0000000..e84f8b1
--- /dev/null
+++ b/experimental/service/res/values-gl/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="car_permission_label_driver_distraction" msgid="4864958399001183048">"Distracción do condutor"</string>
+    <string name="car_permission_desc_driver_distraction" msgid="2506435411900299264">"Ler o nivel de distracción do condutor."</string>
+</resources>
diff --git a/experimental/service/res/values-gu/strings.xml b/experimental/service/res/values-gu/strings.xml
new file mode 100644
index 0000000..d6bd7dd
--- /dev/null
+++ b/experimental/service/res/values-gu/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="car_permission_label_driver_distraction" msgid="4864958399001183048">"ડ્રાઇવરનું ધ્યાન ભટકવું"</string>
+    <string name="car_permission_desc_driver_distraction" msgid="2506435411900299264">"ડ્રાઇવરનું ધ્યાન ભટકવાના સ્તર વિશે જાણો."</string>
+</resources>
diff --git a/experimental/service/res/values-hi/strings.xml b/experimental/service/res/values-hi/strings.xml
new file mode 100644
index 0000000..b5639da
--- /dev/null
+++ b/experimental/service/res/values-hi/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="car_permission_label_driver_distraction" msgid="4864958399001183048">"ड्राइवर का ध्यान भटकना"</string>
+    <string name="car_permission_desc_driver_distraction" msgid="2506435411900299264">"ड्राइवर का ध्यान भटकने के लेवल के बारे में जानें."</string>
+</resources>
diff --git a/experimental/service/res/values-hr/strings.xml b/experimental/service/res/values-hr/strings.xml
new file mode 100644
index 0000000..2fb84b4
--- /dev/null
+++ b/experimental/service/res/values-hr/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="car_permission_label_driver_distraction" msgid="4864958399001183048">"Ometanje vozača"</string>
+    <string name="car_permission_desc_driver_distraction" msgid="2506435411900299264">"Čitaju razinu ometanja vozača."</string>
+</resources>
diff --git a/experimental/service/res/values-hu/strings.xml b/experimental/service/res/values-hu/strings.xml
new file mode 100644
index 0000000..e261861
--- /dev/null
+++ b/experimental/service/res/values-hu/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="car_permission_label_driver_distraction" msgid="4864958399001183048">"Sofőr figyelmetlensége"</string>
+    <string name="car_permission_desc_driver_distraction" msgid="2506435411900299264">"A sofőr figyelmetlenségi szintjének olvasása."</string>
+</resources>
diff --git a/experimental/service/res/values-hy/strings.xml b/experimental/service/res/values-hy/strings.xml
new file mode 100644
index 0000000..77ac717
--- /dev/null
+++ b/experimental/service/res/values-hy/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="car_permission_label_driver_distraction" msgid="4864958399001183048">"Շեղող գործոններ"</string>
+    <string name="car_permission_desc_driver_distraction" msgid="2506435411900299264">"Տեսնել շեղող գործոնների մակարդակը։"</string>
+</resources>
diff --git a/experimental/service/res/values-in/strings.xml b/experimental/service/res/values-in/strings.xml
new file mode 100644
index 0000000..06924d1
--- /dev/null
+++ b/experimental/service/res/values-in/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="car_permission_label_driver_distraction" msgid="4864958399001183048">"Gangguan bagi Pengemudi"</string>
+    <string name="car_permission_desc_driver_distraction" msgid="2506435411900299264">"Baca tingkat gangguan bagi pengemudi."</string>
+</resources>
diff --git a/experimental/service/res/values-is/strings.xml b/experimental/service/res/values-is/strings.xml
new file mode 100644
index 0000000..759c6a9
--- /dev/null
+++ b/experimental/service/res/values-is/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="car_permission_label_driver_distraction" msgid="4864958399001183048">"Truflun ökumanns"</string>
+    <string name="car_permission_desc_driver_distraction" msgid="2506435411900299264">"Athuga hversu miklum truflunum ökumaður verður fyrir."</string>
+</resources>
diff --git a/experimental/service/res/values-it/strings.xml b/experimental/service/res/values-it/strings.xml
new file mode 100644
index 0000000..9436c06
--- /dev/null
+++ b/experimental/service/res/values-it/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="car_permission_label_driver_distraction" msgid="4864958399001183048">"Distrazione dell\'autista"</string>
+    <string name="car_permission_desc_driver_distraction" msgid="2506435411900299264">"Leggi il livello di distrazione dell\'autista"</string>
+</resources>
diff --git a/experimental/service/res/values-iw/strings.xml b/experimental/service/res/values-iw/strings.xml
new file mode 100644
index 0000000..d5b8bb4
--- /dev/null
+++ b/experimental/service/res/values-iw/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="car_permission_label_driver_distraction" msgid="4864958399001183048">"הסחת הדעת של הנהג"</string>
+    <string name="car_permission_desc_driver_distraction" msgid="2506435411900299264">"אישור קריאה של רמת הסחת הדעת של הנהג."</string>
+</resources>
diff --git a/experimental/service/res/values-ja/strings.xml b/experimental/service/res/values-ja/strings.xml
new file mode 100644
index 0000000..1762317
--- /dev/null
+++ b/experimental/service/res/values-ja/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="car_permission_label_driver_distraction" msgid="4864958399001183048">"ドライバーの注意散漫"</string>
+    <string name="car_permission_desc_driver_distraction" msgid="2506435411900299264">"ドライバーの注意散漫レベルの読み取り。"</string>
+</resources>
diff --git a/experimental/service/res/values-ka/strings.xml b/experimental/service/res/values-ka/strings.xml
new file mode 100644
index 0000000..2317561
--- /dev/null
+++ b/experimental/service/res/values-ka/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="car_permission_label_driver_distraction" msgid="4864958399001183048">"მძღოლის გაფანტულობა"</string>
+    <string name="car_permission_desc_driver_distraction" msgid="2506435411900299264">"მძღოლის გაფანტულობის დონის წაკითხვა."</string>
+</resources>
diff --git a/experimental/service/res/values-kk/strings.xml b/experimental/service/res/values-kk/strings.xml
new file mode 100644
index 0000000..eda84f1
--- /dev/null
+++ b/experimental/service/res/values-kk/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="car_permission_label_driver_distraction" msgid="4864958399001183048">"Жүргізушіні алаңдату"</string>
+    <string name="car_permission_desc_driver_distraction" msgid="2506435411900299264">"Жүргізушіні алаңдату деңгейін көру."</string>
+</resources>
diff --git a/experimental/service/res/values-km/strings.xml b/experimental/service/res/values-km/strings.xml
new file mode 100644
index 0000000..ff5d56c
--- /dev/null
+++ b/experimental/service/res/values-km/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="car_permission_label_driver_distraction" msgid="4864958399001183048">"ការរំខាន​របស់អ្នកបើកបរ"</string>
+    <string name="car_permission_desc_driver_distraction" msgid="2506435411900299264">"អានកម្រិត​នៃការរំខាន​របស់អ្នកបើកបរ។"</string>
+</resources>
diff --git a/experimental/service/res/values-kn/strings.xml b/experimental/service/res/values-kn/strings.xml
new file mode 100644
index 0000000..e70dc1b
--- /dev/null
+++ b/experimental/service/res/values-kn/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="car_permission_label_driver_distraction" msgid="4864958399001183048">"ಚಾಲಕನ ವ್ಯಾಕುಲತೆ"</string>
+    <string name="car_permission_desc_driver_distraction" msgid="2506435411900299264">"ಚಾಲಕನ ವ್ಯಾಕುಲತೆಯ ಮಟ್ಟವನ್ನು ಓದಿ."</string>
+</resources>
diff --git a/experimental/service/res/values-ko/strings.xml b/experimental/service/res/values-ko/strings.xml
new file mode 100644
index 0000000..8a239a6
--- /dev/null
+++ b/experimental/service/res/values-ko/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="car_permission_label_driver_distraction" msgid="4864958399001183048">"운전자 주의 분산"</string>
+    <string name="car_permission_desc_driver_distraction" msgid="2506435411900299264">"운전자의 주의 분산 수준을 읽습니다."</string>
+</resources>
diff --git a/experimental/service/res/values-ky/strings.xml b/experimental/service/res/values-ky/strings.xml
new file mode 100644
index 0000000..c73ad9f
--- /dev/null
+++ b/experimental/service/res/values-ky/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="car_permission_label_driver_distraction" msgid="4864958399001183048">"Айдоочуну алаксыткан колдонмолор"</string>
+    <string name="car_permission_desc_driver_distraction" msgid="2506435411900299264">"Айдоочуну алаксыткан колдонмолордун деңгээлин окуу."</string>
+</resources>
diff --git a/experimental/service/res/values-lo/strings.xml b/experimental/service/res/values-lo/strings.xml
new file mode 100644
index 0000000..c57f2b3
--- /dev/null
+++ b/experimental/service/res/values-lo/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="car_permission_label_driver_distraction" msgid="4864958399001183048">"ການເສຍສະມາທິຂອງຜູ້ຂັບ"</string>
+    <string name="car_permission_desc_driver_distraction" msgid="2506435411900299264">"ອ່ານລະດັບການເສຍສະມາທິຂອງຜູ້ຂັບ."</string>
+</resources>
diff --git a/experimental/service/res/values-lt/strings.xml b/experimental/service/res/values-lt/strings.xml
new file mode 100644
index 0000000..0dbc6b6
--- /dev/null
+++ b/experimental/service/res/values-lt/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="car_permission_label_driver_distraction" msgid="4864958399001183048">"Vairuotojo dėmesio atitraukimas"</string>
+    <string name="car_permission_desc_driver_distraction" msgid="2506435411900299264">"Skaityti vairuotojo dėmesio atitraukimo lygio informaciją."</string>
+</resources>
diff --git a/experimental/service/res/values-lv/strings.xml b/experimental/service/res/values-lv/strings.xml
new file mode 100644
index 0000000..3e7afe4
--- /dev/null
+++ b/experimental/service/res/values-lv/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="car_permission_label_driver_distraction" msgid="4864958399001183048">"Autovadītāja uzmanības novēršanas līmenis"</string>
+    <string name="car_permission_desc_driver_distraction" msgid="2506435411900299264">"Lasīt autovadītāja uzmanības novēršanas līmeni."</string>
+</resources>
diff --git a/experimental/service/res/values-mk/strings.xml b/experimental/service/res/values-mk/strings.xml
new file mode 100644
index 0000000..1577970
--- /dev/null
+++ b/experimental/service/res/values-mk/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="car_permission_label_driver_distraction" msgid="4864958399001183048">"Одвлекување на вниманието на возачот"</string>
+    <string name="car_permission_desc_driver_distraction" msgid="2506435411900299264">"Го читаат нивото на одвлекување на вниманието на возачот."</string>
+</resources>
diff --git a/experimental/service/res/values-ml/strings.xml b/experimental/service/res/values-ml/strings.xml
new file mode 100644
index 0000000..26a6bc5
--- /dev/null
+++ b/experimental/service/res/values-ml/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="car_permission_label_driver_distraction" msgid="4864958399001183048">"ഡ്രൈവറുടെ ശ്രദ്ധാ വ്യതിചലനം"</string>
+    <string name="car_permission_desc_driver_distraction" msgid="2506435411900299264">"ഡ്രൈവറുടെ ശ്രദ്ധ വ്യതിചലിച്ചതിന്റെ ലെവൽ റീഡ് ചെയ്യുക."</string>
+</resources>
diff --git a/experimental/service/res/values-mn/strings.xml b/experimental/service/res/values-mn/strings.xml
new file mode 100644
index 0000000..93277d4
--- /dev/null
+++ b/experimental/service/res/values-mn/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="car_permission_label_driver_distraction" msgid="4864958399001183048">"Жолоочийн анхаарал сарниулах зүйлс"</string>
+    <string name="car_permission_desc_driver_distraction" msgid="2506435411900299264">"Жолоочийн анхаарал сарниулах зүйлсийн түвшнийг уншина уу."</string>
+</resources>
diff --git a/experimental/service/res/values-mr/strings.xml b/experimental/service/res/values-mr/strings.xml
new file mode 100644
index 0000000..6477c68
--- /dev/null
+++ b/experimental/service/res/values-mr/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="car_permission_label_driver_distraction" msgid="4864958399001183048">"ड्रायव्हर डिस्ट्रॅक्शन"</string>
+    <string name="car_permission_desc_driver_distraction" msgid="2506435411900299264">"ड्रायव्हर डिस्ट्रॅक्शनची पातळी रीड करा."</string>
+</resources>
diff --git a/experimental/service/res/values-ms/strings.xml b/experimental/service/res/values-ms/strings.xml
new file mode 100644
index 0000000..005e721
--- /dev/null
+++ b/experimental/service/res/values-ms/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="car_permission_label_driver_distraction" msgid="4864958399001183048">"Gangguan Pemandu"</string>
+    <string name="car_permission_desc_driver_distraction" msgid="2506435411900299264">"Baca tahap gangguan pemandu."</string>
+</resources>
diff --git a/experimental/service/res/values-my/strings.xml b/experimental/service/res/values-my/strings.xml
new file mode 100644
index 0000000..6dcec79
--- /dev/null
+++ b/experimental/service/res/values-my/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="car_permission_label_driver_distraction" msgid="4864958399001183048">"ယာဉ်မောင်းသူ အနှောင့်အယှက်ဖြစ်မှု"</string>
+    <string name="car_permission_desc_driver_distraction" msgid="2506435411900299264">"ယာဉ်မောင်းသူ အနှောင့်အယှက်ဖြစ်မှုအဆင့်ကို ဖတ်ရန်။"</string>
+</resources>
diff --git a/experimental/service/res/values-nb/strings.xml b/experimental/service/res/values-nb/strings.xml
new file mode 100644
index 0000000..aa7f821
--- /dev/null
+++ b/experimental/service/res/values-nb/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="car_permission_label_driver_distraction" msgid="4864958399001183048">"Sjåførens distraksjonsnivå"</string>
+    <string name="car_permission_desc_driver_distraction" msgid="2506435411900299264">"Registrer sjåførens distraksjonsnivå."</string>
+</resources>
diff --git a/experimental/service/res/values-ne/strings.xml b/experimental/service/res/values-ne/strings.xml
new file mode 100644
index 0000000..dd3a974
--- /dev/null
+++ b/experimental/service/res/values-ne/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="car_permission_label_driver_distraction" msgid="4864958399001183048">"ड्राइभरको ध्यान भङ्ग गराउने कुरा"</string>
+    <string name="car_permission_desc_driver_distraction" msgid="2506435411900299264">"ड्राइभरको ध्यान भङ्ग गराउने कुराको स्तर पढ्नुहोस्।"</string>
+</resources>
diff --git a/experimental/service/res/values-nl/strings.xml b/experimental/service/res/values-nl/strings.xml
new file mode 100644
index 0000000..c72564f
--- /dev/null
+++ b/experimental/service/res/values-nl/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="car_permission_label_driver_distraction" msgid="4864958399001183048">"Afleidingsniveau van chauffeur"</string>
+    <string name="car_permission_desc_driver_distraction" msgid="2506435411900299264">"Afleidingsniveau van chauffeur lezen"</string>
+</resources>
diff --git a/experimental/service/res/values-or/strings.xml b/experimental/service/res/values-or/strings.xml
new file mode 100644
index 0000000..22e3577
--- /dev/null
+++ b/experimental/service/res/values-or/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="car_permission_label_driver_distraction" msgid="4864958399001183048">"ଡ୍ରାଇଭର୍ ଡିସଟ୍ରାକସନ୍"</string>
+    <string name="car_permission_desc_driver_distraction" msgid="2506435411900299264">"ଡ୍ରାଇଭର୍ ଡିସଟ୍ରାକସନ୍ ସ୍ତର ପଢ଼ନ୍ତୁ"</string>
+</resources>
diff --git a/experimental/service/res/values-pa/strings.xml b/experimental/service/res/values-pa/strings.xml
new file mode 100644
index 0000000..1774246
--- /dev/null
+++ b/experimental/service/res/values-pa/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="car_permission_label_driver_distraction" msgid="4864958399001183048">"ਡਰਾਈਵਰ ਦਾ ਧਿਆਨ ਹਟਣਾ"</string>
+    <string name="car_permission_desc_driver_distraction" msgid="2506435411900299264">"ਡਰਾਈਵਰ ਦੇ ਧਿਆਨ ਹਟਣ ਦੇ ਪੱਧਰ ਬਾਰੇ ਜਾਣੋ।"</string>
+</resources>
diff --git a/experimental/service/res/values-pl/strings.xml b/experimental/service/res/values-pl/strings.xml
new file mode 100644
index 0000000..7054259
--- /dev/null
+++ b/experimental/service/res/values-pl/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="car_permission_label_driver_distraction" msgid="4864958399001183048">"Rozproszenie uwagi kierowcy"</string>
+    <string name="car_permission_desc_driver_distraction" msgid="2506435411900299264">"Odczytywanie informacji o poziomie rozproszenia uwagi kierowcy."</string>
+</resources>
diff --git a/experimental/service/res/values-pt-rPT/strings.xml b/experimental/service/res/values-pt-rPT/strings.xml
new file mode 100644
index 0000000..e8714fc
--- /dev/null
+++ b/experimental/service/res/values-pt-rPT/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="car_permission_label_driver_distraction" msgid="4864958399001183048">"Distração do condutor"</string>
+    <string name="car_permission_desc_driver_distraction" msgid="2506435411900299264">"Avaliar o nível de distração do condutor"</string>
+</resources>
diff --git a/experimental/service/res/values-pt/strings.xml b/experimental/service/res/values-pt/strings.xml
new file mode 100644
index 0000000..024c7ad
--- /dev/null
+++ b/experimental/service/res/values-pt/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="car_permission_label_driver_distraction" msgid="4864958399001183048">"Distração do motorista"</string>
+    <string name="car_permission_desc_driver_distraction" msgid="2506435411900299264">"Ler o nível de distração do motorista."</string>
+</resources>
diff --git a/experimental/service/res/values-ro/strings.xml b/experimental/service/res/values-ro/strings.xml
new file mode 100644
index 0000000..3e96fb5
--- /dev/null
+++ b/experimental/service/res/values-ro/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="car_permission_label_driver_distraction" msgid="4864958399001183048">"Distragerea șoferului"</string>
+    <string name="car_permission_desc_driver_distraction" msgid="2506435411900299264">"Poate citi nivelul de distragere al șoferului."</string>
+</resources>
diff --git a/experimental/service/res/values-ru/strings.xml b/experimental/service/res/values-ru/strings.xml
new file mode 100644
index 0000000..883430d
--- /dev/null
+++ b/experimental/service/res/values-ru/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="car_permission_label_driver_distraction" msgid="4864958399001183048">"Отвлекающие факторы"</string>
+    <string name="car_permission_desc_driver_distraction" msgid="2506435411900299264">"Считывание уровня отвлекающих факторов."</string>
+</resources>
diff --git a/experimental/service/res/values-si/strings.xml b/experimental/service/res/values-si/strings.xml
new file mode 100644
index 0000000..24dde2e
--- /dev/null
+++ b/experimental/service/res/values-si/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="car_permission_label_driver_distraction" msgid="4864958399001183048">"රියදුරු වික්ෂිප්ත වීම"</string>
+    <string name="car_permission_desc_driver_distraction" msgid="2506435411900299264">"රියදුරු වික්ෂිප්ත මට්ටම කියවන්න."</string>
+</resources>
diff --git a/experimental/service/res/values-sk/strings.xml b/experimental/service/res/values-sk/strings.xml
new file mode 100644
index 0000000..fc57d39
--- /dev/null
+++ b/experimental/service/res/values-sk/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="car_permission_label_driver_distraction" msgid="4864958399001183048">"Vyrušenie pozornosti vodiča"</string>
+    <string name="car_permission_desc_driver_distraction" msgid="2506435411900299264">"Čítanie miery vyrušenia pozornosti vodiča."</string>
+</resources>
diff --git a/experimental/service/res/values-sl/strings.xml b/experimental/service/res/values-sl/strings.xml
new file mode 100644
index 0000000..96174f8
--- /dev/null
+++ b/experimental/service/res/values-sl/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="car_permission_label_driver_distraction" msgid="4864958399001183048">"Nepozornost voznika"</string>
+    <string name="car_permission_desc_driver_distraction" msgid="2506435411900299264">"Branje stopnje nepozornosti voznika."</string>
+</resources>
diff --git a/experimental/service/res/values-sq/strings.xml b/experimental/service/res/values-sq/strings.xml
new file mode 100644
index 0000000..ecbc905
--- /dev/null
+++ b/experimental/service/res/values-sq/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="car_permission_label_driver_distraction" msgid="4864958399001183048">"Shpërqendrimi i drejtuesit të makinës"</string>
+    <string name="car_permission_desc_driver_distraction" msgid="2506435411900299264">"Lexo nivelin e shpërqendrimit të drejtuesit të makinës."</string>
+</resources>
diff --git a/experimental/service/res/values-sr/strings.xml b/experimental/service/res/values-sr/strings.xml
new file mode 100644
index 0000000..86ab2f7
--- /dev/null
+++ b/experimental/service/res/values-sr/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="car_permission_label_driver_distraction" msgid="4864958399001183048">"Ометање возача"</string>
+    <string name="car_permission_desc_driver_distraction" msgid="2506435411900299264">"Очитавају ниво ометања возача."</string>
+</resources>
diff --git a/experimental/service/res/values-sv/strings.xml b/experimental/service/res/values-sv/strings.xml
new file mode 100644
index 0000000..0404356
--- /dev/null
+++ b/experimental/service/res/values-sv/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="car_permission_label_driver_distraction" msgid="4864958399001183048">"Förardistraktion"</string>
+    <string name="car_permission_desc_driver_distraction" msgid="2506435411900299264">"Läsa graden av förardistraktion."</string>
+</resources>
diff --git a/experimental/service/res/values-sw/strings.xml b/experimental/service/res/values-sw/strings.xml
new file mode 100644
index 0000000..eb1839e
--- /dev/null
+++ b/experimental/service/res/values-sw/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="car_permission_label_driver_distraction" msgid="4864958399001183048">"Usumbufu kwa Dereva"</string>
+    <string name="car_permission_desc_driver_distraction" msgid="2506435411900299264">"Soma kiwango cha usumbufu kwa dereva."</string>
+</resources>
diff --git a/experimental/service/res/values-ta/strings.xml b/experimental/service/res/values-ta/strings.xml
new file mode 100644
index 0000000..5109d48
--- /dev/null
+++ b/experimental/service/res/values-ta/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="car_permission_label_driver_distraction" msgid="4864958399001183048">"ஓட்டுநரின் கவனச்சிதறல்"</string>
+    <string name="car_permission_desc_driver_distraction" msgid="2506435411900299264">"ஓட்டுநரின் கவனச்சிதறல் நிலையைக் கண்டறி."</string>
+</resources>
diff --git a/experimental/service/res/values-te/strings.xml b/experimental/service/res/values-te/strings.xml
new file mode 100644
index 0000000..c7576de
--- /dev/null
+++ b/experimental/service/res/values-te/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="car_permission_label_driver_distraction" msgid="4864958399001183048">"డ్రైవర్ పరధ్యానం"</string>
+    <string name="car_permission_desc_driver_distraction" msgid="2506435411900299264">"డ్రైవర్ పరధ్యాన స్థాయిని చదువు."</string>
+</resources>
diff --git a/experimental/service/res/values-th/strings.xml b/experimental/service/res/values-th/strings.xml
new file mode 100644
index 0000000..4459040
--- /dev/null
+++ b/experimental/service/res/values-th/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="car_permission_label_driver_distraction" msgid="4864958399001183048">"การเสียสมาธิของผู้ขับ"</string>
+    <string name="car_permission_desc_driver_distraction" msgid="2506435411900299264">"อ่านระดับการเสียสมาธิของผู้ขับ"</string>
+</resources>
diff --git a/experimental/service/res/values-tl/strings.xml b/experimental/service/res/values-tl/strings.xml
new file mode 100644
index 0000000..0c77534
--- /dev/null
+++ b/experimental/service/res/values-tl/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="car_permission_label_driver_distraction" msgid="4864958399001183048">"Gambala sa Nagmamaneho"</string>
+    <string name="car_permission_desc_driver_distraction" msgid="2506435411900299264">"Basahin ang antas ng gambala sa nagmamaneho."</string>
+</resources>
diff --git a/experimental/service/res/values-tr/strings.xml b/experimental/service/res/values-tr/strings.xml
new file mode 100644
index 0000000..5f8ef5d
--- /dev/null
+++ b/experimental/service/res/values-tr/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="car_permission_label_driver_distraction" msgid="4864958399001183048">"Sürücünün Dikkatinin Dağılması"</string>
+    <string name="car_permission_desc_driver_distraction" msgid="2506435411900299264">"Sürücünün dikkatinin dağılma seviyesini oku."</string>
+</resources>
diff --git a/experimental/service/res/values-uk/strings.xml b/experimental/service/res/values-uk/strings.xml
new file mode 100644
index 0000000..9f2fc5f
--- /dev/null
+++ b/experimental/service/res/values-uk/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="car_permission_label_driver_distraction" msgid="4864958399001183048">"Відволікання водія"</string>
+    <string name="car_permission_desc_driver_distraction" msgid="2506435411900299264">"Переглядати рівень відволікання водія."</string>
+</resources>
diff --git a/experimental/service/res/values-ur/strings.xml b/experimental/service/res/values-ur/strings.xml
new file mode 100644
index 0000000..82805cc
--- /dev/null
+++ b/experimental/service/res/values-ur/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="car_permission_label_driver_distraction" msgid="4864958399001183048">"ڈرائیور کا ذہنی انتشار"</string>
+    <string name="car_permission_desc_driver_distraction" msgid="2506435411900299264">"ڈرائیور کے ذہنی انتشار کا لیول پڑھیں۔"</string>
+</resources>
diff --git a/experimental/service/res/values-uz/strings.xml b/experimental/service/res/values-uz/strings.xml
new file mode 100644
index 0000000..4a92473
--- /dev/null
+++ b/experimental/service/res/values-uz/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="car_permission_label_driver_distraction" msgid="4864958399001183048">"Haydovchi diqqatini olish"</string>
+    <string name="car_permission_desc_driver_distraction" msgid="2506435411900299264">"Haydovchi diqqatini olish darajasi haqida batafsil."</string>
+</resources>
diff --git a/experimental/service/res/values-vi/strings.xml b/experimental/service/res/values-vi/strings.xml
new file mode 100644
index 0000000..2ee4c33
--- /dev/null
+++ b/experimental/service/res/values-vi/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="car_permission_label_driver_distraction" msgid="4864958399001183048">"Mức mất tập trung của người lái xe"</string>
+    <string name="car_permission_desc_driver_distraction" msgid="2506435411900299264">"Đọc thông tin về mức mất tập trung của người lái xe."</string>
+</resources>
diff --git a/experimental/service/res/values-zh-rCN/strings.xml b/experimental/service/res/values-zh-rCN/strings.xml
new file mode 100644
index 0000000..66fbb35
--- /dev/null
+++ b/experimental/service/res/values-zh-rCN/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="car_permission_label_driver_distraction" msgid="4864958399001183048">"驾驶员分散注意力"</string>
+    <string name="car_permission_desc_driver_distraction" msgid="2506435411900299264">"读取驾驶员分散注意力等级。"</string>
+</resources>
diff --git a/experimental/service/res/values-zh-rHK/strings.xml b/experimental/service/res/values-zh-rHK/strings.xml
new file mode 100644
index 0000000..3974389
--- /dev/null
+++ b/experimental/service/res/values-zh-rHK/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="car_permission_label_driver_distraction" msgid="4864958399001183048">"司機分心程度"</string>
+    <string name="car_permission_desc_driver_distraction" msgid="2506435411900299264">"讀取司機分心程度。"</string>
+</resources>
diff --git a/experimental/service/res/values-zh-rTW/strings.xml b/experimental/service/res/values-zh-rTW/strings.xml
new file mode 100644
index 0000000..ac74c8a
--- /dev/null
+++ b/experimental/service/res/values-zh-rTW/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="car_permission_label_driver_distraction" msgid="4864958399001183048">"駕駛人分心等級"</string>
+    <string name="car_permission_desc_driver_distraction" msgid="2506435411900299264">"讀取駕駛人分心等級。"</string>
+</resources>
diff --git a/experimental/service/res/values-zu/strings.xml b/experimental/service/res/values-zu/strings.xml
new file mode 100644
index 0000000..0489163
--- /dev/null
+++ b/experimental/service/res/values-zu/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="car_permission_label_driver_distraction" msgid="4864958399001183048">"Ukuphazamiseka komshayeli"</string>
+    <string name="car_permission_desc_driver_distraction" msgid="2506435411900299264">"Funda ileveli yokuphazamisa yomshayeli."</string>
+</resources>
diff --git a/experimental/service/res/values/config.xml b/experimental/service/res/values/config.xml
new file mode 100644
index 0000000..2a8db9b
--- /dev/null
+++ b/experimental/service/res/values/config.xml
@@ -0,0 +1,57 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<!-- Resources to configure experimental car service based on each OEM's preference. -->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+
+    <!-- The preferred driver awareness suppliers for dispatching the current driver awareness
+         level to DriverDistractionExperimentalFeatureService. Each component must be service that
+         extends DriverAwarenessSupplierService. The ordering of this list matters - items should
+         be ordered from lowest to highest priority. That is to say, the first index is the lowest
+         priority supplier and the index of items after that are higher. Data from higher priority
+         suppliers is always preferred unless that data is stale. -->
+    <string-array translatable="false" name="preferredDriverAwarenessSuppliers">
+        <item>
+            com.android.experimentalcar/.SampleExternalDriverAwarenessSupplier
+        </item>
+        <item>
+            com.android.experimentalcar/.GazeDriverAwarenessSupplier
+        </item>
+    </string-array>
+
+    <!-- The maximum, and default, number of permits a driver has. Each unthrottled tap will
+         decrement the current permit count, down to a minimum value of 0. Permits will meanwhile be
+         refreshed at a rate of 1 permit every
+         @integer/driverAwarenessTouchModelPermitRefreshIntervalMs milliseconds, up to this maximum
+         value. -->
+    <integer name="driverAwarenessTouchModelMaxPermits">6</integer>
+
+    <!-- Number of milliseconds for the interval that permits should be refreshed. -->
+    <integer name="driverAwarenessTouchModelPermitRefreshIntervalMs">2000</integer>
+
+    <!-- Number of milliseconds that taps should be ignored following a first tap. -->
+    <integer name="driverAwarenessTouchModelThrottleMs">600</integer>
+
+    <!-- Initial value for the gaze based attention buffer [0, 1]. -->
+    <item name="driverAwarenessGazeModelInitialValue" format="float" type="fraction">1</item>
+
+    <!-- Rate at which the attention buffer decreases when the driver is looking off-road, in attention units/second-->
+    <item name="driverAwarenessGazeModelDecayRate" format="float" type="fraction">0.167</item>
+
+     <!-- Rate at which the attention buffer increases when the driver is looking on-road, in attention units/second-->
+    <item name="driverAwarenessGazeModelGrowthRate" format="float" type="fraction">0.167</item>
+
+</resources>
diff --git a/experimental/service/res/values/strings.xml b/experimental/service/res/values/strings.xml
new file mode 100644
index 0000000..97231b4
--- /dev/null
+++ b/experimental/service/res/values/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_title" translatable="false">Experimental Car service</string>
+    <!-- Permission text: apps can access the driver's distraction level [CHAR LIMIT=NONE] -->
+    <string name="car_permission_label_driver_distraction">read driver\u2019s distraction level</string>
+    <!-- Permission text: apps can access the driver's distraction level [CHAR LIMIT=NONE] -->
+    <string name="car_permission_desc_driver_distraction">Read driver distraction level.</string>
+
+</resources>
diff --git a/experimental/service/src/com/android/experimentalcar/DriverDistractionExperimentalFeatureService.java b/experimental/service/src/com/android/experimentalcar/DriverDistractionExperimentalFeatureService.java
new file mode 100644
index 0000000..e6848a1
--- /dev/null
+++ b/experimental/service/src/com/android/experimentalcar/DriverDistractionExperimentalFeatureService.java
@@ -0,0 +1,787 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.experimentalcar;
+
+import android.annotation.FloatRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.car.Car;
+import android.car.VehiclePropertyIds;
+import android.car.experimental.DriverAwarenessEvent;
+import android.car.experimental.DriverAwarenessSupplierConfig;
+import android.car.experimental.DriverAwarenessSupplierService;
+import android.car.experimental.DriverDistractionChangeEvent;
+import android.car.experimental.ExperimentalCar;
+import android.car.experimental.IDriverAwarenessSupplier;
+import android.car.experimental.IDriverAwarenessSupplierCallback;
+import android.car.experimental.IDriverDistractionChangeListener;
+import android.car.experimental.IDriverDistractionManager;
+import android.car.hardware.CarPropertyValue;
+import android.car.hardware.property.CarPropertyEvent;
+import android.car.hardware.property.CarPropertyManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.IBinder;
+import android.os.RemoteCallbackList;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.util.Log;
+import android.util.Pair;
+
+import com.android.car.CarServiceBase;
+import com.android.car.Utils;
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.io.PrintWriter;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.TimerTask;
+
+/**
+ * Driver Distraction Service for using the driver's awareness, the required awareness of the
+ * driving environment to expose APIs for the driver's current distraction level.
+ *
+ * <p>Allows the registration of multiple {@link IDriverAwarenessSupplier} so that higher accuracy
+ * signals can be used when possible, with a fallback to less accurate signals. The {@link
+ * TouchDriverAwarenessSupplier} is always set to the fallback implementation - it is configured
+ * to send change-events, so its data will not become stale.
+ */
+public final class DriverDistractionExperimentalFeatureService extends
+        IDriverDistractionManager.Stub implements CarServiceBase {
+
+    private static final String TAG = "CAR.DriverDistractionService";
+
+    private static final float DEFAULT_AWARENESS_VALUE_FOR_LOG = 1.0f;
+    private static final float MOVING_REQUIRED_AWARENESS = 1.0f;
+    private static final float STATIONARY_REQUIRED_AWARENESS = 0.0f;
+    private static final int MAX_EVENT_LOG_COUNT = 50;
+    private static final int PROPERTY_UPDATE_RATE_HZ = 5;
+    @VisibleForTesting
+    static final float DEFAULT_AWARENESS_PERCENTAGE = 1.0f;
+
+    private final HandlerThread mClientDispatchHandlerThread;
+    private final Handler mClientDispatchHandler;
+
+    private final Object mLock = new Object();
+
+    @GuardedBy("mLock")
+    private final ArrayDeque<Utils.TransitionLog> mTransitionLogs = new ArrayDeque<>();
+
+    /**
+     * All the active service connections.
+     */
+    @GuardedBy("mLock")
+    private final List<ServiceConnection> mServiceConnections = new ArrayList<>();
+
+    /**
+     * The binder for each supplier.
+     */
+    @GuardedBy("mLock")
+    private final Map<ComponentName, IDriverAwarenessSupplier> mSupplierBinders = new HashMap<>();
+
+    /**
+     * The configuration for each supplier.
+     */
+    @GuardedBy("mLock")
+    private final Map<IDriverAwarenessSupplier, DriverAwarenessSupplierConfig> mSupplierConfigs =
+            new HashMap<>();
+
+    /**
+     * List of driver awareness suppliers that can be used to understand the current driver
+     * awareness level. Ordered from highest to lowest priority.
+     */
+    @GuardedBy("mLock")
+    private final List<IDriverAwarenessSupplier> mPrioritizedDriverAwarenessSuppliers =
+            new ArrayList<>();
+
+    /**
+     * Helper map for looking up the priority rank of a supplier by name. A higher integer value
+     * represents a higher priority.
+     */
+    @GuardedBy("mLock")
+    private final Map<IDriverAwarenessSupplier, Integer> mDriverAwarenessSupplierPriorities =
+            new HashMap<>();
+
+    /**
+     * List of clients listening to UX restriction events.
+     */
+    private final RemoteCallbackList<IDriverDistractionChangeListener> mDistractionClients =
+            new RemoteCallbackList<>();
+
+    /**
+     * Comparator used to sort {@link #mDriverAwarenessSupplierPriorities}.
+     */
+    private final Comparator<IDriverAwarenessSupplier> mPrioritizedSuppliersComparator =
+            (left, right) -> {
+                int leftPri = mDriverAwarenessSupplierPriorities.get(left);
+                int rightPri = mDriverAwarenessSupplierPriorities.get(right);
+                // sort descending
+                return rightPri - leftPri;
+            };
+
+    /**
+     * Keep track of the most recent awareness event for each supplier for use when the data from
+     * higher priority suppliers becomes stale. This is necessary in order to seamlessly handle
+     * fallback scenarios when data from preferred providers becomes stale.
+     */
+    @GuardedBy("mLock")
+    private final Map<IDriverAwarenessSupplier, DriverAwarenessEventWrapper>
+            mCurrentAwarenessEventsMap =
+            new HashMap<>();
+
+    /**
+     * The awareness event that is currently being used to determine the driver awareness level.
+     *
+     * <p>This is null until it is set by the first awareness supplier to send an event
+     */
+    @GuardedBy("mLock")
+    @Nullable
+    private DriverAwarenessEventWrapper mCurrentDriverAwareness;
+
+    /**
+     * Timer to alert when the current driver awareness event has become stale.
+     */
+    @GuardedBy("mLock")
+    private ITimer mExpiredDriverAwarenessTimer;
+
+    /**
+     * The current, non-stale, driver distraction event. Defaults to 100% awareness.
+     */
+    @GuardedBy("mLock")
+    private DriverDistractionChangeEvent mCurrentDistractionEvent;
+
+    /**
+     * The required driver awareness based on the current driving environment, where 1.0 means that
+     * full awareness is required and 0.0 means than no awareness is required.
+     */
+    @FloatRange(from = 0.0f, to = 1.0f)
+    @GuardedBy("mLock")
+    private float mRequiredAwareness = STATIONARY_REQUIRED_AWARENESS;
+
+    @GuardedBy("mLock")
+    private Car mCar;
+
+    @GuardedBy("mLock")
+    private CarPropertyManager mPropertyManager;
+
+    private final Context mContext;
+    private final ITimeSource mTimeSource;
+
+    /**
+     * Create an instance of {@link DriverDistractionExperimentalFeatureService}.
+     *
+     * @param context    the context
+     * @param timeSource the source that provides the current time
+     * @param timer      the timer used for scheduling
+     */
+    DriverDistractionExperimentalFeatureService(
+            Context context,
+            ITimeSource timeSource,
+            ITimer timer) {
+        mContext = context;
+        mTimeSource = timeSource;
+        mExpiredDriverAwarenessTimer = timer;
+        mCurrentDistractionEvent = new DriverDistractionChangeEvent.Builder()
+                .setElapsedRealtimeTimestamp(mTimeSource.elapsedRealtime())
+                .setAwarenessPercentage(DEFAULT_AWARENESS_PERCENTAGE)
+                .build();
+        mClientDispatchHandlerThread = new HandlerThread(TAG);
+        mClientDispatchHandlerThread.start();
+        mClientDispatchHandler = new Handler(mClientDispatchHandlerThread.getLooper());
+    }
+
+    @Override
+    public void init() {
+        // The touch supplier is an internal implementation, so it can be started initiated by its
+        // constructor, unlike other suppliers
+        ComponentName touchComponent = new ComponentName(mContext,
+                TouchDriverAwarenessSupplier.class);
+        TouchDriverAwarenessSupplier touchSupplier = new TouchDriverAwarenessSupplier(mContext,
+                new DriverAwarenessSupplierCallback(touchComponent));
+        addDriverAwarenessSupplier(touchComponent, touchSupplier, /* priority= */ 0);
+        touchSupplier.onReady();
+
+        String[] preferredDriverAwarenessSuppliers = mContext.getResources().getStringArray(
+                R.array.preferredDriverAwarenessSuppliers);
+        for (int i = 0; i < preferredDriverAwarenessSuppliers.length; i++) {
+            String supplierStringName = preferredDriverAwarenessSuppliers[i];
+            ComponentName externalComponent = ComponentName.unflattenFromString(supplierStringName);
+            // the touch supplier has priority 0 and preferred suppliers are higher based on order
+            int priority = i + 1;
+            bindDriverAwarenessSupplierService(externalComponent, priority);
+        }
+
+        synchronized (mLock) {
+            mCar = Car.createCar(mContext);
+            if (mCar != null) {
+                mPropertyManager = (CarPropertyManager) mCar.getCarManager(Car.PROPERTY_SERVICE);
+            } else {
+                Log.e(TAG, "Unable to connect to car in init");
+            }
+        }
+
+        if (mPropertyManager != null) {
+            mPropertyManager.registerCallback(mSpeedPropertyEventCallback,
+                    VehiclePropertyIds.PERF_VEHICLE_SPEED,
+                    PROPERTY_UPDATE_RATE_HZ);
+        } else {
+            Log.e(TAG, "Unable to get car property service.");
+        }
+    }
+
+    @Override
+    public void release() {
+        logd("release");
+        mDistractionClients.kill();
+        synchronized (mLock) {
+            for (ServiceConnection serviceConnection : mServiceConnections) {
+                mContext.unbindService(serviceConnection);
+            }
+            if (mPropertyManager != null) {
+                mPropertyManager.unregisterCallback(mSpeedPropertyEventCallback);
+            }
+            if (mCar != null) {
+                mCar.disconnect();
+            }
+        }
+    }
+
+    @Override
+    public void dump(PrintWriter writer) {
+        writer.println("*DriverDistractionExperimentalFeatureService*");
+        mDistractionClients.dump(writer, "Distraction Clients ");
+        writer.println("Prioritized Driver Awareness Suppliers (highest to lowest priority):");
+        synchronized (mLock) {
+            for (int i = 0; i < mPrioritizedDriverAwarenessSuppliers.size(); i++) {
+                writer.println(
+                        String.format("  %d: %s", i, mPrioritizedDriverAwarenessSuppliers.get(
+                                i).getClass().getName()));
+            }
+            writer.println("Current Driver Awareness:");
+            writer.println("  Value: "
+                    + (mCurrentDriverAwareness == null ? "unknown"
+                    : mCurrentDriverAwareness.mAwarenessEvent.getAwarenessValue()));
+            writer.println("  Supplier: " + (mCurrentDriverAwareness == null ? "unknown"
+                    : mCurrentDriverAwareness.mSupplier.getClass().getSimpleName()));
+            writer.println("  Timestamp (ms since boot): "
+                    + (mCurrentDriverAwareness == null ? "unknown"
+                    : mCurrentDriverAwareness.mAwarenessEvent.getTimeStamp()));
+            writer.println("Current Required Awareness: " + mRequiredAwareness);
+            writer.println("Last Distraction Event:");
+            writer.println("  Value: "
+                    + (mCurrentDistractionEvent == null ? "unknown"
+                    : mCurrentDistractionEvent.getAwarenessPercentage()));
+            writer.println("  Timestamp (ms since boot): "
+                    + (mCurrentDistractionEvent == null ? "unknown"
+                    : mCurrentDistractionEvent.getElapsedRealtimeTimestamp()));
+            writer.println("Change log:");
+            for (Utils.TransitionLog log : mTransitionLogs) {
+                writer.println(log);
+            }
+        }
+    }
+
+    /**
+     * Bind to a {@link DriverAwarenessSupplierService} by its component name.
+     *
+     * @param componentName the name of the {@link DriverAwarenessSupplierService} to bind to.
+     * @param priority      the priority rank of this supplier
+     */
+    private void bindDriverAwarenessSupplierService(ComponentName componentName, int priority) {
+        Intent intent = new Intent();
+        intent.setComponent(componentName);
+        ServiceConnection connection = new DriverAwarenessServiceConnection(priority);
+        synchronized (mLock) {
+            mServiceConnections.add(connection);
+        }
+        if (!mContext.bindServiceAsUser(intent, connection,
+                Context.BIND_AUTO_CREATE | Context.BIND_IMPORTANT, UserHandle.SYSTEM)) {
+            Log.e(TAG, "Unable to bind with intent: " + intent);
+            // TODO(b/146471650) attempt to rebind
+        }
+    }
+
+    @VisibleForTesting
+    void handleDriverAwarenessEvent(DriverAwarenessEventWrapper awarenessEventWrapper) {
+        synchronized (mLock) {
+            handleDriverAwarenessEventLocked(awarenessEventWrapper);
+        }
+    }
+
+    /**
+     * Handle the driver awareness event by:
+     * <ul>
+     *     <li>Cache the driver awareness event for its supplier</li>
+     *     <li>Update the current awareness value</li>
+     *     <li>Register to refresh the awareness value again when the new current expires</li>
+     * </ul>
+     *
+     * @param awarenessEventWrapper the driver awareness event that has occurred
+     */
+    @GuardedBy("mLock")
+    private void handleDriverAwarenessEventLocked(
+            DriverAwarenessEventWrapper awarenessEventWrapper) {
+        // update the current awareness event for the supplier, checking that it is the newest event
+        IDriverAwarenessSupplier supplier = awarenessEventWrapper.mSupplier;
+        long timestamp = awarenessEventWrapper.mAwarenessEvent.getTimeStamp();
+        if (!mCurrentAwarenessEventsMap.containsKey(supplier)
+                || mCurrentAwarenessEventsMap.get(supplier).mAwarenessEvent.getTimeStamp()
+                < timestamp) {
+            mCurrentAwarenessEventsMap.put(awarenessEventWrapper.mSupplier, awarenessEventWrapper);
+        }
+
+        int oldSupplierPriority = mDriverAwarenessSupplierPriorities.get(supplier);
+        float oldAwarenessValue = DEFAULT_AWARENESS_VALUE_FOR_LOG;
+        if (mCurrentDriverAwareness != null) {
+            oldAwarenessValue = mCurrentDriverAwareness.mAwarenessEvent.getAwarenessValue();
+        }
+
+        updateCurrentAwarenessValueLocked();
+
+        int newSupplierPriority = mDriverAwarenessSupplierPriorities.get(
+                mCurrentDriverAwareness.mSupplier);
+        if (mSupplierConfigs.get(mCurrentDriverAwareness.mSupplier).getMaxStalenessMillis()
+                != DriverAwarenessSupplierService.NO_STALENESS
+                && newSupplierPriority >= oldSupplierPriority) {
+            // only reschedule an expiration if this is for a supplier that is the same or higher
+            // priority than the old value. If there is a higher priority supplier with non-stale
+            // data, then mCurrentDriverAwareness won't change even though we received a new event.
+            scheduleExpirationTimerLocked();
+        }
+
+        if (oldAwarenessValue != mCurrentDriverAwareness.mAwarenessEvent.getAwarenessValue()) {
+            logd("Driver awareness updated: "
+                    + mCurrentDriverAwareness.mAwarenessEvent.getAwarenessValue());
+            addTransitionLogLocked(oldAwarenessValue,
+                    awarenessEventWrapper.mAwarenessEvent.getAwarenessValue(),
+                    "Driver awareness updated by "
+                            + awarenessEventWrapper.mSupplier.getClass().getSimpleName());
+        }
+
+        updateCurrentDistractionEventLocked();
+    }
+
+    /**
+     * Get the current awareness value.
+     */
+    @VisibleForTesting
+    DriverAwarenessEventWrapper getCurrentDriverAwareness() {
+        return mCurrentDriverAwareness;
+    }
+
+    /**
+     * Set the drier awareness suppliers. Allows circumventing the {@link #init()} logic.
+     */
+    @VisibleForTesting
+    void setDriverAwarenessSuppliers(
+            List<Pair<IDriverAwarenessSupplier, DriverAwarenessSupplierConfig>> suppliers) {
+        mPrioritizedDriverAwarenessSuppliers.clear();
+        mDriverAwarenessSupplierPriorities.clear();
+        for (int i = 0; i < suppliers.size(); i++) {
+            Pair<IDriverAwarenessSupplier, DriverAwarenessSupplierConfig> pair = suppliers.get(i);
+            mSupplierConfigs.put(pair.first, pair.second);
+            mDriverAwarenessSupplierPriorities.put(pair.first, i);
+            mPrioritizedDriverAwarenessSuppliers.add(pair.first);
+        }
+        mPrioritizedDriverAwarenessSuppliers.sort(mPrioritizedSuppliersComparator);
+    }
+
+    /**
+     * {@link CarPropertyEvent} listener registered with the {@link CarPropertyManager} for getting
+     * speed change notifications.
+     */
+    private final CarPropertyManager.CarPropertyEventCallback mSpeedPropertyEventCallback =
+            new CarPropertyManager.CarPropertyEventCallback() {
+                @Override
+                public void onChangeEvent(CarPropertyValue value) {
+                    synchronized (mLock) {
+                        handleSpeedEventLocked(value);
+                    }
+                }
+
+                @Override
+                public void onErrorEvent(int propId, int zone) {
+                    Log.e(TAG, "Error in callback for vehicle speed");
+                }
+            };
+
+
+    @VisibleForTesting
+    @GuardedBy("mLock")
+    void handleSpeedEventLocked(@NonNull CarPropertyValue value) {
+        if (value.getPropertyId() != VehiclePropertyIds.PERF_VEHICLE_SPEED) {
+            Log.e(TAG, "Unexpected property id: " + value.getPropertyId());
+            return;
+        }
+
+        float oldValue = mRequiredAwareness;
+        if ((Float) value.getValue() > 0) {
+            mRequiredAwareness = MOVING_REQUIRED_AWARENESS;
+        } else {
+            mRequiredAwareness = STATIONARY_REQUIRED_AWARENESS;
+        }
+
+        if (Float.compare(oldValue, mRequiredAwareness) != 0) {
+            logd("Required awareness updated: " + mRequiredAwareness);
+            addTransitionLogLocked(oldValue, mRequiredAwareness, "Required awareness");
+            updateCurrentDistractionEventLocked();
+        }
+    }
+
+    @GuardedBy("mLock")
+    private void updateCurrentDistractionEventLocked() {
+        if (mCurrentDriverAwareness == null) {
+            logd("Driver awareness level is not yet known");
+            return;
+        }
+        float awarenessPercentage;
+        if (mRequiredAwareness == 0) {
+            // avoid divide by 0 error - awareness percentage should be 100% when required
+            // awareness is 0
+            awarenessPercentage = 1.0f;
+        } else {
+            // Cap awareness percentage at 100%
+            awarenessPercentage = Math.min(
+                    mCurrentDriverAwareness.mAwarenessEvent.getAwarenessValue()
+                            / mRequiredAwareness, 1.0f);
+        }
+        if (Float.compare(mCurrentDistractionEvent.getAwarenessPercentage(), awarenessPercentage)
+                == 0) {
+            // no need to dispatch unless there's a change
+            return;
+        }
+
+        addTransitionLogLocked(mCurrentDistractionEvent.getAwarenessPercentage(),
+                awarenessPercentage, "Awareness percentage");
+
+        mCurrentDistractionEvent = new DriverDistractionChangeEvent.Builder()
+                .setElapsedRealtimeTimestamp(mTimeSource.elapsedRealtime())
+                .setAwarenessPercentage(awarenessPercentage)
+                .build();
+
+        // TODO(b/148231321) throttle to emit events at most once every 50ms
+        DriverDistractionChangeEvent changeEvent = mCurrentDistractionEvent;
+        mClientDispatchHandler.post(
+                () -> dispatchCurrentDistractionEventToClientsLocked(changeEvent));
+    }
+
+    @GuardedBy("mLock")
+    private void dispatchCurrentDistractionEventToClientsLocked(
+            DriverDistractionChangeEvent changeEvent) {
+        logd("Dispatching event to clients: " + changeEvent);
+        int numClients = mDistractionClients.beginBroadcast();
+        for (int i = 0; i < numClients; i++) {
+            IDriverDistractionChangeListener callback = mDistractionClients.getBroadcastItem(i);
+            try {
+                callback.onDriverDistractionChange(changeEvent);
+            } catch (RemoteException ignores) {
+                // ignore
+            }
+        }
+        mDistractionClients.finishBroadcast();
+    }
+
+    /**
+     * Internally register the supplier with the specified priority.
+     */
+    private void addDriverAwarenessSupplier(
+            ComponentName componentName,
+            IDriverAwarenessSupplier awarenessSupplier,
+            int priority) {
+        synchronized (mLock) {
+            mSupplierBinders.put(componentName, awarenessSupplier);
+            mDriverAwarenessSupplierPriorities.put(awarenessSupplier, priority);
+            mPrioritizedDriverAwarenessSuppliers.add(awarenessSupplier);
+            mPrioritizedDriverAwarenessSuppliers.sort(mPrioritizedSuppliersComparator);
+        }
+    }
+
+    /**
+     * Remove references to a supplier.
+     */
+    private void removeDriverAwarenessSupplier(ComponentName componentName) {
+        synchronized (mLock) {
+            IDriverAwarenessSupplier supplier = mSupplierBinders.get(componentName);
+            mSupplierBinders.remove(componentName);
+            mDriverAwarenessSupplierPriorities.remove(supplier);
+            mPrioritizedDriverAwarenessSuppliers.remove(supplier);
+        }
+    }
+
+    /**
+     * Update {@link #mCurrentDriverAwareness} based on the current driver awareness events for each
+     * supplier.
+     */
+    @GuardedBy("mLock")
+    private void updateCurrentAwarenessValueLocked() {
+        for (IDriverAwarenessSupplier supplier : mPrioritizedDriverAwarenessSuppliers) {
+            long supplierMaxStaleness = mSupplierConfigs.get(supplier).getMaxStalenessMillis();
+            DriverAwarenessEventWrapper eventForSupplier = mCurrentAwarenessEventsMap.get(supplier);
+            if (eventForSupplier == null) {
+                continue;
+            }
+            if (supplierMaxStaleness == DriverAwarenessSupplierService.NO_STALENESS) {
+                // this supplier can't be stale, so use its information
+                mCurrentDriverAwareness = eventForSupplier;
+                return;
+            }
+
+            long oldestFreshTimestamp = mTimeSource.elapsedRealtime() - supplierMaxStaleness;
+            if (eventForSupplier.mAwarenessEvent.getTimeStamp() > oldestFreshTimestamp) {
+                // value is still fresh, so use it
+                mCurrentDriverAwareness = eventForSupplier;
+                return;
+            }
+        }
+
+        if (mCurrentDriverAwareness == null) {
+            // There must always at least be a fallback supplier with NO_STALENESS configuration.
+            // Since we control this configuration, getting this exception represents a developer
+            // error in initialization.
+            throw new IllegalStateException(
+                    "Unable to determine the current driver awareness value");
+        }
+    }
+
+    /**
+     * Sets a timer to update the refresh the awareness value once the current value has become
+     * stale.
+     */
+    @GuardedBy("mLock")
+    private void scheduleExpirationTimerLocked() {
+        // reschedule the current awareness expiration task
+        mExpiredDriverAwarenessTimer.reset();
+        long delay = mCurrentDriverAwareness.mAwarenessEvent.getTimeStamp()
+                - mTimeSource.elapsedRealtime()
+                + mCurrentDriverAwareness.mMaxStaleness;
+        if (delay < 0) {
+            // somehow the event is already stale
+            synchronized (mLock) {
+                updateCurrentAwarenessValueLocked();
+            }
+            return;
+        }
+        mExpiredDriverAwarenessTimer.schedule(new TimerTask() {
+            @Override
+            public void run() {
+                logd("Driver awareness has become stale. Selecting new awareness level.");
+                synchronized (mLock) {
+                    updateCurrentAwarenessValueLocked();
+                    updateCurrentDistractionEventLocked();
+                }
+            }
+        }, delay);
+
+        logd(String.format(
+                "Current awareness value is stale after %sms and is scheduled to expire in %sms",
+                mCurrentDriverAwareness.mMaxStaleness, delay));
+    }
+
+    /**
+     * Add the state change to the transition log.
+     *
+     * @param oldValue the old value
+     * @param newValue the new value
+     * @param extra    name of the value being changed
+     */
+    @GuardedBy("mLock")
+    private void addTransitionLogLocked(float oldValue, float newValue, String extra) {
+        if (mTransitionLogs.size() >= MAX_EVENT_LOG_COUNT) {
+            mTransitionLogs.remove();
+        }
+
+        Utils.TransitionLog tLog = new Utils.TransitionLog(TAG, oldValue, newValue,
+                System.currentTimeMillis(), extra);
+        mTransitionLogs.add(tLog);
+    }
+
+    private static void logd(String message) {
+        if (Log.isLoggable(TAG, Log.DEBUG)) {
+            Log.d(TAG, message);
+        }
+    }
+
+    @Override
+    public DriverDistractionChangeEvent getLastDistractionEvent() throws RemoteException {
+        IExperimentalCarImpl.assertPermission(mContext,
+                ExperimentalCar.PERMISSION_READ_CAR_DRIVER_DISTRACTION);
+        synchronized (mLock) {
+            return mCurrentDistractionEvent;
+        }
+    }
+
+    @Override
+    public void addDriverDistractionChangeListener(IDriverDistractionChangeListener listener)
+            throws RemoteException {
+        IExperimentalCarImpl.assertPermission(mContext,
+                ExperimentalCar.PERMISSION_READ_CAR_DRIVER_DISTRACTION);
+        if (listener == null) {
+            throw new IllegalArgumentException("IDriverDistractionChangeListener is null");
+        }
+        mDistractionClients.register(listener);
+
+        DriverDistractionChangeEvent changeEvent = mCurrentDistractionEvent;
+        mClientDispatchHandler.post(() -> {
+            try {
+                listener.onDriverDistractionChange(changeEvent);
+            } catch (RemoteException ignores) {
+                // ignore
+            }
+        });
+    }
+
+
+    @Override
+    public void removeDriverDistractionChangeListener(IDriverDistractionChangeListener listener)
+            throws RemoteException {
+        IExperimentalCarImpl.assertPermission(mContext,
+                ExperimentalCar.PERMISSION_READ_CAR_DRIVER_DISTRACTION);
+        if (listener == null) {
+            Log.e(TAG, "unregisterUxRestrictionsChangeListener(): listener null");
+            throw new IllegalArgumentException("Listener is null");
+        }
+        mDistractionClients.unregister(listener);
+    }
+
+    /**
+     * The service connection between this distraction service and a {@link
+     * DriverAwarenessSupplierService}, communicated through {@link IDriverAwarenessSupplier}.
+     */
+    private class DriverAwarenessServiceConnection implements ServiceConnection {
+
+        final int mPriority;
+
+        /**
+         * Create an instance of {@link DriverAwarenessServiceConnection}.
+         *
+         * @param priority the priority of the {@link DriverAwarenessSupplierService} that this
+         *                 connection is for
+         */
+        DriverAwarenessServiceConnection(int priority) {
+            mPriority = priority;
+        }
+
+        @Override
+        public void onServiceConnected(ComponentName name, IBinder binder) {
+            logd("onServiceConnected, name: " + name + ", binder: " + binder);
+            IDriverAwarenessSupplier service = IDriverAwarenessSupplier.Stub.asInterface(
+                    binder);
+            addDriverAwarenessSupplier(name, service, mPriority);
+            try {
+                service.setCallback(new DriverAwarenessSupplierCallback(name));
+                service.onReady();
+            } catch (RemoteException e) {
+                Log.e(TAG, "Unable to call onReady on supplier", e);
+            }
+        }
+
+        @Override
+        public void onServiceDisconnected(ComponentName name) {
+            logd("onServiceDisconnected, name: " + name);
+            removeDriverAwarenessSupplier(name);
+            // TODO(b/146471650) rebind to driver awareness suppliers on service disconnect
+        }
+    }
+
+    /**
+     * Driver awareness listener that keeps a references to some attributes of the supplier.
+     */
+    private class DriverAwarenessSupplierCallback extends IDriverAwarenessSupplierCallback.Stub {
+
+        private final ComponentName mComponentName;
+
+        /**
+         * Construct an instance  of {@link DriverAwarenessSupplierCallback}.
+         *
+         * @param componentName the driver awareness supplier for this listener
+         */
+        DriverAwarenessSupplierCallback(ComponentName componentName) {
+            mComponentName = componentName;
+        }
+
+        @Override
+        public void onDriverAwarenessUpdated(DriverAwarenessEvent event) {
+            IDriverAwarenessSupplier supplier;
+            long maxStaleness;
+            synchronized (mLock) {
+                supplier = mSupplierBinders.get(mComponentName);
+                maxStaleness = mSupplierConfigs.get(supplier).getMaxStalenessMillis();
+            }
+            if (supplier == null) {
+                // this should never happen. Initialization process would not be correct.
+                throw new IllegalStateException(
+                        "No supplier registered for component " + mComponentName);
+            }
+            logd(String.format("Driver awareness updated for %s: %s",
+                    supplier.getClass().getSimpleName(), event));
+            handleDriverAwarenessEvent(
+                    new DriverAwarenessEventWrapper(event, supplier, maxStaleness));
+        }
+
+        @Override
+        public void onConfigLoaded(DriverAwarenessSupplierConfig config) throws RemoteException {
+            synchronized (mLock) {
+                mSupplierConfigs.put(mSupplierBinders.get(mComponentName), config);
+            }
+        }
+    }
+
+    /**
+     * Wrapper for {@link DriverAwarenessEvent} that includes some information from the supplier
+     * that emitted the event.
+     */
+    @VisibleForTesting
+    static class DriverAwarenessEventWrapper {
+        final DriverAwarenessEvent mAwarenessEvent;
+        final IDriverAwarenessSupplier mSupplier;
+        final long mMaxStaleness;
+
+        /**
+         * Construct an instance of {@link DriverAwarenessEventWrapper}.
+         *
+         * @param awarenessEvent the driver awareness event being wrapped
+         * @param supplier       the driver awareness supplier for this listener
+         * @param maxStaleness   the max staleness of the supplier that emitted this event (included
+         *                       to avoid making a binder call)
+         */
+        DriverAwarenessEventWrapper(
+                DriverAwarenessEvent awarenessEvent,
+                IDriverAwarenessSupplier supplier,
+                long maxStaleness) {
+            mAwarenessEvent = awarenessEvent;
+            mSupplier = supplier;
+            mMaxStaleness = maxStaleness;
+        }
+
+        @Override
+        public String toString() {
+            return String.format(
+                    "DriverAwarenessEventWrapper{mAwarenessChangeEvent=%s, mSupplier=%s, "
+                            + "mMaxStaleness=%s}",
+                    mAwarenessEvent, mSupplier, mMaxStaleness);
+        }
+    }
+}
diff --git a/experimental/service/src/com/android/experimentalcar/ExperimentalCarService.java b/experimental/service/src/com/android/experimentalcar/ExperimentalCarService.java
new file mode 100644
index 0000000..bc40af5
--- /dev/null
+++ b/experimental/service/src/com/android/experimentalcar/ExperimentalCarService.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.experimentalcar;
+
+import android.app.Service;
+import android.car.Car;
+import android.content.Intent;
+import android.os.IBinder;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+
+/**
+ * Top class to keep all experimental features.
+ */
+public class ExperimentalCarService extends Service {
+
+    private Car mCar;
+    private final IExperimentalCarImpl mIExperimentalCarImpl = new IExperimentalCarImpl(this);
+
+    @Override
+    public void onCreate() {
+        super.onCreate();
+        // This is for crashing this service when car service crashes.
+        mCar = Car.createCar(this);
+    }
+
+    @Override
+    public void onDestroy() {
+        mIExperimentalCarImpl.release();
+        super.onDestroy();
+    }
+
+    @Override
+    public int onStartCommand(Intent intent, int flags, int startId) {
+        // keep it alive.
+        return START_STICKY;
+    }
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return mIExperimentalCarImpl;
+    }
+
+    @Override
+    protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
+        mIExperimentalCarImpl.dump(fd, writer, args);
+    }
+}
diff --git a/experimental/service/src/com/android/experimentalcar/GazeAttentionProcessor.java b/experimental/service/src/com/android/experimentalcar/GazeAttentionProcessor.java
new file mode 100644
index 0000000..66e3a48
--- /dev/null
+++ b/experimental/service/src/com/android/experimentalcar/GazeAttentionProcessor.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.experimentalcar;
+
+import android.annotation.FloatRange;
+import android.annotation.NonNull;
+import android.car.occupantawareness.GazeDetection;
+import android.util.Log;
+
+/** {@link GazeAttentionProcessor} estimates driver attention based on a gaze history. */
+class GazeAttentionProcessor {
+    private static final String TAG = "Car.OAS.GazeAttentionProcessor";
+    private static final int NOT_SET = -1;
+
+    private final Configuration mConfig;
+
+    /** Current attention value. */
+    @FloatRange(from = 0.0f, to = 1.0f)
+    private float mAttention;
+
+    /** Timestamp of last frame received, in milliseconds since boot. */
+    private long mLastTimestamp = NOT_SET;
+
+    GazeAttentionProcessor(Configuration configuration) {
+        mConfig = configuration;
+        mAttention = mConfig.initialValue;
+    }
+
+    /** Gets the current attention awareness value. */
+    @FloatRange(from = 0.0f, to = 1.0f)
+    public float getAttention() {
+        return mAttention;
+    }
+
+    /**
+     * Updates the current attention with a new gaze detection.
+     *
+     * @param detection New {@link android.car.occupantawareness.GazeDetection} data.
+     * @param timestamp Timestamp that the detection was detected, in milliseconds since boot.
+     * @return Attention value [0, 1].
+     */
+    @FloatRange(from = 0.0f, to = 1.0f)
+    public float updateAttention(@NonNull GazeDetection detection, long timestamp) {
+        // When the first frame of data is received, no duration can be computed. Just capture the
+        // timestamp for the next pass and return the initial buffer value.
+        if (mLastTimestamp == NOT_SET) {
+            logd("First pass, returning 'initialValue=" + mConfig.initialValue);
+            mLastTimestamp = timestamp;
+            return mConfig.initialValue;
+        }
+
+        float startingAttention = mAttention;
+
+        // Compute the time since the last detection, in seconds.
+        float dtSeconds = (float) (timestamp - mLastTimestamp) / 1000.0f;
+
+        if (isOnRoadGaze(detection.gazeTarget)) {
+            logd("Handling on-road gaze");
+            mAttention += dtSeconds * mConfig.growthRate;
+            mAttention = Math.min(mAttention, 1.0f); // Cap value to max of 1.
+        } else {
+            logd("Handling off-road gaze");
+            mAttention -= dtSeconds * mConfig.decayRate;
+            mAttention = Math.max(mAttention, 0.0f); // Cap value to min of 0.
+        }
+
+        // Save current timestamp for next pass.
+        mLastTimestamp = timestamp;
+
+        logd(String.format("updateAttention(): Time=%1.2f. Attention [%f]->[%f]. ",
+                dtSeconds, startingAttention, mAttention));
+
+        return mAttention;
+    }
+
+    /** Gets whether the gaze target is on-road. */
+    private boolean isOnRoadGaze(@GazeDetection.VehicleRegion int gazeTarget) {
+        switch (gazeTarget) {
+            case GazeDetection.VEHICLE_REGION_FORWARD_ROADWAY:
+            case GazeDetection.VEHICLE_REGION_LEFT_ROADWAY:
+            case GazeDetection.VEHICLE_REGION_RIGHT_ROADWAY:
+                return true;
+            default:
+                return false;
+        }
+    }
+
+    private static void logd(String message) {
+        if (Log.isLoggable(TAG, Log.DEBUG)) {
+            Log.d(TAG, message);
+        }
+    }
+
+    /** Configuration settings for {@link GazeAttentionProcessor}. */
+    public static class Configuration {
+        /** Initial value for the attention buffer, [0, 1]. */
+        @FloatRange(from = 0.0f, to = 1.0f)
+        public final float initialValue;
+
+        /**
+         * Rate at which the attention decays when the driver is looking off-road (per second of
+         * off-road looks)
+         */
+        public final float decayRate;
+
+        /**
+         * Rate at which the attention grows when the driver is looking on-road (per second of
+         * on-road looks).
+         */
+        public final float growthRate;
+
+        /**
+         * Creates an instance of {@link Configuration}.
+         *
+         * @param initialValue the initial value that the attention buffer starts at.
+         * @param decayRate the rate at which the attention buffer decreases when the driver is
+         *     looking off-road.
+         * @param growthRate the rate at which the attention buffer increases when the driver is
+         *     looking on-road.
+         */
+        Configuration(float initialValue, float decayRate, float growthRate) {
+            this.initialValue = initialValue;
+            this.decayRate = decayRate;
+            this.growthRate = growthRate;
+        }
+
+        @Override
+        public String toString() {
+            return String.format(
+                    "Configuration{initialValue=%s, decayRate=%s, growthRate=%s}",
+                    initialValue, decayRate, growthRate);
+        }
+    }
+}
diff --git a/experimental/service/src/com/android/experimentalcar/GazeDriverAwarenessSupplier.java b/experimental/service/src/com/android/experimentalcar/GazeDriverAwarenessSupplier.java
new file mode 100644
index 0000000..7fbacbf
--- /dev/null
+++ b/experimental/service/src/com/android/experimentalcar/GazeDriverAwarenessSupplier.java
@@ -0,0 +1,202 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.experimentalcar;
+
+import android.annotation.NonNull;
+import android.car.Car;
+import android.car.experimental.DriverAwarenessEvent;
+import android.car.experimental.DriverAwarenessSupplierService;
+import android.car.occupantawareness.OccupantAwarenessDetection;
+import android.car.occupantawareness.OccupantAwarenessManager;
+import android.car.occupantawareness.SystemStatusEvent;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.os.IBinder;
+import android.util.Log;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+
+/**
+ * A Driver Awareness Supplier that estimates the driver's current level of awareness based on gaze
+ * history.
+ *
+ * <p>Attention is facilitated via {@link OccupantAwarenessManager}, which is an optional component
+ * and may not be available on every platform.
+ */
+public class GazeDriverAwarenessSupplier extends DriverAwarenessSupplierService {
+    private static final String TAG = "Car.OAS.GazeAwarenessSupplier";
+
+    /* Maximum allowable staleness before gaze data should be considered unreliable for attention
+     * monitoring, in milliseconds. */
+    private static final long MAX_STALENESS_MILLIS = 500;
+
+    private final Context mContext;
+    private final Object mLock = new Object();
+    private final ITimeSource mTimeSource;
+    private final GazeAttentionProcessor.Configuration mConfiguration;
+
+    @GuardedBy("mLock")
+    private Car mCar;
+
+    @GuardedBy("mLock")
+    private OccupantAwarenessManager mOasManager;
+
+    @GuardedBy("mLock")
+    private final GazeAttentionProcessor mProcessor;
+
+    public GazeDriverAwarenessSupplier(Context context) {
+        this(context, new SystemTimeSource());
+    }
+
+    @VisibleForTesting
+    GazeDriverAwarenessSupplier(Context context, ITimeSource timeSource) {
+        mContext = context;
+        mTimeSource = timeSource;
+        mConfiguration = loadConfiguration();
+        mProcessor = new GazeAttentionProcessor(mConfiguration);
+    }
+
+    /**
+     * Gets the self-reported maximum allowable staleness before the supplier should be considered
+     * failed, in milliseconds.
+     */
+    @Override
+    public long getMaxStalenessMillis() {
+        return MAX_STALENESS_MILLIS;
+    }
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        logd("onBind with intent: " + intent);
+        return super.onBind(intent);
+    }
+
+    @Override
+    public void onReady() {
+        synchronized (mLock) {
+            mCar = Car.createCar(mContext);
+
+            if (mCar != null) {
+                if (mOasManager == null && mCar.isFeatureEnabled(Car.OCCUPANT_AWARENESS_SERVICE)) {
+                    mOasManager =
+                            (OccupantAwarenessManager)
+                                    mCar.getCarManager(Car.OCCUPANT_AWARENESS_SERVICE);
+
+                    if (mOasManager == null) {
+                        Log.e(TAG, "Failed to get OccupantAwarenessManager.");
+                    }
+                }
+
+                if (mOasManager != null) {
+                    logd("Registering callback with OAS manager");
+                    mOasManager.registerChangeCallback(new ChangeCallback());
+                }
+            }
+        }
+
+        // Send an initial value once the provider is ready, as required by {link
+        // IDriverAwarenessSupplierCallback}.
+        emitAwarenessEvent(
+                new DriverAwarenessEvent(
+                        mTimeSource.elapsedRealtime(), mConfiguration.initialValue));
+    }
+
+    /**
+     * Returns whether this car supports gaze based awareness.
+     *
+     * <p>The underlying Occupant Awareness System is optional and may not be supported on every
+     * vehicle. This method must be called *after* onReady().
+     */
+    public boolean supportsGaze() throws IllegalStateException {
+        synchronized (mLock) {
+            if (mCar == null) {
+                throw new IllegalStateException(
+                        "Must call onReady() before querying for gaze support");
+            }
+
+            // Occupant Awareness is optional and may not be available on this machine. If not
+            // available, the returned manager will be null.
+            if (mOasManager == null) {
+                logd("Occupant Awareness System is not available on this machine");
+                return false;
+            }
+
+            int driver_capability =
+                    mOasManager.getCapabilityForRole(
+                            OccupantAwarenessDetection.VEHICLE_OCCUPANT_DRIVER);
+
+            logd("Driver capabilities flags: " + driver_capability);
+
+            return (driver_capability & SystemStatusEvent.DETECTION_TYPE_DRIVER_MONITORING) > 0;
+        }
+    }
+
+    /** Builds {@link GazeAttentionProcessor.Configuration} using context resources. */
+    private GazeAttentionProcessor.Configuration loadConfiguration() {
+        Resources resources = mContext.getResources();
+
+        return new GazeAttentionProcessor.Configuration(
+                resources.getFloat(R.fraction.driverAwarenessGazeModelInitialValue),
+                resources.getFloat(R.fraction.driverAwarenessGazeModelDecayRate),
+                resources.getFloat(R.fraction.driverAwarenessGazeModelGrowthRate));
+    }
+
+    /** Processes a new detection event, updating the current attention value. */
+    @VisibleForTesting
+    void processDetectionEvent(@NonNull OccupantAwarenessDetection event) {
+        if (event.role == OccupantAwarenessDetection.VEHICLE_OCCUPANT_DRIVER
+                && event.gazeDetection != null) {
+            float awarenessValue;
+            synchronized (mLock) {
+                awarenessValue =
+                        mProcessor.updateAttention(event.gazeDetection, event.timestampMillis);
+            }
+
+            emitAwarenessEvent(new DriverAwarenessEvent(event.timestampMillis, awarenessValue));
+        }
+    }
+
+    @VisibleForTesting
+    void emitAwarenessEvent(@NonNull DriverAwarenessEvent event) {
+        logd("Emitting new event: " + event);
+        onDriverAwarenessUpdated(event);
+    }
+
+    private static void logd(String message) {
+        if (Log.isLoggable(TAG, Log.DEBUG)) {
+            Log.d(TAG, message);
+        }
+    }
+
+    /** Callback when the system status changes or a new detection is made. */
+    private class ChangeCallback extends OccupantAwarenessManager.ChangeCallback {
+        /** Called when the system state changes changes. */
+        @Override
+        public void onSystemStateChanged(@NonNull SystemStatusEvent status) {
+            logd("New status: " + status);
+        }
+
+        /** Called when a detection event is generated. */
+        @Override
+        public void onDetectionEvent(@NonNull OccupantAwarenessDetection event) {
+            logd("New detection: " + event);
+            processDetectionEvent(event);
+        }
+    }
+}
diff --git a/experimental/service/src/com/android/experimentalcar/IExperimentalCarImpl.java b/experimental/service/src/com/android/experimentalcar/IExperimentalCarImpl.java
new file mode 100644
index 0000000..ef45ce0
--- /dev/null
+++ b/experimental/service/src/com/android/experimentalcar/IExperimentalCarImpl.java
@@ -0,0 +1,207 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.experimentalcar;
+
+import android.annotation.MainThread;
+import android.annotation.Nullable;
+import android.car.IExperimentalCar;
+import android.car.IExperimentalCarHelper;
+import android.car.experimental.CarDriverDistractionManager;
+import android.car.experimental.CarTestDemoExperimentalFeatureManager;
+import android.car.experimental.ExperimentalCar;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Process;
+import android.os.RemoteException;
+import android.util.Log;
+
+import com.android.car.CarServiceBase;
+import com.android.internal.annotations.GuardedBy;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Implements IExperimentalCar for experimental features.
+ */
+public final class IExperimentalCarImpl extends IExperimentalCar.Stub {
+
+    private static final String TAG = "CAR.EXPIMPL";
+
+    private static final List<String> ALL_AVAILABLE_FEATURES = Arrays.asList(
+            ExperimentalCar.TEST_EXPERIMENTAL_FEATURE_SERVICE,
+            ExperimentalCar.DRIVER_DISTRACTION_EXPERIMENTAL_FEATURE_SERVICE
+    );
+
+    private final Context mContext;
+
+    private final Handler mMainThreadHandler = new Handler(Looper.getMainLooper());
+
+    private final Object mLock = new Object();
+
+    @GuardedBy("mLock")
+    private boolean mReleased;
+
+    @GuardedBy("mLock")
+    private IExperimentalCarHelper mHelper;
+
+    @GuardedBy("mLock")
+    private ArrayList<CarServiceBase> mRunningServices = new ArrayList<>();
+
+    public IExperimentalCarImpl(Context context) {
+        mContext = context;
+    }
+
+    @Override
+    public void init(IExperimentalCarHelper helper, List<String> enabledFeatures) {
+        // From car service or unit testing only
+        assertCallingFromSystemProcessOrSelf();
+
+        // dispatch to main thread as release is always done in main.
+        mMainThreadHandler.post(() -> {
+            synchronized (mLock) {
+                if (mReleased) {
+                    Log.w(TAG, "init binder call after onDestroy, will ignore");
+                    return;
+                }
+            }
+            ArrayList<CarServiceBase> services = new ArrayList<>();
+            ArrayList<String> startedFeatures = new ArrayList<>();
+            ArrayList<String> classNames = new ArrayList<>();
+            ArrayList<IBinder> binders = new ArrayList<>();
+
+            // This cannot address inter-dependency. That requires re-ordering this in dependency
+            // order.
+            // That should be done when we find such needs. For now, each feature inside here should
+            // not have inter-dependency as they are all optional.
+            for (String feature : enabledFeatures) {
+                CarServiceBase service = constructServiceForFeature(feature);
+                if (service == null) {
+                    Log.e(TAG, "Failed to construct requested feature:" + feature);
+                    continue;
+                }
+                service.init();
+                services.add(service);
+                startedFeatures.add(feature);
+                // If it is not IBinder, then it is internal feature.
+                if (service instanceof IBinder) {
+                    binders.add((IBinder) service);
+                } else {
+                    binders.add(null);
+                }
+                classNames.add(getClassNameForFeature(feature));
+            }
+            try {
+                helper.onInitComplete(ALL_AVAILABLE_FEATURES, startedFeatures, classNames, binders);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Car service crashed?", e);
+                // will be destroyed soon. Just continue and register services for possible cleanup.
+            }
+            synchronized (mLock) {
+                mHelper = helper;
+                mRunningServices.addAll(services);
+            }
+        });
+    }
+
+    // should be called in Service.onDestroy
+    @MainThread
+    void release() {
+        // Copy to handle call release without lock
+        ArrayList<CarServiceBase> services;
+        synchronized (mLock) {
+            if (mReleased) {
+                return;
+            }
+            mReleased = true;
+            services = new ArrayList<>(mRunningServices);
+            mRunningServices.clear();
+        }
+        for (CarServiceBase service : services) {
+            service.release();
+        }
+    }
+
+    /** dump */
+    public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
+        ArrayList<CarServiceBase> services;
+        synchronized (mLock) {
+            writer.println("mReleased:" + mReleased);
+            writer.println("ALL_AVAILABLE_FEATURES:" + ALL_AVAILABLE_FEATURES);
+            services = new ArrayList<>(mRunningServices);
+        }
+        writer.println(" Number of running services:" + services.size());
+        int i = 0;
+        for (CarServiceBase service : services) {
+            writer.print(i + ":");
+            service.dump(writer);
+            i++;
+        }
+    }
+
+    @Nullable
+    private String getClassNameForFeature(String featureName) {
+        switch (featureName) {
+            case ExperimentalCar.TEST_EXPERIMENTAL_FEATURE_SERVICE:
+                return CarTestDemoExperimentalFeatureManager.class.getName();
+            case ExperimentalCar.DRIVER_DISTRACTION_EXPERIMENTAL_FEATURE_SERVICE:
+                return CarDriverDistractionManager.class.getName();
+            default:
+                return null;
+        }
+    }
+
+    @Nullable
+    private CarServiceBase constructServiceForFeature(String featureName) {
+        switch (featureName) {
+            case ExperimentalCar.TEST_EXPERIMENTAL_FEATURE_SERVICE:
+                return new TestDemoExperimentalFeatureService();
+            case ExperimentalCar.DRIVER_DISTRACTION_EXPERIMENTAL_FEATURE_SERVICE:
+                return new DriverDistractionExperimentalFeatureService(
+                        mContext,
+                        new SystemTimeSource(),
+                        new SystemTimer());
+            default:
+                return null;
+        }
+    }
+
+    private static void assertCallingFromSystemProcessOrSelf() {
+        int uid = Binder.getCallingUid();
+        int pid = Binder.getCallingPid();
+        if (uid != Process.SYSTEM_UID && pid != Process.myPid()) {
+            throw new SecurityException("Only allowed from system or self, uid:" + uid
+                    + " pid:" + pid);
+        }
+    }
+
+    /**
+     * Assert that a permission has been granted for the current context.
+     */
+    public static void assertPermission(Context context, String permission) {
+        if (context.checkCallingOrSelfPermission(permission) != PackageManager.PERMISSION_GRANTED) {
+            throw new SecurityException("requires " + permission);
+        }
+    }
+}
diff --git a/experimental/service/src/com/android/experimentalcar/ITimeSource.java b/experimental/service/src/com/android/experimentalcar/ITimeSource.java
new file mode 100644
index 0000000..3a597c3
--- /dev/null
+++ b/experimental/service/src/com/android/experimentalcar/ITimeSource.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.experimentalcar;
+
+import android.os.SystemClock;
+
+/**
+ * Interface to abstract dependency on {@link android.os.SystemClock}.
+ */
+public interface ITimeSource {
+
+    /**
+     * See {@link SystemClock#elapsedRealtime()}.
+     */
+    long elapsedRealtime();
+}
diff --git a/experimental/service/src/com/android/experimentalcar/ITimer.java b/experimental/service/src/com/android/experimentalcar/ITimer.java
new file mode 100644
index 0000000..dbaa3c0
--- /dev/null
+++ b/experimental/service/src/com/android/experimentalcar/ITimer.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.experimentalcar;
+
+import java.util.TimerTask;
+
+/**
+ * Interface to abstract dependency on {@link java.util.Timer}.
+ */
+public interface ITimer {
+
+    /**
+     * Cancel the timer and prepare for new events.
+     */
+    void reset();
+
+    /**
+     * See {@link java.util.Timer#schedule(TimerTask, long)}.
+     */
+    void schedule(TimerTask task, long delay);
+}
diff --git a/experimental/service/src/com/android/experimentalcar/SampleExternalDriverAwarenessSupplier.java b/experimental/service/src/com/android/experimentalcar/SampleExternalDriverAwarenessSupplier.java
new file mode 100644
index 0000000..917ac20
--- /dev/null
+++ b/experimental/service/src/com/android/experimentalcar/SampleExternalDriverAwarenessSupplier.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.experimentalcar;
+
+import android.car.experimental.DriverAwarenessEvent;
+import android.car.experimental.DriverAwarenessSupplierService;
+import android.content.Intent;
+import android.os.IBinder;
+import android.os.SystemClock;
+import android.util.Log;
+
+/**
+ * Simple example of how an external driver awareness supplier service could be implemented.
+ */
+public class SampleExternalDriverAwarenessSupplier extends DriverAwarenessSupplierService {
+
+    private static final String TAG = "SampleExternalDriverAwarenessSupplier";
+    private static final float INITIAL_DRIVER_AWARENESS_VALUE = 1.0f;
+    private static final long MAX_STALENESS_MILLIS = 100L;
+
+    @Override
+    public long getMaxStalenessMillis() {
+        return MAX_STALENESS_MILLIS;
+    }
+
+    @Override
+    public void onReady() {
+        // send an initial event, as required by the IDriverAwarenessSupplierCallback spec
+        onDriverAwarenessUpdated(new DriverAwarenessEvent(SystemClock.elapsedRealtime(),
+                INITIAL_DRIVER_AWARENESS_VALUE));
+    }
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        logd("onBind, intent: " + intent);
+        return super.onBind(intent);
+    }
+
+    private static void logd(String message) {
+        if (Log.isLoggable(TAG, Log.DEBUG)) {
+            Log.d(TAG, message);
+        }
+    }
+}
diff --git a/experimental/service/src/com/android/experimentalcar/SystemTimeSource.java b/experimental/service/src/com/android/experimentalcar/SystemTimeSource.java
new file mode 100644
index 0000000..217b6fe
--- /dev/null
+++ b/experimental/service/src/com/android/experimentalcar/SystemTimeSource.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.experimentalcar;
+
+import android.os.SystemClock;
+
+/**
+ * Time source implementation using {@link SystemClock}.
+ */
+public class SystemTimeSource implements ITimeSource {
+
+    @Override
+    public long elapsedRealtime() {
+        return SystemClock.elapsedRealtime();
+    }
+}
diff --git a/experimental/service/src/com/android/experimentalcar/SystemTimer.java b/experimental/service/src/com/android/experimentalcar/SystemTimer.java
new file mode 100644
index 0000000..9eb1dc5
--- /dev/null
+++ b/experimental/service/src/com/android/experimentalcar/SystemTimer.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.experimentalcar;
+
+import java.util.Timer;
+import java.util.TimerTask;
+
+/**
+ * Implementation of {@link ITimer} that uses {@link Timer}.
+ */
+public class SystemTimer implements ITimer {
+
+    private Timer mTimer = new Timer();
+
+    @Override
+    public void reset() {
+        mTimer.cancel();
+        mTimer = new Timer();
+    }
+
+    @Override
+    public void schedule(TimerTask task, long delay) {
+        mTimer.schedule(task, delay);
+    }
+}
diff --git a/experimental/service/src/com/android/experimentalcar/TestDemoExperimentalFeatureService.java b/experimental/service/src/com/android/experimentalcar/TestDemoExperimentalFeatureService.java
new file mode 100644
index 0000000..1309dd2
--- /dev/null
+++ b/experimental/service/src/com/android/experimentalcar/TestDemoExperimentalFeatureService.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.experimentalcar;
+
+import android.car.experimental.ITestDemoExperimental;
+
+import com.android.car.CarServiceBase;
+
+import java.io.PrintWriter;
+
+/**
+ * Demo service for testing experimental feature.
+ */
+public final class TestDemoExperimentalFeatureService extends ITestDemoExperimental.Stub
+        implements CarServiceBase {
+
+    @Override
+    public void init() {
+        // Nothing to do
+    }
+
+    @Override
+    public void release() {
+        // Nothing to do
+    }
+
+    @Override
+    public void dump(PrintWriter writer) {
+        writer.println("*TestExperimentalFeatureService*");
+    }
+
+    @Override
+    public String ping(String msg) {
+        return msg;
+    }
+}
diff --git a/experimental/service/src/com/android/experimentalcar/TouchDriverAwarenessSupplier.java b/experimental/service/src/com/android/experimentalcar/TouchDriverAwarenessSupplier.java
new file mode 100644
index 0000000..4ed17cd
--- /dev/null
+++ b/experimental/service/src/com/android/experimentalcar/TouchDriverAwarenessSupplier.java
@@ -0,0 +1,341 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.experimentalcar;
+
+import android.car.experimental.DriverAwarenessEvent;
+import android.car.experimental.DriverAwarenessSupplierConfig;
+import android.car.experimental.DriverAwarenessSupplierService;
+import android.car.experimental.IDriverAwarenessSupplier;
+import android.car.experimental.IDriverAwarenessSupplierCallback;
+import android.content.Context;
+import android.hardware.input.InputManager;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.util.Log;
+import android.view.Display;
+import android.view.InputChannel;
+import android.view.InputEvent;
+import android.view.InputEventReceiver;
+import android.view.InputMonitor;
+import android.view.MotionEvent;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+
+/**
+ * A driver awareness supplier that estimates the driver's current awareness level based on touches
+ * on the headunit.
+ */
+public class TouchDriverAwarenessSupplier extends IDriverAwarenessSupplier.Stub {
+
+    private static final String TAG = "Car.TouchAwarenessSupplier";
+    private static final String TOUCH_INPUT_CHANNEL_NAME = "TouchDriverAwarenessInputChannel";
+
+    private static final long MAX_STALENESS = DriverAwarenessSupplierService.NO_STALENESS;
+
+    @VisibleForTesting
+    static final float INITIAL_DRIVER_AWARENESS_VALUE = 1.0f;
+
+    private final AtomicInteger mCurrentPermits = new AtomicInteger();
+    private final ScheduledExecutorService mRefreshScheduler;
+    private final Looper mLooper;
+    private final Context mContext;
+    private final ITimeSource mTimeSource;
+    private final Runnable mRefreshPermitRunnable;
+    private final IDriverAwarenessSupplierCallback mDriverAwarenessSupplierCallback;
+
+    private final Object mLock = new Object();
+
+    @GuardedBy("mLock")
+    private long mLastEventMillis;
+
+    @GuardedBy("mLock")
+    private ScheduledFuture<?> mRefreshScheduleHandle;
+
+    @GuardedBy("mLock")
+    private Config mConfig;
+
+    // Main thread only. Hold onto reference to avoid garbage collection
+    private InputMonitor mInputMonitor;
+
+    // Main thread only. Hold onto reference to avoid garbage collection
+    private InputEventReceiver mInputEventReceiver;
+
+    TouchDriverAwarenessSupplier(Context context,
+            IDriverAwarenessSupplierCallback driverAwarenessSupplierCallback) {
+        this(context, driverAwarenessSupplierCallback, Executors.newScheduledThreadPool(1),
+                Looper.myLooper(), new SystemTimeSource());
+    }
+
+    @VisibleForTesting
+    TouchDriverAwarenessSupplier(
+            Context context,
+            IDriverAwarenessSupplierCallback driverAwarenessSupplierCallback,
+            ScheduledExecutorService refreshScheduler,
+            Looper looper,
+            ITimeSource timeSource) {
+        mContext = context;
+        mDriverAwarenessSupplierCallback = driverAwarenessSupplierCallback;
+        mRefreshScheduler = refreshScheduler;
+        mLooper = looper;
+        mTimeSource = timeSource;
+        mRefreshPermitRunnable =
+                () -> {
+                    synchronized (mLock) {
+                        handlePermitRefreshLocked(mTimeSource.elapsedRealtime());
+                    }
+                };
+    }
+
+
+    @Override
+    public void onReady() {
+        try {
+            mDriverAwarenessSupplierCallback.onConfigLoaded(
+                    new DriverAwarenessSupplierConfig(MAX_STALENESS));
+        } catch (RemoteException e) {
+            Log.e(TAG, "Unable to send config - abandoning ready process", e);
+            return;
+        }
+        // send an initial event, as required by the IDriverAwarenessSupplierCallback spec
+        try {
+            mDriverAwarenessSupplierCallback.onDriverAwarenessUpdated(
+                    new DriverAwarenessEvent(mTimeSource.elapsedRealtime(),
+                            INITIAL_DRIVER_AWARENESS_VALUE));
+        } catch (RemoteException e) {
+            Log.e(TAG, "Unable to emit initial awareness event", e);
+        }
+        synchronized (mLock) {
+            mConfig = loadConfig();
+            logd("Config loaded: " + mConfig);
+            mCurrentPermits.set(mConfig.getMaxPermits());
+        }
+        startTouchMonitoring();
+    }
+
+    @Override
+    public void setCallback(IDriverAwarenessSupplierCallback callback) {
+        // no-op - the callback is initialized in the constructor
+    }
+
+    private Config loadConfig() {
+        int maxPermits = mContext.getResources().getInteger(
+                R.integer.driverAwarenessTouchModelMaxPermits);
+        if (maxPermits <= 0) {
+            throw new IllegalArgumentException("driverAwarenessTouchModelMaxPermits must be >0");
+        }
+        int refreshIntervalMillis = mContext.getResources().getInteger(
+                R.integer.driverAwarenessTouchModelPermitRefreshIntervalMs);
+        if (refreshIntervalMillis <= 0) {
+            throw new IllegalArgumentException(
+                    "driverAwarenessTouchModelPermitRefreshIntervalMs must be >0");
+        }
+        int throttleDurationMillis = mContext.getResources().getInteger(
+                R.integer.driverAwarenessTouchModelThrottleMs);
+        if (throttleDurationMillis <= 0) {
+            throw new IllegalArgumentException("driverAwarenessTouchModelThrottleMs must be >0");
+        }
+        return new Config(maxPermits, refreshIntervalMillis, throttleDurationMillis);
+    }
+
+    /**
+     * Starts monitoring touches.
+     */
+    @VisibleForTesting
+    // TODO(b/146802952) handle touch monitoring on multiple displays
+    void startTouchMonitoring() {
+        InputManager inputManager = (InputManager) mContext.getSystemService(Context.INPUT_SERVICE);
+        mInputMonitor = inputManager.monitorGestureInput(
+                TOUCH_INPUT_CHANNEL_NAME,
+                Display.DEFAULT_DISPLAY);
+        mInputEventReceiver = new TouchReceiver(
+                mInputMonitor.getInputChannel(),
+                mLooper);
+    }
+
+    /**
+     * Refreshes permits on the interval specified by {@code R.integer
+     * .driverAwarenessTouchModelPermitRefreshIntervalMs}.
+     */
+    @GuardedBy("mLock")
+    private void schedulePermitRefreshLocked() {
+        logd("Scheduling permit refresh interval (ms): "
+                + mConfig.getPermitRefreshIntervalMillis());
+        mRefreshScheduleHandle = mRefreshScheduler.scheduleAtFixedRate(
+                mRefreshPermitRunnable,
+                mConfig.getPermitRefreshIntervalMillis(),
+                mConfig.getPermitRefreshIntervalMillis(),
+                TimeUnit.MILLISECONDS);
+    }
+
+    /**
+     * Stops the scheduler for refreshing the number of permits.
+     */
+    @GuardedBy("mLock")
+    private void stopPermitRefreshLocked() {
+        logd("Stopping permit refresh");
+        if (mRefreshScheduleHandle != null) {
+            mRefreshScheduleHandle.cancel(true);
+            mRefreshScheduleHandle = null;
+        }
+    }
+
+    /**
+     * Consume a single permit if the event should not be throttled.
+     */
+    @VisibleForTesting
+    @GuardedBy("mLock")
+    void consumePermitLocked(long timestamp) {
+        long timeSinceLastEvent = timestamp - mLastEventMillis;
+        boolean isEventAccepted = timeSinceLastEvent >= mConfig.getThrottleDurationMillis();
+        if (!isEventAccepted) {
+            logd("Ignoring consumePermit request: event throttled");
+            return;
+        }
+        mLastEventMillis = timestamp;
+        int curPermits = mCurrentPermits.updateAndGet(cur -> Math.max(0, cur - 1));
+        logd("Permit consumed to: " + curPermits);
+
+        if (mRefreshScheduleHandle == null) {
+            schedulePermitRefreshLocked();
+        }
+
+        try {
+            mDriverAwarenessSupplierCallback.onDriverAwarenessUpdated(
+                    new DriverAwarenessEvent(timestamp,
+                            (float) curPermits / mConfig.getMaxPermits()));
+        } catch (RemoteException e) {
+            Log.e(TAG, "Unable to emit awareness event", e);
+        }
+    }
+
+    @VisibleForTesting
+    @GuardedBy("mLock")
+    void handlePermitRefreshLocked(long timestamp) {
+        int curPermits = mCurrentPermits.updateAndGet(
+                cur -> Math.min(cur + 1, mConfig.getMaxPermits()));
+        logd("Permit refreshed to: " + curPermits);
+        if (curPermits == mConfig.getMaxPermits()) {
+            stopPermitRefreshLocked();
+        }
+        try {
+            mDriverAwarenessSupplierCallback.onDriverAwarenessUpdated(
+                    new DriverAwarenessEvent(timestamp,
+                            (float) curPermits / mConfig.getMaxPermits()));
+        } catch (RemoteException e) {
+            Log.e(TAG, "Unable to emit awareness event", e);
+        }
+    }
+
+    private static void logd(String message) {
+        if (Log.isLoggable(TAG, Log.DEBUG)) {
+            Log.d(TAG, message);
+        }
+    }
+
+    /**
+     * Receiver of all touch events. This receiver filters out all events except {@link
+     * MotionEvent#ACTION_UP} events.
+     */
+    private class TouchReceiver extends InputEventReceiver {
+
+        /**
+         * Creates an input event receiver bound to the specified input channel.
+         *
+         * @param inputChannel The input channel.
+         * @param looper       The looper to use when invoking callbacks.
+         */
+        TouchReceiver(InputChannel inputChannel, Looper looper) {
+            super(inputChannel, looper);
+        }
+
+        @Override
+        public void onInputEvent(InputEvent event) {
+            if (!(event instanceof MotionEvent)) {
+                return;
+            }
+
+            MotionEvent motionEvent = (MotionEvent) event;
+            if (motionEvent.getActionMasked() == MotionEvent.ACTION_UP) {
+                logd("ACTION_UP touch received");
+                synchronized (mLock) {
+                    consumePermitLocked(SystemClock.elapsedRealtime());
+                }
+            }
+        }
+    }
+
+    /**
+     * Configuration for a {@link TouchDriverAwarenessSupplier}.
+     */
+    private static class Config {
+
+        private final int mMaxPermits;
+        private final int mPermitRefreshIntervalMillis;
+        private final int mThrottleDurationMillis;
+
+        /**
+         * Creates an instance of {@link Config}.
+         *
+         * @param maxPermits                  the maximum number of permits in the user's
+         *                                    attention buffer. A user's number of permits will
+         *                                    never refresh to a value higher than this.
+         * @param permitRefreshIntervalMillis the refresh interval in milliseconds for refreshing
+         *                                    permits
+         * @param throttleDurationMillis      the duration in milliseconds representing the window
+         *                                    that permit consumption is ignored after an event.
+         */
+        private Config(
+                int maxPermits,
+                int permitRefreshIntervalMillis,
+                int throttleDurationMillis) {
+            mMaxPermits = maxPermits;
+            mPermitRefreshIntervalMillis = permitRefreshIntervalMillis;
+            mThrottleDurationMillis = throttleDurationMillis;
+        }
+
+        int getMaxPermits() {
+            return mMaxPermits;
+        }
+
+        int getPermitRefreshIntervalMillis() {
+            return mPermitRefreshIntervalMillis;
+        }
+
+        int getThrottleDurationMillis() {
+            return mThrottleDurationMillis;
+        }
+
+        @Override
+        public String toString() {
+            return String.format(
+                    "Config{mMaxPermits=%s, mPermitRefreshIntervalMillis=%s, "
+                            + "mThrottleDurationMillis=%s}",
+                    mMaxPermits,
+                    mPermitRefreshIntervalMillis,
+                    mThrottleDurationMillis);
+        }
+    }
+}
diff --git a/experimental/tests/Android.bp b/experimental/tests/Android.bp
new file mode 100644
index 0000000..7996a89
--- /dev/null
+++ b/experimental/tests/Android.bp
@@ -0,0 +1,17 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//
+
+// Include the sub-makefiles
\ No newline at end of file
diff --git a/experimental/tests/experimentalcarservice_unit_test/Android.bp b/experimental/tests/experimentalcarservice_unit_test/Android.bp
new file mode 100644
index 0000000..891a151
--- /dev/null
+++ b/experimental/tests/experimentalcarservice_unit_test/Android.bp
@@ -0,0 +1,48 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Unit tests for the experimental car service
+android_test {
+
+    name: "ExperimentalCarServiceTests",
+
+    srcs: ["src/**/*.java"],
+
+    platform_apis: true,
+    certificate: "platform",
+
+    libs: [
+        "android.car",
+        "android.test.runner",
+        "android.test.base",
+        "android.test.mock",
+    ],
+
+    static_libs: [
+        "androidx.test.core",
+        "androidx.test.ext.junit",
+        "androidx.test.rules",
+        "car-frameworks-service",
+        "mockito-target-extended",
+        "truth-prebuilt",
+        "experimentalcar-service-test-static-lib"
+    ],
+
+    jni_libs: [
+        "libdexmakerjvmtiagent",
+        "libstaticjvmtiagent",
+    ],
+
+    instrumentation_for: "ExperimentalCarService",
+}
diff --git a/experimental/tests/experimentalcarservice_unit_test/AndroidManifest.xml b/experimental/tests/experimentalcarservice_unit_test/AndroidManifest.xml
new file mode 100644
index 0000000..e2cff85
--- /dev/null
+++ b/experimental/tests/experimentalcarservice_unit_test/AndroidManifest.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          xmlns:tools="http://schemas.android.com/tools"
+          android:sharedUserId="com.google.android.car.uid.kitchensink"
+          package="com.android.experimentalcar.experimentalcarservice_unittest">
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="com.android.experimentalcar.experimentalcarservice_unittest"
+                     android:label="Unit Tests for Experimental Car APIs"/>
+
+    <application android:label="ExperimentalCarServiceUnitTest"
+                 tools:replace="android:label"
+                 android:debuggable="true">
+        <uses-library android:name="android.test.runner"/>
+    </application>
+</manifest>
diff --git a/experimental/tests/experimentalcarservice_unit_test/src/com/android/experimentalcar/DriverDistractionExperimentalFeatureServiceTest.java b/experimental/tests/experimentalcarservice_unit_test/src/com/android/experimentalcar/DriverDistractionExperimentalFeatureServiceTest.java
new file mode 100644
index 0000000..bcdac80
--- /dev/null
+++ b/experimental/tests/experimentalcarservice_unit_test/src/com/android/experimentalcar/DriverDistractionExperimentalFeatureServiceTest.java
@@ -0,0 +1,446 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.experimentalcar;
+
+import static com.android.experimentalcar.DriverDistractionExperimentalFeatureService.DEFAULT_AWARENESS_PERCENTAGE;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.car.Car;
+import android.car.VehiclePropertyIds;
+import android.car.experimental.CarDriverDistractionManager;
+import android.car.experimental.DriverAwarenessEvent;
+import android.car.experimental.DriverAwarenessSupplierConfig;
+import android.car.experimental.DriverAwarenessSupplierService;
+import android.car.experimental.DriverDistractionChangeEvent;
+import android.car.experimental.IDriverAwarenessSupplier;
+import android.car.experimental.IDriverAwarenessSupplierCallback;
+import android.car.hardware.CarPropertyValue;
+import android.content.Context;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.util.Pair;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+
+import java.util.Arrays;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
+
+@RunWith(MockitoJUnitRunner.class)
+public class DriverDistractionExperimentalFeatureServiceTest {
+
+    private static final long INITIAL_TIME = 1000L;
+    private static final long PREFERRED_SUPPLIER_STALENESS = 10L;
+
+    private final IDriverAwarenessSupplier mFallbackSupplier =
+            new IDriverAwarenessSupplier.Stub() {
+                @Override
+                public void onReady() throws RemoteException {
+                }
+
+                @Override
+                public void setCallback(IDriverAwarenessSupplierCallback callback)
+                        throws RemoteException {
+                }
+            };
+    private final DriverAwarenessSupplierConfig mFallbackConfig = new DriverAwarenessSupplierConfig(
+            DriverAwarenessSupplierService.NO_STALENESS);
+
+    private final IDriverAwarenessSupplier mPreferredSupplier =
+            new IDriverAwarenessSupplier.Stub() {
+                @Override
+                public void onReady() throws RemoteException {
+                }
+
+                @Override
+                public void setCallback(IDriverAwarenessSupplierCallback callback)
+                        throws RemoteException {
+                }
+            };
+    private final DriverAwarenessSupplierConfig mPreferredSupplierConfig =
+            new DriverAwarenessSupplierConfig(PREFERRED_SUPPLIER_STALENESS);
+
+    // stores the last change event from OnDriverDistractionChange call
+    private DriverDistractionChangeEvent mLastDistractionEvent;
+    private final Semaphore mChangeEventSignal = new Semaphore(0);
+
+    private final CarDriverDistractionManager.OnDriverDistractionChangeListener mChangeListener =
+            new CarDriverDistractionManager.OnDriverDistractionChangeListener() {
+                @Override
+                public void onDriverDistractionChange(DriverDistractionChangeEvent event) {
+                    // should be dispatched to main thread.
+                    assertThat(Looper.getMainLooper()).isEqualTo(Looper.myLooper());
+                    mLastDistractionEvent = event;
+                    mChangeEventSignal.release();
+                }
+            };
+
+    @Mock
+    private Context mContext;
+
+    private DriverDistractionExperimentalFeatureService mService;
+    private CarDriverDistractionManager mManager;
+    private FakeTimeSource mTimeSource;
+    private FakeTimer mTimer;
+
+    @Before
+    public void setUp() throws Exception {
+        mTimeSource = new FakeTimeSource(INITIAL_TIME);
+        mTimer = new FakeTimer();
+        mService = new DriverDistractionExperimentalFeatureService(mContext, mTimeSource, mTimer);
+        mManager = new CarDriverDistractionManager(Car.createCar(mContext), mService);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        if (mService != null) {
+            mService.release();
+        }
+        resetChangeEventWait();
+    }
+
+    @Test
+    public void testHandleDriverAwarenessEvent_updatesCurrentValue_withLatestEvent()
+            throws Exception {
+        mService.setDriverAwarenessSuppliers(Arrays.asList(
+                new Pair<>(mFallbackSupplier, mFallbackConfig)));
+
+        float firstAwarenessValue = 0.7f;
+        emitEvent(mFallbackSupplier, INITIAL_TIME + 1, firstAwarenessValue);
+
+        assertThat(getCurrentAwarenessValue()).isEqualTo(firstAwarenessValue);
+    }
+
+    @Test
+    public void testHandleDriverAwarenessEvent_hasPreferredEvent_ignoresFallbackEvent()
+            throws Exception {
+        mService.setDriverAwarenessSuppliers(Arrays.asList(
+                new Pair<>(mFallbackSupplier, mFallbackConfig),
+                new Pair<>(mPreferredSupplier, mPreferredSupplierConfig)));
+
+        // emit an event from the preferred supplier before the fallback supplier
+        float preferredValue = 0.6f;
+        emitEvent(mPreferredSupplier, INITIAL_TIME + 1, preferredValue);
+        float fallbackValue = 0.7f;
+        emitEvent(mFallbackSupplier, INITIAL_TIME + 2, fallbackValue);
+
+        // even though the fallback supplier has a more recent timestamp, it is not the current
+        // since the event from the preferred supplier is still fresh
+        assertThat(getCurrentAwarenessValue()).isEqualTo(preferredValue);
+    }
+
+    @Test
+    public void testHandleDriverAwarenessEvent_ignoresOldEvents() throws Exception {
+        mService.setDriverAwarenessSuppliers(Arrays.asList(
+                new Pair<>(mFallbackSupplier, mFallbackConfig)));
+
+        float firstAwarenessValue = 0.7f;
+        emitEvent(mFallbackSupplier, INITIAL_TIME + 1, firstAwarenessValue);
+        long oldTime = INITIAL_TIME - 100;
+        emitEvent(mFallbackSupplier, oldTime, 0.6f);
+
+        // the event with the old timestamp shouldn't overwrite the value with a more recent
+        // timestamp
+        assertThat(getCurrentAwarenessValue()).isEqualTo(firstAwarenessValue);
+    }
+
+    @Test
+    public void testPreferredAwarenessEvent_becomesStale_fallsBackToFallbackEvent()
+            throws Exception {
+        setVehicleMoving();
+        mService.setDriverAwarenessSuppliers(Arrays.asList(
+                new Pair<>(mFallbackSupplier, mFallbackConfig),
+                new Pair<>(mPreferredSupplier, mPreferredSupplierConfig)));
+
+        // emit an event from the preferred supplier before the fallback supplier
+        float preferredSupplierAwarenessValue = 0.6f;
+        long preferredSupplierEventTime = INITIAL_TIME + 1;
+        mTimeSource.setElapsedRealtime(preferredSupplierEventTime);
+        emitEvent(mPreferredSupplier, preferredSupplierEventTime, preferredSupplierAwarenessValue);
+        float fallbackSuppplierAwarenessValue = 0.7f;
+        long fallbackSupplierEventTime = INITIAL_TIME + 2;
+        mTimeSource.setElapsedRealtime(fallbackSupplierEventTime);
+        emitEvent(mFallbackSupplier, fallbackSupplierEventTime, fallbackSuppplierAwarenessValue);
+
+        // the preferred supplier still has a fresh event
+        assertThat(getCurrentAwarenessValue()).isEqualTo(preferredSupplierAwarenessValue);
+
+        // go into the future
+        mTimeSource.setElapsedRealtime(
+                preferredSupplierEventTime + PREFERRED_SUPPLIER_STALENESS + 1);
+        mTimer.executePendingTask();
+
+        // the preferred supplier's data has become stale
+        assertThat(getCurrentAwarenessValue()).isEqualTo(fallbackSuppplierAwarenessValue);
+        assertThat(mService.getLastDistractionEvent()).isEqualTo(
+                new DriverDistractionChangeEvent.Builder()
+                        // time is when the event expired
+                        .setElapsedRealtimeTimestamp(mTimeSource.elapsedRealtime())
+                        .setAwarenessPercentage(fallbackSuppplierAwarenessValue)
+                        .build());
+    }
+
+
+    @Test
+    public void testGetLastDistractionEvent_noEvents_returnsDefault() throws Exception {
+        assertThat(mService.getLastDistractionEvent()).isEqualTo(
+                new DriverDistractionChangeEvent.Builder()
+                        .setElapsedRealtimeTimestamp(INITIAL_TIME)
+                        .setAwarenessPercentage(DEFAULT_AWARENESS_PERCENTAGE)
+                        .build());
+    }
+
+    @Test
+    public void testGetLastDistractionEvent_afterEventEmit_returnsLastEvent() throws Exception {
+        setVehicleMoving();
+        mService.setDriverAwarenessSuppliers(Arrays.asList(
+                new Pair<>(mFallbackSupplier, mFallbackConfig)));
+
+        float firstAwarenessValue = 0.7f;
+        mTimeSource.setElapsedRealtime(INITIAL_TIME + 1);
+        emitEvent(mFallbackSupplier, mTimeSource.elapsedRealtime(), firstAwarenessValue);
+
+        assertThat(mService.getLastDistractionEvent()).isEqualTo(
+                new DriverDistractionChangeEvent.Builder()
+                        .setElapsedRealtimeTimestamp(INITIAL_TIME + 1)
+                        .setAwarenessPercentage(0.7f)
+                        .build());
+    }
+
+    @Test
+    public void testManagerRegister_returnsInitialEvent() throws Exception {
+        long eventWaitTimeMs = 300;
+
+        mService.setDriverAwarenessSuppliers(Arrays.asList(
+                new Pair<>(mFallbackSupplier, mFallbackConfig)));
+        resetChangeEventWait();
+        mManager.addDriverDistractionChangeListener(mChangeListener);
+
+        assertThat(waitForCallbackEvent(eventWaitTimeMs)).isTrue();
+        assertThat(mLastDistractionEvent).isEqualTo(
+                new DriverDistractionChangeEvent.Builder()
+                        .setElapsedRealtimeTimestamp(mTimeSource.elapsedRealtime())
+                        .setAwarenessPercentage(DEFAULT_AWARENESS_PERCENTAGE)
+                        .build());
+    }
+
+    @Test
+    public void testManagerRegister_distractionValueUnchanged_doesNotEmitEvent() throws Exception {
+        long eventWaitTimeMs = 300;
+
+        mService.setDriverAwarenessSuppliers(Arrays.asList(
+                new Pair<>(mFallbackSupplier, mFallbackConfig)));
+        resetChangeEventWait();
+        mManager.addDriverDistractionChangeListener(mChangeListener);
+
+        assertThat(waitForCallbackEvent(eventWaitTimeMs)).isTrue();
+        assertThat(mLastDistractionEvent).isEqualTo(
+                new DriverDistractionChangeEvent.Builder()
+                        .setElapsedRealtimeTimestamp(mTimeSource.elapsedRealtime())
+                        .setAwarenessPercentage(DEFAULT_AWARENESS_PERCENTAGE)
+                        .build());
+
+        float firstAwarenessValue = 1.0f;
+        mTimeSource.setElapsedRealtime(INITIAL_TIME + 1);
+        resetChangeEventWait();
+        emitEvent(mFallbackSupplier, mTimeSource.elapsedRealtime(), firstAwarenessValue);
+
+        assertThat(waitForCallbackEvent(eventWaitTimeMs)).isFalse();
+    }
+
+    @Test
+    public void testManagerRegister_receivesChangeEvents() throws Exception {
+        setVehicleMoving();
+        long eventWaitTimeMs = 300;
+
+        mService.setDriverAwarenessSuppliers(Arrays.asList(
+                new Pair<>(mFallbackSupplier, mFallbackConfig)));
+        resetChangeEventWait();
+        mManager.addDriverDistractionChangeListener(mChangeListener);
+
+        assertThat(waitForCallbackEvent(eventWaitTimeMs)).isTrue();
+        assertThat(mLastDistractionEvent).isEqualTo(
+                new DriverDistractionChangeEvent.Builder()
+                        .setElapsedRealtimeTimestamp(mTimeSource.elapsedRealtime())
+                        .setAwarenessPercentage(DEFAULT_AWARENESS_PERCENTAGE)
+                        .build());
+
+        float firstAwarenessValue = 0.7f;
+        mTimeSource.setElapsedRealtime(INITIAL_TIME + 1);
+        resetChangeEventWait();
+        emitEvent(mFallbackSupplier, mTimeSource.elapsedRealtime(), firstAwarenessValue);
+
+        assertThat(waitForCallbackEvent(eventWaitTimeMs)).isTrue();
+        assertThat(mLastDistractionEvent).isEqualTo(
+                new DriverDistractionChangeEvent.Builder()
+                        .setElapsedRealtimeTimestamp(mTimeSource.elapsedRealtime())
+                        .setAwarenessPercentage(firstAwarenessValue)
+                        .build());
+    }
+
+    @Test
+    public void testManagerRegisterUnregister_stopsReceivingEvents() throws Exception {
+        long eventWaitTimeMs = 300;
+
+        mService.setDriverAwarenessSuppliers(Arrays.asList(
+                new Pair<>(mFallbackSupplier, mFallbackConfig)));
+        resetChangeEventWait();
+        mManager.addDriverDistractionChangeListener(mChangeListener);
+        mManager.removeDriverDistractionChangeListener(mChangeListener);
+
+        float firstAwarenessValue = 0.8f;
+        mTimeSource.setElapsedRealtime(INITIAL_TIME + 1);
+        resetChangeEventWait();
+        emitEvent(mFallbackSupplier, mTimeSource.elapsedRealtime(), firstAwarenessValue);
+
+        assertThat(waitForCallbackEvent(eventWaitTimeMs)).isFalse();
+    }
+
+    @Test
+    public void testManagerUnregister_beforeRegister_doesNothing() throws Exception {
+        mManager.removeDriverDistractionChangeListener(mChangeListener);
+    }
+
+    @Test
+    public void testDistractionEvent_noSpeedEventsReceived_awarenessPercentageRemainsFull()
+            throws Exception {
+        mService.setDriverAwarenessSuppliers(Arrays.asList(
+                new Pair<>(mFallbackSupplier, mFallbackConfig)));
+
+        float firstAwarenessValue = 0.7f;
+        mTimeSource.setElapsedRealtime(INITIAL_TIME + 1);
+        emitEvent(mFallbackSupplier, mTimeSource.elapsedRealtime(), firstAwarenessValue);
+
+        // No new distraction event since required awareness is 0 until a speed change occurs
+        assertThat(mService.getLastDistractionEvent()).isEqualTo(
+                new DriverDistractionChangeEvent.Builder()
+                        .setElapsedRealtimeTimestamp(INITIAL_TIME)
+                        .setAwarenessPercentage(1.0f)
+                        .build());
+    }
+
+    @Test
+    public void testRequiredAwareness_multipleSpeedChanges_emitsSingleEvent()
+            throws Exception {
+        setVehicleStopped();
+        mService.setDriverAwarenessSuppliers(Arrays.asList(
+                new Pair<>(mFallbackSupplier, mFallbackConfig)));
+
+        float firstAwarenessValue = 0.7f;
+        mTimeSource.setElapsedRealtime(INITIAL_TIME + 1);
+        emitEvent(mFallbackSupplier, mTimeSource.elapsedRealtime(), firstAwarenessValue);
+        mService.handleSpeedEventLocked(
+                new CarPropertyValue<>(VehiclePropertyIds.PERF_VEHICLE_SPEED, 0, 30.0f));
+
+        // Receive the first speed change event
+        assertThat(mService.getLastDistractionEvent()).isEqualTo(
+                new DriverDistractionChangeEvent.Builder()
+                        .setElapsedRealtimeTimestamp(INITIAL_TIME + 1)
+                        .setAwarenessPercentage(0.7f)
+                        .build());
+
+        mTimeSource.setElapsedRealtime(INITIAL_TIME + 2);
+        mService.handleSpeedEventLocked(
+                new CarPropertyValue<>(VehiclePropertyIds.PERF_VEHICLE_SPEED, 0, 20.0f));
+        mTimeSource.setElapsedRealtime(INITIAL_TIME + 3);
+        mService.handleSpeedEventLocked(
+                new CarPropertyValue<>(VehiclePropertyIds.PERF_VEHICLE_SPEED, 0, 40.0f));
+
+        // Speed changes when already in a moving state don't trigger a new distraction event
+        assertThat(mService.getLastDistractionEvent()).isEqualTo(
+                new DriverDistractionChangeEvent.Builder()
+                        .setElapsedRealtimeTimestamp(INITIAL_TIME + 1)
+                        .setAwarenessPercentage(0.7f)
+                        .build());
+    }
+
+    @Test
+    public void testDistractionEvent_vehicleBecomesStopped_emitsFullAwareness() throws Exception {
+        mService.setDriverAwarenessSuppliers(Arrays.asList(
+                new Pair<>(mFallbackSupplier, mFallbackConfig)));
+
+        setVehicleMoving();
+        float firstAwarenessValue = 0.7f;
+        mTimeSource.setElapsedRealtime(INITIAL_TIME + 1);
+        emitEvent(mFallbackSupplier, mTimeSource.elapsedRealtime(), firstAwarenessValue);
+
+        mTimeSource.setElapsedRealtime(INITIAL_TIME + 2);
+        setVehicleStopped();
+
+        // Awareness percentage is 1.0 since the vehicle is stopped, even though driver awareness
+        // is 0.7
+        assertThat(mService.getLastDistractionEvent()).isEqualTo(
+                new DriverDistractionChangeEvent.Builder()
+                        .setElapsedRealtimeTimestamp(INITIAL_TIME + 2)
+                        .setAwarenessPercentage(1.0f)
+                        .build());
+    }
+
+    private void setVehicleMoving() {
+        mService.handleSpeedEventLocked(
+                new CarPropertyValue<>(VehiclePropertyIds.PERF_VEHICLE_SPEED, 0, 30.0f));
+    }
+
+    private void setVehicleStopped() {
+        mService.handleSpeedEventLocked(
+                new CarPropertyValue<>(VehiclePropertyIds.PERF_VEHICLE_SPEED, 0, 0.0f));
+    }
+
+    private void resetChangeEventWait() {
+        mLastDistractionEvent = null;
+        mChangeEventSignal.drainPermits();
+    }
+
+    private boolean waitForCallbackEvent(long timeoutMs) {
+        boolean acquired = false;
+        try {
+            acquired = mChangeEventSignal.tryAcquire(timeoutMs, TimeUnit.MILLISECONDS);
+        } catch (Exception ignored) {
+
+        }
+        return acquired;
+    }
+
+    private float getCurrentAwarenessValue() {
+        return mService.getCurrentDriverAwareness().mAwarenessEvent.getAwarenessValue();
+    }
+
+    /**
+     * Handle an event as if it were emitted from the specified supplier with the specified time and
+     * value.
+     */
+    private void emitEvent(IDriverAwarenessSupplier supplier, long time, float value)
+            throws RemoteException {
+        long maxStaleness;
+        if (supplier == mFallbackSupplier) {
+            maxStaleness = DriverAwarenessSupplierService.NO_STALENESS;
+        } else {
+            maxStaleness = PREFERRED_SUPPLIER_STALENESS;
+        }
+        mService.handleDriverAwarenessEvent(
+                new DriverDistractionExperimentalFeatureService.DriverAwarenessEventWrapper(
+                        new DriverAwarenessEvent(time, value),
+                        supplier,
+                        maxStaleness));
+    }
+}
diff --git a/experimental/tests/experimentalcarservice_unit_test/src/com/android/experimentalcar/FakeTimeSource.java b/experimental/tests/experimentalcarservice_unit_test/src/com/android/experimentalcar/FakeTimeSource.java
new file mode 100644
index 0000000..eb82e1e
--- /dev/null
+++ b/experimental/tests/experimentalcarservice_unit_test/src/com/android/experimentalcar/FakeTimeSource.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.experimentalcar;
+
+/**
+ * Fake implementation of {@link ITimeSource}.
+ */
+public class FakeTimeSource implements ITimeSource {
+
+    private long mElapsedRealtime;
+
+    /**
+     * Create an instance of {@link FakeTimeSource} with an initial time.
+     */
+    FakeTimeSource(long elapsedRealtime) {
+        mElapsedRealtime = elapsedRealtime;
+    }
+
+    /**
+     * Set the value that will be returned {@link #elapsedRealtime()}.
+     */
+    void setElapsedRealtime(long elapsedRealtime) {
+        mElapsedRealtime = elapsedRealtime;
+    }
+
+    @Override
+    public long elapsedRealtime() {
+        return mElapsedRealtime;
+    }
+}
diff --git a/experimental/tests/experimentalcarservice_unit_test/src/com/android/experimentalcar/FakeTimer.java b/experimental/tests/experimentalcarservice_unit_test/src/com/android/experimentalcar/FakeTimer.java
new file mode 100644
index 0000000..0bd9748
--- /dev/null
+++ b/experimental/tests/experimentalcarservice_unit_test/src/com/android/experimentalcar/FakeTimer.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.experimentalcar;
+
+import java.util.TimerTask;
+
+/**
+ * Fake implementation for {@link ITimer}.
+ */
+public class FakeTimer implements ITimer {
+
+    private TimerTask mPendingTask;
+
+    @Override
+    public void reset() {
+        mPendingTask = null;
+    }
+
+    @Override
+    public void schedule(TimerTask task, long delay) {
+        mPendingTask = task;
+    }
+
+    /**
+     * Immediately execute the scheduled task.
+     */
+    void executePendingTask() {
+        mPendingTask.run();
+    }
+}
diff --git a/experimental/tests/experimentalcarservice_unit_test/src/com/android/experimentalcar/GazeAttentionProcessorTest.java b/experimental/tests/experimentalcarservice_unit_test/src/com/android/experimentalcar/GazeAttentionProcessorTest.java
new file mode 100644
index 0000000..82d60bb
--- /dev/null
+++ b/experimental/tests/experimentalcarservice_unit_test/src/com/android/experimentalcar/GazeAttentionProcessorTest.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.experimentalcar;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertTrue;
+
+import android.car.occupantawareness.GazeDetection;
+import android.car.occupantawareness.OccupantAwarenessDetection;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.junit.MockitoJUnitRunner;
+
+@RunWith(MockitoJUnitRunner.class)
+public class GazeAttentionProcessorTest {
+
+    private static final long FRAME_TIME_MILLIS = 1000; // Milliseconds
+    private static final float INITIAL_VALUE = 1.0f;
+    private static final float GROWTH_RATE = 0.4f;
+    private static final float DECAY_RATE = 0.6f;
+
+    private GazeAttentionProcessor mAttentionProcessor;
+
+    @Before
+    public void setUp() {
+        mAttentionProcessor =
+                new GazeAttentionProcessor(
+                        new GazeAttentionProcessor.Configuration(
+                                INITIAL_VALUE, DECAY_RATE, GROWTH_RATE));
+    }
+
+    @Test
+    public void test_initialValue() {
+        // Initial attention value should match input configuration.
+        assertThat(mAttentionProcessor.getAttention()).isEqualTo(INITIAL_VALUE);
+    }
+
+    @Test
+    public void testUpdateAttention_neverExceedsOne() {
+        // Maximum awareness should never exceed '1'.
+        long timestamp = 1234;
+        final GazeDetection onRoadDetection =
+                buildGazeDetection(GazeDetection.VEHICLE_REGION_FORWARD_ROADWAY);
+
+        // First pass should return initial value.
+        assertThat(mAttentionProcessor.updateAttention(onRoadDetection, timestamp))
+                .isEqualTo(INITIAL_VALUE);
+        timestamp += FRAME_TIME_MILLIS;
+
+        for (int i = 0; i < 100; i++) {
+            // Running on-road again with maxed out attention should max out at '1'.
+            assertTrue(mAttentionProcessor.updateAttention(onRoadDetection, timestamp) <= 1.0f);
+            timestamp += FRAME_TIME_MILLIS;
+        }
+    }
+
+    @Test
+    public void testUpdateAttention_neverBelowZero() {
+        // Minimum awareness should never fall below '0'.
+        long timestamp = 1234;
+        final GazeDetection offRoadDetection =
+                buildGazeDetection(GazeDetection.VEHICLE_REGION_HEAD_UNIT_DISPLAY);
+
+        // First pass should return initial value.
+        assertThat(mAttentionProcessor.updateAttention(offRoadDetection, timestamp))
+                .isEqualTo(INITIAL_VALUE);
+        timestamp += FRAME_TIME_MILLIS;
+
+        for (int i = 0; i < 100; i++) {
+            // Running on-road again with maxed out attention should max out at '1'.
+            assertTrue(mAttentionProcessor.updateAttention(offRoadDetection, timestamp) >= 0);
+            timestamp += FRAME_TIME_MILLIS;
+        }
+    }
+
+    @Test
+    public void testUpdateAttention_offRoadAndOnRoad() {
+
+        final GazeDetection onRoadDetection =
+                buildGazeDetection(GazeDetection.VEHICLE_REGION_FORWARD_ROADWAY);
+
+        final GazeDetection offRoadDetection =
+                buildGazeDetection(GazeDetection.VEHICLE_REGION_HEAD_UNIT_DISPLAY);
+
+        long timestamp = 1234;
+
+        // First pass should return initial value.
+        assertThat(mAttentionProcessor.updateAttention(onRoadDetection, timestamp))
+                .isEqualTo(INITIAL_VALUE);
+        timestamp += FRAME_TIME_MILLIS;
+
+        // Looking off-road should decay the attention.
+        assertThat(mAttentionProcessor.updateAttention(offRoadDetection, timestamp))
+                .isEqualTo(1.0f - DECAY_RATE);
+        timestamp += FRAME_TIME_MILLIS;
+
+        // Looking back up at the roadway should increase the buffer.
+        assertThat(mAttentionProcessor.updateAttention(onRoadDetection, timestamp))
+                .isEqualTo(1.0f - DECAY_RATE + GROWTH_RATE);
+        timestamp += FRAME_TIME_MILLIS;
+    }
+
+    /** Builds a {link GazeDetection} with the specified target for testing. */
+    private GazeDetection buildGazeDetection(@GazeDetection.VehicleRegion int gazeTarget) {
+        return new GazeDetection(
+                OccupantAwarenessDetection.CONFIDENCE_LEVEL_HIGH,
+                null /*leftEyePosition*/,
+                null /*rightEyePosition*/,
+                null /*headAngleUnitVector*/,
+                null /*gazeAngleUnitVector*/,
+                gazeTarget,
+                FRAME_TIME_MILLIS);
+    }
+}
diff --git a/experimental/tests/experimentalcarservice_unit_test/src/com/android/experimentalcar/GazeDriverAwarenessSupplierTest.java b/experimental/tests/experimentalcarservice_unit_test/src/com/android/experimentalcar/GazeDriverAwarenessSupplierTest.java
new file mode 100644
index 0000000..c1f7581
--- /dev/null
+++ b/experimental/tests/experimentalcarservice_unit_test/src/com/android/experimentalcar/GazeDriverAwarenessSupplierTest.java
@@ -0,0 +1,161 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.experimentalcar;
+
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.car.experimental.DriverAwarenessEvent;
+import android.car.occupantawareness.GazeDetection;
+import android.car.occupantawareness.OccupantAwarenessDetection;
+import android.content.Context;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.junit.MockitoJUnitRunner;
+
+@RunWith(MockitoJUnitRunner.class)
+public class GazeDriverAwarenessSupplierTest {
+
+    private static final long START_TIME_MILLIS = 1234L;
+    private static final long FRAME_TIME_MILLIS = 1000L;
+
+    private Context mSpyContext;
+    private GazeDriverAwarenessSupplier mGazeSupplier;
+    private float mInitialValue;
+    private float mGrowthRate;
+    private float mDecayRate;
+    private FakeTimeSource mTimeSource;
+
+    @Before
+    public void setUp() throws Exception {
+        mSpyContext = spy(InstrumentationRegistry.getInstrumentation().getTargetContext());
+
+        mInitialValue =
+                mSpyContext
+                        .getResources()
+                        .getFloat(R.fraction.driverAwarenessGazeModelInitialValue);
+        mGrowthRate =
+                mSpyContext.getResources().getFloat(R.fraction.driverAwarenessGazeModelGrowthRate);
+        mDecayRate =
+                mSpyContext.getResources().getFloat(R.fraction.driverAwarenessGazeModelDecayRate);
+
+        mTimeSource = new FakeTimeSource(START_TIME_MILLIS);
+        mGazeSupplier = spy(new GazeDriverAwarenessSupplier(mSpyContext, mTimeSource));
+    }
+
+    @Test
+    public void testonReady_initialCallbackIsGenerated() throws Exception {
+        // Supplier should return an initial callback after onReady().
+        mGazeSupplier.onReady();
+
+        verify(mGazeSupplier)
+                .emitAwarenessEvent(new DriverAwarenessEvent(START_TIME_MILLIS, mInitialValue));
+    }
+
+    @Test
+    public void testprocessDetectionEvent_noGazeDataProvided() throws Exception {
+        // If detection events happen with *no gaze*, no further events should be generated by the
+        // attention supplier.
+        mGazeSupplier.onReady();
+
+        mGazeSupplier.processDetectionEvent(buildEmptyDetection(START_TIME_MILLIS));
+
+        // Should have exactly one call from the initial onReady(), but no further events.
+        verify(mGazeSupplier, times(1))
+                .emitAwarenessEvent(new DriverAwarenessEvent(START_TIME_MILLIS, mInitialValue));
+    }
+
+    @Test
+    public void testprocessDetectionEvent_neverExceedsOne() throws Exception {
+        // Attention value should never exceed '1' no matter how long the driver looks on-road.
+        mGazeSupplier.onReady();
+
+        // Should have initial callback from onReady().
+        verify(mGazeSupplier)
+                .emitAwarenessEvent(new DriverAwarenessEvent(START_TIME_MILLIS, mInitialValue));
+
+        long timestamp = START_TIME_MILLIS + FRAME_TIME_MILLIS;
+        float attention = mInitialValue;
+
+        for (int i = 0; i < 100; i++) {
+            OccupantAwarenessDetection detection =
+                    buildGazeDetection(timestamp, GazeDetection.VEHICLE_REGION_FORWARD_ROADWAY);
+            mGazeSupplier.processDetectionEvent(detection);
+
+            verify(mGazeSupplier)
+                    .emitAwarenessEvent(new DriverAwarenessEvent(timestamp, attention));
+
+            // Increase attention, but not past 1.
+            attention = Math.min(attention + mGrowthRate, 1.0f);
+            timestamp += FRAME_TIME_MILLIS;
+        }
+    }
+
+    @Test
+    public void testprocessDetectionEvent_neverFallsBelowZero() throws Exception {
+        // Attention value should never fall below '0' no matter how long the driver looks off-road.
+        mGazeSupplier.onReady();
+
+        // Should have initial callback from onReady().
+        verify(mGazeSupplier)
+                .emitAwarenessEvent(new DriverAwarenessEvent(START_TIME_MILLIS, mInitialValue));
+
+        long timestamp = START_TIME_MILLIS + FRAME_TIME_MILLIS;
+        float attention = mInitialValue;
+
+        for (int i = 0; i < 100; i++) {
+            OccupantAwarenessDetection detection =
+                    buildGazeDetection(timestamp, GazeDetection.VEHICLE_REGION_HEAD_UNIT_DISPLAY);
+            mGazeSupplier.processDetectionEvent(detection);
+
+            verify(mGazeSupplier)
+                    .emitAwarenessEvent(new DriverAwarenessEvent(timestamp, attention));
+
+            // Decrement the attention, but not past 0.
+            attention = Math.max(attention - mDecayRate, 0);
+            timestamp += FRAME_TIME_MILLIS;
+        }
+    }
+
+    /** Builds a {link OccupantAwarenessDetection} with the specified target for testing. */
+    private OccupantAwarenessDetection buildGazeDetection(
+            long timestamp, @GazeDetection.VehicleRegion int gazeTarget) {
+        GazeDetection gaze =
+                new GazeDetection(
+                        OccupantAwarenessDetection.CONFIDENCE_LEVEL_HIGH,
+                        null /*leftEyePosition*/,
+                        null /*rightEyePosition*/,
+                        null /*headAngleUnitVector*/,
+                        null /*gazeAngleUnitVector*/,
+                        gazeTarget,
+                        FRAME_TIME_MILLIS);
+
+        return new OccupantAwarenessDetection(
+                OccupantAwarenessDetection.VEHICLE_OCCUPANT_DRIVER, timestamp, true, gaze, null);
+    }
+
+    /** Builds a {link OccupantAwarenessDetection} with the specified target for testing. */
+    private OccupantAwarenessDetection buildEmptyDetection(long timestamp) {
+        return new OccupantAwarenessDetection(
+                OccupantAwarenessDetection.VEHICLE_OCCUPANT_DRIVER, timestamp, true, null, null);
+    }
+}
diff --git a/experimental/tests/experimentalcarservice_unit_test/src/com/android/experimentalcar/TouchDriverAwarenessSupplierTest.java b/experimental/tests/experimentalcarservice_unit_test/src/com/android/experimentalcar/TouchDriverAwarenessSupplierTest.java
new file mode 100644
index 0000000..5ca40eb
--- /dev/null
+++ b/experimental/tests/experimentalcarservice_unit_test/src/com/android/experimentalcar/TouchDriverAwarenessSupplierTest.java
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.experimentalcar;
+
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+
+import android.car.experimental.DriverAwarenessEvent;
+import android.car.experimental.IDriverAwarenessSupplierCallback;
+import android.content.Context;
+import android.content.res.Resources;
+import android.os.Looper;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+
+import java.util.concurrent.ScheduledExecutorService;
+
+@RunWith(MockitoJUnitRunner.class)
+public class TouchDriverAwarenessSupplierTest {
+
+    private static final long INITIAL_TIME = 1000L;
+
+    @Mock
+    private ScheduledExecutorService mScheduledExecutorService;
+
+    @Mock
+    private Looper mLooper;
+
+    @Mock
+    private IDriverAwarenessSupplierCallback mSupplierCallback;
+
+    private TouchDriverAwarenessSupplier mTouchSupplier;
+    private int mMaxPermitsConfig;
+    private int mPermitThrottleMs;
+    private FakeTimeSource mTimeSource;
+    private Context mSpyContext;
+
+    @Before
+    public void setUp() throws Exception {
+        mSpyContext = spy(InstrumentationRegistry.getInstrumentation().getTargetContext());
+        mMaxPermitsConfig = mSpyContext.getResources().getInteger(
+                R.integer.driverAwarenessTouchModelMaxPermits);
+        mPermitThrottleMs = mSpyContext.getResources().getInteger(
+                R.integer.driverAwarenessTouchModelThrottleMs);
+        mTimeSource = new FakeTimeSource(INITIAL_TIME);
+        mTouchSupplier = spy(
+                new TouchDriverAwarenessSupplier(mSpyContext, mSupplierCallback,
+                        mScheduledExecutorService, mLooper,
+                        mTimeSource));
+        doNothing().when(mTouchSupplier).startTouchMonitoring();
+    }
+
+    @Test
+    public void testReady_initializesPermitsToMaxFromConfig() throws Exception {
+        mTouchSupplier.onReady();
+
+        verify(mSupplierCallback).onDriverAwarenessUpdated(
+                new DriverAwarenessEvent(INITIAL_TIME,
+                        TouchDriverAwarenessSupplier.INITIAL_DRIVER_AWARENESS_VALUE));
+    }
+
+    @Test
+    public void testconsumePermitLocked_decrementsPermitCount() throws Exception {
+        mTouchSupplier.onReady();
+
+        mTouchSupplier.consumePermitLocked(INITIAL_TIME);
+
+        verify(mSupplierCallback).onDriverAwarenessUpdated(
+                new DriverAwarenessEvent(INITIAL_TIME,
+                        awarenessValueForPermitCount(mMaxPermitsConfig)));
+    }
+
+    @Test
+    public void testconsumePermitLocked_decrementIgnoredInThrottleWindow() throws Exception {
+        mTouchSupplier.onReady();
+
+        mTouchSupplier.consumePermitLocked(INITIAL_TIME);
+        mTouchSupplier.consumePermitLocked(INITIAL_TIME + 1);
+
+        verify(mSupplierCallback).onDriverAwarenessUpdated(
+                new DriverAwarenessEvent(INITIAL_TIME,
+                        awarenessValueForPermitCount(mMaxPermitsConfig)));
+    }
+
+    @Test
+    public void testconsumePermitLocked_decrementsToMinOf0() throws Exception {
+        int lowMaxPermits = 1;
+
+        Resources spyResources = spy(mSpyContext.getResources());
+        doReturn(spyResources).when(mSpyContext).getResources();
+        doReturn(lowMaxPermits).when(spyResources).getInteger(
+                eq(R.integer.driverAwarenessTouchModelMaxPermits));
+        mTouchSupplier.onReady();
+
+        verify(mSupplierCallback).onDriverAwarenessUpdated(
+                new DriverAwarenessEvent(INITIAL_TIME, 1f));
+        long firstEventTimestamp = INITIAL_TIME + 1 + mPermitThrottleMs;
+        mTouchSupplier.consumePermitLocked(firstEventTimestamp);
+        verify(mSupplierCallback).onDriverAwarenessUpdated(
+                new DriverAwarenessEvent(firstEventTimestamp, 0f));
+        long secondEventTimestamp = INITIAL_TIME + 1 + mPermitThrottleMs;
+        mTouchSupplier.consumePermitLocked(secondEventTimestamp);
+
+        verify(mSupplierCallback).onDriverAwarenessUpdated(
+                new DriverAwarenessEvent(secondEventTimestamp, 0f));
+    }
+
+    @Test
+    public void testhandlePermitRefreshLocked_incrementsPermitCount() throws Exception {
+        mTouchSupplier.onReady();
+
+        mTouchSupplier.consumePermitLocked(INITIAL_TIME);
+        verify(mSupplierCallback).onDriverAwarenessUpdated(
+                new DriverAwarenessEvent(INITIAL_TIME,
+                        awarenessValueForPermitCount(mMaxPermitsConfig - 1)));
+
+        long refreshTimestamp = INITIAL_TIME + 1;
+        mTouchSupplier.handlePermitRefreshLocked(refreshTimestamp);
+        verify(mSupplierCallback).onDriverAwarenessUpdated(
+                new DriverAwarenessEvent(refreshTimestamp,
+                        awarenessValueForPermitCount(mMaxPermitsConfig)));
+    }
+
+    @Test
+    public void testhandlePermitRefreshLocked_incrementsToMaxOfConfig() throws Exception {
+        mTouchSupplier.onReady();
+
+        verify(mSupplierCallback).onDriverAwarenessUpdated(
+                new DriverAwarenessEvent(INITIAL_TIME,
+                        TouchDriverAwarenessSupplier.INITIAL_DRIVER_AWARENESS_VALUE));
+
+        long refreshTimestamp = INITIAL_TIME + 1;
+        mTouchSupplier.handlePermitRefreshLocked(refreshTimestamp);
+        verify(mSupplierCallback).onDriverAwarenessUpdated(
+                new DriverAwarenessEvent(refreshTimestamp,
+                        awarenessValueForPermitCount(mMaxPermitsConfig)));
+    }
+
+    private float awarenessValueForPermitCount(int count) {
+        return count / (float) mMaxPermitsConfig;
+    }
+}
diff --git a/procfs-inspector/client/Android.bp b/procfs-inspector/client/Android.bp
new file mode 100644
index 0000000..91b5e98
--- /dev/null
+++ b/procfs-inspector/client/Android.bp
@@ -0,0 +1,26 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//
+
+java_library {
+
+    name: "com.android.car.procfsinspector-client",
+
+    srcs: [
+        "src/**/*.java",
+        "src/**/I*.aidl",
+    ],
+
+}
diff --git a/procfs-inspector/client/Android.mk b/procfs-inspector/client/Android.mk
deleted file mode 100644
index d9713de..0000000
--- a/procfs-inspector/client/Android.mk
+++ /dev/null
@@ -1,26 +0,0 @@
-# Copyright (C) 2017 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-#
-
-LOCAL_PATH:= $(call my-dir)
-
-include $(CLEAR_VARS)
-
-LOCAL_MODULE := com.android.car.procfsinspector-client
-LOCAL_MODULE_TAGS := optional
-
-LOCAL_SRC_FILES := $(call all-java-files-under, src) $(call all-Iaidl-files-under, src)
-
-include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/procfs-inspector/server/Android.mk b/procfs-inspector/server/Android.mk
index fab1cf8..6924ef5 100644
--- a/procfs-inspector/server/Android.mk
+++ b/procfs-inspector/server/Android.mk
@@ -25,9 +25,6 @@
     process.cpp \
     directory.cpp
 
-LOCAL_C_INCLUDES += \
-    frameworks/base/include
-
 LOCAL_SHARED_LIBRARIES := \
     libbinder \
     liblog \
diff --git a/service/Android.bp b/service/Android.bp
new file mode 100644
index 0000000..06d120b
--- /dev/null
+++ b/service/Android.bp
@@ -0,0 +1,153 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//
+
+// Build the Car service.
+
+genrule {
+    name: "statslog-Car-java-gen",
+    tools: ["stats-log-api-gen"],
+    cmd: "$(location stats-log-api-gen) --java $(out) --module car --javaPackage com.android.car"
+        + " --javaClass CarStatsLog",
+    out: ["com/android/car/CarStatsLog.java"],
+}
+
+car_service_sources = [
+    "src/**/*.java",
+    ":statslog-Car-java-gen",
+]
+
+common_lib_deps = [
+    "android.car.userlib",
+    "android.hidl.base-V1.0-java",
+    "android.hardware.automotive.audiocontrol-V1.0-java",
+    "android.hardware.automotive.vehicle-V2.0-java",
+    "android.hardware.health-V1.0-java",
+    "android.hardware.health-V2.0-java",
+    "android.hardware.automotive.occupant_awareness-java",
+    "vehicle-hal-support-lib",
+    "car-systemtest",
+    "com.android.car.procfsinspector-client",
+    "blestream-protos",
+    "SettingsLib",
+    "androidx.preference_preference",
+    "EncryptionRunner",
+]
+
+android_app {
+    name: "CarService",
+
+    srcs: car_service_sources,
+
+    resource_dirs: ["res"],
+
+    platform_apis: true,
+
+    // Each update should be signed by OEMs
+    certificate: "platform",
+    privileged: true,
+
+    optimize: {
+        proguard_flags_files: ["proguard.flags"],
+        enabled: false,
+    },
+
+    libs: ["android.car"],
+
+    static_libs: common_lib_deps + [
+        "car-frameworks-service",
+    ],
+
+    required: ["privapp_whitelist_com.android.car"],
+
+    // Disable build in PDK, missing aidl import breaks build
+    product_variables: {
+        pdk: {
+            enabled: false,
+        },
+    },
+}
+
+java_library {
+
+    name: "car-service-common-util-static-lib",
+
+    srcs: [
+        "src/com/android/car/CarServiceBase.java",
+        "src/com/android/car/CarServiceUtils.java",
+        "src/com/android/car/CarLog.java",
+        "src/com/android/car/Utils.java",
+    ],
+
+    product_variables: {
+            pdk: {
+                enabled: false,
+            },
+    },
+}
+
+//####################################################################################
+// Build a static library to help mocking various car services in testing. This is meant to be used
+// for internal unit tests around the car service.
+//####################################################################################
+android_library {
+    name: "car-service-test-static-lib",
+
+    srcs: car_service_sources,
+
+    resource_dirs: ["res"],
+
+    libs: [
+        "android.car",
+        "car-frameworks-service",
+    ],
+
+    static_libs: common_lib_deps,
+
+    min_sdk_version: "25",
+
+    product_variables: {
+        pdk: {
+            enabled: false,
+        },
+    },
+}
+
+//####################################################################################
+// Build a library to help generate a testing library for external apps.
+// We do not want to use statically linked libraries, as this will bloat the output jar with classes
+// that can conflict with the user's environment.
+//####################################################################################
+android_library {
+    name: "car-service-test-lib",
+
+    srcs: car_service_sources,
+
+    resource_dirs: ["res"],
+
+    libs: common_lib_deps + [
+        "android.car",
+        "car-frameworks-service",
+    ],
+
+    min_sdk_version: "25",
+
+    product_variables: {
+        pdk: {
+            enabled: false,
+        },
+    },
+}
+
diff --git a/service/Android.mk b/service/Android.mk
deleted file mode 100644
index f9d6032..0000000
--- a/service/Android.mk
+++ /dev/null
@@ -1,107 +0,0 @@
-# Copyright (C) 2015 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-#
-
-# Build the Car service.
-
-#disble build in PDK, missing aidl import breaks build
-ifneq ($(TARGET_BUILD_PDK),true)
-
-LOCAL_PATH:= $(call my-dir)
-
-car_service_sources := $(call all-java-files-under, src)
-
-include $(CLEAR_VARS)
-
-LOCAL_SRC_FILES := $(car_service_sources)
-
-LOCAL_USE_AAPT2 := true
-
-LOCAL_PACKAGE_NAME := CarService
-LOCAL_PRIVATE_PLATFORM_APIS := true
-
-# Each update should be signed by OEMs
-LOCAL_CERTIFICATE := platform
-LOCAL_PRIVILEGED_MODULE := true
-
-LOCAL_PROGUARD_FLAG_FILES := proguard.flags
-LOCAL_PROGUARD_ENABLED := disabled
-
-LOCAL_JAVA_LIBRARIES += \
-    android.car \
-
-LOCAL_STATIC_JAVA_LIBRARIES += \
-    android.car.userlib \
-    android.hidl.base-V1.0-java \
-    android.hardware.automotive.audiocontrol-V1.0-java \
-    android.hardware.automotive.vehicle-V2.0-java \
-    android.hardware.health-V1.0-java \
-    android.hardware.health-V2.0-java \
-    vehicle-hal-support-lib \
-    car-frameworks-service \
-    car-systemtest \
-    com.android.car.procfsinspector-client \
-    blestream-protos \
-
-LOCAL_STATIC_ANDROID_LIBRARIES := \
-    SettingsLib \
-    androidx.preference_preference \
-    EncryptionRunner
-
-LOCAL_REQUIRED_MODULES := privapp_whitelist_com.android.car
-
-include $(BUILD_PACKAGE)
-
-#####################################################################################
-# Build a static library to help mocking various car services in testing
-#####################################################################################
-include $(CLEAR_VARS)
-
-LOCAL_SRC_FILES := $(car_service_sources)
-
-LOCAL_USE_AAPT2 := true
-
-LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
-
-LOCAL_MODULE := car-service-lib-for-test
-
-LOCAL_JAVA_LIBRARIES += \
-    android.car \
-    car-frameworks-service \
-
-LOCAL_STATIC_JAVA_LIBRARIES += \
-    android.car.userlib \
-    android.hidl.base-V1.0-java \
-    android.hardware.automotive.audiocontrol-V1.0-java \
-    android.hardware.automotive.vehicle-V2.0-java \
-    android.hardware.health-V1.0-java \
-    android.hardware.health-V2.0-java \
-    vehicle-hal-support-lib \
-    car-systemtest \
-    com.android.car.procfsinspector-client \
-    blestream-protos \
-
-LOCAL_STATIC_ANDROID_LIBRARIES := \
-    SettingsLib \
-    androidx.preference_preference \
-    EncryptionRunner
-
-LOCAL_MIN_SDK_VERSION := 25
-
-include $(BUILD_STATIC_JAVA_LIBRARY)
-
-include $(call all-makefiles-under,$(LOCAL_PATH))
-
-endif #TARGET_BUILD_PDK
diff --git a/service/AndroidManifest.xml b/service/AndroidManifest.xml
index 59ce65d..0809f17 100644
--- a/service/AndroidManifest.xml
+++ b/service/AndroidManifest.xml
@@ -39,6 +39,15 @@
         android:label="@string/car_permission_label_energy"
         android:description="@string/car_permission_desc_energy" />
 
+    <!-- Allows an application to adjust the vehicle's range remaining information.
+         <p>Protection level: signature|privileged
+     -->
+    <permission
+        android:name="android.car.permission.ADJUST_RANGE_REMAINING"
+        android:protectionLevel="signature|privileged"
+        android:label="@string/car_permission_label_adjust_range_remaining"
+        android:description="@string/car_permission_desc_adjust_range_remaining" />
+
     <!-- Allows an application to read the VIN information.
          <p>Protection level: signature|privileged
     -->
@@ -159,6 +168,15 @@
         android:label="@string/car_permission_label_car_energy_ports"
         android:description="@string/car_permission_desc_car_energy_ports" />
 
+    <!-- Allows an application to control the vehicle fuel and charge port status.
+         <p>Protection level: signature|privileged
+    -->
+    <permission
+        android:name="android.car.permission.CONTROL_CAR_ENERGY_PORTS"
+        android:protectionLevel="signature|privileged"
+        android:label="@string/car_permission_label_control_car_energy_ports"
+        android:description="@string/car_permission_desc_control_car_energy_ports" />
+
     <!-- Allows an application to read the vehicle engine information. For example, it allows an
          application to read the engine oil level, oil temperature, coolant temperature and RPM.
          <p>Protection level: signature|privileged
@@ -236,6 +254,15 @@
         android:label="@string/car_permission_label_car_info"
         android:description="@string/car_permission_desc_car_info" />
 
+    <!-- Allows an application to read information of car's vendor permission.
+         <p>Protection level: signature|privileged
+    -->
+    <permission
+        android:name="android.car.permission.READ_CAR_VENDOR_PERMISSION_INFO"
+        android:protectionLevel="signature|privileged"
+        android:label="@string/car_permission_label_vendor_permission_info"
+        android:description="@string/car_permission_desc_vendor_permission_info" />
+
     <!-- Allows an application to read the vehicle exterior environment information. For example,
          it allows an application to read the vehicle exterior temperature and night mode status.
          <p>Protection level: normal
@@ -457,6 +484,24 @@
         android:label="@string/car_permission_label_car_ux_restrictions_configuration"
         android:description="@string/car_permission_desc_car_ux_restrictions_configuration" />
 
+    <!-- Allows an application to read state data from the 'Occupant Awareness System'.
+         <p>Protection level: signature|privileged
+    -->
+    <permission
+        android:name="android.car.permission.READ_CAR_OCCUPANT_AWARENESS_STATE"
+        android:protectionLevel="signature|privileged"
+        android:label="@string/car_permission_label_read_car_occupant_awareness_state"
+        android:description="@string/car_permission_desc_read_car_occupant_awareness_state" />
+
+    <!-- Allows an application to control the detection graph for the 'Occupant Awareness System'.
+         <p>Protection level: signature|privileged
+    -->
+    <permission
+        android:name="android.car.permission.CONTROL_CAR_OCCUPANT_AWARENESS_SYSTEM"
+        android:protectionLevel="signature|privileged"
+        android:label="@string/car_permission_label_control_car_occupant_awareness_system"
+        android:description="@string/car_permission_desc_control_car_occupant_awareness_system" />
+
     <!-- Allows an application to monitor flash storage usage.
          <p>Protection level: signature|privileged
     -->
@@ -485,6 +530,350 @@
         android:label="@string/car_permission_label_car_test_service"
         android:description="@string/car_permission_desc_car_test_service" />
 
+    <!-- Allows system app to enable / disable / query features in the system.
+     <p>Protection level: signature|privileged
+    -->
+    <permission
+        android:name="android.car.permission.CONTROL_CAR_FEATURES"
+        android:protectionLevel="signature|privileged"
+        android:label="@string/car_permission_label_control_car_features"
+        android:description="@string/car_permission_desc_control_car_features" />
+
+    <!-- Allows an application to use car watchdog service.
+        <p>Protection level: signature|privileged
+    -->
+    <permission
+        android:name="android.car.permission.USE_CAR_WATCHDOG"
+        android:protectionLevel="signature|privileged"
+        android:label="@string/car_permission_label_use_car_watchdog"
+        android:description="@string/car_permission_desc_use_car_watchdog" />
+
+    <!-- Allows an application to read vendor properties related with windows.
+         <p>Protection level: signature|privileged
+    -->
+    <permission
+        android:name="android.car.permission.GET_CAR_VENDOR_CATEGORY_WINDOW"
+        android:protectionLevel="signature|privileged"
+        android:label="@string/car_permission_label_get_car_vendor_category_window"
+        android:description="@string/car_permission_desc_get_car_vendor_category_window" />
+
+    <!-- Allows an application to control vendor properties related with windows.
+         <p>Protection level: signature|privileged
+    -->
+    <permission
+        android:name="android.car.permission.SET_CAR_VENDOR_CATEGORY_WINDOW"
+        android:protectionLevel="signature|privileged"
+        android:label="@string/car_permission_label_set_car_vendor_category_window"
+        android:description="@string/car_permission_desc_set_car_vendor_category_window" />
+
+    <!-- Allows an application to read vendor properties related with doors.
+         <p>Protection level: signature|privileged
+    -->
+    <permission
+        android:name="android.car.permission.GET_CAR_VENDOR_CATEGORY_DOOR"
+        android:protectionLevel="signature|privileged"
+        android:label="@string/car_permission_label_get_car_vendor_category_door"
+        android:description="@string/car_permission_desc_get_car_vendor_category_door" />
+
+    <!-- Allows an application to control vendor properties related with doors.
+         <p>Protection level: signature|privileged
+    -->
+    <permission
+        android:name="android.car.permission.SET_CAR_VENDOR_CATEGORY_DOOR"
+        android:protectionLevel="signature|privileged"
+        android:label="@string/car_permission_label_set_car_vendor_category_door"
+        android:description="@string/car_permission_desc_set_car_vendor_category_door" />
+
+
+    <!-- Allows an application to read vendor properties related with seats.
+         <p>Protection level: signature|privileged
+    -->
+    <permission
+        android:name="android.car.permission.GET_CAR_VENDOR_CATEGORY_SEAT"
+        android:protectionLevel="signature|privileged"
+        android:label="@string/car_permission_label_get_car_vendor_category_seat"
+        android:description="@string/car_permission_desc_get_car_vendor_category_seat" />
+
+    <!-- Allows an application to control vendor properties related with seats.
+         <p>Protection level: signature|privileged
+    -->
+    <permission
+        android:name="android.car.permission.SET_CAR_VENDOR_CATEGORY_SEAT"
+        android:protectionLevel="signature|privileged"
+        android:label="@string/car_permission_label_set_car_vendor_category_seat"
+        android:description="@string/car_permission_desc_set_car_vendor_category_seat" />
+
+    <!-- Allows an application to read vendor properties related with mirrors.
+         <p>Protection level: signature|privileged
+    -->
+    <permission
+        android:name="android.car.permission.GET_CAR_VENDOR_CATEGORY_MIRROR"
+        android:protectionLevel="signature|privileged"
+        android:label="@string/car_permission_label_get_car_vendor_category_mirror"
+        android:description="@string/car_permission_desc_get_car_vendor_category_mirror" />
+
+    <!-- Allows an application to control vendor properties related with mirrors.
+         <p>Protection level: signature|privileged
+    -->
+    <permission
+        android:name="android.car.permission.SET_CAR_VENDOR_CATEGORY_MIRROR"
+        android:protectionLevel="signature|privileged"
+        android:label="@string/car_permission_label_set_car_vendor_category_mirror"
+        android:description="@string/car_permission_desc_set_car_vendor_category_mirror" />
+
+    <!-- Allows an application to read vendor properties related with car information.
+         <p>Protection level: signature|privileged
+    -->
+    <permission
+        android:name="android.car.permission.GET_CAR_VENDOR_CATEGORY_INFO"
+        android:protectionLevel="signature|privileged"
+        android:label="@string/car_permission_label_get_car_vendor_category_info"
+        android:description="@string/car_permission_desc_get_car_vendor_category_info" />
+
+    <!-- Allows an application to control vendor properties related with car information.
+         <p>Protection level: signature|privileged
+    -->
+    <permission
+        android:name="android.car.permission.SET_CAR_VENDOR_CATEGORY_INFO"
+        android:protectionLevel="signature|privileged"
+        android:label="@string/car_permission_label_set_car_vendor_category_info"
+        android:description="@string/car_permission_desc_set_car_vendor_category_info" />
+
+    <!-- Allows an application to read vendor properties related with engine.
+         <p>Protection level: signature|privileged
+    -->
+    <permission
+        android:name="android.car.permission.GET_CAR_VENDOR_CATEGORY_ENGINE"
+        android:protectionLevel="signature|privileged"
+        android:label="@string/car_permission_label_get_car_vendor_category_engine"
+        android:description="@string/car_permission_desc_get_car_vendor_category_engine" />
+
+    <!-- Allows an application to control vendor properties related with engine.
+         <p>Protection level: signature|privileged
+    -->
+    <permission
+        android:name="android.car.permission.SET_CAR_VENDOR_CATEGORY_ENGINE"
+        android:protectionLevel="signature|privileged"
+        android:label="@string/car_permission_label_set_car_vendor_category_engine"
+        android:description="@string/car_permission_desc_set_car_vendor_category_engine" />
+
+    <!-- Allows an application to read vendor properties related with HVAC.
+         <p>Protection level: signature|privileged
+    -->
+    <permission
+        android:name="android.car.permission.GET_CAR_VENDOR_CATEGORY_HVAC"
+        android:protectionLevel="signature|privileged"
+        android:label="@string/car_permission_label_get_car_vendor_category_hvac"
+        android:description="@string/car_permission_desc_get_car_vendor_category_hvac" />
+
+    <!-- Allows an application to control vendor properties related with hvac.
+         <p>Protection level: signature|privileged
+    -->
+    <permission
+        android:name="android.car.permission.SET_CAR_VENDOR_CATEGORY_HVAC"
+        android:protectionLevel="signature|privileged"
+        android:label="@string/car_permission_label_set_car_vendor_category_hvac"
+        android:description="@string/car_permission_desc_set_car_vendor_category_hvac" />
+
+    <!-- Allows an application to read vendor properties related with light.
+         <p>Protection level: signature|privileged
+    -->
+    <permission
+        android:name="android.car.permission.GET_CAR_VENDOR_CATEGORY_LIGHT"
+        android:protectionLevel="signature|privileged"
+        android:label="@string/car_permission_label_get_car_vendor_category_light"
+        android:description="@string/car_permission_desc_get_car_vendor_category_light" />
+
+    <!-- Allows an application to control vendor properties related with light.
+         <p>Protection level: signature|privileged
+    -->
+    <permission
+        android:name="android.car.permission.SET_CAR_VENDOR_CATEGORY_LIGHT"
+        android:protectionLevel="signature|privileged"
+        android:label="@string/car_permission_label_set_car_vendor_category_light"
+        android:description="@string/car_permission_desc_set_car_vendor_category_light" />
+
+    <!-- Allows an application to access vendor properties in category 1.
+         <p>Protection level: signature|privileged
+    -->
+    <permission
+        android:name="android.car.permission.GET_CAR_VENDOR_CATEGORY_1"
+        android:protectionLevel="signature|privileged"
+        android:label="@string/car_permission_label_get_car_vendor_category_1"
+        android:description="@string/car_permission_desc_get_car_vendor_category_1" />
+
+    <!-- Allows an application to control vendor properties in category 1.
+        <p>Protection level: signature|privileged
+    -->
+    <permission
+        android:name="android.car.permission.SET_CAR_VENDOR_CATEGORY_1"
+        android:protectionLevel="signature|privileged"
+        android:label="@string/car_permission_label_set_car_vendor_category_1"
+        android:description="@string/car_permission_desc_set_car_vendor_category_1" />
+
+    <!-- Allows an application to access vendor properties in category 2.
+        <p>Protection level: signature|privileged
+    -->
+    <permission
+        android:name="android.car.permission.GET_CAR_VENDOR_CATEGORY_2"
+        android:protectionLevel="signature|privileged"
+        android:label="@string/car_permission_label_get_car_vendor_category_2"
+        android:description="@string/car_permission_desc_get_car_vendor_category_2" />
+
+    <!-- Allows an application to control vendor properties in category 2.
+        <p>Protection level: signature|privileged
+    -->
+    <permission
+        android:name="android.car.permission.SET_CAR_VENDOR_CATEGORY_2"
+        android:protectionLevel="signature|privileged"
+        android:label="@string/car_permission_label_set_car_vendor_category_2"
+        android:description="@string/car_permission_desc_set_car_vendor_category_2" />
+
+    <!-- Allows an application to access vendor properties in category 3.
+        <p>Protection level: signature|privileged
+    -->
+    <permission
+        android:name="android.car.permission.GET_CAR_VENDOR_CATEGORY_3"
+        android:protectionLevel="signature|privileged"
+        android:label="@string/car_permission_label_get_car_vendor_category_3"
+        android:description="@string/car_permission_desc_get_car_vendor_category_3" />
+
+    <!-- Allows an application to control vendor properties in category 3.
+        <p>Protection level: signature|privileged
+    -->
+    <permission
+        android:name="android.car.permission.SET_CAR_VENDOR_CATEGORY_3"
+        android:protectionLevel="signature|privileged"
+        android:label="@string/car_permission_label_set_car_vendor_category_3"
+        android:description="@string/car_permission_desc_set_car_vendor_category_3" />
+
+    <!-- Allows an application to access vendor properties in category 4.
+         <p>Protection level: signature|privileged
+    -->
+    <permission
+        android:name="android.car.permission.GET_CAR_VENDOR_CATEGORY_4"
+        android:protectionLevel="signature|privileged"
+        android:label="@string/car_permission_label_get_car_vendor_category_4"
+        android:description="@string/car_permission_desc_get_car_vendor_category_4" />
+
+    <!-- Allows an application to control vendor properties in category 4.
+        <p>Protection level: signature|privileged
+    -->
+    <permission
+        android:name="android.car.permission.SET_CAR_VENDOR_CATEGORY_4"
+        android:protectionLevel="signature|privileged"
+        android:label="@string/car_permission_label_set_car_vendor_category_4"
+        android:description="@string/car_permission_desc_set_car_vendor_category_4" />
+
+    <!-- Allows an application to access vendor properties in category 5.
+         <p>Protection level: signature|privileged
+    -->
+    <permission
+        android:name="android.car.permission.GET_CAR_VENDOR_CATEGORY_5"
+        android:protectionLevel="signature|privileged"
+        android:label="@string/car_permission_label_get_car_vendor_category_5"
+        android:description="@string/car_permission_desc_get_car_vendor_category_5" />
+
+    <!-- Allows an application to control vendor properties in category 5.
+        <p>Protection level: signature|privileged
+    -->
+    <permission
+        android:name="android.car.permission.SET_CAR_VENDOR_CATEGORY_5"
+        android:protectionLevel="signature|privileged"
+        android:label="@string/car_permission_label_set_car_vendor_category_5"
+        android:description="@string/car_permission_desc_set_car_vendor_category_5" />
+
+    <!-- Allows an application to access vendor properties in category 6.
+         <p>Protection level: signature|privileged
+    -->
+    <permission
+        android:name="android.car.permission.GET_CAR_VENDOR_CATEGORY_6"
+        android:protectionLevel="signature|privileged"
+        android:label="@string/car_permission_label_get_car_vendor_category_6"
+        android:description="@string/car_permission_desc_get_car_vendor_category_6" />
+
+    <!-- Allows an application to control vendor properties in category 6.
+        <p>Protection level: signature|privileged
+    -->
+    <permission
+        android:name="android.car.permission.SET_CAR_VENDOR_CATEGORY_6"
+        android:protectionLevel="signature|privileged"
+        android:label="@string/car_permission_label_set_car_vendor_category_6"
+        android:description="@string/car_permission_desc_set_car_vendor_category_6" />
+
+    <!-- Allows an application to access vendor properties in category 7.
+        <p>Protection level: signature|privileged
+    -->
+    <permission
+        android:name="android.car.permission.GET_CAR_VENDOR_CATEGORY_7"
+        android:protectionLevel="signature|privileged"
+        android:label="@string/car_permission_label_get_car_vendor_category_7"
+        android:description="@string/car_permission_desc_get_car_vendor_category_7" />
+
+    <!-- Allows an application to control vendor properties in category 7.
+        <p>Protection level: signature|privileged
+    -->
+    <permission
+        android:name="android.car.permission.SET_CAR_VENDOR_CATEGORY_7"
+        android:protectionLevel="signature|privileged"
+        android:label="@string/car_permission_label_set_car_vendor_category_7"
+        android:description="@string/car_permission_desc_set_car_vendor_category_7" />
+
+    <!-- Allows an application to access vendor properties in category 8.
+        <p>Protection level: signature|privileged
+    -->
+    <permission
+        android:name="android.car.permission.GET_CAR_VENDOR_CATEGORY_8"
+        android:protectionLevel="signature|privileged"
+        android:label="@string/car_permission_label_get_car_vendor_category_8"
+        android:description="@string/car_permission_desc_get_car_vendor_category_8" />
+
+    <!-- Allows an application to control vendor properties in category 8.
+        <p>Protection level: signature|privileged
+    -->
+    <permission
+        android:name="android.car.permission.SET_CAR_VENDOR_CATEGORY_8"
+        android:protectionLevel="signature|privileged"
+        android:label="@string/car_permission_label_set_car_vendor_category_8"
+        android:description="@string/car_permission_desc_set_car_vendor_category_8" />
+
+    <!-- Allows an application to access vendor properties in category 9.
+         <p>Protection level: signature|privileged
+    -->
+    <permission
+        android:name="android.car.permission.GET_CAR_VENDOR_CATEGORY_9"
+        android:protectionLevel="signature|privileged"
+        android:label="@string/car_permission_label_get_car_vendor_category_9"
+        android:description="@string/car_permission_desc_get_car_vendor_category_9" />
+
+    <!-- Allows an application to control vendor properties in category 9.
+        <p>Protection level: signature|privileged
+    -->
+    <permission
+        android:name="android.car.permission.SET_CAR_VENDOR_CATEGORY_9"
+        android:protectionLevel="signature|privileged"
+        android:label="@string/car_permission_label_set_car_vendor_category_9"
+        android:description="@string/car_permission_desc_set_car_vendor_category_9" />
+
+    <!-- Allows an application to access vendor properties in category 10.
+         <p>Protection level: signature|privileged
+    -->
+    <permission
+        android:name="android.car.permission.GET_CAR_VENDOR_CATEGORY_10"
+        android:protectionLevel="signature|privileged"
+        android:label="@string/car_permission_label_get_car_vendor_category_10"
+        android:description="@string/car_permission_desc_get_car_vendor_category_10" />
+
+    <!-- Allows an application to control vendor properties in category 10.
+        <p>Protection level: signature|privileged
+    -->
+    <permission
+        android:name="android.car.permission.SET_CAR_VENDOR_CATEGORY_10"
+        android:protectionLevel="signature|privileged"
+        android:label="@string/car_permission_label_set_car_vendor_category_10"
+        android:description="@string/car_permission_desc_set_car_vendor_category_10" />
+
+
     <uses-permission android:name="android.permission.CALL_PHONE" />
     <uses-permission android:name="android.permission.DEVICE_POWER" />
     <uses-permission android:name="android.permission.GRANT_RUNTIME_PERMISSIONS" />
diff --git a/service/OWNERS b/service/OWNERS
new file mode 100644
index 0000000..4b56e5e
--- /dev/null
+++ b/service/OWNERS
@@ -0,0 +1,11 @@
+# Project owners
+keunyoung@google.com
+sgurun@google.com
+gurunagarajan@google.com
+pfg@google.com
+kevinme@google.com
+ankitarora@google.com
+swan@google.com
+pirozzoj@google.com
+twasilczyk@google.com
+felipeal@google.com
diff --git a/service/proto/Android.bp b/service/proto/Android.bp
index 8d500ea..92a73f4 100644
--- a/service/proto/Android.bp
+++ b/service/proto/Android.bp
@@ -5,7 +5,6 @@
         type: "lite",
     },
     srcs: ["*.proto"],
-    no_framework_libs: true,
     jarjar_rules: "jarjar-rules.txt",
     sdk_version: "28",
 }
diff --git a/service/proto/ble_device_message.proto b/service/proto/ble_device_message.proto
new file mode 100644
index 0000000..70896e8
--- /dev/null
+++ b/service/proto/ble_device_message.proto
@@ -0,0 +1,23 @@
+syntax = "proto3";
+
+package aae.blemessagestream;
+
+import "packages/services/Car/service/proto/operation_type.proto";
+
+option java_package = "com.android.car.BleStreamProtos";
+option java_outer_classname = "BleDeviceMessageProto";
+
+// A message between devices.
+message BleDeviceMessage {
+  // The operation that this message represents.
+  OperationType operation = 1;
+
+  // Whether the payload field is encrypted.
+  bool is_payload_encrypted = 2;
+
+  // Identifier of the intended recipient.
+  bytes recipient = 3;
+
+  // The bytes that represent the content for this message.
+  bytes payload = 4;
+}
diff --git a/service/proto/ble_packet.proto b/service/proto/ble_packet.proto
new file mode 100644
index 0000000..109365e
--- /dev/null
+++ b/service/proto/ble_packet.proto
@@ -0,0 +1,22 @@
+syntax = "proto3";
+
+package aae.blemessagestream;
+
+option java_package = "com.android.car.BleStreamProtos";
+option java_outer_classname = "BlePacketProto";
+
+// A packet across a BLE channel.
+message BlePacket {
+  // A 1-based packet number. The first message will have a value of "1" rather
+  // than "0".
+  fixed32 packet_number = 1;
+
+  // The total number of packets in the message stream.
+  int32 total_packets = 2;
+
+  // Id of message for reassembly on other side
+  int32 message_id = 3;
+
+  // The bytes that represent the message content for this packet.
+  bytes payload = 4;
+}
diff --git a/service/res/values-af/strings.xml b/service/res/values-af/strings.xml
index 4bdf6d2..b4a103f 100644
--- a/service/res/values-af/strings.xml
+++ b/service/res/values-af/strings.xml
@@ -22,6 +22,8 @@
     <string name="car_permission_desc_camera" msgid="917024932164501426">"Toegang tot motor se kamera(s)."</string>
     <string name="car_permission_label_energy" msgid="7409144323527821558">"toegang tot motor se dryfkraginligting"</string>
     <string name="car_permission_desc_energy" msgid="3392963810053235407">"Toegang tot jou motor se energie-inligting."</string>
+    <string name="car_permission_label_adjust_range_remaining" msgid="839033553999920138">"verstel motor se oorblywende ritafstand"</string>
+    <string name="car_permission_desc_adjust_range_remaining" msgid="2369321650437370673">"Verstel waarde van motor se oorblywende ritafstand."</string>
     <string name="car_permission_label_hvac" msgid="1499454192558727843">"toegang tot motor se HVAC- (verhitting, ventilasie, lugversorging) stelsel"</string>
     <string name="car_permission_desc_hvac" msgid="3754229695589774195">"Toegang tot jou motor se HVAC (verhitting, venitalise en lugversorging)."</string>
     <string name="car_permission_label_mileage" msgid="4661317074631150551">"toegang tot motor se ryafstandinligting"</string>
@@ -62,15 +64,17 @@
     <string name="car_permission_desc_car_ux_restrictions_configuration" msgid="5711926927484813777">"Stel UX-beperkings op"</string>
     <string name="car_permission_label_car_handle_usb_aoap_device" msgid="72783989504378036">"Kommunikeer met USB-toestel in AOAP-modus"</string>
     <string name="car_permission_desc_car_handle_usb_aoap_device" msgid="273505990971317034">"Laat \'n program toe om in AOAP-modus met \'n toestel te kommunikeer"</string>
+    <string name="car_permission_label_read_car_occupant_awareness_state" msgid="125517953575032758">"Leestoegang tot insittendebewustheidstelsel"</string>
+    <string name="car_permission_desc_read_car_occupant_awareness_state" msgid="188865882598414986">"Laat toe dat status- en bespeuringsdata vir die insittendebewustheidstelsel gelees word"</string>
+    <string name="car_permission_label_control_car_occupant_awareness_system" msgid="7163330266691094542">"Beheer insittendebewustheidstelsel se grafiek"</string>
+    <string name="car_permission_desc_control_car_occupant_awareness_system" msgid="7123482622084531911">"Laat toe dat die begin en einde van die insittendebewustheidstelsel se bespeuringsgrafiek beheer word"</string>
     <string name="car_permission_label_bind_input_service" msgid="6698489034024273750">"Motorinvoerdiens"</string>
     <string name="car_permission_desc_bind_input_service" msgid="1670323419931890170">"Hanteer invoergebeurtenisse"</string>
     <string name="car_can_bus_failure" msgid="2334035748788283914">"CAN-bus het misluk"</string>
     <string name="car_can_bus_failure_desc" msgid="4125516222786484733">"CAN-bus reageer nie. Ontprop hoofeenheidkas, prop dit weer in, en herbegin die motor"</string>
-    <string name="activity_blocked_text" msgid="8088902789540147995">"Jy kan nie hierdie kenmerk gebruik terwyl jy bestuur nie"</string>
+    <string name="activity_blocked_text" msgid="5342114426610711378">"Vir jou veiligheid is dié aktiwiteit nie beskikbaar terwyl jy bestuur nie.\nWag totdat jy geparkeer het om voort te gaan."</string>
     <string name="exit_button_message" msgid="8554690915924055685">"Kies <xliff:g id="EXIT_BUTTON">%s</xliff:g> om oor te begin met veilige programkenmerke."</string>
     <string name="exit_button" msgid="5829638404777671253">"Terug"</string>
-    <string name="exit_button_close_application" msgid="8824289547809332460">"Maak program toe"</string>
-    <string name="exit_button_go_back" msgid="3469083862100560326">"Terug"</string>
     <string name="car_permission_label_diag_read" msgid="7248894224877702604">"lees diagnostiese data"</string>
     <string name="car_permission_desc_diag_read" msgid="1121426363040966178">"Lees diagnostiese data van die motor af."</string>
     <string name="car_permission_label_diag_clear" msgid="4783070510879698157">"vee diagnostiese data uit"</string>
@@ -89,6 +93,8 @@
     <string name="car_permission_desc_car_engine_detailed" msgid="1746863362811347700">"Toegang tot jou motor se gedetailleerde enjininligting."</string>
     <string name="car_permission_label_car_energy_ports" msgid="8548990315169219454">"toegang tot motor se brandstofdeur en laaipoort"</string>
     <string name="car_permission_desc_car_energy_ports" msgid="7771185999828794949">"Toegang tot motor se brandstofdeur en laaipoort."</string>
+    <string name="car_permission_label_control_car_energy_ports" msgid="4375137311026313475">"beheer motor se brandstofdeur en laaipoort"</string>
+    <string name="car_permission_desc_control_car_energy_ports" msgid="7364633710492525387">"Beheer motor se brandstofdeur en laaipoort."</string>
     <string name="car_permission_label_car_identification" msgid="5896712510164020478">"lees motor se identifikasie"</string>
     <string name="car_permission_desc_car_identification" msgid="4132040867171275059">"Toegang tot motor se identifikasie."</string>
     <string name="car_permission_label_control_car_doors" msgid="3032058819250195700">"beheer motor se deure"</string>
@@ -101,6 +107,8 @@
     <string name="car_permission_desc_control_car_seats" msgid="2407536601226470563">"Beheer motor se sitplekke."</string>
     <string name="car_permission_label_car_info" msgid="4707513570676492315">"toegang tot motor se basiese inligting"</string>
     <string name="car_permission_desc_car_info" msgid="2118081474543537653">"Toegang tot motor se basiese inligting."</string>
+    <string name="car_permission_label_vendor_permission_info" msgid="4471260460536888654">"gaan in by motorverkoper se toestemmingsinligting"</string>
+    <string name="car_permission_desc_vendor_permission_info" msgid="8152113853528488398">"Gaan in by motorverkoper se toestemmingsinligting."</string>
     <string name="car_permission_label_car_exterior_lights" msgid="541304469604902110">"lees motor se buiteligtestaat"</string>
     <string name="car_permission_desc_car_exterior_lights" msgid="4038037584100849318">"Toegang tot motor se buiteligtestaat."</string>
     <string name="car_permission_label_control_car_exterior_lights" msgid="101357531386232141">"lees motor se buiteligte"</string>
@@ -127,5 +135,9 @@
     <string name="car_permission_desc_enroll_trust" msgid="4148649994602185130">"Laat inskrywing van vertroude toestelle toe"</string>
     <string name="car_permission_label_car_test_service" msgid="9159328930558208708">"Beheer motor se toetsmodus"</string>
     <string name="car_permission_desc_car_test_service" msgid="7426844534110145843">"Beheer motor se toetsmodus"</string>
+    <string name="car_permission_label_control_car_features" msgid="3905791560378888286">"Aktiveer of deaktiveer motor se kenmerke"</string>
+    <string name="car_permission_desc_control_car_features" msgid="7646711104530599901">"Aktiveer of deaktiveer motor se kenmerke."</string>
+    <string name="car_permission_label_use_car_watchdog" msgid="6973938293170413475">"gebruik motorwaghond"</string>
+    <string name="car_permission_desc_use_car_watchdog" msgid="8244592601805516086">"Gebruik motorwaghond."</string>
     <string name="trust_device_default_name" msgid="4213625926070261253">"My Toestel"</string>
 </resources>
diff --git a/service/res/values-am/strings.xml b/service/res/values-am/strings.xml
index 1d965f1..2f03c34 100644
--- a/service/res/values-am/strings.xml
+++ b/service/res/values-am/strings.xml
@@ -22,6 +22,8 @@
     <string name="car_permission_desc_camera" msgid="917024932164501426">"የእርስዎን ካሜራ(ዎች) ይደርሱበት(ባቸው)።"</string>
     <string name="car_permission_label_energy" msgid="7409144323527821558">"የመኪናውን የኃይል መረጃ ድረስበት"</string>
     <string name="car_permission_desc_energy" msgid="3392963810053235407">"የእርስዎን መኪና ኃይል መረጃ ይድረሱበት።"</string>
+    <string name="car_permission_label_adjust_range_remaining" msgid="839033553999920138">"የመኪና ክልልን ቀሪ አስተካክል"</string>
+    <string name="car_permission_desc_adjust_range_remaining" msgid="2369321650437370673">"የመኪና ክልልን ቀሪ ዋጋ አስተካክል።"</string>
     <string name="car_permission_label_hvac" msgid="1499454192558727843">"የመኪናውን hvac ድረስበት"</string>
     <string name="car_permission_desc_hvac" msgid="3754229695589774195">"የእርስዎን መኪና hvac ይድረሱበት።"</string>
     <string name="car_permission_label_mileage" msgid="4661317074631150551">"የመኪናውን የሄደበት ርቀት ብዛት መረጃ ድረስበት"</string>
@@ -62,15 +64,17 @@
     <string name="car_permission_desc_car_ux_restrictions_configuration" msgid="5711926927484813777">"የUX ገደቦችን ያዋቅሩ"</string>
     <string name="car_permission_label_car_handle_usb_aoap_device" msgid="72783989504378036">"ከዩኤስቢ መሣሪያ ጋር በ AOAP ሁነታ ውስጥ መልዕክት ይለዋወጡ"</string>
     <string name="car_permission_desc_car_handle_usb_aoap_device" msgid="273505990971317034">"አንድ መተግበሪያ ከመሣሪያ ጋር በ AOAP ሁነታ ውስጥ መልዕክት እንዲለዋወጥ ይፈቅዳል"</string>
+    <string name="car_permission_label_read_car_occupant_awareness_state" msgid="125517953575032758">"የተሳፋሪ ማስገንዘቢያ ሥርዓት የንባብ መዳረሻ"</string>
+    <string name="car_permission_desc_read_car_occupant_awareness_state" msgid="188865882598414986">"የተሳፋሪ ማስገንዘቢያ ሥርዓት ሁኔታን እና ፈልጎ ማግኛን ውሂብ ማንበብን ያስችላል"</string>
+    <string name="car_permission_label_control_car_occupant_awareness_system" msgid="7163330266691094542">"የተሳፋሪ ማስገንዘቢያ ሥርዓት ፈልጎ ማወቂያ ግራፍን ይቆጣጠሩ"</string>
+    <string name="car_permission_desc_control_car_occupant_awareness_system" msgid="7123482622084531911">"የተሳፋሪ ማስገንዘቢያ ሥርዓት ፈልጎ ማወቂያ ግራፍ ማስጀመርን እና ማስቆምን መቆጣጠርን ያስችላል"</string>
     <string name="car_permission_label_bind_input_service" msgid="6698489034024273750">"የመኪና ግቤት አገልግሎት"</string>
     <string name="car_permission_desc_bind_input_service" msgid="1670323419931890170">"የግቤት ክስተቶችን ያስተናግዱ"</string>
     <string name="car_can_bus_failure" msgid="2334035748788283914">"CAN አውቶብስ አልተሳካም"</string>
     <string name="car_can_bus_failure_desc" msgid="4125516222786484733">"CAN አውቶብስ ምላሽ አይሰጥም። የጭንቅላት አሃድ መያዣ ሳጥኑን ይሰኩ እና ይንቀሉ በመቀጠል መኪናውን ዳግም ያስጀምሩ"</string>
-    <string name="activity_blocked_text" msgid="8088902789540147995">"እየነዱ ሳለ ይህን ባህሪ መጠቀም አይችሉም"</string>
+    <string name="activity_blocked_text" msgid="5342114426610711378">"ለእርስዎ ደህንነት ሲባል፣ ይህ እንቅስቃሴ እርስዎ በሚነዱበት ጊዜ አይገኝም። \nለመቀጠል እንዲችሉ፣ መኪናዎ እስከሚቆም ድረስ ይጠብቁ።"</string>
     <string name="exit_button_message" msgid="8554690915924055685">"ከደህንነት አስተማማኝ የሆኑ የመተግበሪያ ባህሪያት ጋር መልሶ ለመጀመር፣ <xliff:g id="EXIT_BUTTON">%s</xliff:g>ን ይምረጡ።"</string>
     <string name="exit_button" msgid="5829638404777671253">"ተመለስ"</string>
-    <string name="exit_button_close_application" msgid="8824289547809332460">"መተግበሪያን ዝጋ"</string>
-    <string name="exit_button_go_back" msgid="3469083862100560326">"ተመለስ"</string>
     <string name="car_permission_label_diag_read" msgid="7248894224877702604">"የምርመራ ውሂብን አንብብ"</string>
     <string name="car_permission_desc_diag_read" msgid="1121426363040966178">"ከመኪናው ላይ የምርመራ ውሂብን ያንብቡ።"</string>
     <string name="car_permission_label_diag_clear" msgid="4783070510879698157">"የምርመራ ውሂብን አጽዳ"</string>
@@ -89,6 +93,8 @@
     <string name="car_permission_desc_car_engine_detailed" msgid="1746863362811347700">"የእርስዎን መኪና በዝርዝር የቀረበ የሞተር መረጃ ይድረሱበት።"</string>
     <string name="car_permission_label_car_energy_ports" msgid="8548990315169219454">"የመኪናውን የነዳጅ በር እና የኃይል መሙያ በር ድረስበት"</string>
     <string name="car_permission_desc_car_energy_ports" msgid="7771185999828794949">"የመኪና ነዳጅን በር እና የኃይል መሙያ ወደብ ይድረሱባቸው።"</string>
+    <string name="car_permission_label_control_car_energy_ports" msgid="4375137311026313475">"የመኪናውን የነዳጅ በር እና የኃይል መሙያ በር መድረስ"</string>
+    <string name="car_permission_desc_control_car_energy_ports" msgid="7364633710492525387">"የመኪናውን የነዳጅ በር እና የኃይል መሙያ በር መድረስ።"</string>
     <string name="car_permission_label_car_identification" msgid="5896712510164020478">"የመኪናውን መለያ መታወቂያ አንብብ"</string>
     <string name="car_permission_desc_car_identification" msgid="4132040867171275059">"የመኪና ለይቶ ማወቂያን ይድረሱበት።"</string>
     <string name="car_permission_label_control_car_doors" msgid="3032058819250195700">"የመኪና በሮችን ተቆጣጠር"</string>
@@ -101,6 +107,8 @@
     <string name="car_permission_desc_control_car_seats" msgid="2407536601226470563">"የመኪና ወንበሮችን ይቆጣጠሩ።"</string>
     <string name="car_permission_label_car_info" msgid="4707513570676492315">"የመኪናን መሠረታዊ መረጃ ድረስበት"</string>
     <string name="car_permission_desc_car_info" msgid="2118081474543537653">"የመኪና መሠረታዊ መረጃን ይድረሱበት።"</string>
+    <string name="car_permission_label_vendor_permission_info" msgid="4471260460536888654">"የመኪና አቅራቢ ፈቃድ መረጃን መድረስ"</string>
+    <string name="car_permission_desc_vendor_permission_info" msgid="8152113853528488398">"የመኪና አቅራቢ ፈቃድ መረጃን መድረስ።"</string>
     <string name="car_permission_label_car_exterior_lights" msgid="541304469604902110">"የመኪናውን የውጭ መብራቶች ሁኔታ ድረስበት"</string>
     <string name="car_permission_desc_car_exterior_lights" msgid="4038037584100849318">"የመኪናውን የውጭ መብራቶች ሁነታ ይድረሱባቸው።"</string>
     <string name="car_permission_label_control_car_exterior_lights" msgid="101357531386232141">"የመኪናውን የውጭ መብራቶች አንብብ"</string>
@@ -127,5 +135,9 @@
     <string name="car_permission_desc_enroll_trust" msgid="4148649994602185130">"የታመነ መሣሪያ ምዝገባ ይፍቀዱ"</string>
     <string name="car_permission_label_car_test_service" msgid="9159328930558208708">"የመኪና ሙከራ ሁነታን ተቆጣጠር"</string>
     <string name="car_permission_desc_car_test_service" msgid="7426844534110145843">"የመኪና ሙከራ ሁነታን ተቆጣጠር"</string>
+    <string name="car_permission_label_control_car_features" msgid="3905791560378888286">"የመኪና ባህሪዎችን አንቃ ወይም አሰናክል"</string>
+    <string name="car_permission_desc_control_car_features" msgid="7646711104530599901">"የመኪና ባህሪዎችን አንቃ ወይም አሰናክል"</string>
+    <string name="car_permission_label_use_car_watchdog" msgid="6973938293170413475">"የመኪና ጠባቂን መጠቀም"</string>
+    <string name="car_permission_desc_use_car_watchdog" msgid="8244592601805516086">"የመኪና ጠባቂን መጠቀም።"</string>
     <string name="trust_device_default_name" msgid="4213625926070261253">"የእኔ መሣሪያ"</string>
 </resources>
diff --git a/service/res/values-ar/strings.xml b/service/res/values-ar/strings.xml
index c6cf9e4..0026cb5 100644
--- a/service/res/values-ar/strings.xml
+++ b/service/res/values-ar/strings.xml
@@ -22,6 +22,8 @@
     <string name="car_permission_desc_camera" msgid="917024932164501426">"الوصول إلى الكاميرات في السيارة"</string>
     <string name="car_permission_label_energy" msgid="7409144323527821558">"الحصول على معلومات عن طاقة السيارة"</string>
     <string name="car_permission_desc_energy" msgid="3392963810053235407">"الحصول على معلومات الطاقة في السيارة"</string>
+    <string name="car_permission_label_adjust_range_remaining" msgid="839033553999920138">"ضبط القيمة المتبقية لنطاق السيارة"</string>
+    <string name="car_permission_desc_adjust_range_remaining" msgid="2369321650437370673">"يمكنك ضبط القيمة المتبقية لنطاق السيارة."</string>
     <string name="car_permission_label_hvac" msgid="1499454192558727843">"الحصول على معلومات التدفئة والتهوية وتكييف الهواء في السيارة"</string>
     <string name="car_permission_desc_hvac" msgid="3754229695589774195">"الحصول على معلومات التدفئة والتهوية وتكييف الهواء في السيارة"</string>
     <string name="car_permission_label_mileage" msgid="4661317074631150551">"الحصول على معلومات المسافة المقطوعة بالأميال في سيارتك"</string>
@@ -62,16 +64,17 @@
     <string name="car_permission_desc_car_ux_restrictions_configuration" msgid="5711926927484813777">"ضبط قيود تجربة المُستخدِم"</string>
     <string name="car_permission_label_car_handle_usb_aoap_device" msgid="72783989504378036">"التواصل مع جهاز USB في وضع AOAP"</string>
     <string name="car_permission_desc_car_handle_usb_aoap_device" msgid="273505990971317034">"يتيح للتطبيق التواصل مع جهاز في وضع AOAP."</string>
+    <string name="car_permission_label_read_car_occupant_awareness_state" msgid="125517953575032758">"الوصول لقراءة Occupant Awareness System"</string>
+    <string name="car_permission_desc_read_car_occupant_awareness_state" msgid="188865882598414986">"السماح بقراءة حالة Occupant Awareness System وبيانات التعرف عليه"</string>
+    <string name="car_permission_label_control_car_occupant_awareness_system" msgid="7163330266691094542">"التحكّم في الرسم البياني لنظام Occupant Awareness System"</string>
+    <string name="car_permission_desc_control_car_occupant_awareness_system" msgid="7123482622084531911">"السماح بالتحكّم في بدء وإيقاف الرسم البياني للتعرف على Occupant Awareness System"</string>
     <string name="car_permission_label_bind_input_service" msgid="6698489034024273750">"خدمة إدخال السيارة"</string>
     <string name="car_permission_desc_bind_input_service" msgid="1670323419931890170">"التعامل مع أحداث الإدخال"</string>
     <string name="car_can_bus_failure" msgid="2334035748788283914">"حدث خطأ في موصّل CAN"</string>
     <string name="car_can_bus_failure_desc" msgid="4125516222786484733">"لا استجابة من موصّل CAN. يمكنك فصل صندوق وحدة الرأس وإعادة تشغيل السيارة."</string>
-    <!-- no translation found for activity_blocked_text (8088902789540147995) -->
-    <skip />
+    <string name="activity_blocked_text" msgid="5342114426610711378">"حرصًا على سلامتك، لا يتوفّر هذا النشاط أثناء القيادة.\nللمتابعة، يمكنك الانتظار حتى تتوقّف السيارة."</string>
     <string name="exit_button_message" msgid="8554690915924055685">"للبدء من جديد باستخدام ميزات تطبيق آمنة، اختَر <xliff:g id="EXIT_BUTTON">%s</xliff:g>."</string>
     <string name="exit_button" msgid="5829638404777671253">"رجوع"</string>
-    <string name="exit_button_close_application" msgid="8824289547809332460">"إغلاق التطبيق"</string>
-    <string name="exit_button_go_back" msgid="3469083862100560326">"رجوع"</string>
     <string name="car_permission_label_diag_read" msgid="7248894224877702604">"قراءة البيانات التشخيصية"</string>
     <string name="car_permission_desc_diag_read" msgid="1121426363040966178">"يمكنك قراءة البيانات التشخيصية من السيارة."</string>
     <string name="car_permission_label_diag_clear" msgid="4783070510879698157">"محو البيانات التشخيصية"</string>
@@ -90,6 +93,8 @@
     <string name="car_permission_desc_car_engine_detailed" msgid="1746863362811347700">"يمكنك الحصول على معلومات تفصيلية عن محرّك السيارة."</string>
     <string name="car_permission_label_car_energy_ports" msgid="8548990315169219454">"الحصول على معلومات عن .باب خزان الوقود ومنفذ الشحن"</string>
     <string name="car_permission_desc_car_energy_ports" msgid="7771185999828794949">"يمكنك الحصول على معلومات عن باب خزان الوقود ومنفذ الشحن."</string>
+    <string name="car_permission_label_control_car_energy_ports" msgid="4375137311026313475">"التحكّم في باب خزان الوقود ومنفذ الشحن في السيارة"</string>
+    <string name="car_permission_desc_control_car_energy_ports" msgid="7364633710492525387">"التحكّم في باب خزان الوقود ومنفذ الشحن"</string>
     <string name="car_permission_label_car_identification" msgid="5896712510164020478">"قراءة معلومات عن تحديد هوية السيارة"</string>
     <string name="car_permission_desc_car_identification" msgid="4132040867171275059">"يمكنك الحصول على معلومات عن تحديد هوية السيارة."</string>
     <string name="car_permission_label_control_car_doors" msgid="3032058819250195700">"التحكم في أبواب السيارة"</string>
@@ -102,6 +107,8 @@
     <string name="car_permission_desc_control_car_seats" msgid="2407536601226470563">"يمكنك التحكم في مقاعد السيارة."</string>
     <string name="car_permission_label_car_info" msgid="4707513570676492315">"الحصول على المعلومات الأساسية للسيارة"</string>
     <string name="car_permission_desc_car_info" msgid="2118081474543537653">"يمكنك الحصول على المعلومات الأساسية عن السيارة."</string>
+    <string name="car_permission_label_vendor_permission_info" msgid="4471260460536888654">"الوصول إلى معلومات إذن المورّد للسيارة"</string>
+    <string name="car_permission_desc_vendor_permission_info" msgid="8152113853528488398">"الوصول إلى معلومات إذن المورّد للسيارة"</string>
     <string name="car_permission_label_car_exterior_lights" msgid="541304469604902110">"قراءة معلومات عن حالة الإضاءة الخارجية للسيارة"</string>
     <string name="car_permission_desc_car_exterior_lights" msgid="4038037584100849318">"يمكنك الحصول على معلومات عن حالة الإضاءة الخارجية للسيارة."</string>
     <string name="car_permission_label_control_car_exterior_lights" msgid="101357531386232141">"قراءة معلومات عن الإضاءة الخارجية للسيارة"</string>
@@ -128,5 +135,9 @@
     <string name="car_permission_desc_enroll_trust" msgid="4148649994602185130">"السماح بتسجيل الأجهزة الموثوق بها"</string>
     <string name="car_permission_label_car_test_service" msgid="9159328930558208708">"وضع اختبار التحكم في السيارة"</string>
     <string name="car_permission_desc_car_test_service" msgid="7426844534110145843">"وضع اختبار التحكم في السيارة"</string>
+    <string name="car_permission_label_control_car_features" msgid="3905791560378888286">"تفعيل ميزات السيارة أو إيقافها"</string>
+    <string name="car_permission_desc_control_car_features" msgid="7646711104530599901">"تفعيل ميزات السيارة أو إيقافها"</string>
+    <string name="car_permission_label_use_car_watchdog" msgid="6973938293170413475">"استخدام مراقب نظام السيارة"</string>
+    <string name="car_permission_desc_use_car_watchdog" msgid="8244592601805516086">"استخدام مراقب نظام السيارة"</string>
     <string name="trust_device_default_name" msgid="4213625926070261253">"جهازي"</string>
 </resources>
diff --git a/service/res/values-as/strings.xml b/service/res/values-as/strings.xml
index 1cd4c11..7a29acb 100644
--- a/service/res/values-as/strings.xml
+++ b/service/res/values-as/strings.xml
@@ -22,6 +22,8 @@
     <string name="car_permission_desc_camera" msgid="917024932164501426">"আপোনাৰ গাড়ীৰ কেমেৰা এক্সেছ কৰিব।"</string>
     <string name="car_permission_label_energy" msgid="7409144323527821558">"গাড়ীৰ শক্তিৰ তথ্য এক্সেছ কৰিব"</string>
     <string name="car_permission_desc_energy" msgid="3392963810053235407">"আপোনাৰ গাড়ীৰ শক্তি সম্পৰ্কীয় তথ্য এক্সেছ কৰিব।"</string>
+    <string name="car_permission_label_adjust_range_remaining" msgid="839033553999920138">"গাড়ীখন আৰু কিমান সময়লৈ চলিব সেয়া মিলাওক"</string>
+    <string name="car_permission_desc_adjust_range_remaining" msgid="2369321650437370673">"গাড়ীখন আৰু কিমান সময়লৈ চলিব তাৰ মানটো মিলাওক।"</string>
     <string name="car_permission_label_hvac" msgid="1499454192558727843">"গাড়ীৰ এইছভিএচি এক্সেছ কৰিব"</string>
     <string name="car_permission_desc_hvac" msgid="3754229695589774195">"আপোনাৰ গাড়ীৰ hvac এক্সেছ কৰিব।"</string>
     <string name="car_permission_label_mileage" msgid="4661317074631150551">"গাড়ীৰ মাইলেজৰ তথ্য এক্সেছ কৰিব"</string>
@@ -62,16 +64,17 @@
     <string name="car_permission_desc_car_ux_restrictions_configuration" msgid="5711926927484813777">"UXৰ সীমাবদ্ধতা কনফিগাৰ কৰক"</string>
     <string name="car_permission_label_car_handle_usb_aoap_device" msgid="72783989504378036">"AOAP ম’ডত থকা ইউএছবি ডিভাইচৰ লগত যোগাযোগ কৰিব পাৰে"</string>
     <string name="car_permission_desc_car_handle_usb_aoap_device" msgid="273505990971317034">"কোনো এপক AOAP ম’ডত থকা ডিভাইচৰ লগত যোগাযোগ কৰাৰ অনুমতি দিয়ে"</string>
+    <string name="car_permission_label_read_car_occupant_awareness_state" msgid="125517953575032758">"Occupant Awareness System পঢ়াৰ এক্সেছ"</string>
+    <string name="car_permission_desc_read_car_occupant_awareness_state" msgid="188865882598414986">"Occupant Awareness Systemৰ স্থিতি পঢ়া আৰু ডেটা চিনাক্ত কৰাৰ অনুমতি দিয়ে"</string>
+    <string name="car_permission_label_control_car_occupant_awareness_system" msgid="7163330266691094542">"Occupant Awareness Systemৰ লেখ নিয়ন্ত্ৰণ কৰক"</string>
+    <string name="car_permission_desc_control_car_occupant_awareness_system" msgid="7123482622084531911">"Occupant Awareness System চিনাক্তকৰণৰ লেখৰ আৰম্ভ কৰা আৰু বন্ধ কৰা কার্য নিয়ন্ত্ৰণ কৰাটোৰ অনুমতি দিয়ে"</string>
     <string name="car_permission_label_bind_input_service" msgid="6698489034024273750">"গাড়ীৰ ইনপুট সেৱা"</string>
     <string name="car_permission_desc_bind_input_service" msgid="1670323419931890170">"ইনপুট ইভেণ্ট নিয়ন্ত্ৰণ কৰিব"</string>
     <string name="car_can_bus_failure" msgid="2334035748788283914">"CAN বাছ বিফল হৈছে"</string>
     <string name="car_can_bus_failure_desc" msgid="4125516222786484733">"CAN বাছে সঁহাৰি দিয়া নাই। হে’ড ইউনিট বাকচটো আঁতৰাই পুনৰ লগাওক"</string>
-    <!-- no translation found for activity_blocked_text (8088902789540147995) -->
-    <skip />
+    <string name="activity_blocked_text" msgid="5342114426610711378">"আপোনাৰ নিৰাপত্তাৰ বাবে এই কাৰ্যকলাপটো গাড়ী চলাই থকা সময়ত কৰিব নোৱাৰি।\nগাড়ীখন পাৰ্ক কৰি কামটো কৰক।"</string>
     <string name="exit_button_message" msgid="8554690915924055685">"সুৰক্ষিত এপ্ সুবিধাসহ আকৌ আৰম্ভ কৰিবলৈ <xliff:g id="EXIT_BUTTON">%s</xliff:g> বাছনি কৰক।"</string>
     <string name="exit_button" msgid="5829638404777671253">"উভতি যাওক"</string>
-    <string name="exit_button_close_application" msgid="8824289547809332460">"এপ্‌টো বন্ধ কৰক"</string>
-    <string name="exit_button_go_back" msgid="3469083862100560326">"উভতি যাওক"</string>
     <string name="car_permission_label_diag_read" msgid="7248894224877702604">"ডায়গন’ষ্টিক ডেটা পঢ়িব"</string>
     <string name="car_permission_desc_diag_read" msgid="1121426363040966178">"গাড়ীৰ ডায়গ’ষ্টিক তথ্য পঢ়িব।"</string>
     <string name="car_permission_label_diag_clear" msgid="4783070510879698157">"ডায়গন’ষ্টিক ডেটা মচিব"</string>
@@ -90,6 +93,8 @@
     <string name="car_permission_desc_car_engine_detailed" msgid="1746863362811347700">"আপোনাৰ গাড়ীৰ ইঞ্জিনৰ সবিশেষ তথ্য এক্সেছ কৰিব।"</string>
     <string name="car_permission_label_car_energy_ports" msgid="8548990315169219454">"গাড়ীৰ ইন্ধনৰ দৰ্জা আৰু চাৰ্জ প’ৰ্ট এক্সেছ কৰিব"</string>
     <string name="car_permission_desc_car_energy_ports" msgid="7771185999828794949">"গাড়ীৰ ইন্ধনৰ দৰ্জা আৰু চাৰ্জ প’ৰ্ট এক্সেছ কৰিব।"</string>
+    <string name="car_permission_label_control_car_energy_ports" msgid="4375137311026313475">"গাড়ীৰ ইন্ধনৰ দৰ্জা আৰু চাৰ্জ প’ৰ্ট নিয়ন্ত্ৰণ কৰা"</string>
+    <string name="car_permission_desc_control_car_energy_ports" msgid="7364633710492525387">"গাড়ীৰ ইন্ধনৰ দৰ্জা আৰু চাৰ্জ প’ৰ্ট নিয়ন্ত্ৰণ কৰা।"</string>
     <string name="car_permission_label_car_identification" msgid="5896712510164020478">"গাড়ী চিনাক্তকৰণৰ তথ্য পঢ়িব"</string>
     <string name="car_permission_desc_car_identification" msgid="4132040867171275059">"গাড়ী চিনাক্তকৰণৰ তথ্য এক্সেছ কৰিব।"</string>
     <string name="car_permission_label_control_car_doors" msgid="3032058819250195700">"গাড়ীৰ দৰ্জা নিয়ন্ত্ৰণ কৰিব"</string>
@@ -102,6 +107,8 @@
     <string name="car_permission_desc_control_car_seats" msgid="2407536601226470563">"গাড়ীৰ আসন নিয়ন্ত্ৰণ কৰিব।"</string>
     <string name="car_permission_label_car_info" msgid="4707513570676492315">"গাড়ীৰ মৌলিক তথ্যবোৰ এক্সেছ কৰিব"</string>
     <string name="car_permission_desc_car_info" msgid="2118081474543537653">"গাড়ীৰ মৌলিক তথ্যবোৰ এক্সেছ কৰিব।"</string>
+    <string name="car_permission_label_vendor_permission_info" msgid="4471260460536888654">"গাড়ীৰ বিক্ৰেতাৰ অনুমতি সম্পৰ্কীয় তথ্য এক্সেছ কৰক"</string>
+    <string name="car_permission_desc_vendor_permission_info" msgid="8152113853528488398">"গাড়ীৰ বিক্ৰেতাৰ অনুমতি সম্পৰ্কীয় তথ্য এক্সেছ কৰক।"</string>
     <string name="car_permission_label_car_exterior_lights" msgid="541304469604902110">"গাড়ীৰ বাহ্যিক লাইটৰ স্থিতি পঢ়িব"</string>
     <string name="car_permission_desc_car_exterior_lights" msgid="4038037584100849318">"গাড়ীৰ বাহ্যিক লাইটৰ স্থিতি এক্সেছ কৰিব।"</string>
     <string name="car_permission_label_control_car_exterior_lights" msgid="101357531386232141">"গাড়ীৰ বাহ্যিক লাইট পঢ়িব"</string>
@@ -128,5 +135,9 @@
     <string name="car_permission_desc_enroll_trust" msgid="4148649994602185130">"বিশ্বাসী ডিভাইচ পঞ্জীয়নৰ অনুমতি দিয়ক"</string>
     <string name="car_permission_label_car_test_service" msgid="9159328930558208708">"বাহনৰ পৰীক্ষণ ম’ড নিয়ন্ত্ৰণ কৰক"</string>
     <string name="car_permission_desc_car_test_service" msgid="7426844534110145843">"বাহনৰ পৰীক্ষণ ম’ড নিয়ন্ত্ৰণ কৰক"</string>
+    <string name="car_permission_label_control_car_features" msgid="3905791560378888286">"গাড়ীৰ সুবিধাসমূহ সক্ষম অথবা অক্ষম কৰক"</string>
+    <string name="car_permission_desc_control_car_features" msgid="7646711104530599901">"গাড়ীৰ সুবিধাসমূহ সক্ষম অথবা অক্ষম কৰক।"</string>
+    <string name="car_permission_label_use_car_watchdog" msgid="6973938293170413475">"গাড়ীৰ ৱাচ্‌ডগ ব্যৱহাৰ কৰক"</string>
+    <string name="car_permission_desc_use_car_watchdog" msgid="8244592601805516086">"গাড়ীৰ ৱাচ্‌ডগ ব্যৱহাৰ কৰক।"</string>
     <string name="trust_device_default_name" msgid="4213625926070261253">"মোৰ ডিভাইচ"</string>
 </resources>
diff --git a/service/res/values-az/strings.xml b/service/res/values-az/strings.xml
index 0645268..d812aba 100644
--- a/service/res/values-az/strings.xml
+++ b/service/res/values-az/strings.xml
@@ -22,6 +22,8 @@
     <string name="car_permission_desc_camera" msgid="917024932164501426">"Avtomobilin kameralarına giriş."</string>
     <string name="car_permission_label_energy" msgid="7409144323527821558">"avtomobilin enerji məlumatlarına giriş"</string>
     <string name="car_permission_desc_energy" msgid="3392963810053235407">"Avtomobilin enerji məlumatlarına giriş."</string>
+    <string name="car_permission_label_adjust_range_remaining" msgid="839033553999920138">"avtomobilin yürüş məsafəsi qalığını nizamlamaq"</string>
+    <string name="car_permission_desc_adjust_range_remaining" msgid="2369321650437370673">"Avtomobilin yürüş məsafəsi qalıq dəyərini nizamlamaq."</string>
     <string name="car_permission_label_hvac" msgid="1499454192558727843">"avtomobilin HVAC məlumatlarına giriş"</string>
     <string name="car_permission_desc_hvac" msgid="3754229695589774195">"Avtomobilin HVAC mexanizminə giriş."</string>
     <string name="car_permission_label_mileage" msgid="4661317074631150551">"avtomobilin kilometraj məlumatlarına giriş"</string>
@@ -62,16 +64,17 @@
     <string name="car_permission_desc_car_ux_restrictions_configuration" msgid="5711926927484813777">"UX Məhdudiyyətlərinin Konfiqurasiyası"</string>
     <string name="car_permission_label_car_handle_usb_aoap_device" msgid="72783989504378036">"AOAP rejimində USB cihazı ilə əlaqə qurmaq"</string>
     <string name="car_permission_desc_car_handle_usb_aoap_device" msgid="273505990971317034">"AOAP rejimində tətbiqin cihazla əlaqə qurmasına icazə verir"</string>
+    <string name="car_permission_label_read_car_occupant_awareness_state" msgid="125517953575032758">"Sərnişin Məlumatlılıq Sistemini Oxumaq üçün Giriş"</string>
+    <string name="car_permission_desc_read_car_occupant_awareness_state" msgid="188865882598414986">"Sərnişin Məlumatlılıq Sisteminin vəziyyət və aşkarlama datasını oxumağa icazə verir"</string>
+    <string name="car_permission_label_control_car_occupant_awareness_system" msgid="7163330266691094542">"Sərnişin Məlumatlılıq Sistemi Qrafikinə nəzarət"</string>
+    <string name="car_permission_desc_control_car_occupant_awareness_system" msgid="7123482622084531911">"Sərnişin Məlumatlılıq Sisteminin aşkarlama qrafikinin başladılması və dayandırılmasına nəzarətə icazə verir"</string>
     <string name="car_permission_label_bind_input_service" msgid="6698489034024273750">"Avtomobil Daxiletmə Xidməti"</string>
     <string name="car_permission_desc_bind_input_service" msgid="1670323419931890170">"Daxiletmələri idarə etmək"</string>
     <string name="car_can_bus_failure" msgid="2334035748788283914">"CAN idarəetmə mexanizmi uğursuz oldu"</string>
     <string name="car_can_bus_failure_desc" msgid="4125516222786484733">"CAN mexanizmi cavab vermir. Əsas cihaz panelini ayırın və yenidən qoşun, sonra avtomobili yenidən işə salın"</string>
-    <!-- no translation found for activity_blocked_text (8088902789540147995) -->
-    <skip />
+    <string name="activity_blocked_text" msgid="5342114426610711378">"Təhlükəsizliyiniz üçün bu əməliyyat sürüş zamanı əlçatan deyil.\nDavam etmək üçün park edənədək gözləyin."</string>
     <string name="exit_button_message" msgid="8554690915924055685">"Təhlükəsiz tətbiq xüsusiyyətləri ilə başlamaq üçün <xliff:g id="EXIT_BUTTON">%s</xliff:g> seçin."</string>
     <string name="exit_button" msgid="5829638404777671253">"Geri"</string>
-    <string name="exit_button_close_application" msgid="8824289547809332460">"Tətbiqi qapadın"</string>
-    <string name="exit_button_go_back" msgid="3469083862100560326">"Geri"</string>
     <string name="car_permission_label_diag_read" msgid="7248894224877702604">"diaqnoztika məlumatlarını oxumaq"</string>
     <string name="car_permission_desc_diag_read" msgid="1121426363040966178">"Avtomobilin diaqnoztika məlumatlarını oxumaq."</string>
     <string name="car_permission_label_diag_clear" msgid="4783070510879698157">"diaqnoztika məlumatlarını silmək"</string>
@@ -90,6 +93,8 @@
     <string name="car_permission_desc_car_engine_detailed" msgid="1746863362811347700">"Avtomobilin ətraflı mühərrik məlumatlarına giriş."</string>
     <string name="car_permission_label_car_energy_ports" msgid="8548990315169219454">"avtomobilin yanacaq bölməsinin qapağı və enerji doldurma portuna giriş"</string>
     <string name="car_permission_desc_car_energy_ports" msgid="7771185999828794949">"Avtomobilin yanacaq bölməsinin qapağı və enerji doldurma portuna giriş."</string>
+    <string name="car_permission_label_control_car_energy_ports" msgid="4375137311026313475">"avtomobilin yanacaq bölməsinin qapağı və enerji doldurma portuna nəzarət"</string>
+    <string name="car_permission_desc_control_car_energy_ports" msgid="7364633710492525387">"Avtomobilin yanacaq bölməsinin qapağı və enerji doldurma portuna nəzarət"</string>
     <string name="car_permission_label_car_identification" msgid="5896712510164020478">"avtomobilin identifikasiyasını oxumaq"</string>
     <string name="car_permission_desc_car_identification" msgid="4132040867171275059">"Avtomobilin identifikasiyasına giriş."</string>
     <string name="car_permission_label_control_car_doors" msgid="3032058819250195700">"avtomobilin qapılarını idarə etmək"</string>
@@ -102,6 +107,8 @@
     <string name="car_permission_desc_control_car_seats" msgid="2407536601226470563">"Avtomobilin oturacaqlarını idarə etmək."</string>
     <string name="car_permission_label_car_info" msgid="4707513570676492315">"avtomobilin əsas məlumatlarına giriş"</string>
     <string name="car_permission_desc_car_info" msgid="2118081474543537653">"Avtomobilin əsas məlumatlarına giriş."</string>
+    <string name="car_permission_label_vendor_permission_info" msgid="4471260460536888654">"avtomobilin təchizatçı icazə məlumatına giriş"</string>
+    <string name="car_permission_desc_vendor_permission_info" msgid="8152113853528488398">"Avtomobilin təchizatçı icazə məlumatına giriş."</string>
     <string name="car_permission_label_car_exterior_lights" msgid="541304469604902110">"avtomobilin eksteryer işıqlarının vəziyyətini oxumaq"</string>
     <string name="car_permission_desc_car_exterior_lights" msgid="4038037584100849318">"Avtomobilin eksteryer işıqlarının vəziyyətinə giriş."</string>
     <string name="car_permission_label_control_car_exterior_lights" msgid="101357531386232141">"avtomobilin eksteryer işıqları məlumatlarını oxumaq"</string>
@@ -128,5 +135,9 @@
     <string name="car_permission_desc_enroll_trust" msgid="4148649994602185130">"Güvənli cihaz qeydiyyatına icazə verin"</string>
     <string name="car_permission_label_car_test_service" msgid="9159328930558208708">"avtomobilin sınaq rejimini idarə etmək"</string>
     <string name="car_permission_desc_car_test_service" msgid="7426844534110145843">"avtomobilin sınaq rejimini idarə etmək"</string>
+    <string name="car_permission_label_control_car_features" msgid="3905791560378888286">"Avtomobilin xüsusiyyətlərini aktiv və ya deaktiv etmək"</string>
+    <string name="car_permission_desc_control_car_features" msgid="7646711104530599901">"Avtomobilin xüsusiyyətlərini aktiv və ya deaktiv etmək."</string>
+    <string name="car_permission_label_use_car_watchdog" msgid="6973938293170413475">"avtomobil keşikçisindən istifadə edin"</string>
+    <string name="car_permission_desc_use_car_watchdog" msgid="8244592601805516086">"Avtomobil keşikçisindən istifadə edin."</string>
     <string name="trust_device_default_name" msgid="4213625926070261253">"Cihazım"</string>
 </resources>
diff --git a/service/res/values-b+sr+Latn/strings.xml b/service/res/values-b+sr+Latn/strings.xml
index 21d68b9..4abf472 100644
--- a/service/res/values-b+sr+Latn/strings.xml
+++ b/service/res/values-b+sr+Latn/strings.xml
@@ -22,6 +22,8 @@
     <string name="car_permission_desc_camera" msgid="917024932164501426">"Pristupi kamerama automobila."</string>
     <string name="car_permission_label_energy" msgid="7409144323527821558">"pristup podacima o energiji automobila"</string>
     <string name="car_permission_desc_energy" msgid="3392963810053235407">"Pristupi informacijama o energiji automobila."</string>
+    <string name="car_permission_label_adjust_range_remaining" msgid="839033553999920138">"prilagođavanje preostalog dometa automobila"</string>
+    <string name="car_permission_desc_adjust_range_remaining" msgid="2369321650437370673">"Prilagođavanje vrednosti preostalog dometa automobila."</string>
     <string name="car_permission_label_hvac" msgid="1499454192558727843">"pristup grejanju, ventilaciji i klimatizaciji automobila"</string>
     <string name="car_permission_desc_hvac" msgid="3754229695589774195">"Pristupi grejanju, ventilaciji i klimatizaciji automobila"</string>
     <string name="car_permission_label_mileage" msgid="4661317074631150551">"pristup podacima o kilometraži automobila"</string>
@@ -62,15 +64,17 @@
     <string name="car_permission_desc_car_ux_restrictions_configuration" msgid="5711926927484813777">"Konfiguriše ograničenja KD-a"</string>
     <string name="car_permission_label_car_handle_usb_aoap_device" msgid="72783989504378036">"Komunicira sa USB uređajem u režimu AOAP"</string>
     <string name="car_permission_desc_car_handle_usb_aoap_device" msgid="273505990971317034">"Dozvoljava aplikaciji komunikaciju sa uređajem u režimu AOAP"</string>
+    <string name="car_permission_label_read_car_occupant_awareness_state" msgid="125517953575032758">"Pristup za čitanje za Occupant Awareness System"</string>
+    <string name="car_permission_desc_read_car_occupant_awareness_state" msgid="188865882598414986">"Omogućava čitanje podataka o statusu i otkrivanju za Occupant Awareness System"</string>
+    <string name="car_permission_label_control_car_occupant_awareness_system" msgid="7163330266691094542">"Upravljanje grafikonom za Occupant Awareness System"</string>
+    <string name="car_permission_desc_control_car_occupant_awareness_system" msgid="7123482622084531911">"Omogućava pokretanje i zaustavljanje grafikona otkrivanja za Occupant Awareness System"</string>
     <string name="car_permission_label_bind_input_service" msgid="6698489034024273750">"Usluga automobilskog unosa"</string>
     <string name="car_permission_desc_bind_input_service" msgid="1670323419931890170">"Upravlja događajima unosa"</string>
     <string name="car_can_bus_failure" msgid="2334035748788283914">"Greška CAN magistrale"</string>
     <string name="car_can_bus_failure_desc" msgid="4125516222786484733">"CAN magistrala ne reaguje. Isključite i ponovo uključite glavnu jedinicu i ponovo pokrenite automobil"</string>
-    <string name="activity_blocked_text" msgid="8088902789540147995">"Ne možete da koristite ovu funkciju dok vozite"</string>
+    <string name="activity_blocked_text" msgid="5342114426610711378">"Ova aktivnost nije dostupna dok vozite radi vaše bezbednosti.\nDa biste nastavili, prvo se parkirajte."</string>
     <string name="exit_button_message" msgid="8554690915924055685">"Da biste ponovo počeli sa bezbednim funkcijama aplikacije, izaberite <xliff:g id="EXIT_BUTTON">%s</xliff:g>."</string>
     <string name="exit_button" msgid="5829638404777671253">"Nazad"</string>
-    <string name="exit_button_close_application" msgid="8824289547809332460">"Zatvori aplikaciju"</string>
-    <string name="exit_button_go_back" msgid="3469083862100560326">"Nazad"</string>
     <string name="car_permission_label_diag_read" msgid="7248894224877702604">"čitanje dijagnostičkih podataka"</string>
     <string name="car_permission_desc_diag_read" msgid="1121426363040966178">"Čitanje dijagnostičkih podataka iz automobila."</string>
     <string name="car_permission_label_diag_clear" msgid="4783070510879698157">"brisanje dijagnostičkih podataka"</string>
@@ -89,6 +93,8 @@
     <string name="car_permission_desc_car_engine_detailed" msgid="1746863362811347700">"Pristup detaljnim podacima o motoru automobila."</string>
     <string name="car_permission_label_car_energy_ports" msgid="8548990315169219454">"pristup poklopcu rezervoara za gorivo i portu za punjenje"</string>
     <string name="car_permission_desc_car_energy_ports" msgid="7771185999828794949">"Pristup poklopcu rezervoara za gorivo i portu za punjenje."</string>
+    <string name="car_permission_label_control_car_energy_ports" msgid="4375137311026313475">"upravljanje poklopcem rezervoara za gorivo i portom za punjenje"</string>
+    <string name="car_permission_desc_control_car_energy_ports" msgid="7364633710492525387">"Upravljanje poklopcem rezervoara za gorivo i portom za punjenje."</string>
     <string name="car_permission_label_car_identification" msgid="5896712510164020478">"čitanje podataka za identifikaciju automobila"</string>
     <string name="car_permission_desc_car_identification" msgid="4132040867171275059">"Pristup podacima za identifikaciju automobila."</string>
     <string name="car_permission_label_control_car_doors" msgid="3032058819250195700">"Kontrolisanje vrata automobila"</string>
@@ -101,6 +107,8 @@
     <string name="car_permission_desc_control_car_seats" msgid="2407536601226470563">"Kontrolisanje sedišta u automobilu."</string>
     <string name="car_permission_label_car_info" msgid="4707513570676492315">"pristup osnovnim podacima o automobilu"</string>
     <string name="car_permission_desc_car_info" msgid="2118081474543537653">"Pristup osnovnim podacima o automobilu."</string>
+    <string name="car_permission_label_vendor_permission_info" msgid="4471260460536888654">"pristup informacijama o dozvolama prodavca automobila"</string>
+    <string name="car_permission_desc_vendor_permission_info" msgid="8152113853528488398">"Pristupa informacijama o dozvolama prodavca automobila."</string>
     <string name="car_permission_label_car_exterior_lights" msgid="541304469604902110">"čitanje stanja spoljnih svetla automobila"</string>
     <string name="car_permission_desc_car_exterior_lights" msgid="4038037584100849318">"Pristup stanju spoljnih svetla automobila."</string>
     <string name="car_permission_label_control_car_exterior_lights" msgid="101357531386232141">"čitanje statusa spoljnih svetla automobila"</string>
@@ -127,5 +135,9 @@
     <string name="car_permission_desc_enroll_trust" msgid="4148649994602185130">"Omogući registrovanje pouzdanih uređaja"</string>
     <string name="car_permission_label_car_test_service" msgid="9159328930558208708">"Kontrola režima za testiranje automobila"</string>
     <string name="car_permission_desc_car_test_service" msgid="7426844534110145843">"Kontrola režima za testiranje automobila"</string>
+    <string name="car_permission_label_control_car_features" msgid="3905791560378888286">"Omogućavanje ili onemogućavanje funkcija automobila"</string>
+    <string name="car_permission_desc_control_car_features" msgid="7646711104530599901">"Omogućavanje ili onemogućavanje funkcija automobila."</string>
+    <string name="car_permission_label_use_car_watchdog" msgid="6973938293170413475">"koristi nadzorni tajmer automobila"</string>
+    <string name="car_permission_desc_use_car_watchdog" msgid="8244592601805516086">"Koristi nadzorni tajmer automobila."</string>
     <string name="trust_device_default_name" msgid="4213625926070261253">"Moj uređaj"</string>
 </resources>
diff --git a/service/res/values-be/strings.xml b/service/res/values-be/strings.xml
index a862b34..117f40e 100644
--- a/service/res/values-be/strings.xml
+++ b/service/res/values-be/strings.xml
@@ -22,6 +22,8 @@
     <string name="car_permission_desc_camera" msgid="917024932164501426">"Доступ да камер аўтамабіля."</string>
     <string name="car_permission_label_energy" msgid="7409144323527821558">"дазволіць доступ да інфармацыі пра энергарэсурсы аўтамабіля"</string>
     <string name="car_permission_desc_energy" msgid="3392963810053235407">"Доступ да інфармацыі пра энергарэсурсы аўтамабіля."</string>
+    <string name="car_permission_label_adjust_range_remaining" msgid="839033553999920138">"карэктаваць аўтамабільны прабег без дазапраўкі, які застаўся"</string>
+    <string name="car_permission_desc_adjust_range_remaining" msgid="2369321650437370673">"Карэктаваць значэнне аўтамабільнага прабегу без дазапраўкі, які застаўся."</string>
     <string name="car_permission_label_hvac" msgid="1499454192558727843">"дазволіць доступ да сістэмы АВіК аўтамабіля"</string>
     <string name="car_permission_desc_hvac" msgid="3754229695589774195">"Доступ да сістэмы АВіК аўтамабіля."</string>
     <string name="car_permission_label_mileage" msgid="4661317074631150551">"дазволіць доступ да інфармацыі пра прабег аўтамабіля"</string>
@@ -62,16 +64,17 @@
     <string name="car_permission_desc_car_ux_restrictions_configuration" msgid="5711926927484813777">"Наладжванне абмежаванняў UX"</string>
     <string name="car_permission_label_car_handle_usb_aoap_device" msgid="72783989504378036">"Абменьвацца данымі з USB-прыладай у рэжыме AOAP"</string>
     <string name="car_permission_desc_car_handle_usb_aoap_device" msgid="273505990971317034">"Дазваляе праграме абменьвацца данымі з прыладай у рэжыме AOAP"</string>
+    <string name="car_permission_label_read_car_occupant_awareness_state" msgid="125517953575032758">"Доступ да счытвання даных сістэмы інфармавання пра пасажыра"</string>
+    <string name="car_permission_desc_read_car_occupant_awareness_state" msgid="188865882598414986">"Дазваляе счытваць стан і даныя выяўлення для сістэмы інфармавання пра пасажыра"</string>
+    <string name="car_permission_label_control_car_occupant_awareness_system" msgid="7163330266691094542">"Правяраць графік сістэмы інфармавання пра пасажыра"</string>
+    <string name="car_permission_desc_control_car_occupant_awareness_system" msgid="7123482622084531911">"Дазваляе кантраляваць запуск і прыпыненне графіка выяўлення для сістэмы інфармавання пра пасажыра"</string>
     <string name="car_permission_label_bind_input_service" msgid="6698489034024273750">"Сэрвіс уводу аўтамабіля"</string>
     <string name="car_permission_desc_bind_input_service" msgid="1670323419931890170">"Магчымасць апрацоўваць падзеі ўводу"</string>
     <string name="car_can_bus_failure" msgid="2334035748788283914">"CAN-шына парушана"</string>
     <string name="car_can_bus_failure_desc" msgid="4125516222786484733">"CAN-шына не адказвае. Перападключыце канектар, а затым выключыце запальванне і паўторна завядзіце аўтамабіль"</string>
-    <!-- no translation found for activity_blocked_text (8088902789540147995) -->
-    <skip />
+    <string name="activity_blocked_text" msgid="5342114426610711378">"Дзеля вашай бяспекі гэта дзеянне недаступнае, калі вы за рулём.\nСпачатку прыпаркуйцеся."</string>
     <string name="exit_button_message" msgid="8554690915924055685">"Каб перазапусціць праграму ў бяспечным рэжыме, націсніце кнопку \"<xliff:g id="EXIT_BUTTON">%s</xliff:g>\"."</string>
     <string name="exit_button" msgid="5829638404777671253">"Назад"</string>
-    <string name="exit_button_close_application" msgid="8824289547809332460">"Закрыць праграму"</string>
-    <string name="exit_button_go_back" msgid="3469083862100560326">"Назад"</string>
     <string name="car_permission_label_diag_read" msgid="7248894224877702604">"атрымліваць даныя дыягностыкі"</string>
     <string name="car_permission_desc_diag_read" msgid="1121426363040966178">"Счытванне дыягнастычных даных аўтамабіля"</string>
     <string name="car_permission_label_diag_clear" msgid="4783070510879698157">"ачысціць даныя дыягностыкі"</string>
@@ -90,6 +93,8 @@
     <string name="car_permission_desc_car_engine_detailed" msgid="1746863362811347700">"Доступ да падрабязнай інфармацыі пра рухавік аўтамабіля."</string>
     <string name="car_permission_label_car_energy_ports" msgid="8548990315169219454">"дазволіць доступ да лючка паліўнага бака і порта зарадкі аўтамабіля"</string>
     <string name="car_permission_desc_car_energy_ports" msgid="7771185999828794949">"Доступ да лючка паліўнага бака і порта зарадкі аўтамабіля."</string>
+    <string name="car_permission_label_control_car_energy_ports" msgid="4375137311026313475">"правяраць лючок паліўнага бака і порт зарадкі аўтамабіля"</string>
+    <string name="car_permission_desc_control_car_energy_ports" msgid="7364633710492525387">"Правяраць лючок паліўнага бака і порт зарадкі аўтамабіля."</string>
     <string name="car_permission_label_car_identification" msgid="5896712510164020478">"счытваць ідэнтыфікацыйны нумар аўтамабіля"</string>
     <string name="car_permission_desc_car_identification" msgid="4132040867171275059">"Доступ да ідэнтыфікацыйнага нумара аўтамабіля."</string>
     <string name="car_permission_label_control_car_doors" msgid="3032058819250195700">"кіраваць дзвярыма аўтамабіля"</string>
@@ -102,6 +107,8 @@
     <string name="car_permission_desc_control_car_seats" msgid="2407536601226470563">"Рэгуляванне сядзенняў аўтамабіля."</string>
     <string name="car_permission_label_car_info" msgid="4707513570676492315">"дазволіць доступ да асноўнай інфармацыі пра аўтамабіль"</string>
     <string name="car_permission_desc_car_info" msgid="2118081474543537653">"Доступ да асноўнай інфармацыі пра аўтамабіль."</string>
+    <string name="car_permission_label_vendor_permission_info" msgid="4471260460536888654">"доступ да інфармацыі пра дазволы пастаўшчыка аўтамабіля"</string>
+    <string name="car_permission_desc_vendor_permission_info" msgid="8152113853528488398">"Доступ да інфармацыі пра дазволы пастаўшчыка аўтамабіля."</string>
     <string name="car_permission_label_car_exterior_lights" msgid="541304469604902110">"атрымліваць інфармацыю пра стан знешніх асвятляльных прыбораў аўтамабіля"</string>
     <string name="car_permission_desc_car_exterior_lights" msgid="4038037584100849318">"Доступ да інфармацыі пра стан знешніх асвятляльных прыбораў аўтамабіля."</string>
     <string name="car_permission_label_control_car_exterior_lights" msgid="101357531386232141">"атрымліваць інфармацыю са знешніх асвятляльных прыбораў аўтамабіля"</string>
@@ -128,5 +135,9 @@
     <string name="car_permission_desc_enroll_trust" msgid="4148649994602185130">"Дазволіць рэгістрацыю даверанай прылады"</string>
     <string name="car_permission_label_car_test_service" msgid="9159328930558208708">"Тэставы рэжым кіравання аўтамабілем"</string>
     <string name="car_permission_desc_car_test_service" msgid="7426844534110145843">"Тэставы рэжым кіравання аўтамабілем"</string>
+    <string name="car_permission_label_control_car_features" msgid="3905791560378888286">"Уключыць або выключыць функцыі аўтамабіля"</string>
+    <string name="car_permission_desc_control_car_features" msgid="7646711104530599901">"Уключыць або выключыць функцыі аўтамабіля."</string>
+    <string name="car_permission_label_use_car_watchdog" msgid="6973938293170413475">"выкарыстанне вартавога таймера аўтамабіля"</string>
+    <string name="car_permission_desc_use_car_watchdog" msgid="8244592601805516086">"Выкарыстанне вартавога таймера аўтамабіля."</string>
     <string name="trust_device_default_name" msgid="4213625926070261253">"Мая прылада"</string>
 </resources>
diff --git a/service/res/values-bg/strings.xml b/service/res/values-bg/strings.xml
index 4aa45de..1c5b0ef 100644
--- a/service/res/values-bg/strings.xml
+++ b/service/res/values-bg/strings.xml
@@ -22,6 +22,8 @@
     <string name="car_permission_desc_camera" msgid="917024932164501426">"Достъп до камерата или съответно камерите на автомобила ви."</string>
     <string name="car_permission_label_energy" msgid="7409144323527821558">"достъп до информацията за енергията на автомобила"</string>
     <string name="car_permission_desc_energy" msgid="3392963810053235407">"Достъп до информацията за енергията на автомобила."</string>
+    <string name="car_permission_label_adjust_range_remaining" msgid="839033553999920138">"корекция на отсечката, която трябва да измине автомобилът"</string>
+    <string name="car_permission_desc_adjust_range_remaining" msgid="2369321650437370673">"Коригирайте стойността за отсечката, която трябва да измине автомобилът."</string>
     <string name="car_permission_label_hvac" msgid="1499454192558727843">"достъп до ОВК системата на автомобила"</string>
     <string name="car_permission_desc_hvac" msgid="3754229695589774195">"Достъп до ОВК системата на автомобила."</string>
     <string name="car_permission_label_mileage" msgid="4661317074631150551">"достъп до информацията за километража на автомобила"</string>
@@ -62,15 +64,17 @@
     <string name="car_permission_desc_car_ux_restrictions_configuration" msgid="5711926927484813777">"Конфигуриране на ограничения за ПРП"</string>
     <string name="car_permission_label_car_handle_usb_aoap_device" msgid="72783989504378036">"Комуникиране с USB устройство в режим AOAP"</string>
     <string name="car_permission_desc_car_handle_usb_aoap_device" msgid="273505990971317034">"Разрешава на приложението да комуникира с устройство в режим AOAP"</string>
+    <string name="car_permission_label_read_car_occupant_awareness_state" msgid="125517953575032758">"Достъп за четене на системата за информираност на пътниците"</string>
+    <string name="car_permission_desc_read_car_occupant_awareness_state" msgid="188865882598414986">"Разрешава четенето на състоянието и данните за установяване на системата за информираност на пътниците"</string>
+    <string name="car_permission_label_control_car_occupant_awareness_system" msgid="7163330266691094542">"Управление на графиката за системата за информираност на пътниците"</string>
+    <string name="car_permission_desc_control_car_occupant_awareness_system" msgid="7123482622084531911">"Разрешава управлението на стартирането и спирането на графиката за установяване на системата за информираност на пътниците"</string>
     <string name="car_permission_label_bind_input_service" msgid="6698489034024273750">"Автомобилна услуга за входящи данни"</string>
     <string name="car_permission_desc_bind_input_service" msgid="1670323419931890170">"Обработване на входящи събития"</string>
     <string name="car_can_bus_failure" msgid="2334035748788283914">"Грешка в CAN шината"</string>
     <string name="car_can_bus_failure_desc" msgid="4125516222786484733">"CAN шината не реагира. Изключете и включете отново захранването на основното устройство и рестартирайте автомобила"</string>
-    <string name="activity_blocked_text" msgid="8088902789540147995">"Не можете да използвате тази функция по време на шофиране"</string>
+    <string name="activity_blocked_text" msgid="5342114426610711378">"От съображения за безопасност тази функция не е достъпна при шофиране.\nЗа да продължите, трябва първо да паркирате."</string>
     <string name="exit_button_message" msgid="8554690915924055685">"За да рестартирате приложението и безопасните му функции, изберете „<xliff:g id="EXIT_BUTTON">%s</xliff:g>“."</string>
     <string name="exit_button" msgid="5829638404777671253">"Назад"</string>
-    <string name="exit_button_close_application" msgid="8824289547809332460">"Затваряне"</string>
-    <string name="exit_button_go_back" msgid="3469083862100560326">"Назад"</string>
     <string name="car_permission_label_diag_read" msgid="7248894224877702604">"четене на диагностични данни"</string>
     <string name="car_permission_desc_diag_read" msgid="1121426363040966178">"Четене на диагностични данни от автомобила."</string>
     <string name="car_permission_label_diag_clear" msgid="4783070510879698157">"изчистване на диагностичните данни"</string>
@@ -89,6 +93,8 @@
     <string name="car_permission_desc_car_engine_detailed" msgid="1746863362811347700">"Достъп до подробна информация за двигателя на автомобила."</string>
     <string name="car_permission_label_car_energy_ports" msgid="8548990315169219454">"достъп до капака за резервоара и порта за зареждане на автомобила"</string>
     <string name="car_permission_desc_car_energy_ports" msgid="7771185999828794949">"Достъп до капака за резервоара и порта за зареждане на автомобила."</string>
+    <string name="car_permission_label_control_car_energy_ports" msgid="4375137311026313475">"управление на капака за резервоара и порта за зареждане на автомобила"</string>
+    <string name="car_permission_desc_control_car_energy_ports" msgid="7364633710492525387">"Управление на капака за резервоара и порта за зареждане на автомобила."</string>
     <string name="car_permission_label_car_identification" msgid="5896712510164020478">"четене на идентификационните данни на автомобила"</string>
     <string name="car_permission_desc_car_identification" msgid="4132040867171275059">"Достъп до идентификационните данни на автомобила."</string>
     <string name="car_permission_label_control_car_doors" msgid="3032058819250195700">"контролиране на вратите на автомобила"</string>
@@ -101,6 +107,8 @@
     <string name="car_permission_desc_control_car_seats" msgid="2407536601226470563">"Контролиране на седалките на автомобила."</string>
     <string name="car_permission_label_car_info" msgid="4707513570676492315">"достъп до основна информация за автомобила"</string>
     <string name="car_permission_desc_car_info" msgid="2118081474543537653">"Достъп до основна информация за автомобила."</string>
+    <string name="car_permission_label_vendor_permission_info" msgid="4471260460536888654">"достъп до информацията за разрешенията на автомобилния производител"</string>
+    <string name="car_permission_desc_vendor_permission_info" msgid="8152113853528488398">"Достъп до информацията за разрешенията на автомобилния производител"</string>
     <string name="car_permission_label_car_exterior_lights" msgid="541304469604902110">"четене на състоянието на външните светлини на автомобила"</string>
     <string name="car_permission_desc_car_exterior_lights" msgid="4038037584100849318">"Достъп до състоянието на външните светлини на автомобила."</string>
     <string name="car_permission_label_control_car_exterior_lights" msgid="101357531386232141">"четене на външните светлини на автомобила"</string>
@@ -127,5 +135,9 @@
     <string name="car_permission_desc_enroll_trust" msgid="4148649994602185130">"Разрешаване на регистрирането на надеждно устройство"</string>
     <string name="car_permission_label_car_test_service" msgid="9159328930558208708">"Управление на тестовия режим на автомобила"</string>
     <string name="car_permission_desc_car_test_service" msgid="7426844534110145843">"Управление на тестовия режим на автомобила"</string>
+    <string name="car_permission_label_control_car_features" msgid="3905791560378888286">"Активиране или деактивиране на функциите на автомобила"</string>
+    <string name="car_permission_desc_control_car_features" msgid="7646711104530599901">"Активиране или деактивиране на функциите на автомобила."</string>
+    <string name="car_permission_label_use_car_watchdog" msgid="6973938293170413475">"използване на защитения таймер на автомобила"</string>
+    <string name="car_permission_desc_use_car_watchdog" msgid="8244592601805516086">"Използване на защитения таймер на автомобила."</string>
     <string name="trust_device_default_name" msgid="4213625926070261253">"Моето устройство"</string>
 </resources>
diff --git a/service/res/values-bn/strings.xml b/service/res/values-bn/strings.xml
index e7fa44c..afa6761 100644
--- a/service/res/values-bn/strings.xml
+++ b/service/res/values-bn/strings.xml
@@ -22,6 +22,8 @@
     <string name="car_permission_desc_camera" msgid="917024932164501426">"আপনার গাড়ির ক্যামেরা(গুলি) অ্যাক্সেস করা।"</string>
     <string name="car_permission_label_energy" msgid="7409144323527821558">"আপনার গাড়ির এনার্জির তথ্য অ্যাক্সেস করা"</string>
     <string name="car_permission_desc_energy" msgid="3392963810053235407">"আপনার গাড়ির এনার্জি তথ্য অ্যাক্সেস করা।"</string>
+    <string name="car_permission_label_adjust_range_remaining" msgid="839033553999920138">"গাড়ি আর কত সময় চলবে তা অ্যাডজাস্ট করতে পারবে"</string>
+    <string name="car_permission_desc_adjust_range_remaining" msgid="2369321650437370673">"গাড়ি আর কত সময় চলবে তার মান অ্যাডজাস্ট করতে পারবে।"</string>
     <string name="car_permission_label_hvac" msgid="1499454192558727843">"গাড়ির HVAC অ্যাক্সেস করা"</string>
     <string name="car_permission_desc_hvac" msgid="3754229695589774195">"আপনার গাড়ির HVAC অ্যাক্সেস করা।"</string>
     <string name="car_permission_label_mileage" msgid="4661317074631150551">"গাড়ির মাইলেজ সংক্রান্ত তথ্য অ্যাক্সেস করা"</string>
@@ -62,16 +64,17 @@
     <string name="car_permission_desc_car_ux_restrictions_configuration" msgid="5711926927484813777">"UX বিধিনিষেধ কনফিগার করা"</string>
     <string name="car_permission_label_car_handle_usb_aoap_device" msgid="72783989504378036">"AOAP মোডে ইউএসবি ডিভাইসের সাথে কানেক্ট করুন"</string>
     <string name="car_permission_desc_car_handle_usb_aoap_device" msgid="273505990971317034">"AOAP মোডে ডিভাইসের সাথে অ্যাপকে কানেক্ট করতে অনুমতি দিন"</string>
+    <string name="car_permission_label_read_car_occupant_awareness_state" msgid="125517953575032758">"অকুপেন্ট সচেতনতা সিস্টেম পড়ার অ্যাক্সেস করা"</string>
+    <string name="car_permission_desc_read_car_occupant_awareness_state" msgid="188865882598414986">"অকুপেন্ট সচেতনতা সিস্টেমের জন্য পড়ার স্ট্যাটাস এবং ডিটেকশন ডেটার অনুমতি দেওয়া"</string>
+    <string name="car_permission_label_control_car_occupant_awareness_system" msgid="7163330266691094542">"অকুপেন্ট সচেতনতা সিস্টেম গ্রাফ কন্ট্রোল করা"</string>
+    <string name="car_permission_desc_control_car_occupant_awareness_system" msgid="7123482622084531911">"অকুপেন্ট সচেতনতা সিস্টেম ডিটেকশন গ্রাফের চালু এবং বন্ধ করার ফিচার নিয়ন্ত্রণে অনুমতি দেওয়া"</string>
     <string name="car_permission_label_bind_input_service" msgid="6698489034024273750">"গাড়ির ইনপুট সার্ভিস"</string>
     <string name="car_permission_desc_bind_input_service" msgid="1670323419931890170">"ইনপুট ইভেন্ট হ্যান্ডেল করা"</string>
     <string name="car_can_bus_failure" msgid="2334035748788283914">"CAN বাস কাজ করছে না"</string>
     <string name="car_can_bus_failure_desc" msgid="4125516222786484733">"CAN বাস কাজ করছে না। হেডইউনিট বক্স খুলে নিয়ে আবার লাগান ও গাড়ি রিস্টার্ট করুন"</string>
-    <!-- no translation found for activity_blocked_text (8088902789540147995) -->
-    <skip />
+    <string name="activity_blocked_text" msgid="5342114426610711378">"আপনার সুরক্ষার জন্য, ড্রাইভ করার সময় এটি করা যাবে না।\nচালিয়ে যেতে, গাড়ি পার্ক করা পর্যন্ত অপেক্ষা করুন।"</string>
     <string name="exit_button_message" msgid="8554690915924055685">"অ্যাপের সুরক্ষিত ফিচারগুলি নিয়ে আবার শুরু করতে, <xliff:g id="EXIT_BUTTON">%s</xliff:g> বেছে নিন।"</string>
     <string name="exit_button" msgid="5829638404777671253">"আবার চালু করুন"</string>
-    <string name="exit_button_close_application" msgid="8824289547809332460">"অ্যাপ বন্ধ করুন"</string>
-    <string name="exit_button_go_back" msgid="3469083862100560326">"ফিরুন"</string>
     <string name="car_permission_label_diag_read" msgid="7248894224877702604">"ডায়াগনস্টিক ডেটা দেখা"</string>
     <string name="car_permission_desc_diag_read" msgid="1121426363040966178">"গাড়ির ডায়াগনস্টিক সংক্রান্ত ডেটা দেখা।"</string>
     <string name="car_permission_label_diag_clear" msgid="4783070510879698157">"ডায়াগনস্টিকস ডেটা সরানো"</string>
@@ -90,6 +93,8 @@
     <string name="car_permission_desc_car_engine_detailed" msgid="1746863362811347700">"আপনার গাড়ির ইঞ্জিনের বিশদ তথ্য অ্যাক্সেস করা।"</string>
     <string name="car_permission_label_car_energy_ports" msgid="8548990315169219454">"গাড়ির জ্বালানীর চেম্বারের ঢাকনা ও চার্জ পোর্ট অ্যাক্সেস করা"</string>
     <string name="car_permission_desc_car_energy_ports" msgid="7771185999828794949">"গাড়ির জ্বালানীর চেম্বারের ঢাকনা ও চার্জ পোর্ট অ্যাক্সেস করা।"</string>
+    <string name="car_permission_label_control_car_energy_ports" msgid="4375137311026313475">"গাড়ির জ্বালানীর চেম্বারের ঢাকনা ও চার্জ পোর্ট নিয়ন্ত্রণ করা"</string>
+    <string name="car_permission_desc_control_car_energy_ports" msgid="7364633710492525387">"গাড়ির জ্বালানীর চেম্বারের ঢাকনা ও চার্জ পোর্ট নিয়ন্ত্রণ করা।"</string>
     <string name="car_permission_label_car_identification" msgid="5896712510164020478">"গাড়ির শনাক্তকরণ সংক্রান্ত তথ্য দেখা"</string>
     <string name="car_permission_desc_car_identification" msgid="4132040867171275059">"গাড়ির শনাক্তকরণ সংক্রান্ত তথ্য অ্যাক্সেস করা।"</string>
     <string name="car_permission_label_control_car_doors" msgid="3032058819250195700">"গাড়ির দরজা নিয়ন্ত্রণ করা"</string>
@@ -102,6 +107,8 @@
     <string name="car_permission_desc_control_car_seats" msgid="2407536601226470563">"গাড়ির সিট নিয়ন্ত্রণ করা।"</string>
     <string name="car_permission_label_car_info" msgid="4707513570676492315">"গাড়ির সাধারণ তথ্য অ্যাক্সেস করা"</string>
     <string name="car_permission_desc_car_info" msgid="2118081474543537653">"গাড়ির সাধারণ তথ্য অ্যাক্সেস করা।"</string>
+    <string name="car_permission_label_vendor_permission_info" msgid="4471260460536888654">"গাড়ির ভেন্ডরের অনুমতি সম্পর্কে তথ্য অ্যাক্সেস করুন"</string>
+    <string name="car_permission_desc_vendor_permission_info" msgid="8152113853528488398">"গাড়ির ভেন্ডরের অনুমতি সম্পর্কে তথ্য অ্যাক্সেস করুন।"</string>
     <string name="car_permission_label_car_exterior_lights" msgid="541304469604902110">"গাড়ির এক্সটিরিয়র বা বাইরের দিকের লাইটের স্ট্যাটাস দেখা"</string>
     <string name="car_permission_desc_car_exterior_lights" msgid="4038037584100849318">"গাড়ির এক্সটিরিয়র বা বাইরের দিকের লাইটের স্ট্যাটাস অ্যাক্সেস করা।"</string>
     <string name="car_permission_label_control_car_exterior_lights" msgid="101357531386232141">"গাড়ির এক্সটিরিয়র বা বাইরের দিকের লাইট দেখা"</string>
@@ -128,5 +135,9 @@
     <string name="car_permission_desc_enroll_trust" msgid="4148649994602185130">"বিশ্বস্ত ডিভাইস নথিভুক্ত করার অনুমতি দিন"</string>
     <string name="car_permission_label_car_test_service" msgid="9159328930558208708">"গাড়ির টেস্ট মোড নিয়ন্ত্রণ করুন"</string>
     <string name="car_permission_desc_car_test_service" msgid="7426844534110145843">"গাড়ির টেস্ট মোড নিয়ন্ত্রণ করুন"</string>
+    <string name="car_permission_label_control_car_features" msgid="3905791560378888286">"গাড়ির ফিচার চালু বা বন্ধ করুন"</string>
+    <string name="car_permission_desc_control_car_features" msgid="7646711104530599901">"গাড়ির ফিচার চালু বা বন্ধ করুন।"</string>
+    <string name="car_permission_label_use_car_watchdog" msgid="6973938293170413475">"গাড়ির ওয়াচডগ ফিচারটি ব্যবহার করুন"</string>
+    <string name="car_permission_desc_use_car_watchdog" msgid="8244592601805516086">"গাড়ির ওয়াচডগ ফিচারটি ব্যবহার করুন।"</string>
     <string name="trust_device_default_name" msgid="4213625926070261253">"আমার ডিভাইস"</string>
 </resources>
diff --git a/service/res/values-bs/strings.xml b/service/res/values-bs/strings.xml
index e69c06b..46e6224 100644
--- a/service/res/values-bs/strings.xml
+++ b/service/res/values-bs/strings.xml
@@ -22,6 +22,8 @@
     <string name="car_permission_desc_camera" msgid="917024932164501426">"Pristupiti kameri(ama) automobila."</string>
     <string name="car_permission_label_energy" msgid="7409144323527821558">"pristupiti informacijama o energiji automobila"</string>
     <string name="car_permission_desc_energy" msgid="3392963810053235407">"Pristupiti informacijama o energiji automobila."</string>
+    <string name="car_permission_label_adjust_range_remaining" msgid="839033553999920138">"podesi preostali domet automobila"</string>
+    <string name="car_permission_desc_adjust_range_remaining" msgid="2369321650437370673">"Podesi vrijednost preostalog dometa automobila."</string>
     <string name="car_permission_label_hvac" msgid="1499454192558727843">"pristupiti grijanju, ventilaciji i klimatizaciji automobila"</string>
     <string name="car_permission_desc_hvac" msgid="3754229695589774195">"Pristupiti grijanju, ventilaciji i klimatizaciji automobila."</string>
     <string name="car_permission_label_mileage" msgid="4661317074631150551">"pristupiti informacijama o pređenim kilometrima automobila"</string>
@@ -62,16 +64,17 @@
     <string name="car_permission_desc_car_ux_restrictions_configuration" msgid="5711926927484813777">"Konfigurirajte ograničenja IK-a"</string>
     <string name="car_permission_label_car_handle_usb_aoap_device" msgid="72783989504378036">"Komunicirati s USB uređajem u AOAP načinu"</string>
     <string name="car_permission_desc_car_handle_usb_aoap_device" msgid="273505990971317034">"Omogućava aplikaciji da komunicira s uređajem u AOAP načinu"</string>
+    <string name="car_permission_label_read_car_occupant_awareness_state" msgid="125517953575032758">"Pristup očitavanju za Sistem informiranosti o broju prisutnih"</string>
+    <string name="car_permission_desc_read_car_occupant_awareness_state" msgid="188865882598414986">"Omogućava očitavanje statusa i otkrivanje podataka za Sistem informiranosti o broju prisutnih"</string>
+    <string name="car_permission_label_control_car_occupant_awareness_system" msgid="7163330266691094542">"Kontroliranje grafikona Sistema informiranosti o broju prisutnih"</string>
+    <string name="car_permission_desc_control_car_occupant_awareness_system" msgid="7123482622084531911">"Omogućava kontroliranje početka i zaustavljanja grafikona otkrivanja Sistema informiranosti o broju prisutnih"</string>
     <string name="car_permission_label_bind_input_service" msgid="6698489034024273750">"Usluga unosa za automobil"</string>
     <string name="car_permission_desc_bind_input_service" msgid="1670323419931890170">"Rukovati događajima unosa"</string>
     <string name="car_can_bus_failure" msgid="2334035748788283914">"Greška CAN busa"</string>
     <string name="car_can_bus_failure_desc" msgid="4125516222786484733">"CAN bus ne reagira. Isključite i ponovo uključite glavnu jedinicu i ponovo pokrenite automobil"</string>
-    <!-- no translation found for activity_blocked_text (8088902789540147995) -->
-    <skip />
+    <string name="activity_blocked_text" msgid="5342114426610711378">"Radi vaše sigurnosti, ova aktivnost nije dostupna tokom vožnje.\nDa nastavite, sačekajte da se zaustavite."</string>
     <string name="exit_button_message" msgid="8554690915924055685">"Da počnete ponovo s funkcijama sigurne aplikacije, odaberite <xliff:g id="EXIT_BUTTON">%s</xliff:g>."</string>
     <string name="exit_button" msgid="5829638404777671253">"Nazad"</string>
-    <string name="exit_button_close_application" msgid="8824289547809332460">"Zatvori aplikaciju"</string>
-    <string name="exit_button_go_back" msgid="3469083862100560326">"Nazad"</string>
     <string name="car_permission_label_diag_read" msgid="7248894224877702604">"očitati dijagnostičke podatke"</string>
     <string name="car_permission_desc_diag_read" msgid="1121426363040966178">"Očitati dijagnostičke podatke automobila."</string>
     <string name="car_permission_label_diag_clear" msgid="4783070510879698157">"brisati dijagnostičke podatke"</string>
@@ -90,6 +93,8 @@
     <string name="car_permission_desc_car_engine_detailed" msgid="1746863362811347700">"Pristupiti detaljnim informacijama o motoru automobila."</string>
     <string name="car_permission_label_car_energy_ports" msgid="8548990315169219454">"pristupiti poklopcu rezervoara za gorivo i priključku za punjenje"</string>
     <string name="car_permission_desc_car_energy_ports" msgid="7771185999828794949">"Pristupiti poklopcu rezervoara za gorivo i priključku za punjenje."</string>
+    <string name="car_permission_label_control_car_energy_ports" msgid="4375137311026313475">"kontrolirati poklopac rezervoara za gorivo i priključak za punjenje"</string>
+    <string name="car_permission_desc_control_car_energy_ports" msgid="7364633710492525387">"Kontroliranje poklopca rezervoara za gorivo i priključka za punjenje."</string>
     <string name="car_permission_label_car_identification" msgid="5896712510164020478">"očitati identifikaciju automobila"</string>
     <string name="car_permission_desc_car_identification" msgid="4132040867171275059">"Pristupiti identifikaciji automobila."</string>
     <string name="car_permission_label_control_car_doors" msgid="3032058819250195700">"kontrolirati vrata automobila"</string>
@@ -102,6 +107,8 @@
     <string name="car_permission_desc_control_car_seats" msgid="2407536601226470563">"Kontrolirati sjedala automobila."</string>
     <string name="car_permission_label_car_info" msgid="4707513570676492315">"pristupiti osnovnim podacima automobila"</string>
     <string name="car_permission_desc_car_info" msgid="2118081474543537653">"Pristupiti osnovnim informacijama automobila."</string>
+    <string name="car_permission_label_vendor_permission_info" msgid="4471260460536888654">"pristup informacijama o odobrenjima trgovca automobilima"</string>
+    <string name="car_permission_desc_vendor_permission_info" msgid="8152113853528488398">"Pristup informacijama o odobrenjima trgovca automobilima"</string>
     <string name="car_permission_label_car_exterior_lights" msgid="541304469604902110">"očitati stanje vanjskih svjetala automobila"</string>
     <string name="car_permission_desc_car_exterior_lights" msgid="4038037584100849318">"Pristupiti podacima o stanju vanjskih svjetala automobila."</string>
     <string name="car_permission_label_control_car_exterior_lights" msgid="101357531386232141">"očitati informacije o vanjskim svjetlima automobila"</string>
@@ -128,5 +135,9 @@
     <string name="car_permission_desc_enroll_trust" msgid="4148649994602185130">"Dozvoli prijavu pouzdanih uređaja"</string>
     <string name="car_permission_label_car_test_service" msgid="9159328930558208708">"Kontroliranje testnim načinom automobila"</string>
     <string name="car_permission_desc_car_test_service" msgid="7426844534110145843">"Kontroliranje testnim načinom automobila"</string>
+    <string name="car_permission_label_control_car_features" msgid="3905791560378888286">"Omogućavanje ili onemogućavanje funkcija automobila"</string>
+    <string name="car_permission_desc_control_car_features" msgid="7646711104530599901">"Omogućavanje ili onemogućavanje funkcija automobila."</string>
+    <string name="car_permission_label_use_car_watchdog" msgid="6973938293170413475">"koristi čuvara automobila"</string>
+    <string name="car_permission_desc_use_car_watchdog" msgid="8244592601805516086">"Koristi čuvara automobila."</string>
     <string name="trust_device_default_name" msgid="4213625926070261253">"Moj uređaj"</string>
 </resources>
diff --git a/service/res/values-ca/strings.xml b/service/res/values-ca/strings.xml
index c20c41a..7eb9205 100644
--- a/service/res/values-ca/strings.xml
+++ b/service/res/values-ca/strings.xml
@@ -22,6 +22,8 @@
     <string name="car_permission_desc_camera" msgid="917024932164501426">"Accedir a la càmera del cotxe"</string>
     <string name="car_permission_label_energy" msgid="7409144323527821558">"accedeix a la informació sobre l\'energia del cotxe"</string>
     <string name="car_permission_desc_energy" msgid="3392963810053235407">"Accedir a la informació sobre l\'energia del cotxe"</string>
+    <string name="car_permission_label_adjust_range_remaining" msgid="839033553999920138">"ajustar l\'autonomia restant del cotxe"</string>
+    <string name="car_permission_desc_adjust_range_remaining" msgid="2369321650437370673">"Ajustar el valor de l\'autonomia restant del cotxe."</string>
     <string name="car_permission_label_hvac" msgid="1499454192558727843">"accedeix al sistema HVAC del cotxe"</string>
     <string name="car_permission_desc_hvac" msgid="3754229695589774195">"Accedir al sistema HVAC del cotxe"</string>
     <string name="car_permission_label_mileage" msgid="4661317074631150551">"accedeix a la informació sobre el quilometratge del cotxe"</string>
@@ -62,15 +64,17 @@
     <string name="car_permission_desc_car_ux_restrictions_configuration" msgid="5711926927484813777">"Configurar les restriccions de l\'experiència d\'usuari"</string>
     <string name="car_permission_label_car_handle_usb_aoap_device" msgid="72783989504378036">"Comunica amb un dispositiu USB al mode AOAP"</string>
     <string name="car_permission_desc_car_handle_usb_aoap_device" msgid="273505990971317034">"Permet que una aplicació es comuniqui amb un dispositiu amb el mode AOAP"</string>
+    <string name="car_permission_label_read_car_occupant_awareness_state" msgid="125517953575032758">"Accés de lectura al Sistema de detecció d\'ocupants"</string>
+    <string name="car_permission_desc_read_car_occupant_awareness_state" msgid="188865882598414986">"Permet llegir les dades de detecció i d\'estat del Sistema de detecció d\'ocupants"</string>
+    <string name="car_permission_label_control_car_occupant_awareness_system" msgid="7163330266691094542">"Controlar el gràfic del Sistema de detecció d\'ocupants"</string>
+    <string name="car_permission_desc_control_car_occupant_awareness_system" msgid="7123482622084531911">"Permet controlar l\'inici i la pausa del gràfic de detecció del Sistema de detecció d\'ocupants"</string>
     <string name="car_permission_label_bind_input_service" msgid="6698489034024273750">"Servei d\'entrada del cotxe"</string>
     <string name="car_permission_desc_bind_input_service" msgid="1670323419931890170">"Gestionar els esdeveniments d\'entrada"</string>
     <string name="car_can_bus_failure" msgid="2334035748788283914">"Ha fallat el bus CAN"</string>
     <string name="car_can_bus_failure_desc" msgid="4125516222786484733">"El bus CAN no respon. Desendolla i torna a endollar el capçal i torna a engegar el cotxe."</string>
-    <string name="activity_blocked_text" msgid="8088902789540147995">"No pots fer servir aquesta funció mentre condueixes"</string>
+    <string name="activity_blocked_text" msgid="5342114426610711378">"Per motius de seguretat, aquesta activitat no està disponible mentre condueixes.\nPer continuar, has d\'estar aparcat."</string>
     <string name="exit_button_message" msgid="8554690915924055685">"Per tornar a començar amb unes funcions d\'aplicació segures, selecciona <xliff:g id="EXIT_BUTTON">%s</xliff:g>."</string>
     <string name="exit_button" msgid="5829638404777671253">"Enrere"</string>
-    <string name="exit_button_close_application" msgid="8824289547809332460">"Tanca l\'aplicació"</string>
-    <string name="exit_button_go_back" msgid="3469083862100560326">"Enrere"</string>
     <string name="car_permission_label_diag_read" msgid="7248894224877702604">"llegeix les dades de diagnòstic"</string>
     <string name="car_permission_desc_diag_read" msgid="1121426363040966178">"Llegeix les dades de diagnòstic del cotxe."</string>
     <string name="car_permission_label_diag_clear" msgid="4783070510879698157">"esborra les dades de diagnòstic"</string>
@@ -89,6 +93,8 @@
     <string name="car_permission_desc_car_engine_detailed" msgid="1746863362811347700">"Accedeix a la informació detallada sobre el motor del cotxe."</string>
     <string name="car_permission_label_car_energy_ports" msgid="8548990315169219454">"accedeix a la porta del combustible i al port de càrrega del cotxe"</string>
     <string name="car_permission_desc_car_energy_ports" msgid="7771185999828794949">"Accedeix a la porta del combustible i al port de càrrega del cotxe."</string>
+    <string name="car_permission_label_control_car_energy_ports" msgid="4375137311026313475">"controlar la tapa del dipòsit de combustible i el port de càrrega del cotxe"</string>
+    <string name="car_permission_desc_control_car_energy_ports" msgid="7364633710492525387">"Controlar la tapa del dipòsit de combustible i el port de càrrega del cotxe."</string>
     <string name="car_permission_label_car_identification" msgid="5896712510164020478">"llegeix la identificació del cotxe"</string>
     <string name="car_permission_desc_car_identification" msgid="4132040867171275059">"Accedeix a la identificació del cotxe."</string>
     <string name="car_permission_label_control_car_doors" msgid="3032058819250195700">"controla les portes del cotxe"</string>
@@ -101,6 +107,8 @@
     <string name="car_permission_desc_control_car_seats" msgid="2407536601226470563">"Controla els seients del cotxe."</string>
     <string name="car_permission_label_car_info" msgid="4707513570676492315">"accedeix a la informació bàsica del cotxe"</string>
     <string name="car_permission_desc_car_info" msgid="2118081474543537653">"Accedeix a la informació bàsica del cotxe."</string>
+    <string name="car_permission_label_vendor_permission_info" msgid="4471260460536888654">"accedeix a la informació sobre permisos del fabricant del cotxe"</string>
+    <string name="car_permission_desc_vendor_permission_info" msgid="8152113853528488398">"Accedeix a la informació sobre permisos del fabricant del cotxe."</string>
     <string name="car_permission_label_car_exterior_lights" msgid="541304469604902110">"llegeix l\'estat dels llums exteriors del cotxe"</string>
     <string name="car_permission_desc_car_exterior_lights" msgid="4038037584100849318">"Accedeix a l\'estat dels llums exteriors del cotxe."</string>
     <string name="car_permission_label_control_car_exterior_lights" msgid="101357531386232141">"llegeix els llums exteriors del cotxe"</string>
@@ -127,5 +135,9 @@
     <string name="car_permission_desc_enroll_trust" msgid="4148649994602185130">"Permet el registre de dispositius de confiança"</string>
     <string name="car_permission_label_car_test_service" msgid="9159328930558208708">"Controla el mode de prova del cotxe"</string>
     <string name="car_permission_desc_car_test_service" msgid="7426844534110145843">"Controla el mode de prova del cotxe"</string>
+    <string name="car_permission_label_control_car_features" msgid="3905791560378888286">"Activa o desactiva les funcions del cotxe"</string>
+    <string name="car_permission_desc_control_car_features" msgid="7646711104530599901">"Activa o desactiva les funcions del cotxe."</string>
+    <string name="car_permission_label_use_car_watchdog" msgid="6973938293170413475">"utilitza el temporitzador de vigilància del cotxe"</string>
+    <string name="car_permission_desc_use_car_watchdog" msgid="8244592601805516086">"Utilitza el temporitzador de vigilància del cotxe."</string>
     <string name="trust_device_default_name" msgid="4213625926070261253">"El meu dispositiu"</string>
 </resources>
diff --git a/service/res/values-cs/strings.xml b/service/res/values-cs/strings.xml
index 315dee1..cfaa375 100644
--- a/service/res/values-cs/strings.xml
+++ b/service/res/values-cs/strings.xml
@@ -22,6 +22,8 @@
     <string name="car_permission_desc_camera" msgid="917024932164501426">"Přístup ke kamerám auta."</string>
     <string name="car_permission_label_energy" msgid="7409144323527821558">"přístup k údajům o energii auta"</string>
     <string name="car_permission_desc_energy" msgid="3392963810053235407">"Přístup k údajům o energii auta."</string>
+    <string name="car_permission_label_adjust_range_remaining" msgid="839033553999920138">"úprava dojezdu auta – zbytek"</string>
+    <string name="car_permission_desc_adjust_range_remaining" msgid="2369321650437370673">"Úprava zbývající hodnoty dojezdu auta."</string>
     <string name="car_permission_label_hvac" msgid="1499454192558727843">"přístup k topení, větrání a klimatizaci auta"</string>
     <string name="car_permission_desc_hvac" msgid="3754229695589774195">"Přístup k systému HVAC auta."</string>
     <string name="car_permission_label_mileage" msgid="4661317074631150551">"přístup k ujetým kilometrům auta"</string>
@@ -62,15 +64,17 @@
     <string name="car_permission_desc_car_ux_restrictions_configuration" msgid="5711926927484813777">"Konfigurace omezení uživatelského prostředí"</string>
     <string name="car_permission_label_car_handle_usb_aoap_device" msgid="72783989504378036">"Komunikace se zařízením USB v režimu AOAP"</string>
     <string name="car_permission_desc_car_handle_usb_aoap_device" msgid="273505990971317034">"Umožňuje aplikaci komunikovat se zařízením v režimu AOAP"</string>
+    <string name="car_permission_label_read_car_occupant_awareness_state" msgid="125517953575032758">"Systému detekce uživatele – přístup ke čtení"</string>
+    <string name="car_permission_desc_read_car_occupant_awareness_state" msgid="188865882598414986">"Umožňuje čtení stavu a dat ze systému detekce uživatele"</string>
+    <string name="car_permission_label_control_car_occupant_awareness_system" msgid="7163330266691094542">"Systému detekce uživatele – ovládání grafu"</string>
+    <string name="car_permission_desc_control_car_occupant_awareness_system" msgid="7123482622084531911">"Umožňuje ovládat zahájení a ukončení grafu systému detekce uživatele"</string>
     <string name="car_permission_label_bind_input_service" msgid="6698489034024273750">"Služba vstupu auta"</string>
     <string name="car_permission_desc_bind_input_service" msgid="1670323419931890170">"Zpracování vstupních událostí"</string>
     <string name="car_can_bus_failure" msgid="2334035748788283914">"Sběrnice CAN selhala"</string>
     <string name="car_can_bus_failure_desc" msgid="4125516222786484733">"Sběrnice CAN neodpovídá. Odpojte a opět zapojte autorádio a znovu nastartujte auto"</string>
-    <string name="activity_blocked_text" msgid="8088902789540147995">"Tuto funkci nelze používat při řízení"</string>
+    <string name="activity_blocked_text" msgid="5342114426610711378">"Tato aktivita není při řízení z bezpečnostních důvodů dostupná.\nPokračovat můžete, až zaparkujete."</string>
     <string name="exit_button_message" msgid="8554690915924055685">"Chcete-li začít znovu s bezpečnými funkcemi aplikace, vyberte <xliff:g id="EXIT_BUTTON">%s</xliff:g>."</string>
     <string name="exit_button" msgid="5829638404777671253">"Zpět"</string>
-    <string name="exit_button_close_application" msgid="8824289547809332460">"Zavřít aplikaci"</string>
-    <string name="exit_button_go_back" msgid="3469083862100560326">"Zpět"</string>
     <string name="car_permission_label_diag_read" msgid="7248894224877702604">"čtení diagnostických dat"</string>
     <string name="car_permission_desc_diag_read" msgid="1121426363040966178">"Čtení diagnostických dat z auta."</string>
     <string name="car_permission_label_diag_clear" msgid="4783070510879698157">"vymazat diagnostická data"</string>
@@ -89,6 +93,8 @@
     <string name="car_permission_desc_car_engine_detailed" msgid="1746863362811347700">"Přístup k podrobným údajům o motoru auta."</string>
     <string name="car_permission_label_car_energy_ports" msgid="8548990315169219454">"přístup ke vstupu do nádrže a nabíjecímu portu auta"</string>
     <string name="car_permission_desc_car_energy_ports" msgid="7771185999828794949">"Přístup ke vstupu do nádrže a nabíjecímu portu auta."</string>
+    <string name="car_permission_label_control_car_energy_ports" msgid="4375137311026313475">"přístup ke vstupu do nádrže a nabíjecímu portu auta"</string>
+    <string name="car_permission_desc_control_car_energy_ports" msgid="7364633710492525387">"Přístup ke vstupu do nádrže a nabíjecímu portu auta."</string>
     <string name="car_permission_label_car_identification" msgid="5896712510164020478">"čtení identifikace auta"</string>
     <string name="car_permission_desc_car_identification" msgid="4132040867171275059">"Přístup k identifikaci auta."</string>
     <string name="car_permission_label_control_car_doors" msgid="3032058819250195700">"ovládání dveří auta"</string>
@@ -101,6 +107,8 @@
     <string name="car_permission_desc_control_car_seats" msgid="2407536601226470563">"Ovládání autosedaček."</string>
     <string name="car_permission_label_car_info" msgid="4707513570676492315">"přístup k základním informacím o autu"</string>
     <string name="car_permission_desc_car_info" msgid="2118081474543537653">"Přístup k základním informacím o autu."</string>
+    <string name="car_permission_label_vendor_permission_info" msgid="4471260460536888654">"přístup k informacím o oprávněních dodavatele auta"</string>
+    <string name="car_permission_desc_vendor_permission_info" msgid="8152113853528488398">"Přístup k informacím o oprávněních dodavatele auta."</string>
     <string name="car_permission_label_car_exterior_lights" msgid="541304469604902110">"zjištění stavu vnějších světel auta"</string>
     <string name="car_permission_desc_car_exterior_lights" msgid="4038037584100849318">"Přístup ke stavu vnějších světel auta."</string>
     <string name="car_permission_label_control_car_exterior_lights" msgid="101357531386232141">"ovládání vnějších světel auta"</string>
@@ -127,5 +135,9 @@
     <string name="car_permission_desc_enroll_trust" msgid="4148649994602185130">"Povolit registraci důvěryhodného zařízení"</string>
     <string name="car_permission_label_car_test_service" msgid="9159328930558208708">"Ovládání testovacího režimu auta"</string>
     <string name="car_permission_desc_car_test_service" msgid="7426844534110145843">"Ovládání testovacího režimu auta"</string>
+    <string name="car_permission_label_control_car_features" msgid="3905791560378888286">"Zapnout nebo vypnout funkce auta"</string>
+    <string name="car_permission_desc_control_car_features" msgid="7646711104530599901">"Zapnout nebo vypnout funkce auta."</string>
+    <string name="car_permission_label_use_car_watchdog" msgid="6973938293170413475">"používat sledování auta"</string>
+    <string name="car_permission_desc_use_car_watchdog" msgid="8244592601805516086">"Používat sledování auta."</string>
     <string name="trust_device_default_name" msgid="4213625926070261253">"Moje zařízení"</string>
 </resources>
diff --git a/service/res/values-da/strings.xml b/service/res/values-da/strings.xml
index d62f993..b2c075f 100644
--- a/service/res/values-da/strings.xml
+++ b/service/res/values-da/strings.xml
@@ -22,6 +22,8 @@
     <string name="car_permission_desc_camera" msgid="917024932164501426">"Få adgang til bilens kameraer."</string>
     <string name="car_permission_label_energy" msgid="7409144323527821558">"få adgang til oplysninger om bilens energiforbrug"</string>
     <string name="car_permission_desc_energy" msgid="3392963810053235407">"Få adgang til oplysninger om bilens energiforbrug"</string>
+    <string name="car_permission_label_adjust_range_remaining" msgid="839033553999920138">"juster bilens resterende afstand."</string>
+    <string name="car_permission_desc_adjust_range_remaining" msgid="2369321650437370673">"Juster værdien for bilens resterende afstand."</string>
     <string name="car_permission_label_hvac" msgid="1499454192558727843">"få adgang til bilens ventilation"</string>
     <string name="car_permission_desc_hvac" msgid="3754229695589774195">"Få adgang til bilens ventilationssystem."</string>
     <string name="car_permission_label_mileage" msgid="4661317074631150551">"få adgang til oplysninger om bilens kilometertal"</string>
@@ -62,15 +64,17 @@
     <string name="car_permission_desc_car_ux_restrictions_configuration" msgid="5711926927484813777">"Konfigurer UX-begrænsninger"</string>
     <string name="car_permission_label_car_handle_usb_aoap_device" msgid="72783989504378036">"Kommunikere med en USB-enhed i AOAP-tilstand"</string>
     <string name="car_permission_desc_car_handle_usb_aoap_device" msgid="273505990971317034">"Tillader, at en app kan kommunikere med en enhed i AOAP-tilstand"</string>
+    <string name="car_permission_label_read_car_occupant_awareness_state" msgid="125517953575032758">"Adgang til aflæsning af Occupant Awareness System"</string>
+    <string name="car_permission_desc_read_car_occupant_awareness_state" msgid="188865882598414986">"Giver mulighed for at aflæse status og registreringsdata for Occupant Awareness System"</string>
+    <string name="car_permission_label_control_car_occupant_awareness_system" msgid="7163330266691094542">"Styr grafen for Occupant Awareness System"</string>
+    <string name="car_permission_desc_control_car_occupant_awareness_system" msgid="7123482622084531911">"Giver mulighed for at starte og stoppe registreringsgrafen for Occupant Awareness System"</string>
     <string name="car_permission_label_bind_input_service" msgid="6698489034024273750">"Bilens inputservice"</string>
     <string name="car_permission_desc_bind_input_service" msgid="1670323419931890170">"Håndter input"</string>
     <string name="car_can_bus_failure" msgid="2334035748788283914">"CAN-bus (Controller Area Network) mislykkedes"</string>
     <string name="car_can_bus_failure_desc" msgid="4125516222786484733">"CAN-bus (Controller Area Network) svarer ikke. Afbryd forbindelsen til bilens hovedenhed, tilslut den igen, og genstart bilen"</string>
-    <string name="activity_blocked_text" msgid="8088902789540147995">"Du kan ikke bruge denne funktion, mens du kører"</string>
+    <string name="activity_blocked_text" msgid="5342114426610711378">"Af hensyn til din sikkerhed kan du ikke få adgang til denne aktivitet, mens du kører.\nParkér, før du kan fortsætte."</string>
     <string name="exit_button_message" msgid="8554690915924055685">"Vælg <xliff:g id="EXIT_BUTTON">%s</xliff:g> for at starte forfra med sikre appfunktioner."</string>
     <string name="exit_button" msgid="5829638404777671253">"Tilbage"</string>
-    <string name="exit_button_close_application" msgid="8824289547809332460">"Luk app"</string>
-    <string name="exit_button_go_back" msgid="3469083862100560326">"Tilbage"</string>
     <string name="car_permission_label_diag_read" msgid="7248894224877702604">"tjekke diagnosticeringsdata"</string>
     <string name="car_permission_desc_diag_read" msgid="1121426363040966178">"Tjek diagnosticeringsdata fra bilen."</string>
     <string name="car_permission_label_diag_clear" msgid="4783070510879698157">"rydde diagnosticeringsdata"</string>
@@ -89,6 +93,8 @@
     <string name="car_permission_desc_car_engine_detailed" msgid="1746863362811347700">"Få adgang til detaljerede oplysninger om bilens motor."</string>
     <string name="car_permission_label_car_energy_ports" msgid="8548990315169219454">"få adgang til bilens tankdæksel og opladningsport"</string>
     <string name="car_permission_desc_car_energy_ports" msgid="7771185999828794949">"Få adgang til bilens tankdæksel og opladningsport."</string>
+    <string name="car_permission_label_control_car_energy_ports" msgid="4375137311026313475">"styr bilens tankdæksel og opladningsport"</string>
+    <string name="car_permission_desc_control_car_energy_ports" msgid="7364633710492525387">"Styr bilens tankdæksel og opladningsport."</string>
     <string name="car_permission_label_car_identification" msgid="5896712510164020478">"tjekke bilens identifikation"</string>
     <string name="car_permission_desc_car_identification" msgid="4132040867171275059">"Få adgang til bilens identifikation."</string>
     <string name="car_permission_label_control_car_doors" msgid="3032058819250195700">"styre bilens døre"</string>
@@ -101,6 +107,8 @@
     <string name="car_permission_desc_control_car_seats" msgid="2407536601226470563">"Styr bilens sæder."</string>
     <string name="car_permission_label_car_info" msgid="4707513570676492315">"få adgang til grundlæggende oplysninger om bilen"</string>
     <string name="car_permission_desc_car_info" msgid="2118081474543537653">"Få adgang til grundlæggende oplysninger om bilen."</string>
+    <string name="car_permission_label_vendor_permission_info" msgid="4471260460536888654">"få adgang til oplysninger om tilladelser for bilens producent"</string>
+    <string name="car_permission_desc_vendor_permission_info" msgid="8152113853528488398">"Få adgang til oplysninger om tilladelser for bilens producent."</string>
     <string name="car_permission_label_car_exterior_lights" msgid="541304469604902110">"tjekke status for bilens lygter"</string>
     <string name="car_permission_desc_car_exterior_lights" msgid="4038037584100849318">"Se status for bilens lygter."</string>
     <string name="car_permission_label_control_car_exterior_lights" msgid="101357531386232141">"tjekke bilens lygter"</string>
@@ -127,5 +135,9 @@
     <string name="car_permission_desc_enroll_trust" msgid="4148649994602185130">"Tillad tilmelding af godkendt enhed"</string>
     <string name="car_permission_label_car_test_service" msgid="9159328930558208708">"Styr bilens testtilstand"</string>
     <string name="car_permission_desc_car_test_service" msgid="7426844534110145843">"Styr bilens testtilstand"</string>
+    <string name="car_permission_label_control_car_features" msgid="3905791560378888286">"Aktivér eller deaktiver bilens funktioner"</string>
+    <string name="car_permission_desc_control_car_features" msgid="7646711104530599901">"Aktivér eller deaktiver bilens funktioner."</string>
+    <string name="car_permission_label_use_car_watchdog" msgid="6973938293170413475">"brug bilens watchdog"</string>
+    <string name="car_permission_desc_use_car_watchdog" msgid="8244592601805516086">"Brug bilens watchdog."</string>
     <string name="trust_device_default_name" msgid="4213625926070261253">"Min enhed"</string>
 </resources>
diff --git a/service/res/values-de/strings.xml b/service/res/values-de/strings.xml
index 2d18db9..b472961 100644
--- a/service/res/values-de/strings.xml
+++ b/service/res/values-de/strings.xml
@@ -22,6 +22,8 @@
     <string name="car_permission_desc_camera" msgid="917024932164501426">"Auf Autokamera(s) zugreifen."</string>
     <string name="car_permission_label_energy" msgid="7409144323527821558">"auf Energieinformationen des Autos zuzugreifen"</string>
     <string name="car_permission_desc_energy" msgid="3392963810053235407">"Auf Energieinformationen des Autos zugreifen."</string>
+    <string name="car_permission_label_adjust_range_remaining" msgid="839033553999920138">"Strecke anpassen, die das Auto noch fahren kann"</string>
+    <string name="car_permission_desc_adjust_range_remaining" msgid="2369321650437370673">"Wert für die Strecke anpassen, die das Auto noch fahren kann."</string>
     <string name="car_permission_label_hvac" msgid="1499454192558727843">"auf die Klimaanlage zuzugreifen"</string>
     <string name="car_permission_desc_hvac" msgid="3754229695589774195">"Auf Klimaanlage des Autos zugreifen."</string>
     <string name="car_permission_label_mileage" msgid="4661317074631150551">"auf den Kilometerstand zuzugreifen"</string>
@@ -62,16 +64,17 @@
     <string name="car_permission_desc_car_ux_restrictions_configuration" msgid="5711926927484813777">"UX-Einschränkungen konfigurieren"</string>
     <string name="car_permission_label_car_handle_usb_aoap_device" msgid="72783989504378036">"Mit USB-Gerät im AOAP-Modus kommunizieren"</string>
     <string name="car_permission_desc_car_handle_usb_aoap_device" msgid="273505990971317034">"Ermöglicht einer App, mit einem Gerät im AOAP-Modus zu kommunizieren"</string>
+    <string name="car_permission_label_read_car_occupant_awareness_state" msgid="125517953575032758">"Occupant Awareness System – Lesezugriff"</string>
+    <string name="car_permission_desc_read_car_occupant_awareness_state" msgid="188865882598414986">"Berechtigung, die Status- und Erkennungsdaten des Occupant Awareness System zu lesen"</string>
+    <string name="car_permission_label_control_car_occupant_awareness_system" msgid="7163330266691094542">"Occupant Awareness System Graph steuern"</string>
+    <string name="car_permission_desc_control_car_occupant_awareness_system" msgid="7123482622084531911">"Berechtigung, den Start- und Stoppvorgang des Occupant Awareness System-Erkennungsgraphen zu steuern"</string>
     <string name="car_permission_label_bind_input_service" msgid="6698489034024273750">"Eingabedienst für das Auto"</string>
     <string name="car_permission_desc_bind_input_service" msgid="1670323419931890170">"Eingabe-Ereignisse verwalten"</string>
     <string name="car_can_bus_failure" msgid="2334035748788283914">"CAN-Bus ausgefallen"</string>
     <string name="car_can_bus_failure_desc" msgid="4125516222786484733">"CAN-Bus reagiert nicht. Trenne die Haupteinheit vom Stromnetz, schließe sie wieder an und starte das Auto."</string>
-    <!-- no translation found for activity_blocked_text (8088902789540147995) -->
-    <skip />
+    <string name="activity_blocked_text" msgid="5342114426610711378">"Aus Sicherheitsgründen ist diese Aktivität während der Fahrt nicht verfügbar.\nWarte, bis das Fahrzeug geparkt ist."</string>
     <string name="exit_button_message" msgid="8554690915924055685">"Wähle <xliff:g id="EXIT_BUTTON">%s</xliff:g>, um die App mit sicheren Funktionen neu zu starten."</string>
     <string name="exit_button" msgid="5829638404777671253">"Zurück"</string>
-    <string name="exit_button_close_application" msgid="8824289547809332460">"App schließen"</string>
-    <string name="exit_button_go_back" msgid="3469083862100560326">"Zurück"</string>
     <string name="car_permission_label_diag_read" msgid="7248894224877702604">"Diagnosedaten zu lesen"</string>
     <string name="car_permission_desc_diag_read" msgid="1121426363040966178">"Diagnosedaten des Autos lesen."</string>
     <string name="car_permission_label_diag_clear" msgid="4783070510879698157">"Diagnosedaten zu löschen"</string>
@@ -90,6 +93,8 @@
     <string name="car_permission_desc_car_engine_detailed" msgid="1746863362811347700">"Auf detaillierte Motorinformationen zugreifen."</string>
     <string name="car_permission_label_car_energy_ports" msgid="8548990315169219454">"auf die Tankklappe und die Ladebuchse zuzugreifen"</string>
     <string name="car_permission_desc_car_energy_ports" msgid="7771185999828794949">"Auf Tankklappe und Ladebuchse zugreifen."</string>
+    <string name="car_permission_label_control_car_energy_ports" msgid="4375137311026313475">"Tankklappe und Ladebuchse steuern"</string>
+    <string name="car_permission_desc_control_car_energy_ports" msgid="7364633710492525387">"Tankklappe und Ladebuchse steuern."</string>
     <string name="car_permission_label_car_identification" msgid="5896712510164020478">"die Fahrzeug-Identifizierungsnummer zu lesen"</string>
     <string name="car_permission_desc_car_identification" msgid="4132040867171275059">"Auf Fahrzeug-Identifizierungsnummer zugreifen."</string>
     <string name="car_permission_label_control_car_doors" msgid="3032058819250195700">"die Autotüren zu steuern"</string>
@@ -102,6 +107,8 @@
     <string name="car_permission_desc_control_car_seats" msgid="2407536601226470563">"Autositze steuern."</string>
     <string name="car_permission_label_car_info" msgid="4707513570676492315">"auf grundlegende Fahrzeuginformationen zuzugreifen"</string>
     <string name="car_permission_desc_car_info" msgid="2118081474543537653">"Auf grundlegende Fahrzeuginformationen zugreifen."</string>
+    <string name="car_permission_label_vendor_permission_info" msgid="4471260460536888654">"Auf Informationen zu Berechtigungen des Herstellers zugreifen"</string>
+    <string name="car_permission_desc_vendor_permission_info" msgid="8152113853528488398">"Auf Informationen zu Berechtigungen des Herstellers zugreifen."</string>
     <string name="car_permission_label_car_exterior_lights" msgid="541304469604902110">"Informationen zum Zustand der Außenbeleuchtung zu lesen"</string>
     <string name="car_permission_desc_car_exterior_lights" msgid="4038037584100849318">"Auf Informationen zum Zustand der Außenbeleuchtung zugreifen."</string>
     <string name="car_permission_label_control_car_exterior_lights" msgid="101357531386232141">"Informationen zur Außenbeleuchtung zu lesen"</string>
@@ -128,5 +135,9 @@
     <string name="car_permission_desc_enroll_trust" msgid="4148649994602185130">"Registrieren eines vertrauenswürdigen Geräts erlauben"</string>
     <string name="car_permission_label_car_test_service" msgid="9159328930558208708">"Testmodus des Autos steuern"</string>
     <string name="car_permission_desc_car_test_service" msgid="7426844534110145843">"Testmodus des Autos steuern"</string>
+    <string name="car_permission_label_control_car_features" msgid="3905791560378888286">"Funktionen des Autos aktivieren oder deaktivieren"</string>
+    <string name="car_permission_desc_control_car_features" msgid="7646711104530599901">"Funktionen des Autos aktivieren oder deaktivieren."</string>
+    <string name="car_permission_label_use_car_watchdog" msgid="6973938293170413475">"Watchdog im Auto verwenden"</string>
+    <string name="car_permission_desc_use_car_watchdog" msgid="8244592601805516086">"Watchdog im Auto verwenden."</string>
     <string name="trust_device_default_name" msgid="4213625926070261253">"Mein Gerät"</string>
 </resources>
diff --git a/service/res/values-el/strings.xml b/service/res/values-el/strings.xml
index b9efd6e..7b75754 100644
--- a/service/res/values-el/strings.xml
+++ b/service/res/values-el/strings.xml
@@ -22,6 +22,8 @@
     <string name="car_permission_desc_camera" msgid="917024932164501426">"Πρόσβαση στις κάμερες του αυτοκινήτου σας."</string>
     <string name="car_permission_label_energy" msgid="7409144323527821558">"πρόσβαση στις πληροφορίες ενέργειας του αυτοκινήτου"</string>
     <string name="car_permission_desc_energy" msgid="3392963810053235407">"Πρόσβαση σε πληροφορίες ενέργειας του αυτοκινήτου σας."</string>
+    <string name="car_permission_label_adjust_range_remaining" msgid="839033553999920138">"προσαρμόστε το εύρος αυτοκινήτου που απομένει"</string>
+    <string name="car_permission_desc_adjust_range_remaining" msgid="2369321650437370673">"Προσαρμόστε την τιμή του εύρους αυτοκινήτου που απομένει."</string>
     <string name="car_permission_label_hvac" msgid="1499454192558727843">"πρόσβαση στο σύστημα θέρμανσης-αερισμού-κλιματισμού του αυτοκινήτου"</string>
     <string name="car_permission_desc_hvac" msgid="3754229695589774195">"Πρόσβαση στο σύστημα θέρμανσης, αερισμού, και κλιματισμού του αυτοκινήτου σας."</string>
     <string name="car_permission_label_mileage" msgid="4661317074631150551">"πρόσβαση στις πληροφορίες διανυθείσας απόστασης του αυτοκινήτου"</string>
@@ -62,16 +64,17 @@
     <string name="car_permission_desc_car_ux_restrictions_configuration" msgid="5711926927484813777">"Διαμόρφωση περιορισμών εμπειρίας χρήστη"</string>
     <string name="car_permission_label_car_handle_usb_aoap_device" msgid="72783989504378036">"Επικοινωνία με συσκευή USB σε λειτουργία AOAP"</string>
     <string name="car_permission_desc_car_handle_usb_aoap_device" msgid="273505990971317034">"Επιτρέπει σε μια εφαρμογή να επικοινωνεί με μια συσκευή σε λειτουργία AOAP"</string>
+    <string name="car_permission_label_read_car_occupant_awareness_state" msgid="125517953575032758">"Πρόσβαση ανάγνωσης συστήματος ελέγχου συμπεριφοράς οδηγού"</string>
+    <string name="car_permission_desc_read_car_occupant_awareness_state" msgid="188865882598414986">"Επιτρέπει την ανάγνωση των δεδομένων κατάστασης και ανίχνευσης του συστήματος ελέγχου συμπεριφοράς οδηγού."</string>
+    <string name="car_permission_label_control_car_occupant_awareness_system" msgid="7163330266691094542">"Έλεγχος γραφήματος συστήματος ελέγχου συμπεριφοράς οδηγού"</string>
+    <string name="car_permission_desc_control_car_occupant_awareness_system" msgid="7123482622084531911">"Επιτρέπει τον έλεγχο της έναρξης και της διακοπής του γραφήματος ανίχνευσης του συστήματος ελέγχου συμπεριφοράς οδηγού."</string>
     <string name="car_permission_label_bind_input_service" msgid="6698489034024273750">"Υπηρεσία εισόδου αυτοκινήτου"</string>
     <string name="car_permission_desc_bind_input_service" msgid="1670323419931890170">"Χειρισμός συμβάντων εισόδου"</string>
     <string name="car_can_bus_failure" msgid="2334035748788283914">"Αποτυχία διαύλου CAN"</string>
     <string name="car_can_bus_failure_desc" msgid="4125516222786484733">"Ο δίαυλος CAN δεν αποκρίνεται. Αποσυνδέστε και συνδέστε ξανά το πλαίσιο μονάδας κεφαλής και έπειτα επανεκκινήστε το αυτοκίνητο"</string>
-    <!-- no translation found for activity_blocked_text (8088902789540147995) -->
-    <skip />
+    <string name="activity_blocked_text" msgid="5342114426610711378">"Για λόγους ασφαλείας, η δραστηρ. δεν είναι διαθέσιμη όταν οδηγείτε.\nΓια να συνεχίσετε, περιμένετε μέχρι να σταθμεύσετε."</string>
     <string name="exit_button_message" msgid="8554690915924055685">"Για να ξεκινήσετε από την αρχή με ασφαλείς λειτουργίες εφαρμογής, επιλέξτε <xliff:g id="EXIT_BUTTON">%s</xliff:g>."</string>
     <string name="exit_button" msgid="5829638404777671253">"Πίσω"</string>
-    <string name="exit_button_close_application" msgid="8824289547809332460">"Κλείσιμο εφαρμογής"</string>
-    <string name="exit_button_go_back" msgid="3469083862100560326">"Πίσω"</string>
     <string name="car_permission_label_diag_read" msgid="7248894224877702604">"παρακολούθηση δεδομένων διαγνωστικών στοιχείων"</string>
     <string name="car_permission_desc_diag_read" msgid="1121426363040966178">"Παρακολούθηση δεδομένων διαγνωστικών στοιχείων από το αυτοκίνητο."</string>
     <string name="car_permission_label_diag_clear" msgid="4783070510879698157">"διαγραφή δεδομένων διαγνωστικών στοιχείων"</string>
@@ -90,6 +93,8 @@
     <string name="car_permission_desc_car_engine_detailed" msgid="1746863362811347700">"Πρόσβαση σε λεπτομερείς πληροφορίες του κινητήρα του αυτοκινήτου."</string>
     <string name="car_permission_label_car_energy_ports" msgid="8548990315169219454">"πρόσβαση στο πορτάκι του ρεζερβουάρ και της θύρας φόρτισης"</string>
     <string name="car_permission_desc_car_energy_ports" msgid="7771185999828794949">"Πρόσβαση στη θύρα καυσίμου και το πορτάκι του ρεζερβουάρ."</string>
+    <string name="car_permission_label_control_car_energy_ports" msgid="4375137311026313475">"έλεγχος για τη θύρα καυσίμου και το πορτάκι του ρεζερβουάρ."</string>
+    <string name="car_permission_desc_control_car_energy_ports" msgid="7364633710492525387">"Έλεγχος για τη θύρα καυσίμου και το πορτάκι του ρεζερβουάρ."</string>
     <string name="car_permission_label_car_identification" msgid="5896712510164020478">"παρακολούθηση στοιχείων αυτοκινήτου"</string>
     <string name="car_permission_desc_car_identification" msgid="4132040867171275059">"Πρόσβαση στα στοιχεία του αυτοκινήτου."</string>
     <string name="car_permission_label_control_car_doors" msgid="3032058819250195700">"έλεγχος θυρών αυτοκινήτου"</string>
@@ -102,6 +107,8 @@
     <string name="car_permission_desc_control_car_seats" msgid="2407536601226470563">"Έλεγχος καθισμάτων αυτοκινήτου."</string>
     <string name="car_permission_label_car_info" msgid="4707513570676492315">"πρόσβαση στις βασικές πληροφορίες του αυτοκινήτου"</string>
     <string name="car_permission_desc_car_info" msgid="2118081474543537653">"Πρόσβαση σε βασικές πληροφορίες του αυτοκινήτου."</string>
+    <string name="car_permission_label_vendor_permission_info" msgid="4471260460536888654">"πρόσβαση στις πληροφορίες άδειας πωλητή του αυτοκινήτου"</string>
+    <string name="car_permission_desc_vendor_permission_info" msgid="8152113853528488398">"Πρόσβαση στις πληροφορίες άδειας πωλητή του αυτοκινήτου."</string>
     <string name="car_permission_label_car_exterior_lights" msgid="541304469604902110">"παρακολούθηση κατάστασης εξωτερικών φώτων του αυτοκινήτου"</string>
     <string name="car_permission_desc_car_exterior_lights" msgid="4038037584100849318">"Πρόσβαση στην κατάσταση εξωτερικών φώτων του αυτοκινήτου."</string>
     <string name="car_permission_label_control_car_exterior_lights" msgid="101357531386232141">"παρακολούθηση εξωτερικών φώτων του αυτοκινήτου"</string>
@@ -128,5 +135,9 @@
     <string name="car_permission_desc_enroll_trust" msgid="4148649994602185130">"Να επιτρέπεται η εγγραφή αξιόπιστης συσκευής"</string>
     <string name="car_permission_label_car_test_service" msgid="9159328930558208708">"Έλεγχος της λειτουργίας δοκιμής του αυτοκινήτου"</string>
     <string name="car_permission_desc_car_test_service" msgid="7426844534110145843">"Έλεγχος της λειτουργίας δοκιμής του αυτοκινήτου"</string>
+    <string name="car_permission_label_control_car_features" msgid="3905791560378888286">"Ενεργοποίηση ή απενεργοποίηση των λειτουργιών του αυτοκινήτου."</string>
+    <string name="car_permission_desc_control_car_features" msgid="7646711104530599901">"Ενεργοποίηση ή απενεργοποίηση των λειτουργιών του αυτοκινήτου."</string>
+    <string name="car_permission_label_use_car_watchdog" msgid="6973938293170413475">"χρήση watchdog αυτοκινήτου"</string>
+    <string name="car_permission_desc_use_car_watchdog" msgid="8244592601805516086">"Χρήση watchdog αυτοκινήτου."</string>
     <string name="trust_device_default_name" msgid="4213625926070261253">"Η συσκευή μου"</string>
 </resources>
diff --git a/service/res/values-en-rAU/strings.xml b/service/res/values-en-rAU/strings.xml
index baea94a..1d723ed 100644
--- a/service/res/values-en-rAU/strings.xml
+++ b/service/res/values-en-rAU/strings.xml
@@ -22,6 +22,8 @@
     <string name="car_permission_desc_camera" msgid="917024932164501426">"Access your car’s camera(s)."</string>
     <string name="car_permission_label_energy" msgid="7409144323527821558">"access car’s energy information"</string>
     <string name="car_permission_desc_energy" msgid="3392963810053235407">"Access your car’s energy information."</string>
+    <string name="car_permission_label_adjust_range_remaining" msgid="839033553999920138">"adjust car’s range remaining"</string>
+    <string name="car_permission_desc_adjust_range_remaining" msgid="2369321650437370673">"Adjust car’s range remaining value."</string>
     <string name="car_permission_label_hvac" msgid="1499454192558727843">"access car’s hvac"</string>
     <string name="car_permission_desc_hvac" msgid="3754229695589774195">"Access your car’s HVAC."</string>
     <string name="car_permission_label_mileage" msgid="4661317074631150551">"access car’s mileage information"</string>
@@ -62,15 +64,17 @@
     <string name="car_permission_desc_car_ux_restrictions_configuration" msgid="5711926927484813777">"Configure UX restrictions"</string>
     <string name="car_permission_label_car_handle_usb_aoap_device" msgid="72783989504378036">"Communicate with USB device in AOAP mode"</string>
     <string name="car_permission_desc_car_handle_usb_aoap_device" msgid="273505990971317034">"Allows an app to communicate with a device in AOAP mode"</string>
+    <string name="car_permission_label_read_car_occupant_awareness_state" msgid="125517953575032758">"Occupant awareness system read access"</string>
+    <string name="car_permission_desc_read_car_occupant_awareness_state" msgid="188865882598414986">"Allows reading status and detection data for occupant awareness system"</string>
+    <string name="car_permission_label_control_car_occupant_awareness_system" msgid="7163330266691094542">"Control occupant awareness system graph"</string>
+    <string name="car_permission_desc_control_car_occupant_awareness_system" msgid="7123482622084531911">"Allows controlling the start and stop of the occupant awareness system detection graph"</string>
     <string name="car_permission_label_bind_input_service" msgid="6698489034024273750">"Car input service"</string>
     <string name="car_permission_desc_bind_input_service" msgid="1670323419931890170">"Handle input events"</string>
     <string name="car_can_bus_failure" msgid="2334035748788283914">"CAN bus failed"</string>
     <string name="car_can_bus_failure_desc" msgid="4125516222786484733">"CAN bus does not respond. Unplug and plug back in head unit box and restart the car"</string>
-    <string name="activity_blocked_text" msgid="8088902789540147995">"You can’t use this feature while driving"</string>
+    <string name="activity_blocked_text" msgid="5342114426610711378">"For your safety, this activity isn’t available while driving.\nTo continue, wait until you’ve parked."</string>
     <string name="exit_button_message" msgid="8554690915924055685">"To start again with safe app features, select <xliff:g id="EXIT_BUTTON">%s</xliff:g>."</string>
     <string name="exit_button" msgid="5829638404777671253">"Back"</string>
-    <string name="exit_button_close_application" msgid="8824289547809332460">"Close app"</string>
-    <string name="exit_button_go_back" msgid="3469083862100560326">"Back"</string>
     <string name="car_permission_label_diag_read" msgid="7248894224877702604">"read diagnostic data"</string>
     <string name="car_permission_desc_diag_read" msgid="1121426363040966178">"Read diagnostic data from the car."</string>
     <string name="car_permission_label_diag_clear" msgid="4783070510879698157">"clear diagnostic data"</string>
@@ -89,6 +93,8 @@
     <string name="car_permission_desc_car_engine_detailed" msgid="1746863362811347700">"Access your car’s detailed engine information."</string>
     <string name="car_permission_label_car_energy_ports" msgid="8548990315169219454">"access car’s fuel door and charge port"</string>
     <string name="car_permission_desc_car_energy_ports" msgid="7771185999828794949">"Access car’s fuel door and charge port."</string>
+    <string name="car_permission_label_control_car_energy_ports" msgid="4375137311026313475">"control car’s fuel door and charge port"</string>
+    <string name="car_permission_desc_control_car_energy_ports" msgid="7364633710492525387">"Control car’s fuel door and charge port."</string>
     <string name="car_permission_label_car_identification" msgid="5896712510164020478">"read car’s identification"</string>
     <string name="car_permission_desc_car_identification" msgid="4132040867171275059">"Access car’s identification."</string>
     <string name="car_permission_label_control_car_doors" msgid="3032058819250195700">"control car’s doors"</string>
@@ -101,6 +107,8 @@
     <string name="car_permission_desc_control_car_seats" msgid="2407536601226470563">"Control car’s seats."</string>
     <string name="car_permission_label_car_info" msgid="4707513570676492315">"access car’s basic information"</string>
     <string name="car_permission_desc_car_info" msgid="2118081474543537653">"Access car’s basic information."</string>
+    <string name="car_permission_label_vendor_permission_info" msgid="4471260460536888654">"access car’s vendor permission information"</string>
+    <string name="car_permission_desc_vendor_permission_info" msgid="8152113853528488398">"Access car’s vendor permission information."</string>
     <string name="car_permission_label_car_exterior_lights" msgid="541304469604902110">"read car’s exterior lights state"</string>
     <string name="car_permission_desc_car_exterior_lights" msgid="4038037584100849318">"Access car’s exterior lights state."</string>
     <string name="car_permission_label_control_car_exterior_lights" msgid="101357531386232141">"read car’s exterior lights"</string>
@@ -127,5 +135,9 @@
     <string name="car_permission_desc_enroll_trust" msgid="4148649994602185130">"Allow Trusted Device Enrollment"</string>
     <string name="car_permission_label_car_test_service" msgid="9159328930558208708">"Control car’s test mode"</string>
     <string name="car_permission_desc_car_test_service" msgid="7426844534110145843">"Control car’s test mode"</string>
+    <string name="car_permission_label_control_car_features" msgid="3905791560378888286">"Enable or disable car’s features"</string>
+    <string name="car_permission_desc_control_car_features" msgid="7646711104530599901">"Enable or disable car’s features."</string>
+    <string name="car_permission_label_use_car_watchdog" msgid="6973938293170413475">"use car watchdog"</string>
+    <string name="car_permission_desc_use_car_watchdog" msgid="8244592601805516086">"Use car watchdog."</string>
     <string name="trust_device_default_name" msgid="4213625926070261253">"My Device"</string>
 </resources>
diff --git a/service/res/values-en-rCA/strings.xml b/service/res/values-en-rCA/strings.xml
index baea94a..1d723ed 100644
--- a/service/res/values-en-rCA/strings.xml
+++ b/service/res/values-en-rCA/strings.xml
@@ -22,6 +22,8 @@
     <string name="car_permission_desc_camera" msgid="917024932164501426">"Access your car’s camera(s)."</string>
     <string name="car_permission_label_energy" msgid="7409144323527821558">"access car’s energy information"</string>
     <string name="car_permission_desc_energy" msgid="3392963810053235407">"Access your car’s energy information."</string>
+    <string name="car_permission_label_adjust_range_remaining" msgid="839033553999920138">"adjust car’s range remaining"</string>
+    <string name="car_permission_desc_adjust_range_remaining" msgid="2369321650437370673">"Adjust car’s range remaining value."</string>
     <string name="car_permission_label_hvac" msgid="1499454192558727843">"access car’s hvac"</string>
     <string name="car_permission_desc_hvac" msgid="3754229695589774195">"Access your car’s HVAC."</string>
     <string name="car_permission_label_mileage" msgid="4661317074631150551">"access car’s mileage information"</string>
@@ -62,15 +64,17 @@
     <string name="car_permission_desc_car_ux_restrictions_configuration" msgid="5711926927484813777">"Configure UX restrictions"</string>
     <string name="car_permission_label_car_handle_usb_aoap_device" msgid="72783989504378036">"Communicate with USB device in AOAP mode"</string>
     <string name="car_permission_desc_car_handle_usb_aoap_device" msgid="273505990971317034">"Allows an app to communicate with a device in AOAP mode"</string>
+    <string name="car_permission_label_read_car_occupant_awareness_state" msgid="125517953575032758">"Occupant awareness system read access"</string>
+    <string name="car_permission_desc_read_car_occupant_awareness_state" msgid="188865882598414986">"Allows reading status and detection data for occupant awareness system"</string>
+    <string name="car_permission_label_control_car_occupant_awareness_system" msgid="7163330266691094542">"Control occupant awareness system graph"</string>
+    <string name="car_permission_desc_control_car_occupant_awareness_system" msgid="7123482622084531911">"Allows controlling the start and stop of the occupant awareness system detection graph"</string>
     <string name="car_permission_label_bind_input_service" msgid="6698489034024273750">"Car input service"</string>
     <string name="car_permission_desc_bind_input_service" msgid="1670323419931890170">"Handle input events"</string>
     <string name="car_can_bus_failure" msgid="2334035748788283914">"CAN bus failed"</string>
     <string name="car_can_bus_failure_desc" msgid="4125516222786484733">"CAN bus does not respond. Unplug and plug back in head unit box and restart the car"</string>
-    <string name="activity_blocked_text" msgid="8088902789540147995">"You can’t use this feature while driving"</string>
+    <string name="activity_blocked_text" msgid="5342114426610711378">"For your safety, this activity isn’t available while driving.\nTo continue, wait until you’ve parked."</string>
     <string name="exit_button_message" msgid="8554690915924055685">"To start again with safe app features, select <xliff:g id="EXIT_BUTTON">%s</xliff:g>."</string>
     <string name="exit_button" msgid="5829638404777671253">"Back"</string>
-    <string name="exit_button_close_application" msgid="8824289547809332460">"Close app"</string>
-    <string name="exit_button_go_back" msgid="3469083862100560326">"Back"</string>
     <string name="car_permission_label_diag_read" msgid="7248894224877702604">"read diagnostic data"</string>
     <string name="car_permission_desc_diag_read" msgid="1121426363040966178">"Read diagnostic data from the car."</string>
     <string name="car_permission_label_diag_clear" msgid="4783070510879698157">"clear diagnostic data"</string>
@@ -89,6 +93,8 @@
     <string name="car_permission_desc_car_engine_detailed" msgid="1746863362811347700">"Access your car’s detailed engine information."</string>
     <string name="car_permission_label_car_energy_ports" msgid="8548990315169219454">"access car’s fuel door and charge port"</string>
     <string name="car_permission_desc_car_energy_ports" msgid="7771185999828794949">"Access car’s fuel door and charge port."</string>
+    <string name="car_permission_label_control_car_energy_ports" msgid="4375137311026313475">"control car’s fuel door and charge port"</string>
+    <string name="car_permission_desc_control_car_energy_ports" msgid="7364633710492525387">"Control car’s fuel door and charge port."</string>
     <string name="car_permission_label_car_identification" msgid="5896712510164020478">"read car’s identification"</string>
     <string name="car_permission_desc_car_identification" msgid="4132040867171275059">"Access car’s identification."</string>
     <string name="car_permission_label_control_car_doors" msgid="3032058819250195700">"control car’s doors"</string>
@@ -101,6 +107,8 @@
     <string name="car_permission_desc_control_car_seats" msgid="2407536601226470563">"Control car’s seats."</string>
     <string name="car_permission_label_car_info" msgid="4707513570676492315">"access car’s basic information"</string>
     <string name="car_permission_desc_car_info" msgid="2118081474543537653">"Access car’s basic information."</string>
+    <string name="car_permission_label_vendor_permission_info" msgid="4471260460536888654">"access car’s vendor permission information"</string>
+    <string name="car_permission_desc_vendor_permission_info" msgid="8152113853528488398">"Access car’s vendor permission information."</string>
     <string name="car_permission_label_car_exterior_lights" msgid="541304469604902110">"read car’s exterior lights state"</string>
     <string name="car_permission_desc_car_exterior_lights" msgid="4038037584100849318">"Access car’s exterior lights state."</string>
     <string name="car_permission_label_control_car_exterior_lights" msgid="101357531386232141">"read car’s exterior lights"</string>
@@ -127,5 +135,9 @@
     <string name="car_permission_desc_enroll_trust" msgid="4148649994602185130">"Allow Trusted Device Enrollment"</string>
     <string name="car_permission_label_car_test_service" msgid="9159328930558208708">"Control car’s test mode"</string>
     <string name="car_permission_desc_car_test_service" msgid="7426844534110145843">"Control car’s test mode"</string>
+    <string name="car_permission_label_control_car_features" msgid="3905791560378888286">"Enable or disable car’s features"</string>
+    <string name="car_permission_desc_control_car_features" msgid="7646711104530599901">"Enable or disable car’s features."</string>
+    <string name="car_permission_label_use_car_watchdog" msgid="6973938293170413475">"use car watchdog"</string>
+    <string name="car_permission_desc_use_car_watchdog" msgid="8244592601805516086">"Use car watchdog."</string>
     <string name="trust_device_default_name" msgid="4213625926070261253">"My Device"</string>
 </resources>
diff --git a/service/res/values-en-rGB/strings.xml b/service/res/values-en-rGB/strings.xml
index baea94a..1d723ed 100644
--- a/service/res/values-en-rGB/strings.xml
+++ b/service/res/values-en-rGB/strings.xml
@@ -22,6 +22,8 @@
     <string name="car_permission_desc_camera" msgid="917024932164501426">"Access your car’s camera(s)."</string>
     <string name="car_permission_label_energy" msgid="7409144323527821558">"access car’s energy information"</string>
     <string name="car_permission_desc_energy" msgid="3392963810053235407">"Access your car’s energy information."</string>
+    <string name="car_permission_label_adjust_range_remaining" msgid="839033553999920138">"adjust car’s range remaining"</string>
+    <string name="car_permission_desc_adjust_range_remaining" msgid="2369321650437370673">"Adjust car’s range remaining value."</string>
     <string name="car_permission_label_hvac" msgid="1499454192558727843">"access car’s hvac"</string>
     <string name="car_permission_desc_hvac" msgid="3754229695589774195">"Access your car’s HVAC."</string>
     <string name="car_permission_label_mileage" msgid="4661317074631150551">"access car’s mileage information"</string>
@@ -62,15 +64,17 @@
     <string name="car_permission_desc_car_ux_restrictions_configuration" msgid="5711926927484813777">"Configure UX restrictions"</string>
     <string name="car_permission_label_car_handle_usb_aoap_device" msgid="72783989504378036">"Communicate with USB device in AOAP mode"</string>
     <string name="car_permission_desc_car_handle_usb_aoap_device" msgid="273505990971317034">"Allows an app to communicate with a device in AOAP mode"</string>
+    <string name="car_permission_label_read_car_occupant_awareness_state" msgid="125517953575032758">"Occupant awareness system read access"</string>
+    <string name="car_permission_desc_read_car_occupant_awareness_state" msgid="188865882598414986">"Allows reading status and detection data for occupant awareness system"</string>
+    <string name="car_permission_label_control_car_occupant_awareness_system" msgid="7163330266691094542">"Control occupant awareness system graph"</string>
+    <string name="car_permission_desc_control_car_occupant_awareness_system" msgid="7123482622084531911">"Allows controlling the start and stop of the occupant awareness system detection graph"</string>
     <string name="car_permission_label_bind_input_service" msgid="6698489034024273750">"Car input service"</string>
     <string name="car_permission_desc_bind_input_service" msgid="1670323419931890170">"Handle input events"</string>
     <string name="car_can_bus_failure" msgid="2334035748788283914">"CAN bus failed"</string>
     <string name="car_can_bus_failure_desc" msgid="4125516222786484733">"CAN bus does not respond. Unplug and plug back in head unit box and restart the car"</string>
-    <string name="activity_blocked_text" msgid="8088902789540147995">"You can’t use this feature while driving"</string>
+    <string name="activity_blocked_text" msgid="5342114426610711378">"For your safety, this activity isn’t available while driving.\nTo continue, wait until you’ve parked."</string>
     <string name="exit_button_message" msgid="8554690915924055685">"To start again with safe app features, select <xliff:g id="EXIT_BUTTON">%s</xliff:g>."</string>
     <string name="exit_button" msgid="5829638404777671253">"Back"</string>
-    <string name="exit_button_close_application" msgid="8824289547809332460">"Close app"</string>
-    <string name="exit_button_go_back" msgid="3469083862100560326">"Back"</string>
     <string name="car_permission_label_diag_read" msgid="7248894224877702604">"read diagnostic data"</string>
     <string name="car_permission_desc_diag_read" msgid="1121426363040966178">"Read diagnostic data from the car."</string>
     <string name="car_permission_label_diag_clear" msgid="4783070510879698157">"clear diagnostic data"</string>
@@ -89,6 +93,8 @@
     <string name="car_permission_desc_car_engine_detailed" msgid="1746863362811347700">"Access your car’s detailed engine information."</string>
     <string name="car_permission_label_car_energy_ports" msgid="8548990315169219454">"access car’s fuel door and charge port"</string>
     <string name="car_permission_desc_car_energy_ports" msgid="7771185999828794949">"Access car’s fuel door and charge port."</string>
+    <string name="car_permission_label_control_car_energy_ports" msgid="4375137311026313475">"control car’s fuel door and charge port"</string>
+    <string name="car_permission_desc_control_car_energy_ports" msgid="7364633710492525387">"Control car’s fuel door and charge port."</string>
     <string name="car_permission_label_car_identification" msgid="5896712510164020478">"read car’s identification"</string>
     <string name="car_permission_desc_car_identification" msgid="4132040867171275059">"Access car’s identification."</string>
     <string name="car_permission_label_control_car_doors" msgid="3032058819250195700">"control car’s doors"</string>
@@ -101,6 +107,8 @@
     <string name="car_permission_desc_control_car_seats" msgid="2407536601226470563">"Control car’s seats."</string>
     <string name="car_permission_label_car_info" msgid="4707513570676492315">"access car’s basic information"</string>
     <string name="car_permission_desc_car_info" msgid="2118081474543537653">"Access car’s basic information."</string>
+    <string name="car_permission_label_vendor_permission_info" msgid="4471260460536888654">"access car’s vendor permission information"</string>
+    <string name="car_permission_desc_vendor_permission_info" msgid="8152113853528488398">"Access car’s vendor permission information."</string>
     <string name="car_permission_label_car_exterior_lights" msgid="541304469604902110">"read car’s exterior lights state"</string>
     <string name="car_permission_desc_car_exterior_lights" msgid="4038037584100849318">"Access car’s exterior lights state."</string>
     <string name="car_permission_label_control_car_exterior_lights" msgid="101357531386232141">"read car’s exterior lights"</string>
@@ -127,5 +135,9 @@
     <string name="car_permission_desc_enroll_trust" msgid="4148649994602185130">"Allow Trusted Device Enrollment"</string>
     <string name="car_permission_label_car_test_service" msgid="9159328930558208708">"Control car’s test mode"</string>
     <string name="car_permission_desc_car_test_service" msgid="7426844534110145843">"Control car’s test mode"</string>
+    <string name="car_permission_label_control_car_features" msgid="3905791560378888286">"Enable or disable car’s features"</string>
+    <string name="car_permission_desc_control_car_features" msgid="7646711104530599901">"Enable or disable car’s features."</string>
+    <string name="car_permission_label_use_car_watchdog" msgid="6973938293170413475">"use car watchdog"</string>
+    <string name="car_permission_desc_use_car_watchdog" msgid="8244592601805516086">"Use car watchdog."</string>
     <string name="trust_device_default_name" msgid="4213625926070261253">"My Device"</string>
 </resources>
diff --git a/service/res/values-en-rIN/strings.xml b/service/res/values-en-rIN/strings.xml
index baea94a..1d723ed 100644
--- a/service/res/values-en-rIN/strings.xml
+++ b/service/res/values-en-rIN/strings.xml
@@ -22,6 +22,8 @@
     <string name="car_permission_desc_camera" msgid="917024932164501426">"Access your car’s camera(s)."</string>
     <string name="car_permission_label_energy" msgid="7409144323527821558">"access car’s energy information"</string>
     <string name="car_permission_desc_energy" msgid="3392963810053235407">"Access your car’s energy information."</string>
+    <string name="car_permission_label_adjust_range_remaining" msgid="839033553999920138">"adjust car’s range remaining"</string>
+    <string name="car_permission_desc_adjust_range_remaining" msgid="2369321650437370673">"Adjust car’s range remaining value."</string>
     <string name="car_permission_label_hvac" msgid="1499454192558727843">"access car’s hvac"</string>
     <string name="car_permission_desc_hvac" msgid="3754229695589774195">"Access your car’s HVAC."</string>
     <string name="car_permission_label_mileage" msgid="4661317074631150551">"access car’s mileage information"</string>
@@ -62,15 +64,17 @@
     <string name="car_permission_desc_car_ux_restrictions_configuration" msgid="5711926927484813777">"Configure UX restrictions"</string>
     <string name="car_permission_label_car_handle_usb_aoap_device" msgid="72783989504378036">"Communicate with USB device in AOAP mode"</string>
     <string name="car_permission_desc_car_handle_usb_aoap_device" msgid="273505990971317034">"Allows an app to communicate with a device in AOAP mode"</string>
+    <string name="car_permission_label_read_car_occupant_awareness_state" msgid="125517953575032758">"Occupant awareness system read access"</string>
+    <string name="car_permission_desc_read_car_occupant_awareness_state" msgid="188865882598414986">"Allows reading status and detection data for occupant awareness system"</string>
+    <string name="car_permission_label_control_car_occupant_awareness_system" msgid="7163330266691094542">"Control occupant awareness system graph"</string>
+    <string name="car_permission_desc_control_car_occupant_awareness_system" msgid="7123482622084531911">"Allows controlling the start and stop of the occupant awareness system detection graph"</string>
     <string name="car_permission_label_bind_input_service" msgid="6698489034024273750">"Car input service"</string>
     <string name="car_permission_desc_bind_input_service" msgid="1670323419931890170">"Handle input events"</string>
     <string name="car_can_bus_failure" msgid="2334035748788283914">"CAN bus failed"</string>
     <string name="car_can_bus_failure_desc" msgid="4125516222786484733">"CAN bus does not respond. Unplug and plug back in head unit box and restart the car"</string>
-    <string name="activity_blocked_text" msgid="8088902789540147995">"You can’t use this feature while driving"</string>
+    <string name="activity_blocked_text" msgid="5342114426610711378">"For your safety, this activity isn’t available while driving.\nTo continue, wait until you’ve parked."</string>
     <string name="exit_button_message" msgid="8554690915924055685">"To start again with safe app features, select <xliff:g id="EXIT_BUTTON">%s</xliff:g>."</string>
     <string name="exit_button" msgid="5829638404777671253">"Back"</string>
-    <string name="exit_button_close_application" msgid="8824289547809332460">"Close app"</string>
-    <string name="exit_button_go_back" msgid="3469083862100560326">"Back"</string>
     <string name="car_permission_label_diag_read" msgid="7248894224877702604">"read diagnostic data"</string>
     <string name="car_permission_desc_diag_read" msgid="1121426363040966178">"Read diagnostic data from the car."</string>
     <string name="car_permission_label_diag_clear" msgid="4783070510879698157">"clear diagnostic data"</string>
@@ -89,6 +93,8 @@
     <string name="car_permission_desc_car_engine_detailed" msgid="1746863362811347700">"Access your car’s detailed engine information."</string>
     <string name="car_permission_label_car_energy_ports" msgid="8548990315169219454">"access car’s fuel door and charge port"</string>
     <string name="car_permission_desc_car_energy_ports" msgid="7771185999828794949">"Access car’s fuel door and charge port."</string>
+    <string name="car_permission_label_control_car_energy_ports" msgid="4375137311026313475">"control car’s fuel door and charge port"</string>
+    <string name="car_permission_desc_control_car_energy_ports" msgid="7364633710492525387">"Control car’s fuel door and charge port."</string>
     <string name="car_permission_label_car_identification" msgid="5896712510164020478">"read car’s identification"</string>
     <string name="car_permission_desc_car_identification" msgid="4132040867171275059">"Access car’s identification."</string>
     <string name="car_permission_label_control_car_doors" msgid="3032058819250195700">"control car’s doors"</string>
@@ -101,6 +107,8 @@
     <string name="car_permission_desc_control_car_seats" msgid="2407536601226470563">"Control car’s seats."</string>
     <string name="car_permission_label_car_info" msgid="4707513570676492315">"access car’s basic information"</string>
     <string name="car_permission_desc_car_info" msgid="2118081474543537653">"Access car’s basic information."</string>
+    <string name="car_permission_label_vendor_permission_info" msgid="4471260460536888654">"access car’s vendor permission information"</string>
+    <string name="car_permission_desc_vendor_permission_info" msgid="8152113853528488398">"Access car’s vendor permission information."</string>
     <string name="car_permission_label_car_exterior_lights" msgid="541304469604902110">"read car’s exterior lights state"</string>
     <string name="car_permission_desc_car_exterior_lights" msgid="4038037584100849318">"Access car’s exterior lights state."</string>
     <string name="car_permission_label_control_car_exterior_lights" msgid="101357531386232141">"read car’s exterior lights"</string>
@@ -127,5 +135,9 @@
     <string name="car_permission_desc_enroll_trust" msgid="4148649994602185130">"Allow Trusted Device Enrollment"</string>
     <string name="car_permission_label_car_test_service" msgid="9159328930558208708">"Control car’s test mode"</string>
     <string name="car_permission_desc_car_test_service" msgid="7426844534110145843">"Control car’s test mode"</string>
+    <string name="car_permission_label_control_car_features" msgid="3905791560378888286">"Enable or disable car’s features"</string>
+    <string name="car_permission_desc_control_car_features" msgid="7646711104530599901">"Enable or disable car’s features."</string>
+    <string name="car_permission_label_use_car_watchdog" msgid="6973938293170413475">"use car watchdog"</string>
+    <string name="car_permission_desc_use_car_watchdog" msgid="8244592601805516086">"Use car watchdog."</string>
     <string name="trust_device_default_name" msgid="4213625926070261253">"My Device"</string>
 </resources>
diff --git a/service/res/values-en-rXC/strings.xml b/service/res/values-en-rXC/strings.xml
index 295c5e8..61d5782 100644
--- a/service/res/values-en-rXC/strings.xml
+++ b/service/res/values-en-rXC/strings.xml
@@ -22,6 +22,8 @@
     <string name="car_permission_desc_camera" msgid="917024932164501426">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‎‎‏‏‎‎‏‎‏‏‏‎‎‏‏‏‏‎‏‏‎‏‎‏‎‏‎‎‎‏‎‎‏‏‎‎‏‏‏‏‎‏‎‏‏‏‎‏‏‏‏‏‏‏‏‎‏‏‎‎‏‎‎Access your car’s camera(s).‎‏‎‎‏‎"</string>
     <string name="car_permission_label_energy" msgid="7409144323527821558">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‎‎‏‏‎‏‏‎‏‎‎‏‎‏‎‎‏‎‎‎‏‏‎‎‎‎‎‎‏‏‏‎‎‎‏‎‏‎‏‏‏‏‎‏‎‎‏‎‎‏‎‎‎‏‏‏‏‎‏‏‎‎access car’s energy information‎‏‎‎‏‎"</string>
     <string name="car_permission_desc_energy" msgid="3392963810053235407">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‎‏‎‏‏‏‏‎‎‎‏‎‏‏‎‎‎‏‏‏‎‏‎‏‎‎‏‏‎‏‏‏‏‎‎‏‎‎‏‏‏‏‏‏‎‏‎‏‎‏‎‎‎‏‎‏‏‎‎‏‏‏‏‎Access your car’s energy information.‎‏‎‎‏‎"</string>
+    <string name="car_permission_label_adjust_range_remaining" msgid="839033553999920138">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‎‎‏‎‏‏‏‎‏‎‎‏‎‎‏‏‎‏‏‎‎‎‏‎‎‏‎‎‏‎‎‏‏‎‏‏‎‎‏‏‎‎‏‏‎‎‎‏‏‏‏‏‎‎‎‎‎‎‏‎‏‎‎adjust car’s range remaining‎‏‎‎‏‎"</string>
+    <string name="car_permission_desc_adjust_range_remaining" msgid="2369321650437370673">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‎‏‎‎‎‎‎‏‏‏‎‎‎‎‏‏‎‎‎‎‏‎‏‎‏‏‏‏‏‎‎‏‏‏‎‎‏‎‎‏‏‏‏‎‏‏‎‎‎‏‏‎‎‏‏‎‎‏‏‎‎‎‏‎Adjust car’s range remaining value.‎‏‎‎‏‎"</string>
     <string name="car_permission_label_hvac" msgid="1499454192558727843">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‎‏‏‎‏‎‎‏‏‎‎‏‏‏‏‎‎‏‎‎‎‎‏‏‎‏‎‎‏‎‎‏‏‎‎‏‎‎‎‎‏‏‎‏‏‏‎‏‏‏‎‎‎‏‎‏‎‏‎‎‎‏‏‎access car’s hvac‎‏‎‎‏‎"</string>
     <string name="car_permission_desc_hvac" msgid="3754229695589774195">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‎‏‏‎‏‎‎‎‎‎‏‏‎‎‏‏‎‏‏‎‏‎‎‎‎‎‎‎‏‎‎‎‏‏‏‎‎‏‎‏‏‎‏‏‎‎‏‏‏‏‏‎‎‏‏‎‏‏‏‎‎‏‏‎Access your car’s hvac.‎‏‎‎‏‎"</string>
     <string name="car_permission_label_mileage" msgid="4661317074631150551">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‎‎‎‎‎‎‏‎‏‏‎‎‎‎‎‏‎‏‎‎‏‏‎‎‏‎‏‏‏‎‏‏‎‎‎‎‏‎‏‏‎‏‏‎‏‎‎‏‎‎‏‎‏‏‏‏‎‏‎‏‏‏‎access car’s mileage information‎‏‎‎‏‎"</string>
@@ -62,15 +64,17 @@
     <string name="car_permission_desc_car_ux_restrictions_configuration" msgid="5711926927484813777">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‎‎‏‏‏‏‎‏‎‎‎‏‎‎‏‏‎‏‎‏‏‏‎‏‎‎‎‏‏‏‎‎‎‎‎‏‏‏‏‎‏‏‏‎‎‎‏‎‎‎‎‎‎‏‏‏‎‏‎‎‎‏‎Configure UX Restrictions‎‏‎‎‏‎"</string>
     <string name="car_permission_label_car_handle_usb_aoap_device" msgid="72783989504378036">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‏‏‎‎‏‏‎‎‎‎‎‎‏‎‏‎‎‏‎‏‎‎‏‎‏‎‎‏‏‏‎‎‎‏‏‏‏‏‏‎‏‏‏‏‏‎‏‎‎‎‎‏‎‎‏‎‏‏‎‏‎‎‎Communicate with USB device in AOAP mode‎‏‎‎‏‎"</string>
     <string name="car_permission_desc_car_handle_usb_aoap_device" msgid="273505990971317034">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‏‏‎‏‎‏‏‏‏‎‎‏‎‏‏‏‎‏‏‎‎‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‎‏‏‏‏‎‎‏‎‎‏‎‏‎‏‏‎‏‏‎‎‏‎‏‎‏‎‎Allows an app to communicate with a device in AOAP mode‎‏‎‎‏‎"</string>
+    <string name="car_permission_label_read_car_occupant_awareness_state" msgid="125517953575032758">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‏‏‎‎‏‏‏‎‏‏‏‏‎‏‏‏‏‎‏‏‎‏‏‏‏‎‏‎‎‏‏‏‏‎‏‎‏‏‏‏‎‎‎‏‏‎‎‎‎‏‎‏‏‏‏‎‏‏‎‏‏‎‎Occupant Awareness System Read Access‎‏‎‎‏‎"</string>
+    <string name="car_permission_desc_read_car_occupant_awareness_state" msgid="188865882598414986">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‏‏‎‏‎‏‎‏‎‎‏‏‏‏‎‏‏‏‏‏‏‎‎‏‎‎‎‎‏‎‏‎‎‎‎‎‎‏‎‎‏‏‏‏‎‎‎‎‎‎‏‏‎‏‎‏‎‎‎‏‎‏‎‎Allows reading status and detection data for Occupant Awareness System‎‏‎‎‏‎"</string>
+    <string name="car_permission_label_control_car_occupant_awareness_system" msgid="7163330266691094542">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‎‎‎‏‏‎‏‏‎‏‎‎‏‎‏‎‎‎‎‏‎‏‏‏‎‏‏‎‎‏‎‏‏‎‎‏‎‏‏‏‎‏‏‎‏‎‏‎‎‏‎‎‎‎‎‎‎‏‏‏‎‎Control Occupant Awareness System Graph‎‏‎‎‏‎"</string>
+    <string name="car_permission_desc_control_car_occupant_awareness_system" msgid="7123482622084531911">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‎‎‎‏‎‏‏‎‏‏‎‏‏‏‎‏‏‎‎‎‏‏‎‏‏‎‎‏‏‏‏‏‏‎‏‎‏‎‎‎‎‎‏‎‎‎‏‎‎‎‏‏‎‏‏‎‎‎‏‏‏‎Allows controlling the start and stopping of the Occupant Awareness System detection graph‎‏‎‎‏‎"</string>
     <string name="car_permission_label_bind_input_service" msgid="6698489034024273750">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‎‏‏‏‎‎‏‏‏‏‎‏‎‏‏‏‎‏‎‎‎‎‎‏‎‎‏‎‎‏‎‏‎‎‏‏‎‎‏‏‎‏‏‏‎‏‏‏‎‎‎‏‏‏‎‏‎‏‎‏‏‎‎Car Input Service‎‏‎‎‏‎"</string>
     <string name="car_permission_desc_bind_input_service" msgid="1670323419931890170">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‎‏‏‎‏‏‏‎‎‏‎‏‏‏‎‎‎‏‎‏‏‏‎‎‏‎‎‏‏‎‎‏‏‏‏‏‎‎‏‎‎‎‎‏‏‏‎‎‏‎‏‎‎‎‏‏‏‏‏‏‎‏‎‎Handle input events‎‏‎‎‏‎"</string>
     <string name="car_can_bus_failure" msgid="2334035748788283914">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‎‏‎‎‎‎‎‎‏‏‎‎‏‎‎‎‎‏‎‏‎‎‏‎‎‏‎‎‏‎‏‏‎‏‏‎‎‏‏‏‎‎‏‎‏‏‎‏‎‏‎‏‎‏‎‎‎‎‎‏‎‏‎‎CAN bus failed‎‏‎‎‏‎"</string>
     <string name="car_can_bus_failure_desc" msgid="4125516222786484733">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‎‏‏‏‎‎‏‎‏‎‎‎‎‎‎‏‏‎‎‎‏‏‏‎‎‏‎‎‏‎‏‏‎‎‎‏‏‏‎‎‏‎‎‏‏‎‎‏‎‏‏‏‎‎‏‏‏‏‏‏‏‎‏‎CAN bus does not respond. Unplug and plug back headunit box and restart the car‎‏‎‎‏‎"</string>
-    <string name="activity_blocked_text" msgid="8088902789540147995">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‎‎‎‏‏‏‎‎‎‏‏‏‎‏‎‏‏‏‏‎‎‎‏‎‎‏‏‎‏‎‎‏‏‏‏‎‎‏‏‎‎‎‏‏‎‏‏‎You can’t use this feature while driving‎‏‎‎‏‎"</string>
+    <string name="activity_blocked_text" msgid="5342114426610711378">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‎‎‏‎‏‎‎‎‏‎‎‎‏‏‎‎‎‎‎‎‎‎‏‏‎‎‎‏‎‎‎‏‏‎‏‎‏‏‏‎‎‏‏‏‏‎‏‏‏‏‏‎‏‏‎‏‎‏‎‎‏‎‎For your safety, this activity isn’t available while driving.‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎To continue, wait until you’re parked.‎‏‎‎‏‎"</string>
     <string name="exit_button_message" msgid="8554690915924055685">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‎‏‏‎‏‎‏‏‏‎‎‎‎‏‎‏‏‏‏‎‎‎‎‏‎‎‎‎‎‎‏‏‎‏‏‎‏‏‏‎‏‎‏‎‎‎‎‏‎‏‏‎‏‎‎‎‎‏‎‏‎To start over with safe app features, select ‎‏‎‎‏‏‎<xliff:g id="EXIT_BUTTON">%s</xliff:g>‎‏‎‎‏‏‏‎.‎‏‎‎‏‎"</string>
     <string name="exit_button" msgid="5829638404777671253">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‎‏‎‎‎‎‏‏‏‎‎‏‏‏‎‎‎‎‏‎‎‏‎‎‏‏‏‏‏‎‎‎‎‎‏‏‎‏‏‎‏‏‏‏‏‏‎‏‏‏‏‎‏‎‎‏‎‏‎‏‎‏‎Back‎‏‎‎‏‎"</string>
-    <string name="exit_button_close_application" msgid="8824289547809332460">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‎‏‎‎‏‏‏‎‏‏‎‎‎‏‎‏‏‎‎‏‎‎‏‎‏‏‏‎‎‏‏‎‏‎‎‎‏‎‏‏‏‏‎‎‎‎‏‏‏‎‎‏‏‏‎‏‏‎‎‎Close app‎‏‎‎‏‎"</string>
-    <string name="exit_button_go_back" msgid="3469083862100560326">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‎‏‏‎‎‎‎‎‎‏‎‎‏‎‎‏‎‏‎‏‎‎‏‎‏‏‎‎‏‎‎‏‎‎‎‎‎‎‎‏‎‎‎‏‎‏‏‎‏‎‎‏‎‎‏‏‏‎‎‎‏‏‎‎Back‎‏‎‎‏‎"</string>
     <string name="car_permission_label_diag_read" msgid="7248894224877702604">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‎‎‏‎‎‏‎‎‏‏‎‎‏‎‎‏‏‏‏‏‎‏‏‏‎‎‏‎‎‎‎‏‎‏‎‎‎‎‎‏‏‎‏‏‏‎‏‎‎‏‏‎‏‏‏‎‎‏‏‎‎‎read diagnostic data‎‏‎‎‏‎"</string>
     <string name="car_permission_desc_diag_read" msgid="1121426363040966178">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‎‎‏‏‏‏‏‎‎‏‎‎‎‎‎‎‎‏‏‎‏‏‎‏‎‏‎‏‎‏‏‏‏‎‏‎‏‎‏‏‎‏‎‎‎‎‏‎‏‎‎‏‏‎‎‎‏‎‎‎‏‎‎Read diagnostic data from the car.‎‏‎‎‏‎"</string>
     <string name="car_permission_label_diag_clear" msgid="4783070510879698157">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‎‎‎‎‏‎‎‏‏‎‎‎‎‎‏‏‏‎‎‎‎‏‎‏‎‎‏‎‎‏‏‎‏‏‎‎‎‎‏‎‏‎‎‎‎‏‎‏‏‎‏‏‎‎‏‏‏‎‏‏‎‏‎clear diagnostic data‎‏‎‎‏‎"</string>
@@ -89,6 +93,8 @@
     <string name="car_permission_desc_car_engine_detailed" msgid="1746863362811347700">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‎‏‏‏‎‎‎‎‎‏‏‏‏‏‎‎‎‎‏‏‎‏‎‏‏‏‏‏‎‎‏‎‎‏‎‎‎‏‏‏‎‎‎‎‎‏‏‏‏‎‎‎‎‏‎‏‏‏‏‎‏‎‎‎Access your car’s detailed engine information.‎‏‎‎‏‎"</string>
     <string name="car_permission_label_car_energy_ports" msgid="8548990315169219454">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‎‏‏‎‏‎‏‎‎‏‎‎‎‎‎‏‏‏‎‏‎‏‏‎‎‏‎‏‏‎‏‎‏‎‏‏‏‏‏‎‏‎‏‎‏‎‎‎‎‎‏‏‎‏‏‏‏‏‏‎‎access car’s fuel door and charge port‎‏‎‎‏‎"</string>
     <string name="car_permission_desc_car_energy_ports" msgid="7771185999828794949">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‎‏‎‏‏‏‏‎‏‏‎‎‎‏‏‎‎‏‏‎‎‎‏‏‏‏‏‏‎‎‏‎‎‏‎‏‏‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‎‎‏‎‎‎‏‎‏‎Access car’s fuel door and charge port.‎‏‎‎‏‎"</string>
+    <string name="car_permission_label_control_car_energy_ports" msgid="4375137311026313475">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‎‏‏‏‏‎‎‏‎‏‏‎‏‏‏‏‎‎‏‏‏‎‎‎‎‏‏‎‏‎‎‎‎‏‏‎‎‏‏‎‏‎‎‎‏‏‏‏‏‏‏‏‎‎‏‎‎‎‎‎‎‏‏‎control car’s fuel door and charge port‎‏‎‎‏‎"</string>
+    <string name="car_permission_desc_control_car_energy_ports" msgid="7364633710492525387">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‎‎‏‏‎‎‎‏‏‎‏‎‎‎‏‏‎‏‏‏‏‎‏‎‏‎‏‏‏‎‎‎‏‎‏‏‎‏‏‎‎‎‏‏‎‎‎‏‎‎‎‏‏‎‏‎‎‏‎‏‏‎Control car’s fuel door and charge port.‎‏‎‎‏‎"</string>
     <string name="car_permission_label_car_identification" msgid="5896712510164020478">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‎‏‎‎‎‏‏‏‎‏‎‏‎‏‎‏‎‏‎‏‎‎‏‏‎‎‏‎‎‏‎‎‏‎‏‏‏‎‏‏‏‎‏‏‎‏‏‎‏‎‏‏‎‎‏‏‏‏‏‏‏‎‎read car’s identification‎‏‎‎‏‎"</string>
     <string name="car_permission_desc_car_identification" msgid="4132040867171275059">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‎‏‏‏‎‎‏‎‏‎‏‎‏‏‏‏‏‏‏‎‏‎‏‎‏‎‎‎‏‏‎‏‎‏‏‎‏‎‏‎‎‏‎‎‏‎‎‎‎‎‏‎‎‎‏‎‎‏‏‎‎‏‏‎Access car’s identification.‎‏‎‎‏‎"</string>
     <string name="car_permission_label_control_car_doors" msgid="3032058819250195700">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‎‏‎‏‎‏‎‎‎‎‏‎‏‎‎‎‎‎‎‏‎‎‏‎‏‏‎‏‏‏‎‎‏‏‏‏‎‏‏‎‏‏‏‏‏‏‏‏‎‎‎‏‏‎‎‏‏‏‏‎‏‎‎‎control car’s doors‎‏‎‎‏‎"</string>
@@ -101,6 +107,8 @@
     <string name="car_permission_desc_control_car_seats" msgid="2407536601226470563">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‎‏‎‎‎‎‏‎‏‏‎‏‎‎‏‎‏‎‎‏‎‎‏‏‏‎‎‏‎‎‎‎‏‏‏‏‎‏‏‏‏‎‏‏‎‏‎‏‏‏‎‎‏‎‎‏‎‏‎‎‎‏‏‎Control car’s seats.‎‏‎‎‏‎"</string>
     <string name="car_permission_label_car_info" msgid="4707513570676492315">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‎‎‎‎‎‏‎‏‎‏‎‏‎‎‎‏‏‏‎‎‏‎‏‎‏‎‎‏‏‎‏‎‏‎‏‎‏‏‏‎‎‎‎‎‎‎‏‏‎‎‏‎‎‎‎‎‎‏‏‎‏‏‎access car’s basic information‎‏‎‎‏‎"</string>
     <string name="car_permission_desc_car_info" msgid="2118081474543537653">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‎‏‏‏‏‎‏‎‏‏‎‎‏‎‎‏‏‏‎‏‏‏‏‏‏‏‎‎‎‎‏‎‎‎‎‎‏‏‏‏‎‎‎‎‎‎‎‏‏‎‎‏‏‎‏‏‏‏‏‎‏‎‏‎Access car’s basic information.‎‏‎‎‏‎"</string>
+    <string name="car_permission_label_vendor_permission_info" msgid="4471260460536888654">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‎‏‏‏‏‏‎‎‎‎‎‏‏‎‏‎‎‎‏‏‎‏‏‏‎‏‏‎‎‏‎‏‏‏‏‏‎‎‎‏‎‎‎‏‏‎‎‏‏‏‏‏‏‎‏‎‏‎‎‏‏‏‎‎access car’s vendor permission information‎‏‎‎‏‎"</string>
+    <string name="car_permission_desc_vendor_permission_info" msgid="8152113853528488398">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‎‎‎‏‎‎‏‎‎‎‏‎‎‎‏‎‎‎‎‎‎‏‎‏‏‎‏‏‏‎‏‎‏‎‎‎‎‎‎‏‏‎‏‏‎‎‏‏‏‎‎‏‏‏‎‎‏‏‏‎‎Access car’s vendor permission information.‎‏‎‎‏‎"</string>
     <string name="car_permission_label_car_exterior_lights" msgid="541304469604902110">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‏‏‎‏‏‏‏‏‏‎‎‎‎‎‏‏‎‎‎‏‏‎‎‏‏‎‎‎‏‏‎‎‎‎‎‏‎‎‏‎‎‎‏‎‎‎‎‏‏‎‏‎‎‏‎‎‏‏‎‏‏‏‏‎‎read car’s exterior lights state‎‏‎‎‏‎"</string>
     <string name="car_permission_desc_car_exterior_lights" msgid="4038037584100849318">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‎‏‏‏‎‎‎‎‎‎‎‏‎‎‏‏‏‏‏‏‏‎‏‏‏‎‎‏‎‎‏‏‏‎‎‏‏‏‎‎‎‎‎‎‎‎‏‏‏‏‏‎‏‏‎‏‎‏‎‎‏‏‎‎Access car’s exterior lights state.‎‏‎‎‏‎"</string>
     <string name="car_permission_label_control_car_exterior_lights" msgid="101357531386232141">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‏‏‎‎‏‏‎‏‏‎‏‎‎‎‎‎‎‏‏‎‎‎‎‎‏‎‎‎‏‏‎‏‎‎‎‏‎‏‏‎‎‏‎‏‏‎‎‎‏‏‏‏‎‏‎‏‎‎‏‏‎‏‎read car’s exterior lights‎‏‎‎‏‎"</string>
@@ -127,5 +135,9 @@
     <string name="car_permission_desc_enroll_trust" msgid="4148649994602185130">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‎‏‏‏‎‎‏‏‎‎‏‎‎‏‎‏‏‏‏‎‏‏‏‎‎‏‏‎‎‎‎‏‎‎‎‏‎‎‏‏‎‏‏‎‎‎‎‏‎‎‎‎‎‎‏‏‎‏‎‏‎‏‎‎Allow Trusted Device Enrollment‎‏‎‎‏‎"</string>
     <string name="car_permission_label_car_test_service" msgid="9159328930558208708">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‎‎‎‏‏‏‎‎‎‏‏‏‏‎‎‏‎‎‏‎‎‎‏‎‎‏‎‏‎‏‏‎‏‏‏‎‎‎‎‏‎‏‏‎‏‎‏‎‏‏‎‎‎‏‎‎‎Control car’s test mode‎‏‎‎‏‎"</string>
     <string name="car_permission_desc_car_test_service" msgid="7426844534110145843">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‎‎‏‏‏‎‎‎‏‎‎‎‏‎‏‏‏‎‎‏‏‏‏‎‎‎‎‎‏‎‎‏‏‎‎‎‏‏‎‏‎‏‏‏‏‏‏‎‎‏‏‎‏‎‎‏‏‎‎‏‏‎Control car’s test mode‎‏‎‎‏‎"</string>
+    <string name="car_permission_label_control_car_features" msgid="3905791560378888286">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‎‏‏‎‏‏‎‎‎‏‏‎‏‎‎‎‎‏‎‏‎‎‎‏‎‏‏‏‎‏‎‏‏‏‏‏‎‏‏‎‎‎‏‏‎‏‎‎‎‏‏‎‎‎‎‎‏‎‏‏‏‏‎‎Enable or disable car’s features‎‏‎‎‏‎"</string>
+    <string name="car_permission_desc_control_car_features" msgid="7646711104530599901">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‎‏‎‏‎‎‎‎‏‏‏‏‎‏‎‎‏‎‎‏‏‎‎‏‏‏‏‎‎‎‏‎‎‏‏‎‏‏‎‎‏‎‏‎‎‏‏‎‎‏‏‏‏‏‏‎‏‏‏‎‏‎Enable or disable car’s features.‎‏‎‎‏‎"</string>
+    <string name="car_permission_label_use_car_watchdog" msgid="6973938293170413475">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‎‎‎‎‎‏‏‎‎‏‎‎‎‎‏‏‎‎‏‏‏‏‏‏‎‏‏‎‏‏‎‎‏‏‎‎‏‏‏‏‎‏‎‏‎‏‎‎‏‏‏‏‏‏‎‏‎‎‎‏‏‎use car watchdog‎‏‎‎‏‎"</string>
+    <string name="car_permission_desc_use_car_watchdog" msgid="8244592601805516086">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‎‎‏‎‎‏‏‎‏‎‏‎‏‎‏‎‏‏‎‏‎‏‎‎‏‎‏‎‎‎‏‎‎‏‎‎‎‏‏‎‏‏‏‎‎‏‏‎‏‏‎‏‎‎‏‏‎‏‏‎‎Use car watchdog.‎‏‎‎‏‎"</string>
     <string name="trust_device_default_name" msgid="4213625926070261253">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‎‏‏‏‎‏‎‎‏‏‏‏‎‎‏‏‏‎‎‏‏‏‎‎‏‏‏‎‏‎‎‎‏‏‏‏‎‏‏‏‏‎‎‏‎‏‏‎‏‎‏‎‏‏‎‎‎‎‎‎‏‎‏‎My Device‎‏‎‎‏‎"</string>
 </resources>
diff --git a/service/res/values-es-rUS/strings.xml b/service/res/values-es-rUS/strings.xml
index b73ed61..b2ccbc4 100644
--- a/service/res/values-es-rUS/strings.xml
+++ b/service/res/values-es-rUS/strings.xml
@@ -22,6 +22,8 @@
     <string name="car_permission_desc_camera" msgid="917024932164501426">"Acceder a las cámaras del auto"</string>
     <string name="car_permission_label_energy" msgid="7409144323527821558">"acceder a información de la potencia del vehículo"</string>
     <string name="car_permission_desc_energy" msgid="3392963810053235407">"Acceder a la información de energía del auto"</string>
+    <string name="car_permission_label_adjust_range_remaining" msgid="839033553999920138">"ajustar el valor restante de alcance del vehículo"</string>
+    <string name="car_permission_desc_adjust_range_remaining" msgid="2369321650437370673">"Ajustar el valor restante de alcance del vehículo"</string>
     <string name="car_permission_label_hvac" msgid="1499454192558727843">"acceder al sistema HVAC del vehículo"</string>
     <string name="car_permission_desc_hvac" msgid="3754229695589774195">"Acceder al sistema HVAC del auto."</string>
     <string name="car_permission_label_mileage" msgid="4661317074631150551">"acceder a información sobre el kilometraje del vehículo"</string>
@@ -62,15 +64,17 @@
     <string name="car_permission_desc_car_ux_restrictions_configuration" msgid="5711926927484813777">"Configurar restricciones de UX"</string>
     <string name="car_permission_label_car_handle_usb_aoap_device" msgid="72783989504378036">"Comunicarse con un dispositivo USB en modo AOAP"</string>
     <string name="car_permission_desc_car_handle_usb_aoap_device" msgid="273505990971317034">"Permite que una app se comunique con un dispositivo en modo AOAP"</string>
+    <string name="car_permission_label_read_car_occupant_awareness_state" msgid="125517953575032758">"Acceso de lectura al Sistema de detección de ocupantes"</string>
+    <string name="car_permission_desc_read_car_occupant_awareness_state" msgid="188865882598414986">"Permite leer el estado y los datos de detección del Sistema de detección de ocupantes"</string>
+    <string name="car_permission_label_control_car_occupant_awareness_system" msgid="7163330266691094542">"Control del gráfico del Sistema de detección de ocupantes"</string>
+    <string name="car_permission_desc_control_car_occupant_awareness_system" msgid="7123482622084531911">"Permite controlar el inicio y la detención del gráfico de detección del Sistema de detección de ocupantes"</string>
     <string name="car_permission_label_bind_input_service" msgid="6698489034024273750">"Servicio de entrada del auto"</string>
     <string name="car_permission_desc_bind_input_service" msgid="1670323419931890170">"Controlar eventos de entrada"</string>
     <string name="car_can_bus_failure" msgid="2334035748788283914">"Error de bus CAN"</string>
     <string name="car_can_bus_failure_desc" msgid="4125516222786484733">"CAN bus no responde. Desconecta y vuelve a conectar la caja de la unidad central y enciende nuevamente el auto"</string>
-    <string name="activity_blocked_text" msgid="8088902789540147995">"No puedes usar esta función mientras conduces"</string>
+    <string name="activity_blocked_text" msgid="5342114426610711378">"Por razones de seguridad, esta actividad no está disponible al conducir.\nPara continuar espera hasta que estaciones."</string>
     <string name="exit_button_message" msgid="8554690915924055685">"Para volver a comenzar con funciones de app seguras, selecciona <xliff:g id="EXIT_BUTTON">%s</xliff:g>."</string>
     <string name="exit_button" msgid="5829638404777671253">"Atrás"</string>
-    <string name="exit_button_close_application" msgid="8824289547809332460">"Cerrar app"</string>
-    <string name="exit_button_go_back" msgid="3469083862100560326">"Atrás"</string>
     <string name="car_permission_label_diag_read" msgid="7248894224877702604">"leer datos de diagnóstico"</string>
     <string name="car_permission_desc_diag_read" msgid="1121426363040966178">"Leer datos de diagnóstico del vehículo."</string>
     <string name="car_permission_label_diag_clear" msgid="4783070510879698157">"borrar datos de diagnóstico"</string>
@@ -89,6 +93,8 @@
     <string name="car_permission_desc_car_engine_detailed" msgid="1746863362811347700">"Acceder a información detallada del motor del vehículo."</string>
     <string name="car_permission_label_car_energy_ports" msgid="8548990315169219454">"acceder a la puerta de combustible del vehículo y al puerto de carga"</string>
     <string name="car_permission_desc_car_energy_ports" msgid="7771185999828794949">"Acceder a la puerta de combustible del vehículo y al puerto de carga."</string>
+    <string name="car_permission_label_control_car_energy_ports" msgid="4375137311026313475">"controlar la puerta de combustible del vehículo y el puerto de carga"</string>
+    <string name="car_permission_desc_control_car_energy_ports" msgid="7364633710492525387">"Control de la puerta de combustible del vehículo y el puerto de carga"</string>
     <string name="car_permission_label_car_identification" msgid="5896712510164020478">"leer información sobre la identificación del vehículo"</string>
     <string name="car_permission_desc_car_identification" msgid="4132040867171275059">"Acceder a la identificación del vehículo."</string>
     <string name="car_permission_label_control_car_doors" msgid="3032058819250195700">"controlar las puertas del vehículo"</string>
@@ -101,6 +107,8 @@
     <string name="car_permission_desc_control_car_seats" msgid="2407536601226470563">"Controlar los asientos del vehículo."</string>
     <string name="car_permission_label_car_info" msgid="4707513570676492315">"acceder a información básica del vehículo"</string>
     <string name="car_permission_desc_car_info" msgid="2118081474543537653">"Acceder a información básica del vehículo."</string>
+    <string name="car_permission_label_vendor_permission_info" msgid="4471260460536888654">"acceder a la información de permisos del fabricante del vehículo"</string>
+    <string name="car_permission_desc_vendor_permission_info" msgid="8152113853528488398">"Acceder a la información de permisos del fabricante del vehículo"</string>
     <string name="car_permission_label_car_exterior_lights" msgid="541304469604902110">"leer el estado de luces del exterior del vehículo"</string>
     <string name="car_permission_desc_car_exterior_lights" msgid="4038037584100849318">"Acceder al estado de las luces exteriores del vehículo."</string>
     <string name="car_permission_label_control_car_exterior_lights" msgid="101357531386232141">"leer información sobre luces del exterior del vehículo"</string>
@@ -127,5 +135,9 @@
     <string name="car_permission_desc_enroll_trust" msgid="4148649994602185130">"Permitir inscripción de dispositivos de confianza"</string>
     <string name="car_permission_label_car_test_service" msgid="9159328930558208708">"Controlar el modo de prueba del vehículo"</string>
     <string name="car_permission_desc_car_test_service" msgid="7426844534110145843">"Controlar el modo de prueba del vehículo"</string>
+    <string name="car_permission_label_control_car_features" msgid="3905791560378888286">"Habilitar o inhabilitar las funciones del vehículo"</string>
+    <string name="car_permission_desc_control_car_features" msgid="7646711104530599901">"Habilitar o inhabilitar las funciones del vehículo"</string>
+    <string name="car_permission_label_use_car_watchdog" msgid="6973938293170413475">"usar perro guardián del vehículo"</string>
+    <string name="car_permission_desc_use_car_watchdog" msgid="8244592601805516086">"Usar perro guardián del vehículo"</string>
     <string name="trust_device_default_name" msgid="4213625926070261253">"Mi dispositivo"</string>
 </resources>
diff --git a/service/res/values-es/strings.xml b/service/res/values-es/strings.xml
index e34dfb3..d1e35f7 100644
--- a/service/res/values-es/strings.xml
+++ b/service/res/values-es/strings.xml
@@ -16,12 +16,14 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="car_permission_label" msgid="741004755205554376">"acceder a los datos del coche"</string>
-    <string name="car_permission_desc" msgid="162499818870052725">"Acceder a los datos del coche."</string>
+    <string name="car_permission_label" msgid="741004755205554376">"acceder a la información del coche"</string>
+    <string name="car_permission_desc" msgid="162499818870052725">"Acceder a la información del coche."</string>
     <string name="car_permission_label_camera" msgid="3725702064841827180">"acceder a la cámara del coche"</string>
     <string name="car_permission_desc_camera" msgid="917024932164501426">"Acceder a las cámaras del coche."</string>
     <string name="car_permission_label_energy" msgid="7409144323527821558">"acceder a la información sobre el nivel de energía del coche"</string>
     <string name="car_permission_desc_energy" msgid="3392963810053235407">"Acceder a la información sobre el nivel de energía del coche."</string>
+    <string name="car_permission_label_adjust_range_remaining" msgid="839033553999920138">"Ajuste de la distancia que se puede recorrer con el combustible actual"</string>
+    <string name="car_permission_desc_adjust_range_remaining" msgid="2369321650437370673">"Valor del ajuste de la distancia que se puede recorrer con el combustible actual."</string>
     <string name="car_permission_label_hvac" msgid="1499454192558727843">"acceder al sistema de CVAA del coche"</string>
     <string name="car_permission_desc_hvac" msgid="3754229695589774195">"Acceder al sistema de CVAA del coche."</string>
     <string name="car_permission_label_mileage" msgid="4661317074631150551">"acceder a la información del kilometraje del coche"</string>
@@ -62,15 +64,17 @@
     <string name="car_permission_desc_car_ux_restrictions_configuration" msgid="5711926927484813777">"Configurar restricciones de la experiencia de usuario"</string>
     <string name="car_permission_label_car_handle_usb_aoap_device" msgid="72783989504378036">"Comunicarse con un dispositivo USB en modo AOAP"</string>
     <string name="car_permission_desc_car_handle_usb_aoap_device" msgid="273505990971317034">"Permite que una aplicación se comunique con un dispositivo en modo AOAP"</string>
+    <string name="car_permission_label_read_car_occupant_awareness_state" msgid="125517953575032758">"Consultar el sistema de detección de ocupantes"</string>
+    <string name="car_permission_desc_read_car_occupant_awareness_state" msgid="188865882598414986">"Permite leer el estado y los datos de detección del sistema de detección de ocupantes"</string>
+    <string name="car_permission_label_control_car_occupant_awareness_system" msgid="7163330266691094542">"Controlar el gráfico del sistema de detección de ocupantes"</string>
+    <string name="car_permission_desc_control_car_occupant_awareness_system" msgid="7123482622084531911">"Permite configurar el inicio y las pausas del gráfico de detección del sistema de detección de ocupantes"</string>
     <string name="car_permission_label_bind_input_service" msgid="6698489034024273750">"Servicio de entrada del coche"</string>
     <string name="car_permission_desc_bind_input_service" msgid="1670323419931890170">"Gestionar eventos de entrada"</string>
     <string name="car_can_bus_failure" msgid="2334035748788283914">"Fallo de bus CAN"</string>
     <string name="car_can_bus_failure_desc" msgid="4125516222786484733">"El bus CAN no responde. Desconecta el cabezal, conéctalo de nuevo y reinicia el coche"</string>
-    <string name="activity_blocked_text" msgid="8088902789540147995">"No puedes utilizar esta función mientras conduces"</string>
+    <string name="activity_blocked_text" msgid="5342114426610711378">"Por tu seguridad, esta actividad no está disponible mientras conduces.\nPuedes continuar cuando hayas aparcado."</string>
     <string name="exit_button_message" msgid="8554690915924055685">"Para volver a empezar con funciones de aplicaciones seguras, selecciona <xliff:g id="EXIT_BUTTON">%s</xliff:g>."</string>
     <string name="exit_button" msgid="5829638404777671253">"Atrás"</string>
-    <string name="exit_button_close_application" msgid="8824289547809332460">"Cerrar aplicación"</string>
-    <string name="exit_button_go_back" msgid="3469083862100560326">"Atrás"</string>
     <string name="car_permission_label_diag_read" msgid="7248894224877702604">"consultar datos de diagnóstico"</string>
     <string name="car_permission_desc_diag_read" msgid="1121426363040966178">"Consultar los datos de diagnóstico del coche."</string>
     <string name="car_permission_label_diag_clear" msgid="4783070510879698157">"borrar los datos de diagnóstico"</string>
@@ -89,6 +93,8 @@
     <string name="car_permission_desc_car_engine_detailed" msgid="1746863362811347700">"Acceder a información detallada sobre el motor del coche."</string>
     <string name="car_permission_label_car_energy_ports" msgid="8548990315169219454">"acceder al puerto de carga y al depósito de combustible"</string>
     <string name="car_permission_desc_car_energy_ports" msgid="7771185999828794949">"Acceder al puerto de carga y al depósito de combustible."</string>
+    <string name="car_permission_label_control_car_energy_ports" msgid="4375137311026313475">"controlar el puerto de carga y el depósito de combustible"</string>
+    <string name="car_permission_desc_control_car_energy_ports" msgid="7364633710492525387">"Controlar el puerto de carga y el depósito de combustible."</string>
     <string name="car_permission_label_car_identification" msgid="5896712510164020478">"consultar la identificación del coche"</string>
     <string name="car_permission_desc_car_identification" msgid="4132040867171275059">"Acceder a la identificación del coche."</string>
     <string name="car_permission_label_control_car_doors" msgid="3032058819250195700">"controlar las puertas del coche"</string>
@@ -101,6 +107,8 @@
     <string name="car_permission_desc_control_car_seats" msgid="2407536601226470563">"Controlar los asientos del coche."</string>
     <string name="car_permission_label_car_info" msgid="4707513570676492315">"acceder a la información básica del coche"</string>
     <string name="car_permission_desc_car_info" msgid="2118081474543537653">"Acceder a la información básica del coche."</string>
+    <string name="car_permission_label_vendor_permission_info" msgid="4471260460536888654">"acceder a la información de permisos del proveedor del coche"</string>
+    <string name="car_permission_desc_vendor_permission_info" msgid="8152113853528488398">"Acceder a la información de permisos del proveedor del coche."</string>
     <string name="car_permission_label_car_exterior_lights" msgid="541304469604902110">"consultar el estado de las luces exteriores del coche"</string>
     <string name="car_permission_desc_car_exterior_lights" msgid="4038037584100849318">"Acceder al estado de las luces exteriores del coche."</string>
     <string name="car_permission_label_control_car_exterior_lights" msgid="101357531386232141">"controlar las luces exteriores del coche"</string>
@@ -127,5 +135,9 @@
     <string name="car_permission_desc_enroll_trust" msgid="4148649994602185130">"Permitir registro de dispositivos de confianza"</string>
     <string name="car_permission_label_car_test_service" msgid="9159328930558208708">"Controlar modo de prueba del coche"</string>
     <string name="car_permission_desc_car_test_service" msgid="7426844534110145843">"Controlar modo de prueba del coche"</string>
+    <string name="car_permission_label_control_car_features" msgid="3905791560378888286">"Habilitar o inhabilitar las funciones de un coche"</string>
+    <string name="car_permission_desc_control_car_features" msgid="7646711104530599901">"Habilitar o inhabilitar las funciones de un coche."</string>
+    <string name="car_permission_label_use_car_watchdog" msgid="6973938293170413475">"usar watchdog del coche"</string>
+    <string name="car_permission_desc_use_car_watchdog" msgid="8244592601805516086">"Usar watchdog del coche."</string>
     <string name="trust_device_default_name" msgid="4213625926070261253">"Mi dispositivo"</string>
 </resources>
diff --git a/service/res/values-et/strings.xml b/service/res/values-et/strings.xml
index 695a03a..9d55afa 100644
--- a/service/res/values-et/strings.xml
+++ b/service/res/values-et/strings.xml
@@ -22,6 +22,8 @@
     <string name="car_permission_desc_camera" msgid="917024932164501426">"Juurdepääs auto kaameratele."</string>
     <string name="car_permission_label_energy" msgid="7409144323527821558">"juurdepääs auto energiateabele"</string>
     <string name="car_permission_desc_energy" msgid="3392963810053235407">"Juurdepääs auto energiateabele."</string>
+    <string name="car_permission_label_adjust_range_remaining" msgid="839033553999920138">"kohandage auto järelejäänud kütusega kaetavat vahemaad"</string>
+    <string name="car_permission_desc_adjust_range_remaining" msgid="2369321650437370673">"Kohandage auto järelejäänud kütusega kaetava vahemaa väärtust."</string>
     <string name="car_permission_label_hvac" msgid="1499454192558727843">"juurdepääs auto kliimaseadmele"</string>
     <string name="car_permission_desc_hvac" msgid="3754229695589774195">"Juurdepääs auto kliimatehnikale."</string>
     <string name="car_permission_label_mileage" msgid="4661317074631150551">"juurdepääs auto läbisõiduteabele"</string>
@@ -62,15 +64,17 @@
     <string name="car_permission_desc_car_ux_restrictions_configuration" msgid="5711926927484813777">"Kasutuskogemuse piirangute seadistamine"</string>
     <string name="car_permission_label_car_handle_usb_aoap_device" msgid="72783989504378036">"AOAP-režiimis USB-seadmega suhtlemine"</string>
     <string name="car_permission_desc_car_handle_usb_aoap_device" msgid="273505990971317034">"Lubab rakendusel seadmega AOAP-režiimis suhelda"</string>
+    <string name="car_permission_label_read_car_occupant_awareness_state" msgid="125517953575032758">"Lugemisega juurdepääs süsteemile Occupant Awareness System"</string>
+    <string name="car_permission_desc_read_car_occupant_awareness_state" msgid="188865882598414986">"Võimaldab lugeda süsteemi Occupant Awareness System oleku- ja tuvastamisandmeid"</string>
+    <string name="car_permission_label_control_car_occupant_awareness_system" msgid="7163330266691094542">"Süsteemi Occupant Awareness System graafiku juhtimine"</string>
+    <string name="car_permission_desc_control_car_occupant_awareness_system" msgid="7123482622084531911">"Võimaldab juhtida süsteemi Occupant Awareness System tuvastamisgraafiku käivitamist ja peatamist"</string>
     <string name="car_permission_label_bind_input_service" msgid="6698489034024273750">"Auto sisendteenus"</string>
     <string name="car_permission_desc_bind_input_service" msgid="1670323419931890170">"Sisestussündmuste töötlemine"</string>
     <string name="car_can_bus_failure" msgid="2334035748788283914">"CAN-siin ebaõnnestus"</string>
     <string name="car_can_bus_failure_desc" msgid="4125516222786484733">"CAN-siin ei reageeri. Eemaldage autoraadio üksus ja pange see tagasi ning taaskäivitage auto"</string>
-    <string name="activity_blocked_text" msgid="8088902789540147995">"Te ei saa seda funktsiooni sõidu ajal kasutada"</string>
+    <string name="activity_blocked_text" msgid="5342114426610711378">"Teie turvalisuse tõttu ei ole see toiming sõitmise ajal saadaval.\nJätkamiseks oodake, kuni olete parkinud."</string>
     <string name="exit_button_message" msgid="8554690915924055685">"Uuesti alustamiseks turvaliste rakenduste funktsioonidega valige <xliff:g id="EXIT_BUTTON">%s</xliff:g>."</string>
     <string name="exit_button" msgid="5829638404777671253">"Tagasi"</string>
-    <string name="exit_button_close_application" msgid="8824289547809332460">"Sule rakendus"</string>
-    <string name="exit_button_go_back" msgid="3469083862100560326">"Tagasi"</string>
     <string name="car_permission_label_diag_read" msgid="7248894224877702604">"diagnostikaandmete lugemine"</string>
     <string name="car_permission_desc_diag_read" msgid="1121426363040966178">"Auto diagnostikaandmete lugemine."</string>
     <string name="car_permission_label_diag_clear" msgid="4783070510879698157">"diagnostikaandmete kustutamine"</string>
@@ -89,6 +93,8 @@
     <string name="car_permission_desc_car_engine_detailed" msgid="1746863362811347700">"Juurdepääs auto üksikasjalikule mootoriteabele."</string>
     <string name="car_permission_label_car_energy_ports" msgid="8548990315169219454">"juurdepääs auto kütusepaagi luugile ja avale"</string>
     <string name="car_permission_desc_car_energy_ports" msgid="7771185999828794949">"Juurdepääs auto kütusepaagi luugile ja avale."</string>
+    <string name="car_permission_label_control_car_energy_ports" msgid="4375137311026313475">"auto kütusepaagi luugi ja ava juhtimine"</string>
+    <string name="car_permission_desc_control_car_energy_ports" msgid="7364633710492525387">"Auto kütusepaagi luugi ja ava juhtimine."</string>
     <string name="car_permission_label_car_identification" msgid="5896712510164020478">"auto tuvastamisteabe lugemine"</string>
     <string name="car_permission_desc_car_identification" msgid="4132040867171275059">"Juurdepääs auto tuvastamisteabele."</string>
     <string name="car_permission_label_control_car_doors" msgid="3032058819250195700">"autouste juhtimine"</string>
@@ -101,6 +107,8 @@
     <string name="car_permission_desc_control_car_seats" msgid="2407536601226470563">"Autoistmete juhtimine."</string>
     <string name="car_permission_label_car_info" msgid="4707513570676492315">"juurdepääs auto põhiteabele"</string>
     <string name="car_permission_desc_car_info" msgid="2118081474543537653">"Juurdepääs auto põhiteabele."</string>
+    <string name="car_permission_label_vendor_permission_info" msgid="4471260460536888654">"juurdepääs auto edasimüüja lubade teabele"</string>
+    <string name="car_permission_desc_vendor_permission_info" msgid="8152113853528488398">"Juurdepääs auto edasimüüja lubade teabele."</string>
     <string name="car_permission_label_car_exterior_lights" msgid="541304469604902110">"auto salongitulede oleku lugemine"</string>
     <string name="car_permission_desc_car_exterior_lights" msgid="4038037584100849318">"Juurdepääs auto välistulede olekule."</string>
     <string name="car_permission_label_control_car_exterior_lights" msgid="101357531386232141">"auto välistulede lugemine"</string>
@@ -127,5 +135,9 @@
     <string name="car_permission_desc_enroll_trust" msgid="4148649994602185130">"Usaldusväärse seadme registreerimise lubamine"</string>
     <string name="car_permission_label_car_test_service" msgid="9159328930558208708">"Auto testrežiimi haldamine"</string>
     <string name="car_permission_desc_car_test_service" msgid="7426844534110145843">"Auto testrežiimi haldamine"</string>
+    <string name="car_permission_label_control_car_features" msgid="3905791560378888286">"Auto funktsioonide lubamine ja keelamine"</string>
+    <string name="car_permission_desc_control_car_features" msgid="7646711104530599901">"Võimalik on lubada ja keelata auto funktsioone."</string>
+    <string name="car_permission_label_use_car_watchdog" msgid="6973938293170413475">"kasuta auto valvesüsteemi"</string>
+    <string name="car_permission_desc_use_car_watchdog" msgid="8244592601805516086">"Kasuta auto valvesüsteemi."</string>
     <string name="trust_device_default_name" msgid="4213625926070261253">"Minu seade"</string>
 </resources>
diff --git a/service/res/values-eu/strings.xml b/service/res/values-eu/strings.xml
index 8bd24da..9074592 100644
--- a/service/res/values-eu/strings.xml
+++ b/service/res/values-eu/strings.xml
@@ -22,6 +22,8 @@
     <string name="car_permission_desc_camera" msgid="917024932164501426">"Atzitu autoaren kamerak."</string>
     <string name="car_permission_label_energy" msgid="7409144323527821558">"atzitu autoaren energiari buruzko informazioa"</string>
     <string name="car_permission_desc_energy" msgid="3392963810053235407">"Atzitu autoaren energiari buruzko informazioa"</string>
+    <string name="car_permission_label_adjust_range_remaining" msgid="839033553999920138">"doitu autoari gelditzen zaion gaitasuna"</string>
+    <string name="car_permission_desc_adjust_range_remaining" msgid="2369321650437370673">"Doitu autoari gelditzen zaion gaitasunaren balioa."</string>
     <string name="car_permission_label_hvac" msgid="1499454192558727843">"atzitu autoaren berogailua, haizagailua eta aire-girogailua"</string>
     <string name="car_permission_desc_hvac" msgid="3754229695589774195">"Atzitu autoaren berogailua, haizagailua eta aire-girogailua."</string>
     <string name="car_permission_label_mileage" msgid="4661317074631150551">"atzitu autoaren kilometro kopuruari buruzko informazioa"</string>
@@ -62,16 +64,17 @@
     <string name="car_permission_desc_car_ux_restrictions_configuration" msgid="5711926927484813777">"Konfiguratu erabiltzeko moduaren murriztapenak"</string>
     <string name="car_permission_label_car_handle_usb_aoap_device" msgid="72783989504378036">"Komunikatu AOAP moduan USB bidezko gailuekin"</string>
     <string name="car_permission_desc_car_handle_usb_aoap_device" msgid="273505990971317034">"Gailuekin AOAP moduan komunikatzeko baimena ematen dio aplikazioari."</string>
+    <string name="car_permission_label_read_car_occupant_awareness_state" msgid="125517953575032758">"Bidaiariak hautemateko sistema irakurtzeko sarbidea"</string>
+    <string name="car_permission_desc_read_car_occupant_awareness_state" msgid="188865882598414986">"Bidaiariak hautemateko sistemaren egoerei eta detekzioei buruzko datuak irakurtzeko aukera ematen du"</string>
+    <string name="car_permission_label_control_car_occupant_awareness_system" msgid="7163330266691094542">"Kontrolatu Bidaiariak hautemateko sistemaren grafikoa"</string>
+    <string name="car_permission_desc_control_car_occupant_awareness_system" msgid="7123482622084531911">"Bidaiariak hautemateko sistemaren detekzioen grafikoa noiz hasi eta noiz bukatu kontrolatzeko aukera ematen du"</string>
     <string name="car_permission_label_bind_input_service" msgid="6698489034024273750">"Autoaren sarrerako zerbitzua"</string>
     <string name="car_permission_desc_bind_input_service" msgid="1670323419931890170">"Kudeatu sarrerako gertaerak"</string>
     <string name="car_can_bus_failure" msgid="2334035748788283914">"CAN autobusak huts egin du"</string>
     <string name="car_can_bus_failure_desc" msgid="4125516222786484733">"CAN bus-ak ez du erantzuten. Desentxufatu eta entxufatu berriro gailu nagusia eta berrabiarazi autoa."</string>
-    <!-- no translation found for activity_blocked_text (8088902789540147995) -->
-    <skip />
+    <string name="activity_blocked_text" msgid="5342114426610711378">"Zure segurtasuna bermatzeko, eginbide hau ezin da erabili gidatu bitartean.\nAurrera egiteko, itxaron autoa aparkatu arte."</string>
     <string name="exit_button_message" msgid="8554690915924055685">"Berriro hasi nahi baduzu aplikazioaren eginbide seguruekin, hautatu <xliff:g id="EXIT_BUTTON">%s</xliff:g>."</string>
     <string name="exit_button" msgid="5829638404777671253">"Atzera"</string>
-    <string name="exit_button_close_application" msgid="8824289547809332460">"Itxi aplikazioa"</string>
-    <string name="exit_button_go_back" msgid="3469083862100560326">"Atzera"</string>
     <string name="car_permission_label_diag_read" msgid="7248894224877702604">"irakurri diagnostiko-datuak"</string>
     <string name="car_permission_desc_diag_read" msgid="1121426363040966178">"Irakurri autoaren diagnostiko-datuak."</string>
     <string name="car_permission_label_diag_clear" msgid="4783070510879698157">"garbitu diagnostiko-datuak"</string>
@@ -90,6 +93,8 @@
     <string name="car_permission_desc_car_engine_detailed" msgid="1746863362811347700">"Atzitu autoaren motorrari buruzko informazio xehatua."</string>
     <string name="car_permission_label_car_energy_ports" msgid="8548990315169219454">"atzitu autoaren erregai-deposituaren ataka eta korrontera konektatzeko ataka"</string>
     <string name="car_permission_desc_car_energy_ports" msgid="7771185999828794949">"Atzitu autoaren erregai-deposituaren ataka eta korrontera konektatzeko ataka."</string>
+    <string name="car_permission_label_control_car_energy_ports" msgid="4375137311026313475">"kontrolatu autoaren erregai-deposituaren ataka eta korrontera konektatzeko ataka"</string>
+    <string name="car_permission_desc_control_car_energy_ports" msgid="7364633710492525387">"Kontrolatu autoaren erregai-deposituaren ataka eta korrontera konektatzeko ataka."</string>
     <string name="car_permission_label_car_identification" msgid="5896712510164020478">"irakurri autoaren identifikazioa"</string>
     <string name="car_permission_desc_car_identification" msgid="4132040867171275059">"Atzitu autoaren identifikazioa."</string>
     <string name="car_permission_label_control_car_doors" msgid="3032058819250195700">"kontrolatu autoaren ateak"</string>
@@ -102,6 +107,8 @@
     <string name="car_permission_desc_control_car_seats" msgid="2407536601226470563">"Kontrolatu autoaren eserlekuak."</string>
     <string name="car_permission_label_car_info" msgid="4707513570676492315">"atzitu autoaren oinarrizko informazioa"</string>
     <string name="car_permission_desc_car_info" msgid="2118081474543537653">"Atzitu autoaren oinarrizko informazioa."</string>
+    <string name="car_permission_label_vendor_permission_info" msgid="4471260460536888654">"atzitu autoaren saltzailearen baimenari buruzko informazioa"</string>
+    <string name="car_permission_desc_vendor_permission_info" msgid="8152113853528488398">"Atzitu autoaren saltzailearen baimenari buruzko informazioa"</string>
     <string name="car_permission_label_car_exterior_lights" msgid="541304469604902110">"irakurri autoaren kanpoaldeko argien egoera"</string>
     <string name="car_permission_desc_car_exterior_lights" msgid="4038037584100849318">"Atzitu autoaren kanpoaldeko argien egoera."</string>
     <string name="car_permission_label_control_car_exterior_lights" msgid="101357531386232141">"irakurri autoaren kanpoaldeko argiak"</string>
@@ -128,5 +135,9 @@
     <string name="car_permission_desc_enroll_trust" msgid="4148649994602185130">"Eman gailu fidagarriak erregistratzeko baimena"</string>
     <string name="car_permission_label_car_test_service" msgid="9159328930558208708">"Kontrolatu autoaren proba modua"</string>
     <string name="car_permission_desc_car_test_service" msgid="7426844534110145843">"Kontrolatu autoaren proba modua"</string>
+    <string name="car_permission_label_control_car_features" msgid="3905791560378888286">"Gaitu edo desgaitu autoaren eginbideak"</string>
+    <string name="car_permission_desc_control_car_features" msgid="7646711104530599901">"Gaitu edo desgaitu autoaren eginbideak."</string>
+    <string name="car_permission_label_use_car_watchdog" msgid="6973938293170413475">"erabili autoaren softwarea zaintzeko sistema"</string>
+    <string name="car_permission_desc_use_car_watchdog" msgid="8244592601805516086">"Erabili autoaren softwarea zaintzeko sistema."</string>
     <string name="trust_device_default_name" msgid="4213625926070261253">"Nire gailua"</string>
 </resources>
diff --git a/service/res/values-fa/strings.xml b/service/res/values-fa/strings.xml
index 298d4c1..7f95337 100644
--- a/service/res/values-fa/strings.xml
+++ b/service/res/values-fa/strings.xml
@@ -22,6 +22,8 @@
     <string name="car_permission_desc_camera" msgid="917024932164501426">"دسترسی به دوربین(های) خودرو."</string>
     <string name="car_permission_label_energy" msgid="7409144323527821558">"دسترسی به اطلاعات انرژی خودرو"</string>
     <string name="car_permission_desc_energy" msgid="3392963810053235407">"دسترسی اطلاعات انرژی خودرو."</string>
+    <string name="car_permission_label_adjust_range_remaining" msgid="839033553999920138">"تنظیم مقدار مسافت باقی‌مانده که می‌توان با خودرو سفر کرد"</string>
+    <string name="car_permission_desc_adjust_range_remaining" msgid="2369321650437370673">"تنظیم مقدار مسافت باقی‌مانده که می‌توان با خودرو سفر کرد."</string>
     <string name="car_permission_label_hvac" msgid="1499454192558727843">"دسترسی به اچ‌وی‌ای‌سی خودرو"</string>
     <string name="car_permission_desc_hvac" msgid="3754229695589774195">"دسترسی به اچ‌وی‌ای‌سی خودرو."</string>
     <string name="car_permission_label_mileage" msgid="4661317074631150551">"دسترسی به اطلاعات مسافت طی‌شده خودرو"</string>
@@ -62,16 +64,17 @@
     <string name="car_permission_desc_car_ux_restrictions_configuration" msgid="5711926927484813777">"پیکربندی محدودیت‌های UX"</string>
     <string name="car_permission_label_car_handle_usb_aoap_device" msgid="72783989504378036">"برقراری ارتباط با دستگاه USB در حالت AOAP"</string>
     <string name="car_permission_desc_car_handle_usb_aoap_device" msgid="273505990971317034">"به برنامه‌ای اجازه می‌دهید با دستگاهی در حالت AOAP ارتباط برقرار کند"</string>
+    <string name="car_permission_label_read_car_occupant_awareness_state" msgid="125517953575032758">"دسترسی خواندن «سیستم هوشیاری سرنشین»"</string>
+    <string name="car_permission_desc_read_car_occupant_awareness_state" msgid="188865882598414986">"اجازه می‌دهد وضعیت و داده‌های تشخیص «سیستم هوشیاری سرنشین» خوانده شود"</string>
+    <string name="car_permission_label_control_car_occupant_awareness_system" msgid="7163330266691094542">"کنترل نمودار «سیستم هوشیاری سرنشین»"</string>
+    <string name="car_permission_desc_control_car_occupant_awareness_system" msgid="7123482622084531911">"اجازه می‌دهد شروع و توقف نمودار تشخیص «سیستم هوشیاری سرنشین» کنترل شود"</string>
     <string name="car_permission_label_bind_input_service" msgid="6698489034024273750">"سرویس ورودی خودرو"</string>
     <string name="car_permission_desc_bind_input_service" msgid="1670323419931890170">"مدیریت رویدادهای ورودی"</string>
     <string name="car_can_bus_failure" msgid="2334035748788283914">"گذرگاه CAN ناموفق بود"</string>
     <string name="car_can_bus_failure_desc" msgid="4125516222786484733">"گذرگاه CAN پاسخ نمی‌دهد. محفظه ضبط‌وپخش را جدا و سپس وصل کنید و خودرو را دوباره روشن کنید"</string>
-    <!-- no translation found for activity_blocked_text (8088902789540147995) -->
-    <skip />
+    <string name="activity_blocked_text" msgid="5342114426610711378">"بنا به دلایل ایمنی، این فعالیت درحین رانندگی دردسترس نیست.\n برای ادامه، تا زمان توقف خودرو صبر کنید."</string>
     <string name="exit_button_message" msgid="8554690915924055685">"برای شروع مجدد با ویژگی‌های برنامه امن، <xliff:g id="EXIT_BUTTON">%s</xliff:g> را انتخاب کنید."</string>
     <string name="exit_button" msgid="5829638404777671253">"برگشت"</string>
-    <string name="exit_button_close_application" msgid="8824289547809332460">"بستن برنامه"</string>
-    <string name="exit_button_go_back" msgid="3469083862100560326">"برگشت"</string>
     <string name="car_permission_label_diag_read" msgid="7248894224877702604">"خواندن داده‌های عیب‌یابی"</string>
     <string name="car_permission_desc_diag_read" msgid="1121426363040966178">"داده‌های عیب‌یابی خودرو را بخوانید."</string>
     <string name="car_permission_label_diag_clear" msgid="4783070510879698157">"پاک کردن داده‌های عیب‌یابی"</string>
@@ -90,6 +93,8 @@
     <string name="car_permission_desc_car_engine_detailed" msgid="1746863362811347700">"به اطلاعات کامل موتور خودرو دسترسی پیدا کنید."</string>
     <string name="car_permission_label_car_energy_ports" msgid="8548990315169219454">"دسترسی به درب باک و درگاه شارژ خودرو"</string>
     <string name="car_permission_desc_car_energy_ports" msgid="7771185999828794949">"به درب باک و درگاه شارژ دسترسی پیدا کنید."</string>
+    <string name="car_permission_label_control_car_energy_ports" msgid="4375137311026313475">"درب باک و درگاه شارژ خودرو کنترل شود"</string>
+    <string name="car_permission_desc_control_car_energy_ports" msgid="7364633710492525387">"درب باک و درگاه شارژ خودرو کنترل شود."</string>
     <string name="car_permission_label_car_identification" msgid="5896712510164020478">"خواندن شناسه خودرو"</string>
     <string name="car_permission_desc_car_identification" msgid="4132040867171275059">"به شناسه خودرو دسترسی پیدا کنید."</string>
     <string name="car_permission_label_control_car_doors" msgid="3032058819250195700">"کنترل درهای خودرو"</string>
@@ -102,6 +107,8 @@
     <string name="car_permission_desc_control_car_seats" msgid="2407536601226470563">"صندلی‌های خودرو را کنترل کنید."</string>
     <string name="car_permission_label_car_info" msgid="4707513570676492315">"دسترسی به اطلاعات اصلی خودرو"</string>
     <string name="car_permission_desc_car_info" msgid="2118081474543537653">"به اطلاعات اصلی خودرو دسترسی پیدا کنید."</string>
+    <string name="car_permission_label_vendor_permission_info" msgid="4471260460536888654">"دسترسی به اطلاعات مجوز فروشنده خودرو"</string>
+    <string name="car_permission_desc_vendor_permission_info" msgid="8152113853528488398">"به اطلاعات مجوز فروشنده خودرو دسترسی پیدا کنید."</string>
     <string name="car_permission_label_car_exterior_lights" msgid="541304469604902110">"خواندن وضعیت چراغ‌های خارجی خودرو"</string>
     <string name="car_permission_desc_car_exterior_lights" msgid="4038037584100849318">"به وضعیت چراغ‌های خارجی خودرو دسترسی پیدا کنید."</string>
     <string name="car_permission_label_control_car_exterior_lights" msgid="101357531386232141">"خواندن چراغ‌های خارجی خودرو"</string>
@@ -128,5 +135,9 @@
     <string name="car_permission_desc_enroll_trust" msgid="4148649994602185130">"مجاز کردن ثبت‌نام دستگاه مطمئن"</string>
     <string name="car_permission_label_car_test_service" msgid="9159328930558208708">"کنترل حالت آزمایش خودرو"</string>
     <string name="car_permission_desc_car_test_service" msgid="7426844534110145843">"کنترل حالت آزمایش خودرو"</string>
+    <string name="car_permission_label_control_car_features" msgid="3905791560378888286">"فعال کردن یا غیرفعال کردن ویژگی‌های خودرو"</string>
+    <string name="car_permission_desc_control_car_features" msgid="7646711104530599901">"ویژگی‌های خودرو را فعال یا غیرفعال کنید."</string>
+    <string name="car_permission_label_use_car_watchdog" msgid="6973938293170413475">"استفاده از زمان‌سنج مراقب خودرو"</string>
+    <string name="car_permission_desc_use_car_watchdog" msgid="8244592601805516086">"استفاده از زمان‌سنج مراقب خودرو"</string>
     <string name="trust_device_default_name" msgid="4213625926070261253">"دستگاه من"</string>
 </resources>
diff --git a/service/res/values-fi/strings.xml b/service/res/values-fi/strings.xml
index 2843221..4ab264a 100644
--- a/service/res/values-fi/strings.xml
+++ b/service/res/values-fi/strings.xml
@@ -22,6 +22,8 @@
     <string name="car_permission_desc_camera" msgid="917024932164501426">"käyttää auton kameroita"</string>
     <string name="car_permission_label_energy" msgid="7409144323527821558">"käyttää auton energiatietoja"</string>
     <string name="car_permission_desc_energy" msgid="3392963810053235407">"nähdä auton energiatiedot"</string>
+    <string name="car_permission_label_adjust_range_remaining" msgid="839033553999920138">"säädä auton jäljellä olevaa toimintamatkaa"</string>
+    <string name="car_permission_desc_adjust_range_remaining" msgid="2369321650437370673">"Säädä auton jäljellä olevan toimintamatkan arvoa."</string>
     <string name="car_permission_label_hvac" msgid="1499454192558727843">"käyttää auton ilmastointia ja lämmitystä"</string>
     <string name="car_permission_desc_hvac" msgid="3754229695589774195">"käyttää auton lämmitys-,ilmanvaihto- ja ilmastointijärjestelmää"</string>
     <string name="car_permission_label_mileage" msgid="4661317074631150551">"käyttää tietoja ajetuista kilometreistä"</string>
@@ -62,16 +64,17 @@
     <string name="car_permission_desc_car_ux_restrictions_configuration" msgid="5711926927484813777">"Määritä UX-rajoitukset"</string>
     <string name="car_permission_label_car_handle_usb_aoap_device" msgid="72783989504378036">"kommunikoida USB-laitteen kanssa AOAP-tilassa"</string>
     <string name="car_permission_desc_car_handle_usb_aoap_device" msgid="273505990971317034">"Sallii sovelluksen kommunikoida laitteen kanssa AOAP-tilassa"</string>
+    <string name="car_permission_label_read_car_occupant_awareness_state" msgid="125517953575032758">"Occupant Awareness Systemin lukemisoikeudet"</string>
+    <string name="car_permission_desc_read_car_occupant_awareness_state" msgid="188865882598414986">"Sallii Occupant Awareness Systemin tilan ja tunnistusdatan lukemisen"</string>
+    <string name="car_permission_label_control_car_occupant_awareness_system" msgid="7163330266691094542">"Ohjata Occupant Awareness System Graphia"</string>
+    <string name="car_permission_desc_control_car_occupant_awareness_system" msgid="7123482622084531911">"Sallii Occupant Awareness Systemin käynnistyksen ja pysäytyksen ohjaamisen"</string>
     <string name="car_permission_label_bind_input_service" msgid="6698489034024273750">"Auton syötepalvelu"</string>
     <string name="car_permission_desc_bind_input_service" msgid="1670323419931890170">"käsitellä syötteitä"</string>
     <string name="car_can_bus_failure" msgid="2334035748788283914">"CAN-väylä hylättiin"</string>
     <string name="car_can_bus_failure_desc" msgid="4125516222786484733">"CAN-väylä ei vastaa. Irrota pääyksikkö ja liitä se takaisin. Käynnistä auto sitten uudelleen."</string>
-    <!-- no translation found for activity_blocked_text (8088902789540147995) -->
-    <skip />
+    <string name="activity_blocked_text" msgid="5342114426610711378">"Turvallisuussyistä toiminto ei ole käytettävissä ajon aikana.\nVoit jatkaa, kun olet pysäköinyt auton."</string>
     <string name="exit_button_message" msgid="8554690915924055685">"Jos haluat aloittaa alusta turvallisilla sovellusominaisuuksilla, valitse <xliff:g id="EXIT_BUTTON">%s</xliff:g>."</string>
     <string name="exit_button" msgid="5829638404777671253">"Takaisin"</string>
-    <string name="exit_button_close_application" msgid="8824289547809332460">"Sulje sovellus"</string>
-    <string name="exit_button_go_back" msgid="3469083862100560326">"Takaisin"</string>
     <string name="car_permission_label_diag_read" msgid="7248894224877702604">"nähdä diagnostiikkadataa"</string>
     <string name="car_permission_desc_diag_read" msgid="1121426363040966178">"lukea auton diagnostiikkadataa"</string>
     <string name="car_permission_label_diag_clear" msgid="4783070510879698157">"poistaa diagnostiikkatiedot"</string>
@@ -90,6 +93,8 @@
     <string name="car_permission_desc_car_engine_detailed" msgid="1746863362811347700">"käyttää auton moottorin yksityiskohtaisia tietoja"</string>
     <string name="car_permission_label_car_energy_ports" msgid="8548990315169219454">"käyttää auton polttoaineluukkua ja latausliitäntää"</string>
     <string name="car_permission_desc_car_energy_ports" msgid="7771185999828794949">"käyttää auton polttoaineluukkua ja latausliitäntää"</string>
+    <string name="car_permission_label_control_car_energy_ports" msgid="4375137311026313475">"ohjata auton polttoaineluukkua ja latausliitäntää"</string>
+    <string name="car_permission_desc_control_car_energy_ports" msgid="7364633710492525387">"Ohjata auton polttoaineluukkua ja latausliitäntää."</string>
     <string name="car_permission_label_car_identification" msgid="5896712510164020478">"nähdä auton tunnistetiedot"</string>
     <string name="car_permission_desc_car_identification" msgid="4132040867171275059">"käyttää auton tunnistetietoja"</string>
     <string name="car_permission_label_control_car_doors" msgid="3032058819250195700">"ohjata auton ovia"</string>
@@ -102,6 +107,8 @@
     <string name="car_permission_desc_control_car_seats" msgid="2407536601226470563">"ohjata auton istuimia"</string>
     <string name="car_permission_label_car_info" msgid="4707513570676492315">"käyttää auton perustietoja"</string>
     <string name="car_permission_desc_car_info" msgid="2118081474543537653">"käyttää auton perustietoja"</string>
+    <string name="car_permission_label_vendor_permission_info" msgid="4471260460536888654">"tarkistaa auton myyjän lupatiedot"</string>
+    <string name="car_permission_desc_vendor_permission_info" msgid="8152113853528488398">"tarkistaa auton myyjän lupatiedot"</string>
     <string name="car_permission_label_car_exterior_lights" msgid="541304469604902110">"nähdä auton ulkovalojen tilan"</string>
     <string name="car_permission_desc_car_exterior_lights" msgid="4038037584100849318">"käyttää auton ulkovalojen tilaa"</string>
     <string name="car_permission_label_control_car_exterior_lights" msgid="101357531386232141">"nähdä auton ulkovalot"</string>
@@ -128,5 +135,9 @@
     <string name="car_permission_desc_enroll_trust" msgid="4148649994602185130">"Salli luotetun laitteen rekisteröinti"</string>
     <string name="car_permission_label_car_test_service" msgid="9159328930558208708">"Ohjaa auton testaustilaa"</string>
     <string name="car_permission_desc_car_test_service" msgid="7426844534110145843">"Ohjaa auton testaustilaa"</string>
+    <string name="car_permission_label_control_car_features" msgid="3905791560378888286">"Ota auton ominaisuuksia käyttöön tai poista niitä käytöstä"</string>
+    <string name="car_permission_desc_control_car_features" msgid="7646711104530599901">"Ota auton ominaisuuksia käyttöön tai poista niitä käytöstä."</string>
+    <string name="car_permission_label_use_car_watchdog" msgid="6973938293170413475">"käytä auton vauhtiajastinta"</string>
+    <string name="car_permission_desc_use_car_watchdog" msgid="8244592601805516086">"Käytä auton vauhtiajastinta."</string>
     <string name="trust_device_default_name" msgid="4213625926070261253">"Oma laite"</string>
 </resources>
diff --git a/service/res/values-fr-rCA/strings.xml b/service/res/values-fr-rCA/strings.xml
index 6a8a91f..de9fa03 100644
--- a/service/res/values-fr-rCA/strings.xml
+++ b/service/res/values-fr-rCA/strings.xml
@@ -16,12 +16,14 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="car_permission_label" msgid="741004755205554376">"Accéder aux renseignements relatifs à la voiture"</string>
+    <string name="car_permission_label" msgid="741004755205554376">"accéder aux renseignements relatifs à la voiture"</string>
     <string name="car_permission_desc" msgid="162499818870052725">"Accéder aux renseignements relatifs à la voiture."</string>
     <string name="car_permission_label_camera" msgid="3725702064841827180">"accéder à la caméra de la voiture"</string>
     <string name="car_permission_desc_camera" msgid="917024932164501426">"Accéder aux caméras de la voiture."</string>
     <string name="car_permission_label_energy" msgid="7409144323527821558">"accéder aux renseignements énergétiques de la voiture"</string>
     <string name="car_permission_desc_energy" msgid="3392963810053235407">"Accéder aux renseignements énergétiques de la voiture."</string>
+    <string name="car_permission_label_adjust_range_remaining" msgid="839033553999920138">"réglez l\'autonomie restante du véhicule"</string>
+    <string name="car_permission_desc_adjust_range_remaining" msgid="2369321650437370673">"Réglez la valeur de l\'autonomie restante du véhicule."</string>
     <string name="car_permission_label_hvac" msgid="1499454192558727843">"accéder à l\'élément CVC de la voiture"</string>
     <string name="car_permission_desc_hvac" msgid="3754229695589774195">"Accéder à l\'élément CVC de la voiture."</string>
     <string name="car_permission_label_mileage" msgid="4661317074631150551">"accéder au kilométrage de la voiture"</string>
@@ -62,15 +64,17 @@
     <string name="car_permission_desc_car_ux_restrictions_configuration" msgid="5711926927484813777">"Configurer les restrictions relatives à l\'expérience utilisateur"</string>
     <string name="car_permission_label_car_handle_usb_aoap_device" msgid="72783989504378036">"Communiquer avec les appareils USB en mode AOAP"</string>
     <string name="car_permission_desc_car_handle_usb_aoap_device" msgid="273505990971317034">"Permet à une application de communiquer avec un appareil en mode AOAP"</string>
+    <string name="car_permission_label_read_car_occupant_awareness_state" msgid="125517953575032758">"Accès en lecture au système de détection des occupants"</string>
+    <string name="car_permission_desc_read_car_occupant_awareness_state" msgid="188865882598414986">"Permet de lire les données liées à l\'état et à la détection du système de détection des occupants"</string>
+    <string name="car_permission_label_control_car_occupant_awareness_system" msgid="7163330266691094542">"Contrôler le graphique du système de détection des occupants"</string>
+    <string name="car_permission_desc_control_car_occupant_awareness_system" msgid="7123482622084531911">"Permet de contrôler le démarrage et l\'arrêt du graphique de détection du système de détection des occupants"</string>
     <string name="car_permission_label_bind_input_service" msgid="6698489034024273750">"Service d\'entrée de la voiture"</string>
     <string name="car_permission_desc_bind_input_service" msgid="1670323419931890170">"Gérer les événements d\'entrée"</string>
     <string name="car_can_bus_failure" msgid="2334035748788283914">"Défaillance du bus de données CAN"</string>
     <string name="car_can_bus_failure_desc" msgid="4125516222786484733">"Le bus de données CAN ne répond pas. Débranchez et rebranchez le boîtier de l\'unité centrale, puis redémarrez la voiture"</string>
-    <string name="activity_blocked_text" msgid="8088902789540147995">"Vous ne pouvez pas utiliser cette fonctionnalité en conduisant"</string>
+    <string name="activity_blocked_text" msgid="5342114426610711378">"Pour votre sécurité, cette activité est bloquée lorsque vous conduisez.\nVous devez être stationné pour continuer."</string>
     <string name="exit_button_message" msgid="8554690915924055685">"Pour recommencer avec des fonctionnalités d\'application sécurisées, sélectionnez <xliff:g id="EXIT_BUTTON">%s</xliff:g>."</string>
     <string name="exit_button" msgid="5829638404777671253">"Retour"</string>
-    <string name="exit_button_close_application" msgid="8824289547809332460">"Fermer l\'application"</string>
-    <string name="exit_button_go_back" msgid="3469083862100560326">"Retour"</string>
     <string name="car_permission_label_diag_read" msgid="7248894224877702604">"lire des données de diagnostic"</string>
     <string name="car_permission_desc_diag_read" msgid="1121426363040966178">"Lire des données de diagnostic à partir de la voiture."</string>
     <string name="car_permission_label_diag_clear" msgid="4783070510879698157">"effacer les données de diagnostic"</string>
@@ -89,6 +93,8 @@
     <string name="car_permission_desc_car_engine_detailed" msgid="1746863362811347700">"Accéder aux renseignements détaillés sur le moteur de votre voiture."</string>
     <string name="car_permission_label_car_energy_ports" msgid="8548990315169219454">"accéder à la porte du réservoir de carburant et au port de recharge de la voiture"</string>
     <string name="car_permission_desc_car_energy_ports" msgid="7771185999828794949">"Accéder à la porte du réservoir de carburant et au port de recharge de la voiture."</string>
+    <string name="car_permission_label_control_car_energy_ports" msgid="4375137311026313475">"contrôler la porte du réservoir de carburant et le port de recharge du véhicule"</string>
+    <string name="car_permission_desc_control_car_energy_ports" msgid="7364633710492525387">"Contrôler la porte du réservoir de carburant et le port de recharge du véhicule."</string>
     <string name="car_permission_label_car_identification" msgid="5896712510164020478">"lire l\'identification de la voiture"</string>
     <string name="car_permission_desc_car_identification" msgid="4132040867171275059">"Accéder à l\'identification de la voiture."</string>
     <string name="car_permission_label_control_car_doors" msgid="3032058819250195700">"contrôler les portières de la voiture"</string>
@@ -101,6 +107,8 @@
     <string name="car_permission_desc_control_car_seats" msgid="2407536601226470563">"Contrôler les sièges de la voiture."</string>
     <string name="car_permission_label_car_info" msgid="4707513570676492315">"accéder aux renseignements de base de la voiture"</string>
     <string name="car_permission_desc_car_info" msgid="2118081474543537653">"Accéder aux renseignements de base de la voiture."</string>
+    <string name="car_permission_label_vendor_permission_info" msgid="4471260460536888654">"accéder aux renseignements d\'autorisation du fournisseur du véhicule"</string>
+    <string name="car_permission_desc_vendor_permission_info" msgid="8152113853528488398">"Accédez aux renseignements d\'autorisation du fournisseur du véhicule."</string>
     <string name="car_permission_label_car_exterior_lights" msgid="541304469604902110">"lire l\'état des feux extérieurs de la voiture"</string>
     <string name="car_permission_desc_car_exterior_lights" msgid="4038037584100849318">"Accéder à l\'état des feux extérieurs de la voiture."</string>
     <string name="car_permission_label_control_car_exterior_lights" msgid="101357531386232141">"lire les feux extérieurs de la voiture"</string>
@@ -127,5 +135,9 @@
     <string name="car_permission_desc_enroll_trust" msgid="4148649994602185130">"Autoriser l\'inscription d\'un appareil de confiance"</string>
     <string name="car_permission_label_car_test_service" msgid="9159328930558208708">"Contrôler le mode test du véhicule"</string>
     <string name="car_permission_desc_car_test_service" msgid="7426844534110145843">"Contrôler le mode test du véhicule"</string>
+    <string name="car_permission_label_control_car_features" msgid="3905791560378888286">"Activez ou désactivez les fonctionnalités du véhicule"</string>
+    <string name="car_permission_desc_control_car_features" msgid="7646711104530599901">"Activez ou désactivez les fonctionnalités du véhicule."</string>
+    <string name="car_permission_label_use_car_watchdog" msgid="6973938293170413475">"utilisez le service de surveillance automobile"</string>
+    <string name="car_permission_desc_use_car_watchdog" msgid="8244592601805516086">"Utilisez le service de surveillance automobile."</string>
     <string name="trust_device_default_name" msgid="4213625926070261253">"Mon appareil"</string>
 </resources>
diff --git a/service/res/values-fr/strings.xml b/service/res/values-fr/strings.xml
index c907edb..5d68a8f 100644
--- a/service/res/values-fr/strings.xml
+++ b/service/res/values-fr/strings.xml
@@ -22,6 +22,8 @@
     <string name="car_permission_desc_camera" msgid="917024932164501426">"Accéder aux caméras de la voiture."</string>
     <string name="car_permission_label_energy" msgid="7409144323527821558">"Accéder aux informations relatives à l\'énergie de la voiture"</string>
     <string name="car_permission_desc_energy" msgid="3392963810053235407">"Accéder aux informations énergétiques de la voiture."</string>
+    <string name="car_permission_label_adjust_range_remaining" msgid="839033553999920138">"ajuster l\'autonomie restante de la voiture"</string>
+    <string name="car_permission_desc_adjust_range_remaining" msgid="2369321650437370673">"Ajustez la valeur de l\'autonomie restante de la voiture."</string>
     <string name="car_permission_label_hvac" msgid="1499454192558727843">"Accéder aux éléments CVC de la voiture"</string>
     <string name="car_permission_desc_hvac" msgid="3754229695589774195">"Accéder aux éléments CVC de la voiture"</string>
     <string name="car_permission_label_mileage" msgid="4661317074631150551">"Accéder aux informations sur le kilométrage de la voiture"</string>
@@ -62,15 +64,17 @@
     <string name="car_permission_desc_car_ux_restrictions_configuration" msgid="5711926927484813777">"Configurer les restrictions relatives à l\'expérience utilisateur"</string>
     <string name="car_permission_label_car_handle_usb_aoap_device" msgid="72783989504378036">"Communiquer avec un appareil USB en mode AOAP"</string>
     <string name="car_permission_desc_car_handle_usb_aoap_device" msgid="273505990971317034">"Autorise une application à communiquer avec un appareil en mode AOAP"</string>
+    <string name="car_permission_label_read_car_occupant_awareness_state" msgid="125517953575032758">"Accès en lecture au système de perception de l\'occupant"</string>
+    <string name="car_permission_desc_read_car_occupant_awareness_state" msgid="188865882598414986">"Permet la lecture des données liées à l\'état et à la détection du système de perception de l\'occupant"</string>
+    <string name="car_permission_label_control_car_occupant_awareness_system" msgid="7163330266691094542">"Contrôler le graphique du système de perception de l\'occupant"</string>
+    <string name="car_permission_desc_control_car_occupant_awareness_system" msgid="7123482622084531911">"Permet de contrôler le lancement et l\'arrêt du graphique de détection du système de perception de l\'occupant"</string>
     <string name="car_permission_label_bind_input_service" msgid="6698489034024273750">"Service d\'entrée de la voiture"</string>
     <string name="car_permission_desc_bind_input_service" msgid="1670323419931890170">"Gérer les événements d\'entrée"</string>
     <string name="car_can_bus_failure" msgid="2334035748788283914">"Échec du bus de données CAN"</string>
     <string name="car_can_bus_failure_desc" msgid="4125516222786484733">"Le bus de données CAN ne répond pas. Débranchez et rebranchez le boîtier de l\'unité principale, puis redémarrez la voiture"</string>
-    <string name="activity_blocked_text" msgid="8088902789540147995">"Vous ne pouvez pas utiliser cette fonctionnalité en conduisant"</string>
+    <string name="activity_blocked_text" msgid="5342114426610711378">"Pour votre sécurité, cette activité n\'est pas disponible pendant la conduite.\nPour continuer, attendez d\'être garé."</string>
     <string name="exit_button_message" msgid="8554690915924055685">"Pour recommencer avec des fonctionnalités d\'application sécurisées, sélectionnez <xliff:g id="EXIT_BUTTON">%s</xliff:g>."</string>
     <string name="exit_button" msgid="5829638404777671253">"Retour"</string>
-    <string name="exit_button_close_application" msgid="8824289547809332460">"Fermer l\'application"</string>
-    <string name="exit_button_go_back" msgid="3469083862100560326">"Retour"</string>
     <string name="car_permission_label_diag_read" msgid="7248894224877702604">"Lire les données de diagnostic"</string>
     <string name="car_permission_desc_diag_read" msgid="1121426363040966178">"Lire les données de diagnostic de la voiture."</string>
     <string name="car_permission_label_diag_clear" msgid="4783070510879698157">"Effacer les données de diagnostic"</string>
@@ -89,6 +93,8 @@
     <string name="car_permission_desc_car_engine_detailed" msgid="1746863362811347700">"Accéder à des informations détaillées sur le moteur de la voiture."</string>
     <string name="car_permission_label_car_energy_ports" msgid="8548990315169219454">"Accéder à la trappe à carburant et au port de recharge de la voiture"</string>
     <string name="car_permission_desc_car_energy_ports" msgid="7771185999828794949">"Accéder à la trappe à carburant et au port de recharge de la voiture."</string>
+    <string name="car_permission_label_control_car_energy_ports" msgid="4375137311026313475">"contrôler la trappe à carburant et le port de recharge de la voiture"</string>
+    <string name="car_permission_desc_control_car_energy_ports" msgid="7364633710492525387">"Contrôler la trappe à carburant et le port de recharge de la voiture."</string>
     <string name="car_permission_label_car_identification" msgid="5896712510164020478">"Lire l\'identification de la voiture"</string>
     <string name="car_permission_desc_car_identification" msgid="4132040867171275059">"Accéder à l\'identification de la voiture."</string>
     <string name="car_permission_label_control_car_doors" msgid="3032058819250195700">"Contrôler les portes de la voiture"</string>
@@ -101,6 +107,8 @@
     <string name="car_permission_desc_control_car_seats" msgid="2407536601226470563">"Contrôler les sièges de la voiture."</string>
     <string name="car_permission_label_car_info" msgid="4707513570676492315">"Accéder aux informations de base de la voiture"</string>
     <string name="car_permission_desc_car_info" msgid="2118081474543537653">"Accéder aux informations de base relatives à la voiture."</string>
+    <string name="car_permission_label_vendor_permission_info" msgid="4471260460536888654">"accéder aux informations sur les autorisations des fournisseurs pour la voiture"</string>
+    <string name="car_permission_desc_vendor_permission_info" msgid="8152113853528488398">"Accéder aux informations sur les autorisations des fournisseurs pour la voiture."</string>
     <string name="car_permission_label_car_exterior_lights" msgid="541304469604902110">"Lire l\'état des phares de la voiture"</string>
     <string name="car_permission_desc_car_exterior_lights" msgid="4038037584100849318">"Accéder à l\'état des phares de la voiture."</string>
     <string name="car_permission_label_control_car_exterior_lights" msgid="101357531386232141">"Lire l\'état des phares de la voiture"</string>
@@ -127,5 +135,9 @@
     <string name="car_permission_desc_enroll_trust" msgid="4148649994602185130">"Autoriser l\'enregistrement de l\'appareil vérifié"</string>
     <string name="car_permission_label_car_test_service" msgid="9159328930558208708">"Contrôler le mode de test de la voiture"</string>
     <string name="car_permission_desc_car_test_service" msgid="7426844534110145843">"Contrôler le mode de test de la voiture"</string>
+    <string name="car_permission_label_control_car_features" msgid="3905791560378888286">"Activer ou désactiver les fonctionnalités de la voiture"</string>
+    <string name="car_permission_desc_control_car_features" msgid="7646711104530599901">"Activez ou désactivez les fonctionnalités de la voiture."</string>
+    <string name="car_permission_label_use_car_watchdog" msgid="6973938293170413475">"utiliser le watchdog de la voiture"</string>
+    <string name="car_permission_desc_use_car_watchdog" msgid="8244592601805516086">"Utiliser le watchdog de la voiture."</string>
     <string name="trust_device_default_name" msgid="4213625926070261253">"Mon appareil"</string>
 </resources>
diff --git a/service/res/values-gl/strings.xml b/service/res/values-gl/strings.xml
index 6a42379..a089221 100644
--- a/service/res/values-gl/strings.xml
+++ b/service/res/values-gl/strings.xml
@@ -22,6 +22,8 @@
     <string name="car_permission_desc_camera" msgid="917024932164501426">"Acceder ás cámaras do coche."</string>
     <string name="car_permission_label_energy" msgid="7409144323527821558">"acceder a información sobre o nivel de enerxía do coche"</string>
     <string name="car_permission_desc_energy" msgid="3392963810053235407">"Acceder a información de enerxía do coche."</string>
+    <string name="car_permission_label_adjust_range_remaining" msgid="839033553999920138">"axustar autonomía restante do coche"</string>
+    <string name="car_permission_desc_adjust_range_remaining" msgid="2369321650437370673">"Axusta o valor de autonomía restante do coche."</string>
     <string name="car_permission_label_hvac" msgid="1499454192558727843">"acceder ao sistema de climatización do coche"</string>
     <string name="car_permission_desc_hvac" msgid="3754229695589774195">"Acceder ao sistema de HVAC coche."</string>
     <string name="car_permission_label_mileage" msgid="4661317074631150551">"acceder a información da quilometraxe do coche"</string>
@@ -62,15 +64,17 @@
     <string name="car_permission_desc_car_ux_restrictions_configuration" msgid="5711926927484813777">"Configurar restricións da experiencia de usuario"</string>
     <string name="car_permission_label_car_handle_usb_aoap_device" msgid="72783989504378036">"Comunicarse con dispositivos USB no modo AOAP"</string>
     <string name="car_permission_desc_car_handle_usb_aoap_device" msgid="273505990971317034">"Permite que unha aplicación se comunique cun dispositivo no modo AOAP"</string>
+    <string name="car_permission_label_read_car_occupant_awareness_state" msgid="125517953575032758">"Acceso de lectura ao sistema de detección de ocupantes"</string>
+    <string name="car_permission_desc_read_car_occupant_awareness_state" msgid="188865882598414986">"Permite ler os datos da detección e do estado do sistema de detección de ocupantes"</string>
+    <string name="car_permission_label_control_car_occupant_awareness_system" msgid="7163330266691094542">"Controlar o gráfico do sistema de detección de ocupantes"</string>
+    <string name="car_permission_desc_control_car_occupant_awareness_system" msgid="7123482622084531911">"Permite controlar o inicio e a parada do gráfico de detección do sistema de detección de ocupantes"</string>
     <string name="car_permission_label_bind_input_service" msgid="6698489034024273750">"Servizo de entrada do coche"</string>
     <string name="car_permission_desc_bind_input_service" msgid="1670323419931890170">"Controlar os eventos de entrada"</string>
     <string name="car_can_bus_failure" msgid="2334035748788283914">"Produciuse un erro no bus CAN"</string>
     <string name="car_can_bus_failure_desc" msgid="4125516222786484733">"O bus CAN non responde. Desconecta a caixa da unidade principal, conéctaa de novo e reinicia o coche"</string>
-    <string name="activity_blocked_text" msgid="8088902789540147995">"Non podes utilizar esta función mentres conduces"</string>
+    <string name="activity_blocked_text" msgid="5342114426610711378">"Pola túa seguranza, esta actividade non está dispoñible mentres conduces.\nPodes continuar cando aparques."</string>
     <string name="exit_button_message" msgid="8554690915924055685">"Para comezar de novo coas funcións de aplicacións seguras, selecciona o botón <xliff:g id="EXIT_BUTTON">%s</xliff:g>."</string>
     <string name="exit_button" msgid="5829638404777671253">"Atrás"</string>
-    <string name="exit_button_close_application" msgid="8824289547809332460">"Pechar aplicación"</string>
-    <string name="exit_button_go_back" msgid="3469083862100560326">"Atrás"</string>
     <string name="car_permission_label_diag_read" msgid="7248894224877702604">"ler datos de diagnóstico"</string>
     <string name="car_permission_desc_diag_read" msgid="1121426363040966178">"Ler datos de diagnóstico do coche."</string>
     <string name="car_permission_label_diag_clear" msgid="4783070510879698157">"borrar datos de diagnóstico"</string>
@@ -89,6 +93,8 @@
     <string name="car_permission_desc_car_engine_detailed" msgid="1746863362811347700">"Acceder a información detallada do motor do coche."</string>
     <string name="car_permission_label_car_energy_ports" msgid="8548990315169219454">"acceder ao depósito de combustible e ao porto de carga do coche"</string>
     <string name="car_permission_desc_car_energy_ports" msgid="7771185999828794949">"Acceder ao depósito de combustible e ao porto de carga do coche."</string>
+    <string name="car_permission_label_control_car_energy_ports" msgid="4375137311026313475">"controlar o depósito de combustible e o porto de carga do coche"</string>
+    <string name="car_permission_desc_control_car_energy_ports" msgid="7364633710492525387">"Controlar o depósito de combustible e o porto de carga do coche."</string>
     <string name="car_permission_label_car_identification" msgid="5896712510164020478">"ler a identificación do coche"</string>
     <string name="car_permission_desc_car_identification" msgid="4132040867171275059">"Acceder á identificación do coche."</string>
     <string name="car_permission_label_control_car_doors" msgid="3032058819250195700">"controlar as portas do coche"</string>
@@ -101,6 +107,8 @@
     <string name="car_permission_desc_control_car_seats" msgid="2407536601226470563">"Controlar os asentos do coche."</string>
     <string name="car_permission_label_car_info" msgid="4707513570676492315">"acceder a información básica do coche"</string>
     <string name="car_permission_desc_car_info" msgid="2118081474543537653">"Acceder a información básica do coche."</string>
+    <string name="car_permission_label_vendor_permission_info" msgid="4471260460536888654">"acceder á información sobre os permisos do vendedor do coche"</string>
+    <string name="car_permission_desc_vendor_permission_info" msgid="8152113853528488398">"Acceder á información sobre os permisos do vendedor do coche."</string>
     <string name="car_permission_label_car_exterior_lights" msgid="541304469604902110">"ler o estado das luces exteriores do dispositivo"</string>
     <string name="car_permission_desc_car_exterior_lights" msgid="4038037584100849318">"Acceder ao estado das luces exteriores do coche."</string>
     <string name="car_permission_label_control_car_exterior_lights" msgid="101357531386232141">"ler as luces exteriores do coche"</string>
@@ -127,5 +135,9 @@
     <string name="car_permission_desc_enroll_trust" msgid="4148649994602185130">"Permitir inscrición de dispositivos de confianza"</string>
     <string name="car_permission_label_car_test_service" msgid="9159328930558208708">"Controlar o modo de proba do coche"</string>
     <string name="car_permission_desc_car_test_service" msgid="7426844534110145843">"Controlar o modo de proba do coche"</string>
+    <string name="car_permission_label_control_car_features" msgid="3905791560378888286">"Activar ou desactivar funcións do coche"</string>
+    <string name="car_permission_desc_control_car_features" msgid="7646711104530599901">"Activa ou desactiva as funcións do coche."</string>
+    <string name="car_permission_label_use_car_watchdog" msgid="6973938293170413475">"utilizar sistema de vixilancia do coche"</string>
+    <string name="car_permission_desc_use_car_watchdog" msgid="8244592601805516086">"Utiliza o sistema de vixilancia do coche."</string>
     <string name="trust_device_default_name" msgid="4213625926070261253">"Dispositivo"</string>
 </resources>
diff --git a/service/res/values-gu/strings.xml b/service/res/values-gu/strings.xml
index 33a5783..468d41d 100644
--- a/service/res/values-gu/strings.xml
+++ b/service/res/values-gu/strings.xml
@@ -22,6 +22,8 @@
     <string name="car_permission_desc_camera" msgid="917024932164501426">"તમારી કારના કૅમેરાને ઍક્સેસ કરવાની મંજૂરી આપો."</string>
     <string name="car_permission_label_energy" msgid="7409144323527821558">"કારની ઊર્જાની માહિતીને ઍક્સેસ કરો"</string>
     <string name="car_permission_desc_energy" msgid="3392963810053235407">"તમારી કારની ઊર્જાની માહિતી ઍક્સેસ કરવાની મંજૂરી આપો."</string>
+    <string name="car_permission_label_adjust_range_remaining" msgid="839033553999920138">"કારની રેંજનું બાકીનું મૂલ્ય ગોઠવો"</string>
+    <string name="car_permission_desc_adjust_range_remaining" msgid="2369321650437370673">"કારની રેંજનું બાકીનું મૂલ્ય ગોઠવો."</string>
     <string name="car_permission_label_hvac" msgid="1499454192558727843">"કારના HVACને ઍક્સેસ કરો"</string>
     <string name="car_permission_desc_hvac" msgid="3754229695589774195">"તમારી કારની hvac ઍક્સેસ કરો."</string>
     <string name="car_permission_label_mileage" msgid="4661317074631150551">"કારના માઇલેજની માહિતીને ઍક્સેસ કરો"</string>
@@ -62,16 +64,17 @@
     <string name="car_permission_desc_car_ux_restrictions_configuration" msgid="5711926927484813777">"UX પ્રતિબંધોને ગોઠવણી કરો"</string>
     <string name="car_permission_label_car_handle_usb_aoap_device" msgid="72783989504378036">"USB ડિવાઇસ સાથે AOAP મોડમાં સંવાદ સાધો"</string>
     <string name="car_permission_desc_car_handle_usb_aoap_device" msgid="273505990971317034">"કોઈ ઍપને અન્ય ડિવાઇસ સાથે AOAP મોડમાં સંવાદ સાધવાની મંજૂરી આપે છે"</string>
+    <string name="car_permission_label_read_car_occupant_awareness_state" msgid="125517953575032758">"\'કારમાં સવાર લોકોની જાગરૂકતા સંબંધિત સિસ્ટમ\'ને વાંચવા માટેનો ઍક્સેસ"</string>
+    <string name="car_permission_desc_read_car_occupant_awareness_state" msgid="188865882598414986">"\'કારમાં સવાર લોકોની જાગરૂકતા સંબંધિત સિસ્ટમ\'ના સ્ટેટસ અને તેની જાણકારીના ડેટાને વાંચવાની મંજૂરી આપે છે"</string>
+    <string name="car_permission_label_control_car_occupant_awareness_system" msgid="7163330266691094542">"\'કારમાં સવાર લોકોની જાગરૂકતા સંબંધિત સિસ્ટમ\'ના ગ્રાફને નિયંત્રિત કરો"</string>
+    <string name="car_permission_desc_control_car_occupant_awareness_system" msgid="7123482622084531911">"\'કારમાં સવાર લોકોની જાગરૂકતા સંબંધિત સિસ્ટમ\'ની જાણકારીના ગ્રાફનું નિયંત્રણ શરૂ કરવાની અને રોકવાની મંજૂરી આપે છે"</string>
     <string name="car_permission_label_bind_input_service" msgid="6698489034024273750">"કારની ઇનપુટ સેવા"</string>
     <string name="car_permission_desc_bind_input_service" msgid="1670323419931890170">"ઇનપુટ ઇવેન્ટ્સને હૅન્ડલ કરો"</string>
     <string name="car_can_bus_failure" msgid="2334035748788283914">"CAN બસ નિષ્ફળ રહી"</string>
     <string name="car_can_bus_failure_desc" msgid="4125516222786484733">"CAN બસ પ્રતિસાદ આપતી નથી. હેડયુનિટ બોક્સને અનપ્લગ કરી ફરી પ્લગ કરો અને કારને ફરી શરૂ કરો"</string>
-    <!-- no translation found for activity_blocked_text (8088902789540147995) -->
-    <skip />
+    <string name="activity_blocked_text" msgid="5342114426610711378">"તમારી સલામતી માટે કાર ચલાવતી વખતે આ પ્રવૃત્તિ ઉપલબ્ધ નથી.\nચાલુ રાખવા માટે કાર પાર્ક થવા સુધી રાહ જુઓ."</string>
     <string name="exit_button_message" msgid="8554690915924055685">"સુરક્ષિત ઍપ્લિકેશન સુવિધાઓ સાથે ફરી શરૂ કરવા, <xliff:g id="EXIT_BUTTON">%s</xliff:g> પસંદ કરો."</string>
     <string name="exit_button" msgid="5829638404777671253">"પાછળ"</string>
-    <string name="exit_button_close_application" msgid="8824289547809332460">"ઍપ બંધ કરો"</string>
-    <string name="exit_button_go_back" msgid="3469083862100560326">"પાછળ"</string>
     <string name="car_permission_label_diag_read" msgid="7248894224877702604">"નિદાનનો ડેટા વાંચો"</string>
     <string name="car_permission_desc_diag_read" msgid="1121426363040966178">"કારમાંથી નિદાનનો ડેટા વાંચો."</string>
     <string name="car_permission_label_diag_clear" msgid="4783070510879698157">"નિદાનનો ડેટા સાફ કરો"</string>
@@ -90,6 +93,8 @@
     <string name="car_permission_desc_car_engine_detailed" msgid="1746863362811347700">"તમારી કારના એન્જિનની વિગતવાર માહિતીને ઍક્સેસ કરો."</string>
     <string name="car_permission_label_car_energy_ports" msgid="8548990315169219454">"કારના ઇંધણના દરવાજા અને ચાર્જ પોર્ટને ઍક્સેસ કરો"</string>
     <string name="car_permission_desc_car_energy_ports" msgid="7771185999828794949">"કારના ઇંધણના દરવાજા અને ચાર્જ પોર્ટને ઍક્સેસ કરો."</string>
+    <string name="car_permission_label_control_car_energy_ports" msgid="4375137311026313475">"કારની \'ઈંધણની ટાંકી\'ના દરવાજા અને ચાર્જ પોર્ટનું નિયંત્રણ કરો"</string>
+    <string name="car_permission_desc_control_car_energy_ports" msgid="7364633710492525387">"કારની \'ઈંધણની ટાંકી\'ના દરવાજા અને ચાર્જ પોર્ટનું નિયંત્રણ કરો."</string>
     <string name="car_permission_label_car_identification" msgid="5896712510164020478">"કારની ઓળખ વાંચો"</string>
     <string name="car_permission_desc_car_identification" msgid="4132040867171275059">"કારની ઓળખને ઍક્સેસ કરો."</string>
     <string name="car_permission_label_control_car_doors" msgid="3032058819250195700">"કારના દરવાજાને નિયંત્રિત કરો"</string>
@@ -102,6 +107,8 @@
     <string name="car_permission_desc_control_car_seats" msgid="2407536601226470563">"કારની સીટને નિયંત્રિત કરો."</string>
     <string name="car_permission_label_car_info" msgid="4707513570676492315">"કારની પ્રાથમિક માહિતીને ઍક્સેસ કરો"</string>
     <string name="car_permission_desc_car_info" msgid="2118081474543537653">"કારની મૂળભૂત માહિતીને ઍક્સેસ કરો."</string>
+    <string name="car_permission_label_vendor_permission_info" msgid="4471260460536888654">"કારના વિક્રેતાની પરવાનગી વિશેની માહિતીને ઍક્સેસ કરો"</string>
+    <string name="car_permission_desc_vendor_permission_info" msgid="8152113853528488398">"કારના વિક્રેતાની પરવાનગી વિશેની માહિતીને ઍક્સેસ કરો."</string>
     <string name="car_permission_label_car_exterior_lights" msgid="541304469604902110">"કારની બહારની લાઇટની સ્થિતિને વાંચો"</string>
     <string name="car_permission_desc_car_exterior_lights" msgid="4038037584100849318">"કારની બહારની લાઇટની સ્થિતિને ઍક્સેસ કરો."</string>
     <string name="car_permission_label_control_car_exterior_lights" msgid="101357531386232141">"કારની બહારની લાઇટ વિશે વાંચો"</string>
@@ -128,5 +135,9 @@
     <string name="car_permission_desc_enroll_trust" msgid="4148649994602185130">"વિશ્વસનીય ડિવાઇસના નોંધણીની મંજૂરી આપો"</string>
     <string name="car_permission_label_car_test_service" msgid="9159328930558208708">"કારના પરીક્ષણ મોડને નિયંત્રિત કરો"</string>
     <string name="car_permission_desc_car_test_service" msgid="7426844534110145843">"કારના પરીક્ષણ મોડને નિયંત્રિત કરો"</string>
+    <string name="car_permission_label_control_car_features" msgid="3905791560378888286">"કારની સુવિધા ચાલુ અથવા બંધ કરો"</string>
+    <string name="car_permission_desc_control_car_features" msgid="7646711104530599901">"કારની સુવિધા ચાલુ અથવા બંધ કરો."</string>
+    <string name="car_permission_label_use_car_watchdog" msgid="6973938293170413475">"કાર વૉચડોગ સુવિધાનો ઉપયોગ કરો"</string>
+    <string name="car_permission_desc_use_car_watchdog" msgid="8244592601805516086">"કાર વૉચડોગ સુવિધાનો ઉપયોગ કરો."</string>
     <string name="trust_device_default_name" msgid="4213625926070261253">"મારું ડિવાઇસ"</string>
 </resources>
diff --git a/service/res/values-hi/strings.xml b/service/res/values-hi/strings.xml
index a558385..bd67d31 100644
--- a/service/res/values-hi/strings.xml
+++ b/service/res/values-hi/strings.xml
@@ -22,6 +22,8 @@
     <string name="car_permission_desc_camera" msgid="917024932164501426">"आपकी कार के कैमरे ऐक्सेस कर सकता है."</string>
     <string name="car_permission_label_energy" msgid="7409144323527821558">"कार की ऊर्जा की जानकारी ऐक्सेस कर सकता है"</string>
     <string name="car_permission_desc_energy" msgid="3392963810053235407">"आपकी कार की ऊर्जा से जुड़ी जानकारी ऐक्सेस कर सकता है."</string>
+    <string name="car_permission_label_adjust_range_remaining" msgid="839033553999920138">"मौजूदा बैटरी या फ़्यूल में कार कितनी दूरी तय कर सकती है, इस मान में बदलाव करें."</string>
+    <string name="car_permission_desc_adjust_range_remaining" msgid="2369321650437370673">"मौजूदा बैटरी या फ़्यूल में कार कितनी दूरी तय कर सकती है, इस मान में बदलाव करें."</string>
     <string name="car_permission_label_hvac" msgid="1499454192558727843">"कार का एचवीएसी ऐक्सेस कर सकता है"</string>
     <string name="car_permission_desc_hvac" msgid="3754229695589774195">"आपकी कार का एचवीएसी ऐक्सेस कर सकता है."</string>
     <string name="car_permission_label_mileage" msgid="4661317074631150551">"कार के माइलेज की जानकारी ऐक्सेस कर सकता है"</string>
@@ -62,16 +64,17 @@
     <string name="car_permission_desc_car_ux_restrictions_configuration" msgid="5711926927484813777">"UX की पाबंदियां कॉन्फ़िगर करें"</string>
     <string name="car_permission_label_car_handle_usb_aoap_device" msgid="72783989504378036">"एओएपी मोड में यूएसबी डिवाइस से कनेक्ट करें"</string>
     <string name="car_permission_desc_car_handle_usb_aoap_device" msgid="273505990971317034">"ऐप्लिकेशन एओएपी मोड में किसी डिवाइस से कनेक्ट हो सकता है"</string>
+    <string name="car_permission_label_read_car_occupant_awareness_state" msgid="125517953575032758">"Occupant Awareness System से मिले डेटा को सिर्फ़ पढ़ने की अनुमति"</string>
+    <string name="car_permission_desc_read_car_occupant_awareness_state" msgid="188865882598414986">"इससे Occupant Awareness System की स्थिति और डिटेक्शन सिस्टम के नतीजों को पढ़ने की अनुमति मिलती है. डिटेक्शन सिस्टम यह बताता है कि ड्राइवर कहां देख रहा है."</string>
+    <string name="car_permission_label_control_car_occupant_awareness_system" msgid="7163330266691094542">"इससे ऐप्लिकेशन को Occupant Awareness System Graph कंट्रोल करने की अनुमति मिलती है"</string>
+    <string name="car_permission_desc_control_car_occupant_awareness_system" msgid="7123482622084531911">"इससे Occupant Awareness System के डिटेक्शन ग्राफ़ को शुरू करने या रोकने की अनुमति मिलती है"</string>
     <string name="car_permission_label_bind_input_service" msgid="6698489034024273750">"कार की इनपुट सेवा"</string>
     <string name="car_permission_desc_bind_input_service" msgid="1670323419931890170">"इनपुट से जुड़े इवेंट प्रबंधित कर सकता है"</string>
     <string name="car_can_bus_failure" msgid="2334035748788283914">"\'CAN बस\' काम नहीं कर पा रहा है"</string>
     <string name="car_can_bus_failure_desc" msgid="4125516222786484733">"\'CAN बस\' जवाब नहीं दे रहा है. हेडयूनिट बॉक्स का प्लग निकालकर वापस लगाएं और कार को रीस्टार्ट करें"</string>
-    <!-- no translation found for activity_blocked_text (8088902789540147995) -->
-    <skip />
+    <string name="activity_blocked_text" msgid="5342114426610711378">"आपकी सुरक्षा के लिए, गाड़ी चलाते समय यह गतिविधि मौजूद नहीं रहती है.\nजारी रखने के लिए गाड़ी पार्क करनी होगी."</string>
     <string name="exit_button_message" msgid="8554690915924055685">"सुरक्षित ऐप्लिकेशन सुविधाएं फिर से शुरू करने के लिए, <xliff:g id="EXIT_BUTTON">%s</xliff:g> चुनें."</string>
     <string name="exit_button" msgid="5829638404777671253">"वापस शुरू करें"</string>
-    <string name="exit_button_close_application" msgid="8824289547809332460">"ऐप्लिकेशन बंद करें"</string>
-    <string name="exit_button_go_back" msgid="3469083862100560326">"वापस जाएं"</string>
     <string name="car_permission_label_diag_read" msgid="7248894224877702604">"गड़बड़ी की पहचान का डेटा देख सकता है"</string>
     <string name="car_permission_desc_diag_read" msgid="1121426363040966178">"कार की \'गड़बड़ी की पहचान का डेटा\' देख सकता है."</string>
     <string name="car_permission_label_diag_clear" msgid="4783070510879698157">"गड़बड़ी की पहचान का डेटा मिटा सकता है"</string>
@@ -90,6 +93,8 @@
     <string name="car_permission_desc_car_engine_detailed" msgid="1746863362811347700">"आपकी कार के इंजन की पूरी जानकारी ऐक्सेस कर सकता है."</string>
     <string name="car_permission_label_car_energy_ports" msgid="8548990315169219454">"कार की ईंधन टंकी का ढक्कन और चार्जिंग पोर्ट ऐक्सेस कर सकता है"</string>
     <string name="car_permission_desc_car_energy_ports" msgid="7771185999828794949">"कार की ईंधन टंकी का ढक्कन और चार्जिंग पोर्ट ऐक्सेस कर सकता है."</string>
+    <string name="car_permission_label_control_car_energy_ports" msgid="4375137311026313475">"इससे ऐप्लिकेशन को कार की ईंधन टंकी का ढक्कन और चार्जिंग पोर्ट कंट्रोल करने की अनुमति मिलती है"</string>
+    <string name="car_permission_desc_control_car_energy_ports" msgid="7364633710492525387">"इससे ऐप्लिकेशन को कार की ईंधन टंकी का ढक्कन और चार्जिंग पोर्ट कंट्रोल करने की अनुमति मिलती है."</string>
     <string name="car_permission_label_car_identification" msgid="5896712510164020478">"कार की पहचान देख सकता है"</string>
     <string name="car_permission_desc_car_identification" msgid="4132040867171275059">"कार की पहचान ऐक्सेस कर सकता है."</string>
     <string name="car_permission_label_control_car_doors" msgid="3032058819250195700">"कार के दरवाज़े नियंत्रित कर सकता है"</string>
@@ -102,6 +107,8 @@
     <string name="car_permission_desc_control_car_seats" msgid="2407536601226470563">"कार की सीटें नियंत्रित कर सकता है."</string>
     <string name="car_permission_label_car_info" msgid="4707513570676492315">"कार की बुनियादी जानकारी ऐक्सेस कर सकता है"</string>
     <string name="car_permission_desc_car_info" msgid="2118081474543537653">"कार की बुनियादी जानकारी ऐक्सेस कर सकता है."</string>
+    <string name="car_permission_label_vendor_permission_info" msgid="4471260460536888654">"कार के वेंडर की अनुमति संबंधित जानकारी ऐक्सेस करें"</string>
+    <string name="car_permission_desc_vendor_permission_info" msgid="8152113853528488398">"कार के वेंडर की अनुमति संबंधित जानकारी ऐक्सेस करें."</string>
     <string name="car_permission_label_car_exterior_lights" msgid="541304469604902110">"कार के बाहरी हिस्से में लगी लाइटों की स्थिति देख सकता है"</string>
     <string name="car_permission_desc_car_exterior_lights" msgid="4038037584100849318">"कार के बाहरी हिस्से में लगी लाइटों की स्थिति ऐक्सेस कर सकता है."</string>
     <string name="car_permission_label_control_car_exterior_lights" msgid="101357531386232141">"कार के बाहरी हिस्से में लगी लाइटें नियंत्रित कर सकता है"</string>
@@ -128,5 +135,9 @@
     <string name="car_permission_desc_enroll_trust" msgid="4148649994602185130">"भरोसेमंद डिवाइस का नाम दर्ज करने की अनुमति दें"</string>
     <string name="car_permission_label_car_test_service" msgid="9159328930558208708">"कार के जांच मोड को नियंत्रित करें"</string>
     <string name="car_permission_desc_car_test_service" msgid="7426844534110145843">"कार के जांच मोड को नियंत्रित करें"</string>
+    <string name="car_permission_label_control_car_features" msgid="3905791560378888286">"कार की सुविधाएं चालू या बंद करें"</string>
+    <string name="car_permission_desc_control_car_features" msgid="7646711104530599901">"कार की सुविधाएं चालू या बंद करें."</string>
+    <string name="car_permission_label_use_car_watchdog" msgid="6973938293170413475">"कार के वॉचडॉग का इस्तेमाल करें"</string>
+    <string name="car_permission_desc_use_car_watchdog" msgid="8244592601805516086">"कार के वॉचडॉग का इस्तेमाल करें."</string>
     <string name="trust_device_default_name" msgid="4213625926070261253">"मेरा डिवाइस"</string>
 </resources>
diff --git a/service/res/values-hr/strings.xml b/service/res/values-hr/strings.xml
index fac2505..011c217 100644
--- a/service/res/values-hr/strings.xml
+++ b/service/res/values-hr/strings.xml
@@ -22,6 +22,8 @@
     <string name="car_permission_desc_camera" msgid="917024932164501426">"pristupiti kamerama automobila"</string>
     <string name="car_permission_label_energy" msgid="7409144323527821558">"pristupiti podacima o energiji automobila"</string>
     <string name="car_permission_desc_energy" msgid="3392963810053235407">"pristupiti informacijama o energiji automobila"</string>
+    <string name="car_permission_label_adjust_range_remaining" msgid="839033553999920138">"korekcija preostalog dometa automobila"</string>
+    <string name="car_permission_desc_adjust_range_remaining" msgid="2369321650437370673">"Korekcija vrijednosti preostalog dometa automobila."</string>
     <string name="car_permission_label_hvac" msgid="1499454192558727843">"pristupiti grijanju, ventilaciji i klimatizaciji automobila"</string>
     <string name="car_permission_desc_hvac" msgid="3754229695589774195">"pristupiti grijanju, ventilaciji i klimatizaciji vašeg automobila"</string>
     <string name="car_permission_label_mileage" msgid="4661317074631150551">"pristupiti podacima o kilometraži automobila"</string>
@@ -62,16 +64,17 @@
     <string name="car_permission_desc_car_ux_restrictions_configuration" msgid="5711926927484813777">"konfigurirati ograničenja UX-a"</string>
     <string name="car_permission_label_car_handle_usb_aoap_device" msgid="72783989504378036">"komunicirati s USB uređajem u AOAP načinu"</string>
     <string name="car_permission_desc_car_handle_usb_aoap_device" msgid="273505990971317034">"Aplikaciji omogućuje da komunicira s uređajem u AOAP načinu"</string>
+    <string name="car_permission_label_read_car_occupant_awareness_state" msgid="125517953575032758">"Pristup za čitanje za Sustav detektiranja prisutnosti"</string>
+    <string name="car_permission_desc_read_car_occupant_awareness_state" msgid="188865882598414986">"Omogućuje očitavanje statusa i podataka o detektiranju za Sustav detektiranja prisutnosti"</string>
+    <string name="car_permission_label_control_car_occupant_awareness_system" msgid="7163330266691094542">"Kontrola grafikona Sustava detektiranja prisutnosti"</string>
+    <string name="car_permission_desc_control_car_occupant_awareness_system" msgid="7123482622084531911">"Omogućuje kontroliranje početka i završetka grafikona detektiranja za Sustav detektiranja prisutnosti"</string>
     <string name="car_permission_label_bind_input_service" msgid="6698489034024273750">"usluga automobilskog unosa"</string>
     <string name="car_permission_desc_bind_input_service" msgid="1670323419931890170">"rukovati događajima unosa"</string>
     <string name="car_can_bus_failure" msgid="2334035748788283914">"Pogreška CAN busa"</string>
     <string name="car_can_bus_failure_desc" msgid="4125516222786484733">"CAN bus ne odgovara. Iskopčajte i ponovo ukopčajte glavnu jedinicu i ponovo pokrenite automobil"</string>
-    <!-- no translation found for activity_blocked_text (8088902789540147995) -->
-    <skip />
+    <string name="activity_blocked_text" msgid="5342114426610711378">"Radi vaše sigurnosti ta aktivnost nije dostupna tijekom vožnje.\nDa biste nastavili, pričekajte dok se ne zaustavite."</string>
     <string name="exit_button_message" msgid="8554690915924055685">"Da biste započeli ponovo sa sigurnim značajkama aplikacije, odaberite <xliff:g id="EXIT_BUTTON">%s</xliff:g>."</string>
     <string name="exit_button" msgid="5829638404777671253">"Natrag"</string>
-    <string name="exit_button_close_application" msgid="8824289547809332460">"Zatvori aplikaciju"</string>
-    <string name="exit_button_go_back" msgid="3469083862100560326">"Natrag"</string>
     <string name="car_permission_label_diag_read" msgid="7248894224877702604">"čitati dijagnostičke podatke"</string>
     <string name="car_permission_desc_diag_read" msgid="1121426363040966178">"čitati dijagnostičke podatke automobila"</string>
     <string name="car_permission_label_diag_clear" msgid="4783070510879698157">"izbrisati dijagnostičke podatke"</string>
@@ -90,6 +93,8 @@
     <string name="car_permission_desc_car_engine_detailed" msgid="1746863362811347700">"pristupiti detaljnim podacima o motoru automobila"</string>
     <string name="car_permission_label_car_energy_ports" msgid="8548990315169219454">"pristupiti poklopcu spremnika za gorivo i priključku za punjenje na automobilu"</string>
     <string name="car_permission_desc_car_energy_ports" msgid="7771185999828794949">"pristupiti poklopcu spremnika za gorivo i priključku za punjenje na automobilu"</string>
+    <string name="car_permission_label_control_car_energy_ports" msgid="4375137311026313475">"kontrolirati poklopac spremnika za gorivo i priključak za punjenje na automobilu"</string>
+    <string name="car_permission_desc_control_car_energy_ports" msgid="7364633710492525387">"kontrolirati poklopac spremnika za gorivo i priključak za punjenje na automobilu."</string>
     <string name="car_permission_label_car_identification" msgid="5896712510164020478">"čitati identifikaciju automobila"</string>
     <string name="car_permission_desc_car_identification" msgid="4132040867171275059">"pristupiti identifikaciji automobila"</string>
     <string name="car_permission_label_control_car_doors" msgid="3032058819250195700">"upravljati automobilskim vratima"</string>
@@ -102,6 +107,8 @@
     <string name="car_permission_desc_control_car_seats" msgid="2407536601226470563">"upravljati automobilskim sjedalima"</string>
     <string name="car_permission_label_car_info" msgid="4707513570676492315">"pristupiti osnovnim podacima automobila"</string>
     <string name="car_permission_desc_car_info" msgid="2118081474543537653">"pristupiti osnovnim podacima automobila"</string>
+    <string name="car_permission_label_vendor_permission_info" msgid="4471260460536888654">"pristupanje informacijama o dopuštenju dobavljača automobila"</string>
+    <string name="car_permission_desc_vendor_permission_info" msgid="8152113853528488398">"Pristupanje informacijama o dopuštenju dobavljača automobila."</string>
     <string name="car_permission_label_car_exterior_lights" msgid="541304469604902110">"čitati stanje vanjskih svjetala automobila"</string>
     <string name="car_permission_desc_car_exterior_lights" msgid="4038037584100849318">"pristupiti stanju vanjskih svjetala automobila"</string>
     <string name="car_permission_label_control_car_exterior_lights" msgid="101357531386232141">"čitati podatke o vanjskim svjetlima automobila"</string>
@@ -128,5 +135,9 @@
     <string name="car_permission_desc_enroll_trust" msgid="4148649994602185130">"Dopusti prijavu pouzdanih uređaja"</string>
     <string name="car_permission_label_car_test_service" msgid="9159328930558208708">"Upravljajte probnim načinom automobila"</string>
     <string name="car_permission_desc_car_test_service" msgid="7426844534110145843">"Upravljajte probnim načinom automobila"</string>
+    <string name="car_permission_label_control_car_features" msgid="3905791560378888286">"Omogućivanje ili onemogućivanje značajki automobila"</string>
+    <string name="car_permission_desc_control_car_features" msgid="7646711104530599901">"Omogućivanje ili onemogućivanje značajki automobila."</string>
+    <string name="car_permission_label_use_car_watchdog" msgid="6973938293170413475">"koristi čuvara automobila."</string>
+    <string name="car_permission_desc_use_car_watchdog" msgid="8244592601805516086">"Koristi čuvara automobila."</string>
     <string name="trust_device_default_name" msgid="4213625926070261253">"Moj uređaj"</string>
 </resources>
diff --git a/service/res/values-hu/strings.xml b/service/res/values-hu/strings.xml
index 27b70b5..6326bd9 100644
--- a/service/res/values-hu/strings.xml
+++ b/service/res/values-hu/strings.xml
@@ -22,6 +22,8 @@
     <string name="car_permission_desc_camera" msgid="917024932164501426">"Hozzáférhet az autó kameráihoz."</string>
     <string name="car_permission_label_energy" msgid="7409144323527821558">"hozzáférhet az autó energiafelhasználására vonatkozó adatokhoz"</string>
     <string name="car_permission_desc_energy" msgid="3392963810053235407">"Hozzáférhet az autó energiafelhasználására vonatkozó adatokhoz."</string>
+    <string name="car_permission_label_adjust_range_remaining" msgid="839033553999920138">"az autó fennmaradó hatótávolságának módosítása"</string>
+    <string name="car_permission_desc_adjust_range_remaining" msgid="2369321650437370673">"Az autó fennmaradó hatótávolságának módosítása."</string>
     <string name="car_permission_label_hvac" msgid="1499454192558727843">"hozzáférhet az autó HVAC-adataihoz"</string>
     <string name="car_permission_desc_hvac" msgid="3754229695589774195">"Hozzáférhet az autó HVAC-adataihoz."</string>
     <string name="car_permission_label_mileage" msgid="4661317074631150551">"hozzáférhet az autó kilométeradataihoz"</string>
@@ -62,16 +64,17 @@
     <string name="car_permission_desc_car_ux_restrictions_configuration" msgid="5711926927484813777">"Felhasználói élményre vonatkozó korlátozások beállítása"</string>
     <string name="car_permission_label_car_handle_usb_aoap_device" msgid="72783989504378036">"USB-eszközzel való kommunikáció AOAP módban"</string>
     <string name="car_permission_desc_car_handle_usb_aoap_device" msgid="273505990971317034">"Lehetővé teszi az alkalmazás számára, hogy AOAP módban kommunikáljon az adott eszközzel"</string>
+    <string name="car_permission_label_read_car_occupant_awareness_state" msgid="125517953575032758">"Occupant Awareness System olvasási hozzáférés"</string>
+    <string name="car_permission_desc_read_car_occupant_awareness_state" msgid="188865882598414986">"Engedélyezi a státusz- és észlelési adatok olvasását az Occupant Awareness System számára"</string>
+    <string name="car_permission_label_control_car_occupant_awareness_system" msgid="7163330266691094542">"Az Occupant Awareness System grafikonjának irányítása"</string>
+    <string name="car_permission_desc_control_car_occupant_awareness_system" msgid="7123482622084531911">"Engedélyezi az Occupant Awareness System észlelési grafikonjának indítását és leállítását"</string>
     <string name="car_permission_label_bind_input_service" msgid="6698489034024273750">"Az autó beviteli szolgáltatása"</string>
     <string name="car_permission_desc_bind_input_service" msgid="1670323419931890170">"Kezelheti a beviteli eseményeket"</string>
     <string name="car_can_bus_failure" msgid="2334035748788283914">"A CAN-busz hibát észlelt"</string>
     <string name="car_can_bus_failure_desc" msgid="4125516222786484733">"A CAN-busz nem válaszol. Csatlakoztassa újra a fejegységet, és indítsa újra az autót."</string>
-    <!-- no translation found for activity_blocked_text (8088902789540147995) -->
-    <skip />
+    <string name="activity_blocked_text" msgid="5342114426610711378">"Az Ön biztonsága érdekében ez a tevékenység nem végezhető vezetés közben.\nLeparkolás után folytathatja a tevékenységet."</string>
     <string name="exit_button_message" msgid="8554690915924055685">"Ha biztonságos alkalmazásfunkciókkal szeretné újrakezdeni, válassza a következő gombot: <xliff:g id="EXIT_BUTTON">%s</xliff:g>."</string>
     <string name="exit_button" msgid="5829638404777671253">"Vissza"</string>
-    <string name="exit_button_close_application" msgid="8824289547809332460">"Alkalmazás bezárása"</string>
-    <string name="exit_button_go_back" msgid="3469083862100560326">"Vissza"</string>
     <string name="car_permission_label_diag_read" msgid="7248894224877702604">"olvashatja a diagnosztikai adatokat"</string>
     <string name="car_permission_desc_diag_read" msgid="1121426363040966178">"Olvashatja az autó diagnosztikai adatait."</string>
     <string name="car_permission_label_diag_clear" msgid="4783070510879698157">"törölheti a diagnosztikai adatokat"</string>
@@ -90,6 +93,8 @@
     <string name="car_permission_desc_car_engine_detailed" msgid="1746863362811347700">"Hozzáférhet az autó motorjának részletes adataihoz."</string>
     <string name="car_permission_label_car_energy_ports" msgid="8548990315169219454">"hozzáférhet az autó tanksapkájához és töltőnyílásához"</string>
     <string name="car_permission_desc_car_energy_ports" msgid="7771185999828794949">"Hozzáférhet az autó tanksapkájához és töltőnyílásához."</string>
+    <string name="car_permission_label_control_car_energy_ports" msgid="4375137311026313475">"vezérelheti az autó tanksapkáját és töltőnyílását"</string>
+    <string name="car_permission_desc_control_car_energy_ports" msgid="7364633710492525387">"Vezérelheti az autó tanksapkáját és töltőnyílását."</string>
     <string name="car_permission_label_car_identification" msgid="5896712510164020478">"olvashatja a jármű-azonosító számot"</string>
     <string name="car_permission_desc_car_identification" msgid="4132040867171275059">"Hozzáférhet a jármű-azonosító számhoz."</string>
     <string name="car_permission_label_control_car_doors" msgid="3032058819250195700">"vezérelheti az autó ajtóit"</string>
@@ -102,6 +107,8 @@
     <string name="car_permission_desc_control_car_seats" msgid="2407536601226470563">"Vezérelheti az autó üléseit."</string>
     <string name="car_permission_label_car_info" msgid="4707513570676492315">"hozzáférhet az autó alapvető adataihoz"</string>
     <string name="car_permission_desc_car_info" msgid="2118081474543537653">"Hozzáférhet az autó alapvető adataihoz."</string>
+    <string name="car_permission_label_vendor_permission_info" msgid="4471260460536888654">"hozzáférhet az autó gyártóengedélyeivel kapcsolatos adataihoz"</string>
+    <string name="car_permission_desc_vendor_permission_info" msgid="8152113853528488398">"Hozzáférhet az autó gyártóengedélyeivel kapcsolatos adataihoz."</string>
     <string name="car_permission_label_car_exterior_lights" msgid="541304469604902110">"olvashatja az autó külső világításának állapotáról szóló adatokat"</string>
     <string name="car_permission_desc_car_exterior_lights" msgid="4038037584100849318">"Hozzáférhet az autó külső világításának állapotához."</string>
     <string name="car_permission_label_control_car_exterior_lights" msgid="101357531386232141">"olvashatja az autó külső világítására vonatkozó adatokat"</string>
@@ -128,5 +135,9 @@
     <string name="car_permission_desc_enroll_trust" msgid="4148649994602185130">"Megbízható eszköz regisztrálásának engedélyezése"</string>
     <string name="car_permission_label_car_test_service" msgid="9159328930558208708">"Vezérelheti az autó tesztüzemmódját"</string>
     <string name="car_permission_desc_car_test_service" msgid="7426844534110145843">"Vezérelheti az autó tesztüzemmódját"</string>
+    <string name="car_permission_label_control_car_features" msgid="3905791560378888286">"Az autó funkcióinak engedélyezése vagy tiltása"</string>
+    <string name="car_permission_desc_control_car_features" msgid="7646711104530599901">"Az autó funkcióinak engedélyezése vagy tiltása."</string>
+    <string name="car_permission_label_use_car_watchdog" msgid="6973938293170413475">"autófigyelő használata"</string>
+    <string name="car_permission_desc_use_car_watchdog" msgid="8244592601805516086">"Autófigyelő használata."</string>
     <string name="trust_device_default_name" msgid="4213625926070261253">"Saját eszköz"</string>
 </resources>
diff --git a/service/res/values-hy/strings.xml b/service/res/values-hy/strings.xml
index 38cbeaf..ae977ac 100644
--- a/service/res/values-hy/strings.xml
+++ b/service/res/values-hy/strings.xml
@@ -22,6 +22,8 @@
     <string name="car_permission_desc_camera" msgid="917024932164501426">"Կառավարել մեքենայի տեսախցիկ(ներ)ը"</string>
     <string name="car_permission_label_energy" msgid="7409144323527821558">"օգտագործել մեքենայի լիցքի մասին տվյալները"</string>
     <string name="car_permission_desc_energy" msgid="3392963810053235407">"Ընթերցել մեքենայի էներգառեսուրսների մասին տվյալները"</string>
+    <string name="car_permission_label_adjust_range_remaining" msgid="839033553999920138">"փոխել տարածությունը, որը մեքենան կանցնի մինչև հաջորդ լցակայանը"</string>
+    <string name="car_permission_desc_adjust_range_remaining" msgid="2369321650437370673">"Փոխել տարածությունը, որը մեքենան կանցնի մինչև հաջորդ լցակայանը"</string>
     <string name="car_permission_label_hvac" msgid="1499454192558727843">"օգտագործել մեքենայի HVAC համակարգի տվյալները"</string>
     <string name="car_permission_desc_hvac" msgid="3754229695589774195">"Կառավարել HVAC համակարգը"</string>
     <string name="car_permission_label_mileage" msgid="4661317074631150551">"օգտագործել մեքենայի վազքի տվյալները"</string>
@@ -62,16 +64,17 @@
     <string name="car_permission_desc_car_ux_restrictions_configuration" msgid="5711926927484813777">"Սահմանափակել գործառույթների օգտագործումը"</string>
     <string name="car_permission_label_car_handle_usb_aoap_device" msgid="72783989504378036">"Հաղորդակցվել USB սարքի հետ AOAP ռեժիմում"</string>
     <string name="car_permission_desc_car_handle_usb_aoap_device" msgid="273505990971317034">"Հավելվածին թույլ է տալիս հաղորդակցվել սարքի հետ AOAP ռեժիմում"</string>
+    <string name="car_permission_label_read_car_occupant_awareness_state" msgid="125517953575032758">"Ուղևորի ներկայության որոշման համակարգի կարգավիճակի ընթերցման թույլտվություն"</string>
+    <string name="car_permission_desc_read_car_occupant_awareness_state" msgid="188865882598414986">"Թույլ է տալիս կարդալ ուղևորի ներկայության որոշման համակարգի կարգավիճակը և տվյալները"</string>
+    <string name="car_permission_label_control_car_occupant_awareness_system" msgid="7163330266691094542">"Կառավարել ուղևորի ներկայության որոշման համակարգի տրամագիրը"</string>
+    <string name="car_permission_desc_control_car_occupant_awareness_system" msgid="7123482622084531911">"Թույլ է տալիս կառավարել ուղևորի ներկայության որոշման համակարգի աշխատանքի տրամագրի գործարկումը և դադարեցումը"</string>
     <string name="car_permission_label_bind_input_service" msgid="6698489034024273750">"Ներածման ծառայություն"</string>
     <string name="car_permission_desc_bind_input_service" msgid="1670323419931890170">"Մշակել ներածման իրադարձությունները"</string>
     <string name="car_can_bus_failure" msgid="2334035748788283914">"CAN անվադողի սխալ"</string>
     <string name="car_can_bus_failure_desc" msgid="4125516222786484733">"CAN անվադողը չի պատասխանում: Անջատեք և նորից միացրեք միակցիչը, ապա անջատեք վառոցքը և վերագործարկեք մեքենան:"</string>
-    <!-- no translation found for activity_blocked_text (8088902789540147995) -->
-    <skip />
+    <string name="activity_blocked_text" msgid="5342114426610711378">"Անվտանգության նկատառումներով այս գործողությունը հասանելի չէ վարելու ռեժիմում:\nՇարունակելու համար կայանեք մեքենան:"</string>
     <string name="exit_button_message" msgid="8554690915924055685">"Հավելվածն անվտանգ ռեժիմում վերագործարկելու համար սեղմեք<xliff:g id="EXIT_BUTTON">%s</xliff:g> կոճակը:"</string>
     <string name="exit_button" msgid="5829638404777671253">"Հետ"</string>
-    <string name="exit_button_close_application" msgid="8824289547809332460">"Փակել հավելվածը"</string>
-    <string name="exit_button_go_back" msgid="3469083862100560326">"Հետ"</string>
     <string name="car_permission_label_diag_read" msgid="7248894224877702604">"կարդալ ախտորոշման մասին տվյալները"</string>
     <string name="car_permission_desc_diag_read" msgid="1121426363040966178">"Կարդալ մեքենայի ախտորոշման տվյալները։"</string>
     <string name="car_permission_label_diag_clear" msgid="4783070510879698157">"մաքրել ախտորոշման տվյալները"</string>
@@ -90,6 +93,8 @@
     <string name="car_permission_desc_car_engine_detailed" msgid="1746863362811347700">"Օգտագործել շարժիչի մանրամասն տվյալները։"</string>
     <string name="car_permission_label_car_energy_ports" msgid="8548990315169219454">"օգտագործել մեքենայի վառելիքի բաքի կափարիչի և լիցքավորման վարդակի տվյալները"</string>
     <string name="car_permission_desc_car_energy_ports" msgid="7771185999828794949">"Օգտագործել մեքենայի վառելիքի բաքի կափարիչի և լիցքավորման վարդակի տվյալները։"</string>
+    <string name="car_permission_label_control_car_energy_ports" msgid="4375137311026313475">"Կառավարել մեքենայի վառելիքի բաքի կափարիչի և լիցքավորման վարդակի տվյալները"</string>
+    <string name="car_permission_desc_control_car_energy_ports" msgid="7364633710492525387">"Կառավարել մեքենայի վառելիքի բաքի կափարիչի և լիցքավորման վարդակի տվյալները։"</string>
     <string name="car_permission_label_car_identification" msgid="5896712510164020478">"կարդալ մեքենայի նույնականացման տվյալները"</string>
     <string name="car_permission_desc_car_identification" msgid="4132040867171275059">"Օգտագործել մեքենայի նույնականացման տվյալները։"</string>
     <string name="car_permission_label_control_car_doors" msgid="3032058819250195700">"կառավարել մեքենայի դռները"</string>
@@ -102,6 +107,8 @@
     <string name="car_permission_desc_control_car_seats" msgid="2407536601226470563">"Կառավարել մեքենայի նստատեղերը։"</string>
     <string name="car_permission_label_car_info" msgid="4707513570676492315">"օգտագործել մեքենայի հիմնական տվյալները"</string>
     <string name="car_permission_desc_car_info" msgid="2118081474543537653">"Օգտագործել մեքենայի հիմնական տվյալները։"</string>
+    <string name="car_permission_label_vendor_permission_info" msgid="4471260460536888654">"օգտագործել մեքենայի վաճառողի մասին տվյալները"</string>
+    <string name="car_permission_desc_vendor_permission_info" msgid="8152113853528488398">"Օգտագործել մեքենայի վաճառողի մասին տվյալները։"</string>
     <string name="car_permission_label_car_exterior_lights" msgid="541304469604902110">"կարդալ մեքենայի արտաքին լուսավորության կարգավիճակի տվյալները"</string>
     <string name="car_permission_desc_car_exterior_lights" msgid="4038037584100849318">"Օգտագործել մեքենայի արտաքին լուսավորության կարգավիճակի մասին տվյալները։"</string>
     <string name="car_permission_label_control_car_exterior_lights" msgid="101357531386232141">"կարդալ մեքենայի արտաքին լուսավորության կարգավիճակի մասին տվյալները"</string>
@@ -128,5 +135,9 @@
     <string name="car_permission_desc_enroll_trust" msgid="4148649994602185130">"Թույլատրել վստահելի սարքի գրանցումը"</string>
     <string name="car_permission_label_car_test_service" msgid="9159328930558208708">"Կառավարել ավտոմեքենայի փորձարկման ռեժիմը"</string>
     <string name="car_permission_desc_car_test_service" msgid="7426844534110145843">"Կառավարել ավտոմեքենայի փորձարկման ռեժիմը"</string>
+    <string name="car_permission_label_control_car_features" msgid="3905791560378888286">"Միացնել կամ անջատել մեքենայի գործառույթները"</string>
+    <string name="car_permission_desc_control_car_features" msgid="7646711104530599901">"Միացնել կամ անջատել մեքենայի գործառույթները"</string>
+    <string name="car_permission_label_use_car_watchdog" msgid="6973938293170413475">"oգտագործել մեքենայի պահապանին"</string>
+    <string name="car_permission_desc_use_car_watchdog" msgid="8244592601805516086">"Օգտագործել մեքենայի պահապանին"</string>
     <string name="trust_device_default_name" msgid="4213625926070261253">"Իմ սարքը"</string>
 </resources>
diff --git a/service/res/values-in/strings.xml b/service/res/values-in/strings.xml
index ef4bdad..32b2650 100644
--- a/service/res/values-in/strings.xml
+++ b/service/res/values-in/strings.xml
@@ -22,6 +22,8 @@
     <string name="car_permission_desc_camera" msgid="917024932164501426">"Mengakses kamera mobil Anda."</string>
     <string name="car_permission_label_energy" msgid="7409144323527821558">"mengakses informasi energi mobil"</string>
     <string name="car_permission_desc_energy" msgid="3392963810053235407">"Mengakses informasi energi mobil Anda."</string>
+    <string name="car_permission_label_adjust_range_remaining" msgid="839033553999920138">"sesuaikan jangkauan mobil yang tersisa"</string>
+    <string name="car_permission_desc_adjust_range_remaining" msgid="2369321650437370673">"Sesuaikan nilai jangkauan mobil yang tersisa."</string>
     <string name="car_permission_label_hvac" msgid="1499454192558727843">"mengakses hvac mobil"</string>
     <string name="car_permission_desc_hvac" msgid="3754229695589774195">"Mengakses hvac mobil Anda."</string>
     <string name="car_permission_label_mileage" msgid="4661317074631150551">"mengakses informasi jarak tempuh mobil"</string>
@@ -62,16 +64,17 @@
     <string name="car_permission_desc_car_ux_restrictions_configuration" msgid="5711926927484813777">"Mengonfigurasi Batasan UX"</string>
     <string name="car_permission_label_car_handle_usb_aoap_device" msgid="72783989504378036">"Berkomunikasi dengan perangkat USB dalam mode AOAP"</string>
     <string name="car_permission_desc_car_handle_usb_aoap_device" msgid="273505990971317034">"Mengizinkan aplikasi untuk berkomunikasi dengan perangkat dalam mode AOAP"</string>
+    <string name="car_permission_label_read_car_occupant_awareness_state" msgid="125517953575032758">"Akses Baca Sistem Awareness Penumpang"</string>
+    <string name="car_permission_desc_read_car_occupant_awareness_state" msgid="188865882598414986">"Mengizinkan pembacaan status dan data deteksi untuk Sistem Awareness Penumpang"</string>
+    <string name="car_permission_label_control_car_occupant_awareness_system" msgid="7163330266691094542">"Kontrol Grafik Sistem Awareness Penumpang"</string>
+    <string name="car_permission_desc_control_car_occupant_awareness_system" msgid="7123482622084531911">"Mengizinkan pengontrolan mulai dan berhentinya grafik deteksi Sistem Awareness Penumpang"</string>
     <string name="car_permission_label_bind_input_service" msgid="6698489034024273750">"Layanan Masukan Mobil"</string>
     <string name="car_permission_desc_bind_input_service" msgid="1670323419931890170">"Menangani aktivitas masukan"</string>
     <string name="car_can_bus_failure" msgid="2334035748788283914">"CAN bus gagal"</string>
     <string name="car_can_bus_failure_desc" msgid="4125516222786484733">"CAN bus tidak merespons. Cabut dan colokkan kembali boks headunit, lalu nyalakan ulang mobil"</string>
-    <!-- no translation found for activity_blocked_text (8088902789540147995) -->
-    <skip />
+    <string name="activity_blocked_text" msgid="5342114426610711378">"Demi keamanan Anda, aktivitas ini tidak tersedia saat Anda mengemudi.\nUntuk melanjutkan, tunggu hingga mobil terparkir."</string>
     <string name="exit_button_message" msgid="8554690915924055685">"Untuk mulai dari awal dengan fitur apl yang aman, pilih <xliff:g id="EXIT_BUTTON">%s</xliff:g>."</string>
     <string name="exit_button" msgid="5829638404777671253">"Kembali"</string>
-    <string name="exit_button_close_application" msgid="8824289547809332460">"Tutup aplikasi"</string>
-    <string name="exit_button_go_back" msgid="3469083862100560326">"Kembali"</string>
     <string name="car_permission_label_diag_read" msgid="7248894224877702604">"membaca data diagnostik"</string>
     <string name="car_permission_desc_diag_read" msgid="1121426363040966178">"Membaca data diagnostik dari mobil."</string>
     <string name="car_permission_label_diag_clear" msgid="4783070510879698157">"menghapus data diagnostik"</string>
@@ -90,6 +93,8 @@
     <string name="car_permission_desc_car_engine_detailed" msgid="1746863362811347700">"Mengakses informasi mendetail tentang mesin mobil Anda."</string>
     <string name="car_permission_label_car_energy_ports" msgid="8548990315169219454">"mengakses tutup tangki bahan bakar dan lubang colokan pengisi daya mobil"</string>
     <string name="car_permission_desc_car_energy_ports" msgid="7771185999828794949">"Mengakses tutup tangki bahan bakar dan lubang colokan pengisi daya mobil."</string>
+    <string name="car_permission_label_control_car_energy_ports" msgid="4375137311026313475">"mengontrol tutup tangki bahan bakar dan port pengisian daya di mobil"</string>
+    <string name="car_permission_desc_control_car_energy_ports" msgid="7364633710492525387">"Mengontrol tutup tangki bahan bakar dan port pengisian daya di mobil."</string>
     <string name="car_permission_label_car_identification" msgid="5896712510164020478">"membaca identifikasi mobil"</string>
     <string name="car_permission_desc_car_identification" msgid="4132040867171275059">"Mengakses identifikasi mobil."</string>
     <string name="car_permission_label_control_car_doors" msgid="3032058819250195700">"mengontrol pintu mobil"</string>
@@ -102,6 +107,8 @@
     <string name="car_permission_desc_control_car_seats" msgid="2407536601226470563">"Mengontrol tempat duduk mobil."</string>
     <string name="car_permission_label_car_info" msgid="4707513570676492315">"mengakses informasi dasar mobil"</string>
     <string name="car_permission_desc_car_info" msgid="2118081474543537653">"Mengakses informasi dasar mobil."</string>
+    <string name="car_permission_label_vendor_permission_info" msgid="4471260460536888654">"mengakses informasi izin vendor mobil"</string>
+    <string name="car_permission_desc_vendor_permission_info" msgid="8152113853528488398">"Mengakses informasi izin vendor mobil."</string>
     <string name="car_permission_label_car_exterior_lights" msgid="541304469604902110">"membaca status lampu eksterior mobil"</string>
     <string name="car_permission_desc_car_exterior_lights" msgid="4038037584100849318">"Mengakses status lampu eksterior mobil."</string>
     <string name="car_permission_label_control_car_exterior_lights" msgid="101357531386232141">"membaca lampu eksterior mobil"</string>
@@ -128,5 +135,9 @@
     <string name="car_permission_desc_enroll_trust" msgid="4148649994602185130">"Izinkan Pendaftaran Perangkat Dipercaya"</string>
     <string name="car_permission_label_car_test_service" msgid="9159328930558208708">"Mode uji untuk mengontrol mobil"</string>
     <string name="car_permission_desc_car_test_service" msgid="7426844534110145843">"Mode uji untuk mengontrol mobil"</string>
+    <string name="car_permission_label_control_car_features" msgid="3905791560378888286">"Aktifkan atau nonaktifkan fitur mobil"</string>
+    <string name="car_permission_desc_control_car_features" msgid="7646711104530599901">"Aktifkan atau nonaktifkan fitur mobil."</string>
+    <string name="car_permission_label_use_car_watchdog" msgid="6973938293170413475">"gunakan watchdog mobil"</string>
+    <string name="car_permission_desc_use_car_watchdog" msgid="8244592601805516086">"Gunakan watchdog mobil."</string>
     <string name="trust_device_default_name" msgid="4213625926070261253">"Perangkat Saya"</string>
 </resources>
diff --git a/service/res/values-is/strings.xml b/service/res/values-is/strings.xml
index 547e3a3..56a18dc 100644
--- a/service/res/values-is/strings.xml
+++ b/service/res/values-is/strings.xml
@@ -22,6 +22,8 @@
     <string name="car_permission_desc_camera" msgid="917024932164501426">"Fá aðgang að myndavél(um) bílsins."</string>
     <string name="car_permission_label_energy" msgid="7409144323527821558">"fá aðgang að upplýsingum um orkunotkun bílsins"</string>
     <string name="car_permission_desc_energy" msgid="3392963810053235407">"Aðgangur að upplýsingum um orkunotkun bílsins."</string>
+    <string name="car_permission_label_adjust_range_remaining" msgid="839033553999920138">"stilla gildi fyrir eftirstandandi drægi bílsins"</string>
+    <string name="car_permission_desc_adjust_range_remaining" msgid="2369321650437370673">"Stilla gildi fyrir eftirstandandi drægi bílsins."</string>
     <string name="car_permission_label_hvac" msgid="1499454192558727843">"fá aðgang að hitun og loftræstingu bílsins"</string>
     <string name="car_permission_desc_hvac" msgid="3754229695589774195">"Fá aðgang að hitun og loftræstingu bílsins."</string>
     <string name="car_permission_label_mileage" msgid="4661317074631150551">"fá aðgang að upplýsingum um ekna vegalengd bílsins"</string>
@@ -62,16 +64,17 @@
     <string name="car_permission_desc_car_ux_restrictions_configuration" msgid="5711926927484813777">"Stilla takmarkanir á upplifun notanda"</string>
     <string name="car_permission_label_car_handle_usb_aoap_device" msgid="72783989504378036">"Eiga samskipti við USB-tæki í AOAP-stillingu"</string>
     <string name="car_permission_desc_car_handle_usb_aoap_device" msgid="273505990971317034">"Gerir forriti kleift að eiga í samskiptum við tæki í AOAP-stillingu"</string>
+    <string name="car_permission_label_read_car_occupant_awareness_state" msgid="125517953575032758">"Lesaðgangur að Occupant Awareness System"</string>
+    <string name="car_permission_desc_read_car_occupant_awareness_state" msgid="188865882598414986">"Leyfir lestur á stöðu og kennslagögnum fyrir Occupant Awareness System"</string>
+    <string name="car_permission_label_control_car_occupant_awareness_system" msgid="7163330266691094542">"Stjórna riti fyrir Occupant Awareness System"</string>
+    <string name="car_permission_desc_control_car_occupant_awareness_system" msgid="7123482622084531911">"Leyfir stjórnun á kennslariti fyrir Occupant Awareness System"</string>
     <string name="car_permission_label_bind_input_service" msgid="6698489034024273750">"Inntaksþjónusta bíls"</string>
     <string name="car_permission_desc_bind_input_service" msgid="1670323419931890170">"Stjórna inntakstilvikum"</string>
     <string name="car_can_bus_failure" msgid="2334035748788283914">"Bilun í CAN-gagnabraut"</string>
     <string name="car_can_bus_failure_desc" msgid="4125516222786484733">"CAN-gagnabraut svarar ekki. Taktu stjórneiningarboxið úr sambandi, settu það aftur í samband og gangsettu bílinn aftur."</string>
-    <!-- no translation found for activity_blocked_text (8088902789540147995) -->
-    <skip />
+    <string name="activity_blocked_text" msgid="5342114426610711378">"Af öryggisástæðum er þessi aðgerð ekki í boði við akstur.\nLeggðu ökutækinu áður en þú heldur áfram."</string>
     <string name="exit_button_message" msgid="8554690915924055685">"Til að byrja aftur að setja upp örugga forritseiginleika skaltu velja <xliff:g id="EXIT_BUTTON">%s</xliff:g>."</string>
     <string name="exit_button" msgid="5829638404777671253">"Til baka"</string>
-    <string name="exit_button_close_application" msgid="8824289547809332460">"Loka forriti"</string>
-    <string name="exit_button_go_back" msgid="3469083862100560326">"Til baka"</string>
     <string name="car_permission_label_diag_read" msgid="7248894224877702604">"lesa greiningargögn"</string>
     <string name="car_permission_desc_diag_read" msgid="1121426363040966178">"Lesa greiningargögn úr bílnum."</string>
     <string name="car_permission_label_diag_clear" msgid="4783070510879698157">"hreinsa greiningargögn"</string>
@@ -90,6 +93,8 @@
     <string name="car_permission_desc_car_engine_detailed" msgid="1746863362811347700">"Fá aðgang að ítarlegum upplýsingum um vél bílsins."</string>
     <string name="car_permission_label_car_energy_ports" msgid="8548990315169219454">"fá aðgang að bensínloki og hleðslutengi bílsins"</string>
     <string name="car_permission_desc_car_energy_ports" msgid="7771185999828794949">"Fá aðgang að bensínloki og hleðslutengi bílsins."</string>
+    <string name="car_permission_label_control_car_energy_ports" msgid="4375137311026313475">"stjórna bensínloki og hleðslutengi bílsins"</string>
+    <string name="car_permission_desc_control_car_energy_ports" msgid="7364633710492525387">"Stjórna bensínloki og hleðslutengi bílsins."</string>
     <string name="car_permission_label_car_identification" msgid="5896712510164020478">"lesa auðkenni bílsins"</string>
     <string name="car_permission_desc_car_identification" msgid="4132040867171275059">"Fá aðgang að auðkenni bílsins."</string>
     <string name="car_permission_label_control_car_doors" msgid="3032058819250195700">"stjórna hurðum bílsins"</string>
@@ -102,6 +107,8 @@
     <string name="car_permission_desc_control_car_seats" msgid="2407536601226470563">"Stjórna bílsætum."</string>
     <string name="car_permission_label_car_info" msgid="4707513570676492315">"fá aðgang að grunnupplýsingum bílsins"</string>
     <string name="car_permission_desc_car_info" msgid="2118081474543537653">"Fá aðgang að grunnupplýsingum bílsins."</string>
+    <string name="car_permission_label_vendor_permission_info" msgid="4471260460536888654">"fá aðgang að heimildarupplýsingum bílasala"</string>
+    <string name="car_permission_desc_vendor_permission_info" msgid="8152113853528488398">"Fá aðgang að heimildarupplýsingum bílasala."</string>
     <string name="car_permission_label_car_exterior_lights" msgid="541304469604902110">"lesa stöðu ljósa bíls að utanverðu"</string>
     <string name="car_permission_desc_car_exterior_lights" msgid="4038037584100849318">"Fá aðgang að stöðu ljósa bíls að utanverðu."</string>
     <string name="car_permission_label_control_car_exterior_lights" msgid="101357531386232141">"lesa ljós bíls að utanverðu"</string>
@@ -128,5 +135,9 @@
     <string name="car_permission_desc_enroll_trust" msgid="4148649994602185130">"Heimila skráningu sem traust tæki"</string>
     <string name="car_permission_label_car_test_service" msgid="9159328930558208708">"Stjórna prófunarstillingu bíls"</string>
     <string name="car_permission_desc_car_test_service" msgid="7426844534110145843">"Stjórna prófunarstillingu bíls"</string>
+    <string name="car_permission_label_control_car_features" msgid="3905791560378888286">"Kveikja eða slökkva á bíleiginleikum"</string>
+    <string name="car_permission_desc_control_car_features" msgid="7646711104530599901">"Kveikja eða slökkva á bíleiginleikum."</string>
+    <string name="car_permission_label_use_car_watchdog" msgid="6973938293170413475">"nota bílaeftirlitsaðila"</string>
+    <string name="car_permission_desc_use_car_watchdog" msgid="8244592601805516086">"Nota bílaeftirlitsaðila."</string>
     <string name="trust_device_default_name" msgid="4213625926070261253">"Tækið mitt"</string>
 </resources>
diff --git a/service/res/values-it/strings.xml b/service/res/values-it/strings.xml
index 80e738f..4b807c0 100644
--- a/service/res/values-it/strings.xml
+++ b/service/res/values-it/strings.xml
@@ -22,6 +22,8 @@
     <string name="car_permission_desc_camera" msgid="917024932164501426">"Consente di accedere alle videocamere dell\'automobile."</string>
     <string name="car_permission_label_energy" msgid="7409144323527821558">"Accesso alle informazioni sulla carica dell\'automobile"</string>
     <string name="car_permission_desc_energy" msgid="3392963810053235407">"Consente di accedere alle informazioni sulla carica dell\'automobile."</string>
+    <string name="car_permission_label_adjust_range_remaining" msgid="839033553999920138">"regolazione dell\'autonomia rimanente dell\'auto"</string>
+    <string name="car_permission_desc_adjust_range_remaining" msgid="2369321650437370673">"Consente di regolare il valore dell\'autonomia rimanente dell\'auto."</string>
     <string name="car_permission_label_hvac" msgid="1499454192558727843">"Accesso al sistema HVAC dell\'automobile"</string>
     <string name="car_permission_desc_hvac" msgid="3754229695589774195">"Consente di accedere al sistema HVAC dell\'automobile."</string>
     <string name="car_permission_label_mileage" msgid="4661317074631150551">"Accesso alle informazioni sul consumo di carburante dell\'automobile"</string>
@@ -62,15 +64,17 @@
     <string name="car_permission_desc_car_ux_restrictions_configuration" msgid="5711926927484813777">"Consente di configurare le limitazioni dell\'esperienza utente."</string>
     <string name="car_permission_label_car_handle_usb_aoap_device" msgid="72783989504378036">"Comunicazione con dispositivo USB in modalità AOAP"</string>
     <string name="car_permission_desc_car_handle_usb_aoap_device" msgid="273505990971317034">"Consente a un\'app di comunicare con un dispositivo in modalità AOAP"</string>
+    <string name="car_permission_label_read_car_occupant_awareness_state" msgid="125517953575032758">"Accesso in lettura all\'Occupant Awareness System"</string>
+    <string name="car_permission_desc_read_car_occupant_awareness_state" msgid="188865882598414986">"Consente di leggere lo stato e i dati di rilevamento dell\'Occupant Awareness System."</string>
+    <string name="car_permission_label_control_car_occupant_awareness_system" msgid="7163330266691094542">"Controllo del grafico dell\'Occupant Awareness System"</string>
+    <string name="car_permission_desc_control_car_occupant_awareness_system" msgid="7123482622084531911">"Consente di controllare l\'avvio e l\'interruzione del grafico di rilevamento dell\'Occupant Awareness System."</string>
     <string name="car_permission_label_bind_input_service" msgid="6698489034024273750">"Servizio di input dell\'auto"</string>
     <string name="car_permission_desc_bind_input_service" msgid="1670323419931890170">"Consente di gestire gli eventi di input."</string>
     <string name="car_can_bus_failure" msgid="2334035748788283914">"Comunicazione tramite bus CAN non riuscita"</string>
     <string name="car_can_bus_failure_desc" msgid="4125516222786484733">"Il bus CAN non risponde. Scollega e ricollega l\'unità principale e riaccendi il motore."</string>
-    <string name="activity_blocked_text" msgid="8088902789540147995">"Non è possibile usare questa funzionalità durante la guida"</string>
+    <string name="activity_blocked_text" msgid="5342114426610711378">"Per motivi di sicurezza, questa attività non è disponibile durante la guida.\nPotrai continuare quando avrai parcheggiato."</string>
     <string name="exit_button_message" msgid="8554690915924055685">"Seleziona <xliff:g id="EXIT_BUTTON">%s</xliff:g> per ricominciare con le funzionalità sicure dell\'app."</string>
     <string name="exit_button" msgid="5829638404777671253">"Indietro"</string>
-    <string name="exit_button_close_application" msgid="8824289547809332460">"Chiudi app"</string>
-    <string name="exit_button_go_back" msgid="3469083862100560326">"Indietro"</string>
     <string name="car_permission_label_diag_read" msgid="7248894224877702604">"Lettura dei dati diagnostici"</string>
     <string name="car_permission_desc_diag_read" msgid="1121426363040966178">"Consente di leggere i dati diagnostici dell\'automobile."</string>
     <string name="car_permission_label_diag_clear" msgid="4783070510879698157">"Cancellazione dei dati diagnostici"</string>
@@ -89,6 +93,8 @@
     <string name="car_permission_desc_car_engine_detailed" msgid="1746863362811347700">"Consente di accedere a informazioni dettagliate sul motore dell\'automobile."</string>
     <string name="car_permission_label_car_energy_ports" msgid="8548990315169219454">"Accesso al coperchio del serbatoio e allo sportello di ricarica dell\'automobile"</string>
     <string name="car_permission_desc_car_energy_ports" msgid="7771185999828794949">"Consente di accedere al coperchio del serbatoio e allo sportello di ricarica dell\'automobile."</string>
+    <string name="car_permission_label_control_car_energy_ports" msgid="4375137311026313475">"Controllo del coperchio del serbatoio e dello sportello di ricarica dell\'automobile"</string>
+    <string name="car_permission_desc_control_car_energy_ports" msgid="7364633710492525387">"Consente di controllare il coperchio del serbatoio e lo sportello di ricarica dell\'automobile."</string>
     <string name="car_permission_label_car_identification" msgid="5896712510164020478">"Lettura dell\'identificazione dell\'automobile"</string>
     <string name="car_permission_desc_car_identification" msgid="4132040867171275059">"Consente di accedere all\'identificazione dell\'automobile."</string>
     <string name="car_permission_label_control_car_doors" msgid="3032058819250195700">"Controllo delle portiere dell\'automobile"</string>
@@ -101,6 +107,8 @@
     <string name="car_permission_desc_control_car_seats" msgid="2407536601226470563">"Consente di regolare i sedili dell\'automobile."</string>
     <string name="car_permission_label_car_info" msgid="4707513570676492315">"Accesso alle informazioni di base dell\'automobile"</string>
     <string name="car_permission_desc_car_info" msgid="2118081474543537653">"Consente di accedere alle informazioni di base dell\'automobile."</string>
+    <string name="car_permission_label_vendor_permission_info" msgid="4471260460536888654">"Accesso alle informazioni sulle autorizzazioni del produttore dell\'automobile"</string>
+    <string name="car_permission_desc_vendor_permission_info" msgid="8152113853528488398">"Consente di accedere alle informazioni sulle autorizzazioni del produttore dell\'automobile."</string>
     <string name="car_permission_label_car_exterior_lights" msgid="541304469604902110">"Lettura dello stato delle luci esterne dell\'automobile"</string>
     <string name="car_permission_desc_car_exterior_lights" msgid="4038037584100849318">"Consente di accedere allo stato delle luci esterne dell\'automobile."</string>
     <string name="car_permission_label_control_car_exterior_lights" msgid="101357531386232141">"Lettura delle luci esterne dell\'automobile"</string>
@@ -127,5 +135,9 @@
     <string name="car_permission_desc_enroll_trust" msgid="4148649994602185130">"Consenti la registrazione di dispositivi attendibili"</string>
     <string name="car_permission_label_car_test_service" msgid="9159328930558208708">"Controllo della modalità di test dell\'auto"</string>
     <string name="car_permission_desc_car_test_service" msgid="7426844534110145843">"Controllo della modalità di test dell\'auto"</string>
+    <string name="car_permission_label_control_car_features" msgid="3905791560378888286">"Attiva o disattiva le funzionalità dell\'auto"</string>
+    <string name="car_permission_desc_control_car_features" msgid="7646711104530599901">"Attiva o disattiva le funzionalità dell\'auto."</string>
+    <string name="car_permission_label_use_car_watchdog" msgid="6973938293170413475">"Uso del watchdog dell\'auto"</string>
+    <string name="car_permission_desc_use_car_watchdog" msgid="8244592601805516086">"Consente di usare il watchdog dell\'auto."</string>
     <string name="trust_device_default_name" msgid="4213625926070261253">"Mio dispositivo"</string>
 </resources>
diff --git a/service/res/values-iw/strings.xml b/service/res/values-iw/strings.xml
index 413e886..7315e75 100644
--- a/service/res/values-iw/strings.xml
+++ b/service/res/values-iw/strings.xml
@@ -22,6 +22,8 @@
     <string name="car_permission_desc_camera" msgid="917024932164501426">"גישה למצלמות הרכב."</string>
     <string name="car_permission_label_energy" msgid="7409144323527821558">"גישה למידע על נתוני צריכת האנרגיה של הרכב"</string>
     <string name="car_permission_desc_energy" msgid="3392963810053235407">"גישה למידע על אנרגיית הרכב"</string>
+    <string name="car_permission_label_adjust_range_remaining" msgid="839033553999920138">"התאמה של הטווח הנותר של הרכב"</string>
+    <string name="car_permission_desc_adjust_range_remaining" msgid="2369321650437370673">"התאמת הערך של הטווח הנותר של הרכב."</string>
     <string name="car_permission_label_hvac" msgid="1499454192558727843">"גישה למערכת החימום, הקירור והאוורור של הרכב"</string>
     <string name="car_permission_desc_hvac" msgid="3754229695589774195">"גישה למערכת החימום, האוורור ומיזוג האוויר (HVAC) של הרכב."</string>
     <string name="car_permission_label_mileage" msgid="4661317074631150551">"גישה לנתוני הקילומטראז\' של הרכב"</string>
@@ -62,16 +64,17 @@
     <string name="car_permission_desc_car_ux_restrictions_configuration" msgid="5711926927484813777">"הגדרת הגבלות של חוויית משתמש (UX)"</string>
     <string name="car_permission_label_car_handle_usb_aoap_device" msgid="72783989504378036">"תקשורת באמצעות מכשיר USB במצב AOAP"</string>
     <string name="car_permission_desc_car_handle_usb_aoap_device" msgid="273505990971317034">"מאפשרת לאפליקציה לתקשר עם מכשיר במצב AOAP ‏(Android Open Accessory Protocol)"</string>
+    <string name="car_permission_label_read_car_occupant_awareness_state" msgid="125517953575032758">"גישת קריאה אל מערכת המודעות לתפוסה (Occupant Awareness)"</string>
+    <string name="car_permission_desc_read_car_occupant_awareness_state" msgid="188865882598414986">"הרשאה לסטטוס קריאה ונתוני זיהוי למערכת המודעות לתפוסה (Occupant Awareness)"</string>
+    <string name="car_permission_label_control_car_occupant_awareness_system" msgid="7163330266691094542">"שליטה בתרשים של מערכת המודעות לתפוסה (Occupant Awareness)"</string>
+    <string name="car_permission_desc_control_car_occupant_awareness_system" msgid="7123482622084531911">"הרשאה לשליטה בהתחלה ובסיום של תרשים הזיהוי של מערכת המודעות לתפוסה (Occupant Awareness)"</string>
     <string name="car_permission_label_bind_input_service" msgid="6698489034024273750">"שירות הקלט של הרכב"</string>
     <string name="car_permission_desc_bind_input_service" msgid="1670323419931890170">"ניהול אירועי קלט"</string>
     <string name="car_can_bus_failure" msgid="2334035748788283914">"פרוטוקול CAN bus נכשל"</string>
     <string name="car_can_bus_failure_desc" msgid="4125516222786484733">"פרוטוקול CAN bus לא מגיב. יש לנתק ולחבר שוב את מערכת הסטריאו ולהתניע מחדש את הרכב"</string>
-    <!-- no translation found for activity_blocked_text (8088902789540147995) -->
-    <skip />
+    <string name="activity_blocked_text" msgid="5342114426610711378">"מטעמי בטיחות, פעילות זו אינה זמינה בזמן נהיגה.\nכדי להמשיך, צריך להמתין עד לחניית הרכב."</string>
     <string name="exit_button_message" msgid="8554690915924055685">"כדי להפעיל מחדש את האפליקציה במצב בטוח, יש ללחוץ על <xliff:g id="EXIT_BUTTON">%s</xliff:g>."</string>
     <string name="exit_button" msgid="5829638404777671253">"הקודם"</string>
-    <string name="exit_button_close_application" msgid="8824289547809332460">"סגירת האפליקציה"</string>
-    <string name="exit_button_go_back" msgid="3469083862100560326">"חזרה"</string>
     <string name="car_permission_label_diag_read" msgid="7248894224877702604">"קריאת נתוני אבחון"</string>
     <string name="car_permission_desc_diag_read" msgid="1121426363040966178">"קריאת נתוני אבחון מהרכב."</string>
     <string name="car_permission_label_diag_clear" msgid="4783070510879698157">"מחיקת נתוני האבחון"</string>
@@ -90,6 +93,8 @@
     <string name="car_permission_desc_car_engine_detailed" msgid="1746863362811347700">"גישה למידע מפורט על מנוע הרכב."</string>
     <string name="car_permission_label_car_energy_ports" msgid="8548990315169219454">"גישה לפתח מכל הדלק וליציאת הטעינה של הרכב"</string>
     <string name="car_permission_desc_car_energy_ports" msgid="7771185999828794949">"גישה לפתח מכל הדלק וליציאת הטעינה של הרכב."</string>
+    <string name="car_permission_label_control_car_energy_ports" msgid="4375137311026313475">"שליטה בפתח מכל הדלק וביציאת הטעינה של המכונית"</string>
+    <string name="car_permission_desc_control_car_energy_ports" msgid="7364633710492525387">"שליטה בפתח מכל הדלק וביציאת הטעינה של המכונית."</string>
     <string name="car_permission_label_car_identification" msgid="5896712510164020478">"קריאת פרטי הזיהוי של הרכב"</string>
     <string name="car_permission_desc_car_identification" msgid="4132040867171275059">"גישה לפרטי הזיהוי של הרכב."</string>
     <string name="car_permission_label_control_car_doors" msgid="3032058819250195700">"שליטה בדלתות הרכב"</string>
@@ -102,6 +107,8 @@
     <string name="car_permission_desc_control_car_seats" msgid="2407536601226470563">"שליטה במושבי הרכב."</string>
     <string name="car_permission_label_car_info" msgid="4707513570676492315">"גישה לנתונים הבסיסיים של הרכב"</string>
     <string name="car_permission_desc_car_info" msgid="2118081474543537653">"גישה לנתונים הבסיסיים של הרכב."</string>
+    <string name="car_permission_label_vendor_permission_info" msgid="4471260460536888654">"מתן גישה למידע לגבי הרשאת הספק של המכונית"</string>
+    <string name="car_permission_desc_vendor_permission_info" msgid="8152113853528488398">"מתן גישה למידע לגבי הרשאת הספק של המכונית."</string>
     <string name="car_permission_label_car_exterior_lights" msgid="541304469604902110">"קריאה של מצב הפנסים החיצוניים של הרכב"</string>
     <string name="car_permission_desc_car_exterior_lights" msgid="4038037584100849318">"גישה למצב הפנסים החיצוניים של הרכב."</string>
     <string name="car_permission_label_control_car_exterior_lights" msgid="101357531386232141">"קריאת נתונים על הפנסים החיצוניים של הרכב"</string>
@@ -128,5 +135,9 @@
     <string name="car_permission_desc_enroll_trust" msgid="4148649994602185130">"הפעלת רישום של מכשירים מהימנים"</string>
     <string name="car_permission_label_car_test_service" msgid="9159328930558208708">"שליטה במצב הבדיקה של הרכב"</string>
     <string name="car_permission_desc_car_test_service" msgid="7426844534110145843">"שליטה במצב הבדיקה של הרכב"</string>
+    <string name="car_permission_label_control_car_features" msgid="3905791560378888286">"הפעלה או השבתה של תכונות המכונית"</string>
+    <string name="car_permission_desc_control_car_features" msgid="7646711104530599901">"הפעלה או השבתה של תכונות המכונית."</string>
+    <string name="car_permission_label_use_car_watchdog" msgid="6973938293170413475">"שימוש בטיימר המפקח (watchdog) של המכונית"</string>
+    <string name="car_permission_desc_use_car_watchdog" msgid="8244592601805516086">"שימוש בטיימר המפקח (watchdog) של המכונית."</string>
     <string name="trust_device_default_name" msgid="4213625926070261253">"המכשיר שלי"</string>
 </resources>
diff --git a/service/res/values-ja/strings.xml b/service/res/values-ja/strings.xml
index f5914ec..8dfb2d9 100644
--- a/service/res/values-ja/strings.xml
+++ b/service/res/values-ja/strings.xml
@@ -22,6 +22,8 @@
     <string name="car_permission_desc_camera" msgid="917024932164501426">"車載カメラにアクセスします。"</string>
     <string name="car_permission_label_energy" msgid="7409144323527821558">"車のエネルギー情報へのアクセス"</string>
     <string name="car_permission_desc_energy" msgid="3392963810053235407">"車のエネルギー情報にアクセスします。"</string>
+    <string name="car_permission_label_adjust_range_remaining" msgid="839033553999920138">"車の航続可能距離の調整"</string>
+    <string name="car_permission_desc_adjust_range_remaining" msgid="2369321650437370673">"車の航続可能距離の値を調整します。"</string>
     <string name="car_permission_label_hvac" msgid="1499454192558727843">"車のエアコン ユニットへのアクセス"</string>
     <string name="car_permission_desc_hvac" msgid="3754229695589774195">"車のエアコン ユニットにアクセスします。"</string>
     <string name="car_permission_label_mileage" msgid="4661317074631150551">"車の走行距離情報へのアクセス"</string>
@@ -62,16 +64,17 @@
     <string name="car_permission_desc_car_ux_restrictions_configuration" msgid="5711926927484813777">"UX 制限を設定します"</string>
     <string name="car_permission_label_car_handle_usb_aoap_device" msgid="72783989504378036">"AOAP モードでの USB デバイスとの通信"</string>
     <string name="car_permission_desc_car_handle_usb_aoap_device" msgid="273505990971317034">"AOAP モードでのデバイスとの通信をアプリに許可"</string>
+    <string name="car_permission_label_read_car_occupant_awareness_state" msgid="125517953575032758">"乗員検知システムの読み取りアクセス"</string>
+    <string name="car_permission_desc_read_car_occupant_awareness_state" msgid="188865882598414986">"乗員検知システムのステータスと検知データの読み取りを許可します"</string>
+    <string name="car_permission_label_control_car_occupant_awareness_system" msgid="7163330266691094542">"乗員検知システムグラフの操作"</string>
+    <string name="car_permission_desc_control_car_occupant_awareness_system" msgid="7123482622084531911">"乗員検知システムの検知グラフの開始および終了操作を許可します"</string>
     <string name="car_permission_label_bind_input_service" msgid="6698489034024273750">"車の入力サービス"</string>
     <string name="car_permission_desc_bind_input_service" msgid="1670323419931890170">"入力イベントを処理します"</string>
     <string name="car_can_bus_failure" msgid="2334035748788283914">"CAN バスでエラーが発生しました"</string>
     <string name="car_can_bus_failure_desc" msgid="4125516222786484733">"CAN バスが応答しません。ヘッドユニット ボックスのプラグを抜いて接続し直し、車を再始動してください"</string>
-    <!-- no translation found for activity_blocked_text (8088902789540147995) -->
-    <skip />
+    <string name="activity_blocked_text" msgid="5342114426610711378">"安全上の理由により、運転中はこのアクティビティをご利用いただけません。\n続行するには駐車してください。"</string>
     <string name="exit_button_message" msgid="8554690915924055685">"アプリをセーフモードで再起動するには、<xliff:g id="EXIT_BUTTON">%s</xliff:g> を選択します。"</string>
     <string name="exit_button" msgid="5829638404777671253">"戻る"</string>
-    <string name="exit_button_close_application" msgid="8824289547809332460">"アプリを閉じる"</string>
-    <string name="exit_button_go_back" msgid="3469083862100560326">"戻る"</string>
     <string name="car_permission_label_diag_read" msgid="7248894224877702604">"診断データの読み取り"</string>
     <string name="car_permission_desc_diag_read" msgid="1121426363040966178">"車から診断データを読み取ります。"</string>
     <string name="car_permission_label_diag_clear" msgid="4783070510879698157">"診断データの消去"</string>
@@ -90,6 +93,8 @@
     <string name="car_permission_desc_car_engine_detailed" msgid="1746863362811347700">"車の詳細なエンジン情報にアクセスします。"</string>
     <string name="car_permission_label_car_energy_ports" msgid="8548990315169219454">"車の給油口と充電ポートへのアクセス"</string>
     <string name="car_permission_desc_car_energy_ports" msgid="7771185999828794949">"車の給油口と充電ポートにアクセスします。"</string>
+    <string name="car_permission_label_control_car_energy_ports" msgid="4375137311026313475">"車の給油口と充電ポートの制御"</string>
+    <string name="car_permission_desc_control_car_energy_ports" msgid="7364633710492525387">"車の給油口と充電ポートの制御"</string>
     <string name="car_permission_label_car_identification" msgid="5896712510164020478">"車の ID の読み取り"</string>
     <string name="car_permission_desc_car_identification" msgid="4132040867171275059">"車の ID にアクセスします。"</string>
     <string name="car_permission_label_control_car_doors" msgid="3032058819250195700">"車のドアの操作"</string>
@@ -102,6 +107,8 @@
     <string name="car_permission_desc_control_car_seats" msgid="2407536601226470563">"カーシートを調節します。"</string>
     <string name="car_permission_label_car_info" msgid="4707513570676492315">"車の基本情報へのアクセス"</string>
     <string name="car_permission_desc_car_info" msgid="2118081474543537653">"車の基本情報にアクセスします。"</string>
+    <string name="car_permission_label_vendor_permission_info" msgid="4471260460536888654">"車のベンダー権限情報へのアクセス"</string>
+    <string name="car_permission_desc_vendor_permission_info" msgid="8152113853528488398">"車のベンダー権限情報にアクセスします。"</string>
     <string name="car_permission_label_car_exterior_lights" msgid="541304469604902110">"車のエクステリア ライトの状態の読み取り"</string>
     <string name="car_permission_desc_car_exterior_lights" msgid="4038037584100849318">"車のエクステリア ライトの状態にアクセスします。"</string>
     <string name="car_permission_label_control_car_exterior_lights" msgid="101357531386232141">"車のエクステリア ライトの読み取り"</string>
@@ -128,5 +135,9 @@
     <string name="car_permission_desc_enroll_trust" msgid="4148649994602185130">"信頼できるデバイスの登録を許可"</string>
     <string name="car_permission_label_car_test_service" msgid="9159328930558208708">"車のテストモードの制御"</string>
     <string name="car_permission_desc_car_test_service" msgid="7426844534110145843">"車のテストモードの制御"</string>
+    <string name="car_permission_label_control_car_features" msgid="3905791560378888286">"車の機能を有効または無効にします"</string>
+    <string name="car_permission_desc_control_car_features" msgid="7646711104530599901">"車の機能を有効または無効にします。"</string>
+    <string name="car_permission_label_use_car_watchdog" msgid="6973938293170413475">"車のウォッチドッグの使用"</string>
+    <string name="car_permission_desc_use_car_watchdog" msgid="8244592601805516086">"車のウォッチドッグの使用。"</string>
     <string name="trust_device_default_name" msgid="4213625926070261253">"自分のデバイス"</string>
 </resources>
diff --git a/service/res/values-ka/strings.xml b/service/res/values-ka/strings.xml
index 46ad23a..98995a5 100644
--- a/service/res/values-ka/strings.xml
+++ b/service/res/values-ka/strings.xml
@@ -22,6 +22,8 @@
     <string name="car_permission_desc_camera" msgid="917024932164501426">"თქვენი მანქანის კამერებზე წვდომა."</string>
     <string name="car_permission_label_energy" msgid="7409144323527821558">"მანქანის ენერგორესურსების ინფორმაციაზე წვდომა"</string>
     <string name="car_permission_desc_energy" msgid="3392963810053235407">"თქვენი მანქანის ენერგორესურსების ინფორმაციაზე წვდომა."</string>
+    <string name="car_permission_label_adjust_range_remaining" msgid="839033553999920138">"მანქანის დიაპაზონის ნარჩენის კორექტირება"</string>
+    <string name="car_permission_desc_adjust_range_remaining" msgid="2369321650437370673">"მანქანის დიაპაზონის დარჩენილი მნიშვნელობის კორექტირება."</string>
     <string name="car_permission_label_hvac" msgid="1499454192558727843">"მანქანის HVAC (გათბობა, ვენტილაცია და ჰაერის კონდიცირება) სისტემაზე წვდომა"</string>
     <string name="car_permission_desc_hvac" msgid="3754229695589774195">"თქვენი მანქანის HVAC (გათბობა, ვენტილაცია და ჰაერის კონდიცირება) სისტემაზე წვდომა."</string>
     <string name="car_permission_label_mileage" msgid="4661317074631150551">"მანქანის გარბენის შესახებ ინფორმაციაზე წვდომა"</string>
@@ -62,15 +64,17 @@
     <string name="car_permission_desc_car_ux_restrictions_configuration" msgid="5711926927484813777">"UX შეზღუდვების კონფიგურაცია"</string>
     <string name="car_permission_label_car_handle_usb_aoap_device" msgid="72783989504378036">"USB მოწყობილობასთან AOAP რეჟიმში კომუნიკაცია"</string>
     <string name="car_permission_desc_car_handle_usb_aoap_device" msgid="273505990971317034">"აპს აძლევს მოწყობილობასთან AOAP რეჟიმში კომუნიკაციის საშუალებას"</string>
+    <string name="car_permission_label_read_car_occupant_awareness_state" msgid="125517953575032758">"წვდომა მძღოლის ინფორმირების სისტემის წასაკითხად"</string>
+    <string name="car_permission_desc_read_car_occupant_awareness_state" msgid="188865882598414986">"იძლევა მძღოლის ინფორმირების სისტემის სტატუსისა და ამოცნობის მონაცემების წაკითხვის საშუალებას"</string>
+    <string name="car_permission_label_control_car_occupant_awareness_system" msgid="7163330266691094542">"მძღოლის ინფორმირების სისტემის დიაგრამის მართვა"</string>
+    <string name="car_permission_desc_control_car_occupant_awareness_system" msgid="7123482622084531911">"იძლევა მძღოლის ინფორმირების სისტემის გაშვების მართვისა და ამოცნობის დიაგრამის შეწყვეტის საშუალებას"</string>
     <string name="car_permission_label_bind_input_service" msgid="6698489034024273750">"მანქანის შეყვანის სერვისი"</string>
     <string name="car_permission_desc_bind_input_service" msgid="1670323419931890170">"შეტანის მოვლენების დამუშავება"</string>
     <string name="car_can_bus_failure" msgid="2334035748788283914">"წარმოიშვა CAN-სალტის შეცდომა"</string>
     <string name="car_can_bus_failure_desc" msgid="4125516222786484733">"CAN-სალტე არ რეაგირებს. გამოაერთეთ და ხელახლა მიაერთეთ საინფორმაციო-გასართობი მოწყობილობა, შემდეგ კი ხელახლა დაქოქეთ მანქანა"</string>
-    <string name="activity_blocked_text" msgid="8088902789540147995">"მანქანის მართვისას ამ ფუნქციას ვერ გამოიყენებთ"</string>
+    <string name="activity_blocked_text" msgid="5342114426610711378">"ეს აქტივობა მიუწვდომელია მანქანის მართვისას, უსაფრთხოების მოსაზრებებიდან გამომდინარე.\nგთხოვთ, ჯერ გააჩეროთ მანქანა."</string>
     <string name="exit_button_message" msgid="8554690915924055685">"აპის უსაფრთხო რეჟიმში გასაშვებად აირჩიეთ „<xliff:g id="EXIT_BUTTON">%s</xliff:g>“."</string>
     <string name="exit_button" msgid="5829638404777671253">"უკან"</string>
-    <string name="exit_button_close_application" msgid="8824289547809332460">"აპის დახურვა"</string>
-    <string name="exit_button_go_back" msgid="3469083862100560326">"უკან"</string>
     <string name="car_permission_label_diag_read" msgid="7248894224877702604">"დიაგნოსტიკური მონაცემების წაკითხვა"</string>
     <string name="car_permission_desc_diag_read" msgid="1121426363040966178">"დიაგნოსტიკური მონაცემების წაკითხვა მანქანიდან."</string>
     <string name="car_permission_label_diag_clear" msgid="4783070510879698157">"დიაგნოსტიკური მონაცემების გასუფთავება"</string>
@@ -89,6 +93,8 @@
     <string name="car_permission_desc_car_engine_detailed" msgid="1746863362811347700">"მანქანის ძრავის დეტალურ ინფორმაციაზე წვდომა."</string>
     <string name="car_permission_label_car_energy_ports" msgid="8548990315169219454">"მანქანის საწვავის ავზის ხუფზე და დამტენ პორტზე წვდომა"</string>
     <string name="car_permission_desc_car_energy_ports" msgid="7771185999828794949">"მანქანის საწვავის ავზის ხუფზე და დამტენ პორტზე წვდომა."</string>
+    <string name="car_permission_label_control_car_energy_ports" msgid="4375137311026313475">"მანქანის საწვავის ავზის კარის და დამტენი პორტის მართვა"</string>
+    <string name="car_permission_desc_control_car_energy_ports" msgid="7364633710492525387">"მანქანის საწვავის ავზის კარის და დამტენი პორტის მართვა."</string>
     <string name="car_permission_label_car_identification" msgid="5896712510164020478">"მანქანის საიდენტიფიკაციო მონაცემების წაკითხვა"</string>
     <string name="car_permission_desc_car_identification" msgid="4132040867171275059">"მანქანის საიდენტიფიკაციო მონაცემებზე წვდომა."</string>
     <string name="car_permission_label_control_car_doors" msgid="3032058819250195700">"მანქანის კარების გაკონტროლება"</string>
@@ -101,6 +107,8 @@
     <string name="car_permission_desc_control_car_seats" msgid="2407536601226470563">"მანქანის სავარძლების გაკონტროლება."</string>
     <string name="car_permission_label_car_info" msgid="4707513570676492315">"მანქანის ძირითად ინფორმაციაზე წვდომა"</string>
     <string name="car_permission_desc_car_info" msgid="2118081474543537653">"მანქანის ძირითად ინფორმაციაზე წვდომა."</string>
+    <string name="car_permission_label_vendor_permission_info" msgid="4471260460536888654">"მანქანის მწარმოებლის ნებართვასთან დაკავშირებულ ინფორმაციაზე წვდომა"</string>
+    <string name="car_permission_desc_vendor_permission_info" msgid="8152113853528488398">"მანქანის მწარმოებლის ნებართვასთან დაკავშირებულ ინფორმაციაზე წვდომა."</string>
     <string name="car_permission_label_car_exterior_lights" msgid="541304469604902110">"მანქანის გარე განათების მდგომარეობის წაკითხვა"</string>
     <string name="car_permission_desc_car_exterior_lights" msgid="4038037584100849318">"მანქანის გარე განათების მდგომარეობაზე წვდომა."</string>
     <string name="car_permission_label_control_car_exterior_lights" msgid="101357531386232141">"მანქანის გარე განათების წაკითხვა"</string>
@@ -127,5 +135,9 @@
     <string name="car_permission_desc_enroll_trust" msgid="4148649994602185130">"სანდო მოწყობილობის რეგისტრაციის დაშვება"</string>
     <string name="car_permission_label_car_test_service" msgid="9159328930558208708">"მანქანის სატესტო რეჟიმის გაკონტროლება"</string>
     <string name="car_permission_desc_car_test_service" msgid="7426844534110145843">"მანქანის სატესტო რეჟიმის გაკონტროლება"</string>
+    <string name="car_permission_label_control_car_features" msgid="3905791560378888286">"მანქანის ფუნქციების ჩართვა ან გათიშვა"</string>
+    <string name="car_permission_desc_control_car_features" msgid="7646711104530599901">"მანქანის ფუნქციების ჩართვა ან გათიშვა."</string>
+    <string name="car_permission_label_use_car_watchdog" msgid="6973938293170413475">"მანქანის დაცვის მოწყობილობის გამოყენება"</string>
+    <string name="car_permission_desc_use_car_watchdog" msgid="8244592601805516086">"მანქანის დაცვის მოწყობილობის გამოყენება."</string>
     <string name="trust_device_default_name" msgid="4213625926070261253">"ჩემი მოწყობილობა"</string>
 </resources>
diff --git a/service/res/values-kk/strings.xml b/service/res/values-kk/strings.xml
index b3e8c7c..077f44e 100644
--- a/service/res/values-kk/strings.xml
+++ b/service/res/values-kk/strings.xml
@@ -22,6 +22,8 @@
     <string name="car_permission_desc_camera" msgid="917024932164501426">"Көліктің камераларын пайдалануға болады."</string>
     <string name="car_permission_label_energy" msgid="7409144323527821558">"көлік қуаты туралы ақпаратты пайдалану"</string>
     <string name="car_permission_desc_energy" msgid="3392963810053235407">"Көліктің қуаты туралы ақпаратты көруге болады."</string>
+    <string name="car_permission_label_adjust_range_remaining" msgid="839033553999920138">"көліктің қалған жүріп өтетін жолын өзгерту"</string>
+    <string name="car_permission_desc_adjust_range_remaining" msgid="2369321650437370673">"Көліктің қалған жүріп өтетін жолының мәнін өзгерту"</string>
     <string name="car_permission_label_hvac" msgid="1499454192558727843">"көліктің климат басқару жүйесін пайдалану"</string>
     <string name="car_permission_desc_hvac" msgid="3754229695589774195">"Көліктің кондиционерін пайдалануға болады."</string>
     <string name="car_permission_label_mileage" msgid="4661317074631150551">"көліктің километражы туралы ақпаратты пайдалану"</string>
@@ -62,16 +64,17 @@
     <string name="car_permission_desc_car_ux_restrictions_configuration" msgid="5711926927484813777">"UX шектеулерін конфигурациялау"</string>
     <string name="car_permission_label_car_handle_usb_aoap_device" msgid="72783989504378036">"USB құрылғысымен AOAP режимінде байланыс орнату"</string>
     <string name="car_permission_desc_car_handle_usb_aoap_device" msgid="273505990971317034">"Қолданбаға AOAP режиміндегі құрылғымен байланыс орнатуға мүмкіндік береді."</string>
+    <string name="car_permission_label_read_car_occupant_awareness_state" msgid="125517953575032758">"Жолаушыларды бақылау жүйесін оқуға рұқсат ету"</string>
+    <string name="car_permission_desc_read_car_occupant_awareness_state" msgid="188865882598414986">"Жолаушыларды бақылау жүйесінің күйін оқуға және деректерді анықтауға мүмкіндік береді."</string>
+    <string name="car_permission_label_control_car_occupant_awareness_system" msgid="7163330266691094542">"Жолаушыларды бақылау жүйесі графигі"</string>
+    <string name="car_permission_desc_control_car_occupant_awareness_system" msgid="7123482622084531911">"Жолаушыларды бақылау жүйесі графигінің басталуы мен аяқталуын басқаруға мүмкіндік береді."</string>
     <string name="car_permission_label_bind_input_service" msgid="6698489034024273750">"Көліктің дерек енгізу қызметі"</string>
     <string name="car_permission_desc_bind_input_service" msgid="1670323419931890170">"Деректерді енгізу оқиғаларын басқаруға болады"</string>
     <string name="car_can_bus_failure" msgid="2334035748788283914">"CAN шинасы істен шықты"</string>
     <string name="car_can_bus_failure_desc" msgid="4125516222786484733">"CAN шинасы жауап бермейді. Негізгі модульді ажыратып, қайта жалғаңыз және көлікті қайта оталдырыңыз"</string>
-    <!-- no translation found for activity_blocked_text (8088902789540147995) -->
-    <skip />
+    <string name="activity_blocked_text" msgid="5342114426610711378">"Көлік жүргізгенде, қауіпсіздік мақсатында бұл әрекетті орындау мүмкін емес.\nЖалғастыру үшін көлікті тұраққа қойыңыз."</string>
     <string name="exit_button_message" msgid="8554690915924055685">"Қолданбаны қауіпсіз күйде қайта іске қосу үшін <xliff:g id="EXIT_BUTTON">%s</xliff:g> түймесін басыңыз."</string>
     <string name="exit_button" msgid="5829638404777671253">"Артқа"</string>
-    <string name="exit_button_close_application" msgid="8824289547809332460">"Қолданбаны жабу"</string>
-    <string name="exit_button_go_back" msgid="3469083862100560326">"Артқа"</string>
     <string name="car_permission_label_diag_read" msgid="7248894224877702604">"диагностикалық деректерді көру"</string>
     <string name="car_permission_desc_diag_read" msgid="1121426363040966178">"Көліктің диагностикалық деректерін көру."</string>
     <string name="car_permission_label_diag_clear" msgid="4783070510879698157">"диагностикалық деректерді өшіру"</string>
@@ -90,6 +93,8 @@
     <string name="car_permission_desc_car_engine_detailed" msgid="1746863362811347700">"Көліктің қозғалтқышы туралы толық ақпаратты пайдалану."</string>
     <string name="car_permission_label_car_energy_ports" msgid="8548990315169219454">"көліктің жанармай құю саңылауын және зарядтау портын пайдалану"</string>
     <string name="car_permission_desc_car_energy_ports" msgid="7771185999828794949">"Көліктің жанармай құю саңылауын және зарядтау портын пайдалану."</string>
+    <string name="car_permission_label_control_car_energy_ports" msgid="4375137311026313475">"көліктің жанармай құю саңылауын және зарядтау портын пайдалану"</string>
+    <string name="car_permission_desc_control_car_energy_ports" msgid="7364633710492525387">"Көліктің жанармай құю саңылауын және зарядтау портын пайдалану."</string>
     <string name="car_permission_label_car_identification" msgid="5896712510164020478">"көліктің идентификациялық нөмірін көру"</string>
     <string name="car_permission_desc_car_identification" msgid="4132040867171275059">"Көліктің идентификациялық нөмірін пайдалану."</string>
     <string name="car_permission_label_control_car_doors" msgid="3032058819250195700">"көлік есіктерін басқару"</string>
@@ -102,6 +107,8 @@
     <string name="car_permission_desc_control_car_seats" msgid="2407536601226470563">"Көлік орындықтарын басқару."</string>
     <string name="car_permission_label_car_info" msgid="4707513570676492315">"көліктің негізгі ақпаратын пайдалану"</string>
     <string name="car_permission_desc_car_info" msgid="2118081474543537653">"Көлік туралы негізгі ақпаратты пайдалану."</string>
+    <string name="car_permission_label_vendor_permission_info" msgid="4471260460536888654">"автокөлік өндірушісінің рұқсаты туралы ақпаратты пайдалану"</string>
+    <string name="car_permission_desc_vendor_permission_info" msgid="8152113853528488398">"Автокөлік өндірушісінің рұқсаты туралы ақпаратты пайдалану."</string>
     <string name="car_permission_label_car_exterior_lights" msgid="541304469604902110">"көліктің сыртқы шамдарының күйін көру"</string>
     <string name="car_permission_desc_car_exterior_lights" msgid="4038037584100849318">"Көліктің сыртқы шамдарының күйін көру."</string>
     <string name="car_permission_label_control_car_exterior_lights" msgid="101357531386232141">"көліктің сыртқы шамдарын көру"</string>
@@ -128,5 +135,9 @@
     <string name="car_permission_desc_enroll_trust" msgid="4148649994602185130">"Сенімді құрылғыларды тіркеуге рұқсат ету"</string>
     <string name="car_permission_label_car_test_service" msgid="9159328930558208708">"Көліктің сынақ режимін бақылау"</string>
     <string name="car_permission_desc_car_test_service" msgid="7426844534110145843">"Көліктің сынақ режимін бақылау"</string>
+    <string name="car_permission_label_control_car_features" msgid="3905791560378888286">"Көлік функцияларын қосыңыз немесе өшіріңіз."</string>
+    <string name="car_permission_desc_control_car_features" msgid="7646711104530599901">"Көлік функцияларын қосыңыз немесе өшіріңіз."</string>
+    <string name="car_permission_label_use_car_watchdog" msgid="6973938293170413475">"көлік бақылау жүйесін пайдалану"</string>
+    <string name="car_permission_desc_use_car_watchdog" msgid="8244592601805516086">"Көлік бақылау жүйесін пайдалану."</string>
     <string name="trust_device_default_name" msgid="4213625926070261253">"Құрылғым"</string>
 </resources>
diff --git a/service/res/values-km/strings.xml b/service/res/values-km/strings.xml
index 1794f28..896ff3c 100644
--- a/service/res/values-km/strings.xml
+++ b/service/res/values-km/strings.xml
@@ -22,6 +22,8 @@
     <string name="car_permission_desc_camera" msgid="917024932164501426">"ចូលប្រើ​កាមេរ៉ារបស់​រថយន្តអ្នក។"</string>
     <string name="car_permission_label_energy" msgid="7409144323527821558">"ចូលប្រើ​ព័ត៌មាន​អំពីថាមពលរបស់​រថយន្ត"</string>
     <string name="car_permission_desc_energy" msgid="3392963810053235407">"ចូលប្រើ​ព័ត៌មាន​ថាមពលរបស់​រថយន្តអ្នក។"</string>
+    <string name="car_permission_label_adjust_range_remaining" msgid="839033553999920138">"កែតម្រូវ​រយៈចម្ងាយ​ដែលរថយន្តអាចបន្ត​ដំណើរការបាន"</string>
+    <string name="car_permission_desc_adjust_range_remaining" msgid="2369321650437370673">"កែតម្រូវ​តម្លៃ​នៃរយៈចម្ងាយ​ដែលរថយន្តអាចបន្ត​ដំណើរការបាន។"</string>
     <string name="car_permission_label_hvac" msgid="1499454192558727843">"ចូលប្រើ​ប្រព័ន្ធកម្តៅ ខ្យល់ និងម៉ាស៊ីនត្រជាក់​របស់​រថយន្ត"</string>
     <string name="car_permission_desc_hvac" msgid="3754229695589774195">"ចូលប្រើ​ប្រព័ន្ធកម្តៅ ខ្យល់ និងម៉ាស៊ីនត្រជាក់​របស់រថយន្តអ្នក។"</string>
     <string name="car_permission_label_mileage" msgid="4661317074631150551">"ចូលប្រើព័ត៌មាន​អំពីរយៈចម្ងាយរត់​របស់រថយន្ត"</string>
@@ -62,16 +64,17 @@
     <string name="car_permission_desc_car_ux_restrictions_configuration" msgid="5711926927484813777">"កំណត់រចនាសម្ព័ន្ធ​ការរឹតបន្តឹង UX"</string>
     <string name="car_permission_label_car_handle_usb_aoap_device" msgid="72783989504378036">"ទាក់ទងជាមួយ​ឧបករណ៍ USB តាមរយៈមុខងារ AOAP"</string>
     <string name="car_permission_desc_car_handle_usb_aoap_device" msgid="273505990971317034">"អនុញ្ញាតឱ្យ​កម្មវិធី​ទាក់ទង​ជាមួយឧបករណ៍តាមរយៈមុខងារ AOAP"</string>
+    <string name="car_permission_label_read_car_occupant_awareness_state" msgid="125517953575032758">"សិទ្ធិ​ចូលអាន​ប្រព័ន្ធ​នៃ​ការយល់ដឹង​អំពី​អ្នកជិះ​ក្នុងរថយន្ត"</string>
+    <string name="car_permission_desc_read_car_occupant_awareness_state" msgid="188865882598414986">"អនុញ្ញាត​ការអាន​ស្ថានភាព និង​ទិន្នន័យ​អំពី​ការចាប់​ប្រព័ន្ធ​នៃ​ការយល់ដឹង​អំពី​អ្នកជិះ​ក្នុងរថយន្ត"</string>
+    <string name="car_permission_label_control_car_occupant_awareness_system" msgid="7163330266691094542">"គ្រប់គ្រង​ក្រាហ្វ​អំពី​ប្រព័ន្ធ​នៃ​ការយល់ដឹង​អំពី​អ្នកជិះ​ក្នុងរថយន្ត"</string>
+    <string name="car_permission_desc_control_car_occupant_awareness_system" msgid="7123482622084531911">"អនុញ្ញាត​ការគ្រប់គ្រង​ការចាប់ផ្ដើម និង​ការបញ្ឈប់​ក្រាហ្វ​អំពីការចាប់​ប្រព័ន្ធ​នៃ​ការយល់ដឹង​អំពី​អ្នកជិះ​ក្នុងរថយន្ត"</string>
     <string name="car_permission_label_bind_input_service" msgid="6698489034024273750">"សេវាកម្ម​បញ្ចូលរបស់​រថយន្ត"</string>
     <string name="car_permission_desc_bind_input_service" msgid="1670323419931890170">"គ្រប់គ្រង​ព្រឹត្តិការណ៍​បញ្ចូល"</string>
     <string name="car_can_bus_failure" msgid="2334035748788283914">"មិនអាច​ដំណើរការ CAN bus បានទេ"</string>
     <string name="car_can_bus_failure_desc" msgid="4125516222786484733">"CAN bus មិនឆ្លើយតបទេ។ សូមផ្ដាច់ រួចភ្ជាប់​ប្រអប់​ឧបករណ៍ចាក់តន្ត្រី​ម្តងទៀត បន្ទាប់មក​បញ្ឆេះ​រថយន្ត​ឡើងវិញ"</string>
-    <!-- no translation found for activity_blocked_text (8088902789540147995) -->
-    <skip />
+    <string name="activity_blocked_text" msgid="5342114426610711378">"ដើម្បី​សុវត្ថិភាព​របស់អ្នក សកម្មភាពនេះ​មិនអាចប្រើបានទេ ពេល​កំពុងបើកបរ។\nដើម្បីបន្ត សូមរង់ចាំ​រហូតទាល់តែ​អ្នកចត​រថយន្តរួច។"</string>
     <string name="exit_button_message" msgid="8554690915924055685">"ដើម្បី​ចាប់ផ្តើមឡើងវិញ​ដោយប្រើ​មុខងារកម្មវិធី​ដែលមានសុវត្ថិភាព សូមជ្រើសរើស <xliff:g id="EXIT_BUTTON">%s</xliff:g> ។"</string>
     <string name="exit_button" msgid="5829638404777671253">"ថយក្រោយ"</string>
-    <string name="exit_button_close_application" msgid="8824289547809332460">"បិទកម្មវិធី"</string>
-    <string name="exit_button_go_back" msgid="3469083862100560326">"ថយក្រោយ"</string>
     <string name="car_permission_label_diag_read" msgid="7248894224877702604">"អានទិន្នន័យ​វិភាគ"</string>
     <string name="car_permission_desc_diag_read" msgid="1121426363040966178">"អាន​អំពីទិន្នន័យ​វិភាគពី​រថយន្ត។"</string>
     <string name="car_permission_label_diag_clear" msgid="4783070510879698157">"សម្អាត​ទិន្នន័យ​វិភាគ"</string>
@@ -90,6 +93,8 @@
     <string name="car_permission_desc_car_engine_detailed" msgid="1746863362811347700">"ចូលប្រើ​ព័ត៌មាន​លម្អិត​អំពីម៉ាស៊ីនរថយន្តរបស់អ្នក។"</string>
     <string name="car_permission_label_car_energy_ports" msgid="8548990315169219454">"ចូលប្រើ​គម្របសាំង និងរន្ធសាក​អាគុយរថយន្ត។"</string>
     <string name="car_permission_desc_car_energy_ports" msgid="7771185999828794949">"ចូលប្រើ​គម្របសាំង និងរន្ធសាក​អាគុយរថយន្ត។"</string>
+    <string name="car_permission_label_control_car_energy_ports" msgid="4375137311026313475">"គ្រប់គ្រង​គម្រប​សាំង និង​រន្ធ​សាក​អាគុយ​រថយន្ត"</string>
+    <string name="car_permission_desc_control_car_energy_ports" msgid="7364633710492525387">"គ្រប់គ្រង​គម្របសាំង និង​រន្ធ​សាក​អាគុយ​រថយន្ត​។"</string>
     <string name="car_permission_label_car_identification" msgid="5896712510164020478">"អាន​អត្តសញ្ញាណរថយន្ត"</string>
     <string name="car_permission_desc_car_identification" msgid="4132040867171275059">"ចូលប្រើ​អត្តសញ្ញាណរថយន្ត។"</string>
     <string name="car_permission_label_control_car_doors" msgid="3032058819250195700">"គ្រប់គ្រង​ទ្វាររថយន្ត"</string>
@@ -102,6 +107,8 @@
     <string name="car_permission_desc_control_car_seats" msgid="2407536601226470563">"គ្រប់គ្រង​កៅអី​រថយន្ត។"</string>
     <string name="car_permission_label_car_info" msgid="4707513570676492315">"ចូលប្រើ​ព័ត៌មាន​មូលដ្ឋាន​របស់រថយន្ត"</string>
     <string name="car_permission_desc_car_info" msgid="2118081474543537653">"ចូលប្រើ​ព័ត៌មាន​មូលដ្ឋាន​របស់រថយន្ត។"</string>
+    <string name="car_permission_label_vendor_permission_info" msgid="4471260460536888654">"ចូលប្រើ​ព័ត៌មាន​អំពី​ការអនុញ្ញាត​ពី​អ្នកលក់​របស់​រថយន្ត"</string>
+    <string name="car_permission_desc_vendor_permission_info" msgid="8152113853528488398">"ចូលប្រើ​ព័ត៌មាន​អំពី​ការអនុញ្ញាត​ពី​អ្នកលក់​របស់​រថយន្ត​។"</string>
     <string name="car_permission_label_car_exterior_lights" msgid="541304469604902110">"អានស្ថានភាពភ្លើងផ្នែក​ខាងក្រៅ​រថយន្ត"</string>
     <string name="car_permission_desc_car_exterior_lights" msgid="4038037584100849318">"ចូលប្រើ​ស្ថានភាពភ្លើង​ផ្នែកខាងក្រៅ​រថយន្ត។"</string>
     <string name="car_permission_label_control_car_exterior_lights" msgid="101357531386232141">"អានភ្លើងផ្នែកខាងក្រៅ​រថយន្ត"</string>
@@ -128,5 +135,9 @@
     <string name="car_permission_desc_enroll_trust" msgid="4148649994602185130">"អនុញ្ញាតឱ្យចុះ​ឈ្មោះឧបករណ៍​ដែល​ទុក​ចិត្ត"</string>
     <string name="car_permission_label_car_test_service" msgid="9159328930558208708">"គ្រប់គ្រង​មុខងារ​ធ្វើតេស្តរបស់​រថយន្ត"</string>
     <string name="car_permission_desc_car_test_service" msgid="7426844534110145843">"គ្រប់គ្រង​មុខងារ​ធ្វើតេស្តរបស់​រថយន្ត"</string>
+    <string name="car_permission_label_control_car_features" msgid="3905791560378888286">"បើក ឬ​បិទ​មុខងារ​របស់​រថយន្ត"</string>
+    <string name="car_permission_desc_control_car_features" msgid="7646711104530599901">"បើក ឬ​បិទ​មុខងារ​របស់​រថយន្ត​។"</string>
+    <string name="car_permission_label_use_car_watchdog" msgid="6973938293170413475">"ប្រើប្រាស់សេវា​មើល​ការ​ខុស​ត្រូវ​ស្ថានភាព​កម្មវិធី​របស់​ឡាន"</string>
+    <string name="car_permission_desc_use_car_watchdog" msgid="8244592601805516086">"ប្រើប្រាស់​សេវា​មើល​ការ​ខុស​ត្រូវ​ស្ថានភាព​កម្មវិធី​របស់​ឡាន។"</string>
     <string name="trust_device_default_name" msgid="4213625926070261253">"ឧបករណ៍របស់ខ្ញុំ"</string>
 </resources>
diff --git a/service/res/values-kn/strings.xml b/service/res/values-kn/strings.xml
index 7c8e225..81b2f32 100644
--- a/service/res/values-kn/strings.xml
+++ b/service/res/values-kn/strings.xml
@@ -22,6 +22,8 @@
     <string name="car_permission_desc_camera" msgid="917024932164501426">"ನಿಮ್ಮ ಕಾರಿನ ಕ್ಯಾಮರವನ್ನು(ಗಳನ್ನು) ಪ್ರವೇಶಿಸಿ."</string>
     <string name="car_permission_label_energy" msgid="7409144323527821558">"ಕಾರಿನ ಶಕ್ತಿಯ ಬಳಕೆಯ ಮಾಹಿತಿ ಕುರಿತು ಪ್ರವೇಶಿಸಿ"</string>
     <string name="car_permission_desc_energy" msgid="3392963810053235407">"ನಿಮ್ಮ ಕಾರಿನ ಶಕ್ತಿ ಮಾಹಿತಿಯನ್ನು ಪಡೆಯಿರಿ."</string>
+    <string name="car_permission_label_adjust_range_remaining" msgid="839033553999920138">"ಬಾಕಿ ಉಳಿದ ಕಾರ್‌ನ ಶ್ರೇಣಿಯನ್ನು ಸರಿಹೊಂದಿಸಿ"</string>
+    <string name="car_permission_desc_adjust_range_remaining" msgid="2369321650437370673">"ಕಾರ್‌ನ ಶ್ರೇಣಿಯ ಬಾಕಿ ಉಳಿದ ಮೌಲ್ಯವನ್ನು ಸರಿಹೊಂದಿಸಿ."</string>
     <string name="car_permission_label_hvac" msgid="1499454192558727843">"ಕಾರಿನ ಹವಾನಿಯಂತ್ರಕದ ಮಾಹಿತಿ ಪಡೆಯಿರಿ"</string>
     <string name="car_permission_desc_hvac" msgid="3754229695589774195">"ಕಾರಿನ ಎಚ್‌ವಿಎಸಿಯ ಮಾಹಿತಿನ್ನು ಪಡೆಯಿರಿ."</string>
     <string name="car_permission_label_mileage" msgid="4661317074631150551">"ಕಾರಿನ ಮೈಲೇಜ್ ಮಾಹಿತಿಯನ್ನು ಪಡೆಯಿರಿ"</string>
@@ -62,16 +64,17 @@
     <string name="car_permission_desc_car_ux_restrictions_configuration" msgid="5711926927484813777">"ಯುಎಕ್ಸ್ ನಿರ್ಬಂಧಗಳನ್ನು ಸಂರಚಿಸು"</string>
     <string name="car_permission_label_car_handle_usb_aoap_device" msgid="72783989504378036">"AOAP ಮೋಡ್‌ನಲ್ಲಿ USB ಸಾಧನದ ಜೊತೆಗೆ ಸಂವಹನ ನಡೆಸಿ"</string>
     <string name="car_permission_desc_car_handle_usb_aoap_device" msgid="273505990971317034">"AOAP ಮೋಡ್‌ನಲ್ಲಿ ಸಾಧನದ ಜೊತೆಗೆ ಸಂವಹನ ನಡೆಸಲು ಆ್ಯಪ್‌ಗೆ ಅನುಮತಿಸುತ್ತದೆ"</string>
+    <string name="car_permission_label_read_car_occupant_awareness_state" msgid="125517953575032758">"Occupant Awareness System ರೀಡ್ ಆ್ಯಕ್ಸೆಸ್"</string>
+    <string name="car_permission_desc_read_car_occupant_awareness_state" msgid="188865882598414986">"Occupant Awareness System ಗಾಗಿ ಓದುವ ಸ್ಥಿತಿ ಮತ್ತು ಪತ್ತೆಹಚ್ಚುವಿಕೆ ಡೇಟಾವನ್ನು ಅನುಮತಿಸುತ್ತದೆ"</string>
+    <string name="car_permission_label_control_car_occupant_awareness_system" msgid="7163330266691094542">"Occupant Awareness System Graph ಅನ್ನು ನಿಯಂತ್ರಿಸಿ"</string>
+    <string name="car_permission_desc_control_car_occupant_awareness_system" msgid="7123482622084531911">"Occupant Awareness System ಪತ್ತೆಹಚ್ಚುವಿಕೆ ಗ್ರಾಫ್‌ನ ಪ್ರಾರಂಭಿಸಲು ಮತ್ತು ನಿಲ್ಲಿಸುವುದನ್ನು ನಿಯಂತ್ರಿಸಲು ನಿಮಗೆ ಅನುಮತಿಸುತ್ತದೆ"</string>
     <string name="car_permission_label_bind_input_service" msgid="6698489034024273750">"ಕಾರಿನ ಇನ್‌ಪುಟ್ ಸೇವೆ"</string>
     <string name="car_permission_desc_bind_input_service" msgid="1670323419931890170">"ಊಡಿಕೆ ಘಟನೆಗಳನ್ನು ನಿಯಂತ್ರಿಸಿ"</string>
     <string name="car_can_bus_failure" msgid="2334035748788283914">"ಸಿಎಎನ್ ಬಸ್ ಕೆಟ್ಟಿದೆ"</string>
     <string name="car_can_bus_failure_desc" msgid="4125516222786484733">"CAN ಬಸ್ ಸ್ಪಂದಿಸುತ್ತಿಲ್ಲ. ಹೆಡ್ ಯುನಿಟ್ ಪೆಟ್ಟಿಗೆಯನ್ನು ಅನ್‌ಪ್ಲಗ್ ಮಾಡಿ ಮತ್ತೆ ಪ್ಲಗ್ ಮಾಡಿ ಕಾರನ್ನು ಪುನರಾರಂಭಿಸಿ"</string>
-    <!-- no translation found for activity_blocked_text (8088902789540147995) -->
-    <skip />
+    <string name="activity_blocked_text" msgid="5342114426610711378">"ನಿಮ್ಮ ಸುರಕ್ಷೆಗಾಗಿ, ಈ ಚಟುವಟಿಕೆಯು ವಾಹನ ಚಲಾಯಿಸುತ್ತಿರುವಾಗ ಲಭ್ಯವಿಲ್ಲ. ಮುಂದುವರೆಯಲು \n ಮಾಡಿ, ನೀವು ಪಾರ್ಕ್ ಮಾಡುವ ತನಕ ಕಾಯಿರಿ."</string>
     <string name="exit_button_message" msgid="8554690915924055685">"ಆಪ್‌ನ ಸುರಕ್ಷೆ ಗುಣಲಕ್ಷಣಗಳನ್ನು ಒಳಗೊಂಡು ಮತ್ತೆ ಪ್ರಾರಂಭಿಸಲು, <xliff:g id="EXIT_BUTTON">%s</xliff:g> ಆಯ್ಕೆ ಮಾಡಿ."</string>
     <string name="exit_button" msgid="5829638404777671253">"ಹಿಂದಕ್ಕೆ"</string>
-    <string name="exit_button_close_application" msgid="8824289547809332460">"ಆ್ಯಪ್ ಮುಚ್ಚಿರಿ"</string>
-    <string name="exit_button_go_back" msgid="3469083862100560326">"ಹಿಂದೆ"</string>
     <string name="car_permission_label_diag_read" msgid="7248894224877702604">"ಡಯಾಗ್ನೋಸ್ಟಿಕ್ ಡೇಟಾವನ್ನು ಓದಿ"</string>
     <string name="car_permission_desc_diag_read" msgid="1121426363040966178">"ಕಾರಿನಲ್ಲಿರುವ ಅದರ ಡಯಾಗ್ನೋಸ್ಟಿಕ್ ಡೇಟಾವನ್ನು ಓದಿ."</string>
     <string name="car_permission_label_diag_clear" msgid="4783070510879698157">"ಡಯಾಗ್ನೋಸ್ಟಿಕ್ ಡೇಟಾವನ್ನು ತೆರವುಗೊಳಿಸಿ"</string>
@@ -90,6 +93,8 @@
     <string name="car_permission_desc_car_engine_detailed" msgid="1746863362811347700">"ಕಾರಿನ ಇಂಜಿನ್‌ನ ವಿವರಣೆಯ ಮಾಹಿತಿಯನ್ನು ಪ್ರವೇಶಿಸಿ."</string>
     <string name="car_permission_label_car_energy_ports" msgid="8548990315169219454">"ಕಾರಿನ ಇಂಧನ ಪಂಪ್‌ನ ಮುಚ್ಚಳ ಮತ್ತು ಚಾರ್ಜ್ ಪೋರ್ಟ್ ಮಾಹಿತಿ ಪಡೆಯಿರಿ"</string>
     <string name="car_permission_desc_car_energy_ports" msgid="7771185999828794949">"ಕಾರಿನ ಇಂಧನ ಪಂಪ್‌ನ ಮುಚ್ಚಳ ಮತ್ತು ಚಾರ್ಜ್ ಪೋರ್ಟ್ ಅನ್ನು ಪ್ರವೇಶಿಸಿ."</string>
+    <string name="car_permission_label_control_car_energy_ports" msgid="4375137311026313475">"ಕಾರ್‌ನ ಇಂಧನ ಪಂಪ್‌ನ ಮುಚ್ಚಳ ಮತ್ತು ಚಾರ್ಜ್ ಪೋರ್ಟ್ ಅನ್ನು ನಿಯಂತ್ರಿಸಿ"</string>
+    <string name="car_permission_desc_control_car_energy_ports" msgid="7364633710492525387">"ಕಾರ್‌ನ ಇಂಧನ ಪಂಪ್‌ನ ಮುಚ್ಚಳ ಮತ್ತು ಚಾರ್ಜ್ ಪೋರ್ಟ್ ಅನ್ನು ನಿಯಂತ್ರಿಸಿ."</string>
     <string name="car_permission_label_car_identification" msgid="5896712510164020478">"ಕಾರಿನ ಗುರುತನ್ನು ಓದಿ"</string>
     <string name="car_permission_desc_car_identification" msgid="4132040867171275059">"ಕಾರಿನ ಗುರುತನ್ನು ಪಡೆಯಿರಿ."</string>
     <string name="car_permission_label_control_car_doors" msgid="3032058819250195700">"ಕಾರಿನ ಬಾಗಿಲುಗಳನ್ನು ನಿಯಂತ್ರಿಸಿ"</string>
@@ -102,6 +107,8 @@
     <string name="car_permission_desc_control_car_seats" msgid="2407536601226470563">"ಕಾರಿನ ಆಸನಗಳನ್ನು ನಿಯಂತ್ರಿಸಿ."</string>
     <string name="car_permission_label_car_info" msgid="4707513570676492315">"ಕಾರಿನ ಮೂಲ ಮಾಹಿತಿಯನ್ನು ಪಡೆಯಿರಿ"</string>
     <string name="car_permission_desc_car_info" msgid="2118081474543537653">"ಕಾರಿನ ಮೂಲ ಮಾಹಿತಿಯನ್ನು ಪ್ರವೇಶಿಸಿ."</string>
+    <string name="car_permission_label_vendor_permission_info" msgid="4471260460536888654">"ಕಾರಿನ ಮಾರಾಟಗಾರರ ಅನುಮತಿ ಮಾಹಿತಿಯನ್ನು ಪ್ರವೇಶಿಸಿ"</string>
+    <string name="car_permission_desc_vendor_permission_info" msgid="8152113853528488398">"ಕಾರಿನ ಮಾರಾಟಗಾರರ ಅನುಮತಿ ಮಾಹಿತಿಯನ್ನು ಪ್ರವೇಶಿಸಿ."</string>
     <string name="car_permission_label_car_exterior_lights" msgid="541304469604902110">"ಕಾರಿನ ಹೊರಾಂಗಣ ಲೈಟ್‌ಗಳ ಸ್ಥಿತಿಯ ಮಾಹಿತಿಯನ್ನು ಓದಿ"</string>
     <string name="car_permission_desc_car_exterior_lights" msgid="4038037584100849318">"ಕಾರಿನ ಹೊರಾಂಗಣ ಲೈಟ್‌ಗಳ ಸ್ಥಿತಿಯನ್ನು ಪ್ರವೇಶಿಸಿ."</string>
     <string name="car_permission_label_control_car_exterior_lights" msgid="101357531386232141">"ಕಾರಿನ ಹೊರಾಂಗಣ ಲೈಟ್‌ಗಳ ಮಾಹಿತಿಯನ್ನು ಓದಿ"</string>
@@ -128,5 +135,9 @@
     <string name="car_permission_desc_enroll_trust" msgid="4148649994602185130">"ವಿಶ್ವಾಸಾರ್ಹ ಸಾಧನಗಳ ನೋಂದಣಿಯನ್ನು ಅನುಮತಿಸಿ"</string>
     <string name="car_permission_label_car_test_service" msgid="9159328930558208708">"ಕಾರ್‌ನ ಪರೀಕ್ಷಾ ಮೋಡ್ ಅನ್ನು ನಿಯಂತ್ರಿಸಿ"</string>
     <string name="car_permission_desc_car_test_service" msgid="7426844534110145843">"ಕಾರ್‌ನ ಪರೀಕ್ಷಾ ಮೋಡ್ ಅನ್ನು ನಿಯಂತ್ರಿಸಿ"</string>
+    <string name="car_permission_label_control_car_features" msgid="3905791560378888286">"ಕಾರ್ ಫೀಚರ್‌ಗಳನ್ನು ಸಕ್ರಿಯಗೊಳಿಸಿ ಅಥವಾ ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಿ"</string>
+    <string name="car_permission_desc_control_car_features" msgid="7646711104530599901">"ಕಾರ್ ಫೀಚರ್‌ಗಳನ್ನು ಸಕ್ರಿಯಗೊಳಿಸಿ ಅಥವಾ ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಿ."</string>
+    <string name="car_permission_label_use_car_watchdog" msgid="6973938293170413475">"ಕಾರ್ ಮಾನಿಟರಿಂಗ್ ಟೈಮರ್ ಬಳಸಿ"</string>
+    <string name="car_permission_desc_use_car_watchdog" msgid="8244592601805516086">"ಕಾರ್ ಮಾನಿಟರಿಂಗ್ ಟೈಮರ್ ಬಳಸಿ."</string>
     <string name="trust_device_default_name" msgid="4213625926070261253">"ನನ್ನ ಸಾಧನ"</string>
 </resources>
diff --git a/service/res/values-ko/strings.xml b/service/res/values-ko/strings.xml
index e1f213f..f334829 100644
--- a/service/res/values-ko/strings.xml
+++ b/service/res/values-ko/strings.xml
@@ -22,6 +22,8 @@
     <string name="car_permission_desc_camera" msgid="917024932164501426">"차량 카메라에 액세스"</string>
     <string name="car_permission_label_energy" msgid="7409144323527821558">"차량 에너지 정보 액세스"</string>
     <string name="car_permission_desc_energy" msgid="3392963810053235407">"차량 에너지 정보에 액세스"</string>
+    <string name="car_permission_label_adjust_range_remaining" msgid="839033553999920138">"차량의 잔여 주행거리 조정"</string>
+    <string name="car_permission_desc_adjust_range_remaining" msgid="2369321650437370673">"차량의 잔여 주행거리를 조정하세요."</string>
     <string name="car_permission_label_hvac" msgid="1499454192558727843">"차량 공조기에 액세스"</string>
     <string name="car_permission_desc_hvac" msgid="3754229695589774195">"차량 공조기에 액세스합니다."</string>
     <string name="car_permission_label_mileage" msgid="4661317074631150551">"차량 주행 거리 정보에 액세스"</string>
@@ -62,16 +64,17 @@
     <string name="car_permission_desc_car_ux_restrictions_configuration" msgid="5711926927484813777">"UX 제한사항 설정"</string>
     <string name="car_permission_label_car_handle_usb_aoap_device" msgid="72783989504378036">"AOAP 모드의 USB 기기와 통신"</string>
     <string name="car_permission_desc_car_handle_usb_aoap_device" msgid="273505990971317034">"AOAP 모드의 기기와 통신하도록 앱을 허용하세요."</string>
+    <string name="car_permission_label_read_car_occupant_awareness_state" msgid="125517953575032758">"Occupant Awareness System 읽기 액세스"</string>
+    <string name="car_permission_desc_read_car_occupant_awareness_state" msgid="188865882598414986">"Occupant Awareness System의 상태 및 감지 데이터를 읽는 것을 허용합니다."</string>
+    <string name="car_permission_label_control_car_occupant_awareness_system" msgid="7163330266691094542">"Occupant Awareness System 그래프 제어"</string>
+    <string name="car_permission_desc_control_car_occupant_awareness_system" msgid="7123482622084531911">"Occupant Awareness System 감지 그래프의 시작 및 중지를 제어하는 것을 허용합니다."</string>
     <string name="car_permission_label_bind_input_service" msgid="6698489034024273750">"차량 입력 서비스"</string>
     <string name="car_permission_desc_bind_input_service" msgid="1670323419931890170">"입력 이벤트 처리"</string>
     <string name="car_can_bus_failure" msgid="2334035748788283914">"CAN 버스 실패"</string>
     <string name="car_can_bus_failure_desc" msgid="4125516222786484733">"CAN 버스가 응답하지 않습니다. 헤드유닛 박스를 분리한 후 다시 연결한 다음 시동을 다시 걸어 보세요."</string>
-    <!-- no translation found for activity_blocked_text (8088902789540147995) -->
-    <skip />
+    <string name="activity_blocked_text" msgid="5342114426610711378">"안전을 위해 운전 중에는 이용할 수 없는 활동입니다.\n계속하려면 먼저 주차하세요."</string>
     <string name="exit_button_message" msgid="8554690915924055685">"안전한 앱 기능으로 다시 시작하려면 <xliff:g id="EXIT_BUTTON">%s</xliff:g>을(를) 선택하세요."</string>
     <string name="exit_button" msgid="5829638404777671253">"뒤로"</string>
-    <string name="exit_button_close_application" msgid="8824289547809332460">"앱 닫기"</string>
-    <string name="exit_button_go_back" msgid="3469083862100560326">"뒤로"</string>
     <string name="car_permission_label_diag_read" msgid="7248894224877702604">"진단 데이터 읽기"</string>
     <string name="car_permission_desc_diag_read" msgid="1121426363040966178">"차량의 진단 데이터를 읽습니다."</string>
     <string name="car_permission_label_diag_clear" msgid="4783070510879698157">"진단 데이터 삭제"</string>
@@ -90,6 +93,8 @@
     <string name="car_permission_desc_car_engine_detailed" msgid="1746863362811347700">"차량의 상세한 엔진 정보에 액세스합니다."</string>
     <string name="car_permission_label_car_energy_ports" msgid="8548990315169219454">"차량 주유구 캡 및 충전 포트 액세스"</string>
     <string name="car_permission_desc_car_energy_ports" msgid="7771185999828794949">"차량의 주유구 캡 및 충전 포트에 액세스합니다."</string>
+    <string name="car_permission_label_control_car_energy_ports" msgid="4375137311026313475">"차량 주유구 캡 및 충전 포트 액세스 제어"</string>
+    <string name="car_permission_desc_control_car_energy_ports" msgid="7364633710492525387">"차량 주유구 캡 및 충전 포트 액세스를 제어합니다."</string>
     <string name="car_permission_label_car_identification" msgid="5896712510164020478">"차량 ID 읽기"</string>
     <string name="car_permission_desc_car_identification" msgid="4132040867171275059">"차량 ID에 액세스합니다."</string>
     <string name="car_permission_label_control_car_doors" msgid="3032058819250195700">"차량 도어 제어"</string>
@@ -102,6 +107,8 @@
     <string name="car_permission_desc_control_car_seats" msgid="2407536601226470563">"차량 시트를 제어합니다."</string>
     <string name="car_permission_label_car_info" msgid="4707513570676492315">"차량의 기본 정보에 액세스"</string>
     <string name="car_permission_desc_car_info" msgid="2118081474543537653">"차량의 기본 정보에 액세스합니다."</string>
+    <string name="car_permission_label_vendor_permission_info" msgid="4471260460536888654">"차량 공급업체 권한 정보에 액세스합니다."</string>
+    <string name="car_permission_desc_vendor_permission_info" msgid="8152113853528488398">"차량 공급업체 권한 정보에 액세스합니다."</string>
     <string name="car_permission_label_car_exterior_lights" msgid="541304469604902110">"차량 외부 조명 상태 읽기"</string>
     <string name="car_permission_desc_car_exterior_lights" msgid="4038037584100849318">"차량의 외부 조명 상태에 액세스합니다."</string>
     <string name="car_permission_label_control_car_exterior_lights" msgid="101357531386232141">"차량 외부 조명 읽기"</string>
@@ -128,5 +135,9 @@
     <string name="car_permission_desc_enroll_trust" msgid="4148649994602185130">"신뢰할 수 있는 기기 등록 허용"</string>
     <string name="car_permission_label_car_test_service" msgid="9159328930558208708">"차량 테스트 모드 제어"</string>
     <string name="car_permission_desc_car_test_service" msgid="7426844534110145843">"차량 테스트 모드 제어"</string>
+    <string name="car_permission_label_control_car_features" msgid="3905791560378888286">"차량 기능 사용 설정 또는 사용 중지"</string>
+    <string name="car_permission_desc_control_car_features" msgid="7646711104530599901">"차량 기능 사용 설정 또는 사용 중지"</string>
+    <string name="car_permission_label_use_car_watchdog" msgid="6973938293170413475">"차량 워치독을 사용합니다."</string>
+    <string name="car_permission_desc_use_car_watchdog" msgid="8244592601805516086">"차량 워치독을 사용합니다."</string>
     <string name="trust_device_default_name" msgid="4213625926070261253">"내 기기"</string>
 </resources>
diff --git a/service/res/values-ky/strings.xml b/service/res/values-ky/strings.xml
index d1504d1..f0ec715 100644
--- a/service/res/values-ky/strings.xml
+++ b/service/res/values-ky/strings.xml
@@ -22,6 +22,8 @@
     <string name="car_permission_desc_camera" msgid="917024932164501426">"Унааңыздын камераларын колдонуу."</string>
     <string name="car_permission_label_energy" msgid="7409144323527821558">"унаанын кубаты тууралуу маалыматты көрүү"</string>
     <string name="car_permission_desc_energy" msgid="3392963810053235407">"Унааңыздын кубаты тууралуу маалыматты көрүү."</string>
+    <string name="car_permission_label_adjust_range_remaining" msgid="839033553999920138">"унаа дагы канча аралыкты басып өтөрүн тууралоо"</string>
+    <string name="car_permission_desc_adjust_range_remaining" msgid="2369321650437370673">"Унаа дагы канча аралыкты басып өтөрүн тууралоо."</string>
     <string name="car_permission_label_hvac" msgid="1499454192558727843">"унаанын жылыткыч жана вентилиция тутумдарын көрүү"</string>
     <string name="car_permission_desc_hvac" msgid="3754229695589774195">"Унааңыздын жылыткыч жана вентилиция тутумдарын көрүү."</string>
     <string name="car_permission_label_mileage" msgid="4661317074631150551">"унаанын километраж маалыматын көрүү"</string>
@@ -62,16 +64,17 @@
     <string name="car_permission_desc_car_ux_restrictions_configuration" msgid="5711926927484813777">"КТ чектөөлөрүн конфигурациялоо"</string>
     <string name="car_permission_label_car_handle_usb_aoap_device" msgid="72783989504378036">"USB түзмөгү менен AOAP режиминде байланышуу"</string>
     <string name="car_permission_desc_car_handle_usb_aoap_device" msgid="273505990971317034">"Колдономого түзмөк менен AOAP режиминде байланышууга мүмкүнчүлүк берет"</string>
+    <string name="car_permission_label_read_car_occupant_awareness_state" msgid="125517953575032758">"Тургундарды көзөмөлдөө тутумунун абалын окууга мүмкүнчүлүк алуу"</string>
+    <string name="car_permission_desc_read_car_occupant_awareness_state" msgid="188865882598414986">"Тургундарды көзөмөлдөө тутумундагы статусту окуп, маалыматты аныктоого мүмкүнчүлүк берет"</string>
+    <string name="car_permission_label_control_car_occupant_awareness_system" msgid="7163330266691094542">"Тургундарды көзөмөлдөө тутумунун диаграммасы"</string>
+    <string name="car_permission_desc_control_car_occupant_awareness_system" msgid="7123482622084531911">"Тургундарды көзөмөлдөө тутумун аныктоо диаграммасын иштетүүнү жана токтотууну көзөмөлдөөгө мүмкүнчүлүк берет"</string>
     <string name="car_permission_label_bind_input_service" msgid="6698489034024273750">"Унаанын киргизүү кызматы"</string>
     <string name="car_permission_desc_bind_input_service" msgid="1670323419931890170">"Киргизүү аракеттерин башкаруу"</string>
     <string name="car_can_bus_failure" msgid="2334035748788283914">"CAN иштебей калды"</string>
     <string name="car_can_bus_failure_desc" msgid="4125516222786484733">"CAN жооп бербей жатат. Башкы шайман блогун сууруп, кайра сайгандан кийин унааны кайра жүргүзүңүз"</string>
-    <!-- no translation found for activity_blocked_text (8088902789540147995) -->
-    <skip />
+    <string name="activity_blocked_text" msgid="5342114426610711378">"Коопсуздугуңузду коргоо максатында, бул иш-аракетти унаа айдап баратканда аткарууга болбойт.\nТоктоп туруп, улантыңыз."</string>
     <string name="exit_button_message" msgid="8554690915924055685">"Колдонмонун коопсуз функцияларын иштетүү үчүн <xliff:g id="EXIT_BUTTON">%s</xliff:g> баскычын басыңыз."</string>
     <string name="exit_button" msgid="5829638404777671253">"Артка"</string>
-    <string name="exit_button_close_application" msgid="8824289547809332460">"Колдонмону жабуу"</string>
-    <string name="exit_button_go_back" msgid="3469083862100560326">"Артка"</string>
     <string name="car_permission_label_diag_read" msgid="7248894224877702604">"мүчүлүштүктөрдү аныктоо дайындарын окуу"</string>
     <string name="car_permission_desc_diag_read" msgid="1121426363040966178">"Унаадагы мүчүлүштүктөрдү аныктоо дайындарын окуу."</string>
     <string name="car_permission_label_diag_clear" msgid="4783070510879698157">"мүчүлүштүктөрдү аныктоо дайындарын тазалоо"</string>
@@ -90,6 +93,8 @@
     <string name="car_permission_desc_car_engine_detailed" msgid="1746863362811347700">"Унааңыздын кыймылдаткычы тууралуу толук маалыматты көрүү."</string>
     <string name="car_permission_label_car_energy_ports" msgid="8548990315169219454">"унаанын май куюучу тешигине жана кубаттоо оюкчасына мүмкүнчүлүк алуу"</string>
     <string name="car_permission_desc_car_energy_ports" msgid="7771185999828794949">"Унаанын май куюучу тешигине жана кубаттоо оюкчасына мүмкүнчүлүк алуу."</string>
+    <string name="car_permission_label_control_car_energy_ports" msgid="4375137311026313475">"унаанын май куюучу тешигин жана кубаттоо оюкчасын көзөмөлдөө"</string>
+    <string name="car_permission_desc_control_car_energy_ports" msgid="7364633710492525387">"Унаанын май куюучу тешигин жана кубаттоо оюкчасын көзөмөлдөө."</string>
     <string name="car_permission_label_car_identification" msgid="5896712510164020478">"унаанын идентификаторун көрүү"</string>
     <string name="car_permission_desc_car_identification" msgid="4132040867171275059">"Унаанын идентификаторун көрүү."</string>
     <string name="car_permission_label_control_car_doors" msgid="3032058819250195700">"унаанын эшиктерин көзөмөлдөө"</string>
@@ -102,6 +107,8 @@
     <string name="car_permission_desc_control_car_seats" msgid="2407536601226470563">"Унаадагы орундуктарды көзөмөлдөө."</string>
     <string name="car_permission_label_car_info" msgid="4707513570676492315">"унаанын негизги маалыматына мүмкүнчүлүк алуу"</string>
     <string name="car_permission_desc_car_info" msgid="2118081474543537653">"Унаанын негизги маалыматына мүмкүнчүлүк алуу."</string>
+    <string name="car_permission_label_vendor_permission_info" msgid="4471260460536888654">"унааны сатуучунун уруксатына тиешелүү маалымат"</string>
+    <string name="car_permission_desc_vendor_permission_info" msgid="8152113853528488398">"Унааны сатуучунун уруксатына тиешелүү маалыматты көрүү."</string>
     <string name="car_permission_label_car_exterior_lights" msgid="541304469604902110">"унаанын сыртындагы жарыктарынын абалын көрүү"</string>
     <string name="car_permission_desc_car_exterior_lights" msgid="4038037584100849318">"Унаанын сыртындагы жарыктарынын абалына мүмкүнчүлүк алуу."</string>
     <string name="car_permission_label_control_car_exterior_lights" msgid="101357531386232141">"унаанын сыртындагы жарыктарын көзөмөлдөө"</string>
@@ -128,5 +135,9 @@
     <string name="car_permission_desc_enroll_trust" msgid="4148649994602185130">"Ишенимдүү түзмөктү каттоого уруксат берүү"</string>
     <string name="car_permission_label_car_test_service" msgid="9159328930558208708">"Унаанын сыноо режимин көзөмөлдөө"</string>
     <string name="car_permission_desc_car_test_service" msgid="7426844534110145843">"Унаанын сыноо режимин көзөмөлдөө"</string>
+    <string name="car_permission_label_control_car_features" msgid="3905791560378888286">"Унаанын функцияларын иштетүү же өчүрүү"</string>
+    <string name="car_permission_desc_control_car_features" msgid="7646711104530599901">"Унаанын функцияларын иштетүү же өчүрүү."</string>
+    <string name="car_permission_label_use_car_watchdog" msgid="6973938293170413475">"унаанын кароолун колдонуу"</string>
+    <string name="car_permission_desc_use_car_watchdog" msgid="8244592601805516086">"Унаанын кароолун колдонуу."</string>
     <string name="trust_device_default_name" msgid="4213625926070261253">"Түзмөгүм"</string>
 </resources>
diff --git a/service/res/values-lo/strings.xml b/service/res/values-lo/strings.xml
index 46f4e2e..e7ebb33 100644
--- a/service/res/values-lo/strings.xml
+++ b/service/res/values-lo/strings.xml
@@ -22,6 +22,8 @@
     <string name="car_permission_desc_camera" msgid="917024932164501426">"ເຂົ້າເຖິງກ້ອງຂອງລົດທ່ານ."</string>
     <string name="car_permission_label_energy" msgid="7409144323527821558">"ເຂົ້າເຖິງຂໍ້ມູນພະລັງງານຂອງລົດ"</string>
     <string name="car_permission_desc_energy" msgid="3392963810053235407">"ເຂົ້າເຖິງຂໍ້ມູນພະລັງງານຂອງລົດທ່ານ."</string>
+    <string name="car_permission_label_adjust_range_remaining" msgid="839033553999920138">"ປັບໄລຍະແລ່ນທີ່ເຫຼືອຢູ່ຂອງລົດ"</string>
+    <string name="car_permission_desc_adjust_range_remaining" msgid="2369321650437370673">"ປັບຄ່າໄລຍະແລ່ນທີ່ເຫຼືອຢູ່ຂອງລົດ"</string>
     <string name="car_permission_label_hvac" msgid="1499454192558727843">"ເຂົ້າເຖິງ HVAC ຂອງລົດ"</string>
     <string name="car_permission_desc_hvac" msgid="3754229695589774195">"ເຂົ້າເຖິງ HVAC ຂອງລົດທ່ານ."</string>
     <string name="car_permission_label_mileage" msgid="4661317074631150551">"ເຂົ້າເຖິງຂໍ້ມູນໄລຍະໄມລ໌ຂອງລົດ"</string>
@@ -62,16 +64,17 @@
     <string name="car_permission_desc_car_ux_restrictions_configuration" msgid="5711926927484813777">"ກຳນົດຄ່າຂໍ້ຈຳກັດ UX"</string>
     <string name="car_permission_label_car_handle_usb_aoap_device" msgid="72783989504378036">"ສື່ສານກັບອຸປະກອນ USB ໃນໂໝດ AOAP"</string>
     <string name="car_permission_desc_car_handle_usb_aoap_device" msgid="273505990971317034">"ອະນຸຍາດໃຫ້ແອັບສື່ສານກັບອຸປະກອນໃນໂໝດ AOAP"</string>
+    <string name="car_permission_label_read_car_occupant_awareness_state" msgid="125517953575032758">"ສິດເຂົ້າເຖິງການອ່ານຂອງລະບົບການຮັບຮູ້ວ່າມີຄົນຢູ່"</string>
+    <string name="car_permission_desc_read_car_occupant_awareness_state" msgid="188865882598414986">"ອະນຸຍາດການອ່ານຂໍ້ມູນສະຖານະ ແລະ ການກວດຫາສຳລັບລະບົບການຮັບຮູ້ວ່າມີຄົນຢູ່"</string>
+    <string name="car_permission_label_control_car_occupant_awareness_system" msgid="7163330266691094542">"ຄວບຄຸມກຣາຟຂອງລະບົບການຮັບຮູ້ວ່າມີຄົນຢູ່"</string>
+    <string name="car_permission_desc_control_car_occupant_awareness_system" msgid="7123482622084531911">"ອະນຸຍາດການຄວບຄຸມການເລີ່ມ ແລະ ການຢຸດສຳລັບກຣາຟການກວດຫາຂອງລະບົບການຮັບຮູ້ວ່າມີຄົນຢູ່"</string>
     <string name="car_permission_label_bind_input_service" msgid="6698489034024273750">"ການບໍລິການປ້ອນຂໍ້ມູນຂອງລົດ"</string>
     <string name="car_permission_desc_bind_input_service" msgid="1670323419931890170">"ຈັດການເຫດການປ້ອນຂໍ້ມູນ"</string>
     <string name="car_can_bus_failure" msgid="2334035748788283914">"CAN bus ບໍ່ສຳເລັດ"</string>
     <string name="car_can_bus_failure_desc" msgid="4125516222786484733">"CAN bus ບໍ່ຕອບສະໜອງ. ຖອດປລັກກ່ອງເຄື່ອງຫຼິ້ນວິທະຍຸ (Headunit) ແລ້ວສຽບເຂົ້າຄືນໃໝ່ ແລະ ຣິສະຕາດລົດ"</string>
-    <!-- no translation found for activity_blocked_text (8088902789540147995) -->
-    <skip />
+    <string name="activity_blocked_text" msgid="5342114426610711378">"ເພື່ອຄວາມປອດໄພຂອງທ່ານ, ກິດຈະກຳນີ້ບໍ່ມີໃຫ້ນໍາໃຊ້ໃນຂະນະທີ່ທ່ານກຳລັງຂັບລົດຢູ່. \nເພື່ອສືບຕໍ່, ກະລຸນາລໍຖ້າຈົນກວ່າວ່າທ່ານຈອດລົດ."</string>
     <string name="exit_button_message" msgid="8554690915924055685">"ເພື່ອເລີ່ມຕົ້ນຄືນໃໝ່ດ້ວຍຄຸນສົມບັດແອັບທີ່ປອດໄພ,​ ກະລຸນາເລືອກ <xliff:g id="EXIT_BUTTON">%s</xliff:g>."</string>
     <string name="exit_button" msgid="5829638404777671253">"ກັບຄືນ"</string>
-    <string name="exit_button_close_application" msgid="8824289547809332460">"ປິດແອັບ"</string>
-    <string name="exit_button_go_back" msgid="3469083862100560326">"ກັບຄືນ"</string>
     <string name="car_permission_label_diag_read" msgid="7248894224877702604">"ອ່ານຂໍ້ມູນການວິເຄາະ"</string>
     <string name="car_permission_desc_diag_read" msgid="1121426363040966178">"ອ່ານຂໍ້ມູນການວິເຄາະຈາກລົດ."</string>
     <string name="car_permission_label_diag_clear" msgid="4783070510879698157">"ລຶບລ້າງຂໍ້ມູນການວິເຄາະ"</string>
@@ -90,6 +93,8 @@
     <string name="car_permission_desc_car_engine_detailed" msgid="1746863362811347700">"ເຂົ້າເຖິງຂໍ້ມູນເຄື່ອງຈັກລະອຽດຂອງລົດທ່ານ."</string>
     <string name="car_permission_label_car_energy_ports" msgid="8548990315169219454">"ເຂົ້າເຖິງຂໍ້ມູນຝານໍ້າມັນ ແລະ ຮູສາກໄຟຂອງລົດ"</string>
     <string name="car_permission_desc_car_energy_ports" msgid="7771185999828794949">"ເຂົ້າເຖິງຂໍ້ມູນຝານໍ້າມັນ ແລະ ຮູສາກໄຟຂອງລົດ."</string>
+    <string name="car_permission_label_control_car_energy_ports" msgid="4375137311026313475">"ຄວບຄຸມຂໍ້ມູນຝານໍ້າມັນ ແລະ ຮູສາກຂອງລົດ"</string>
+    <string name="car_permission_desc_control_car_energy_ports" msgid="7364633710492525387">"ຄວບຄຸມຂໍ້ມູນຝານໍ້າມັນ ແລະ ຮູສາກຂອງລົດ."</string>
     <string name="car_permission_label_car_identification" msgid="5896712510164020478">"ອ່ານການລະບຸຕົວລົດ"</string>
     <string name="car_permission_desc_car_identification" msgid="4132040867171275059">"ເຂົ້າເຖິງການລະບຸຕົວລົດ."</string>
     <string name="car_permission_label_control_car_doors" msgid="3032058819250195700">"ຄວບຄຸມປະຕູລົດ"</string>
@@ -102,6 +107,8 @@
     <string name="car_permission_desc_control_car_seats" msgid="2407536601226470563">"ຄວບຄຸມບ່ອນນັ່ງໃນລົດ."</string>
     <string name="car_permission_label_car_info" msgid="4707513570676492315">"ເຂົ້າເຖິງຂໍ້ມູນພື້ນຖານຂອງລົດ"</string>
     <string name="car_permission_desc_car_info" msgid="2118081474543537653">"ເຂົ້າເຖິງຂໍ້ມູນພື້ນຖານຂອງລົດ."</string>
+    <string name="car_permission_label_vendor_permission_info" msgid="4471260460536888654">"ເຂົ້າເຖິງຂໍ້ມູນການອະນຸຍາດຂອງຜູ້ຂາຍລົດ"</string>
+    <string name="car_permission_desc_vendor_permission_info" msgid="8152113853528488398">"ເຂົ້າເຖິງຂໍ້ມູນການອະນຸຍາດຂອງຜູ້ຂາຍລົດ."</string>
     <string name="car_permission_label_car_exterior_lights" msgid="541304469604902110">"ອ່ານສະຖານະໄຟພາຍນອກຂອງລົດ"</string>
     <string name="car_permission_desc_car_exterior_lights" msgid="4038037584100849318">"ເຂົ້າເຖິງສະຖານະໄພພາຍນອກຂອງລົດ."</string>
     <string name="car_permission_label_control_car_exterior_lights" msgid="101357531386232141">"ອ່ານສະຖານໄຟພາຍນອກລົດ"</string>
@@ -128,5 +135,9 @@
     <string name="car_permission_desc_enroll_trust" msgid="4148649994602185130">"ອະນຸຍາດການລົງທະບຽນອຸປະກອນທີ່ເຊື່ອຖືໄດ້"</string>
     <string name="car_permission_label_car_test_service" msgid="9159328930558208708">"ຄວບຄຸມໂໝດທົດສອບຂອງລົດ"</string>
     <string name="car_permission_desc_car_test_service" msgid="7426844534110145843">"ຄວບຄຸມໂໝດທົດສອບຂອງລົດ"</string>
+    <string name="car_permission_label_control_car_features" msgid="3905791560378888286">"ເປີດນຳໃຊ້ ຫຼື ປິດການນຳໃຊ້ຄຸນສົມບັດຂອງລົດ"</string>
+    <string name="car_permission_desc_control_car_features" msgid="7646711104530599901">"ເປີດນຳໃຊ້ ຫຼື ປິດການນຳໃຊ້ຄຸນສົມບັດຂອງລົດ."</string>
+    <string name="car_permission_label_use_car_watchdog" msgid="6973938293170413475">"ໃຊ້ Watchdog ໃນລົດ"</string>
+    <string name="car_permission_desc_use_car_watchdog" msgid="8244592601805516086">"ໃຊ້ Watchdog ໃນລົດ."</string>
     <string name="trust_device_default_name" msgid="4213625926070261253">"ອຸປະກອນຂອງຂ້ອຍ"</string>
 </resources>
diff --git a/service/res/values-lt/strings.xml b/service/res/values-lt/strings.xml
index a28cd9d..7dac0d2 100644
--- a/service/res/values-lt/strings.xml
+++ b/service/res/values-lt/strings.xml
@@ -22,6 +22,8 @@
     <string name="car_permission_desc_camera" msgid="917024932164501426">"Pasiekti automobilio fotoaparatą (-us)."</string>
     <string name="car_permission_label_energy" msgid="7409144323527821558">"pasiekti automobilio energijos informaciją"</string>
     <string name="car_permission_desc_energy" msgid="3392963810053235407">"Pasiekti automobilio energijos informaciją."</string>
+    <string name="car_permission_label_adjust_range_remaining" msgid="839033553999920138">"koreguoti automobilio likusį atstumą"</string>
+    <string name="car_permission_desc_adjust_range_remaining" msgid="2369321650437370673">"Koreguokite automobilio likusio atstumo vertę."</string>
     <string name="car_permission_label_hvac" msgid="1499454192558727843">"pasiekti automobilio HVAC"</string>
     <string name="car_permission_desc_hvac" msgid="3754229695589774195">"Pasiekti automobilio HVAC."</string>
     <string name="car_permission_label_mileage" msgid="4661317074631150551">"pasiekti automobilio ridos informaciją"</string>
@@ -62,15 +64,17 @@
     <string name="car_permission_desc_car_ux_restrictions_configuration" msgid="5711926927484813777">"Konfigūruoti NP apribojimus"</string>
     <string name="car_permission_label_car_handle_usb_aoap_device" msgid="72783989504378036">"Užmegzti ryšį su USB įrenginiu AOAP režimu"</string>
     <string name="car_permission_desc_car_handle_usb_aoap_device" msgid="273505990971317034">"Programai leidžiama užmegzti ryšį su įrenginiu AOAP režimu"</string>
+    <string name="car_permission_label_read_car_occupant_awareness_state" msgid="125517953575032758">"Keleivių stebėjimo sistemos skaitymo prieiga"</string>
+    <string name="car_permission_desc_read_car_occupant_awareness_state" msgid="188865882598414986">"Leidžiama skaityti duomenis apie keleivių stebėjimo sistemos skaitymo būseną ir aptikimą"</string>
+    <string name="car_permission_label_control_car_occupant_awareness_system" msgid="7163330266691094542">"Valdyti keleivių stebėjimo sistemos diagramą"</string>
+    <string name="car_permission_desc_control_car_occupant_awareness_system" msgid="7123482622084531911">"Leidžiama įjungti ir sustabdyti keleivių stebėjimo sistemos aptikimo diagramą"</string>
     <string name="car_permission_label_bind_input_service" msgid="6698489034024273750">"Automobilio įvesties paslauga"</string>
     <string name="car_permission_desc_bind_input_service" msgid="1670323419931890170">"Apdoroti įvesties įvykius."</string>
     <string name="car_can_bus_failure" msgid="2334035748788283914">"CAN BUS klaida"</string>
     <string name="car_can_bus_failure_desc" msgid="4125516222786484733">"CAN BUS nereaguoja. Atjunkite ir vėl prijunkite pagrindinio įtaiso dėžutę."</string>
-    <string name="activity_blocked_text" msgid="8088902789540147995">"Negalite naudoti šios funkcijos vairuodami"</string>
+    <string name="activity_blocked_text" msgid="5342114426610711378">"Siekiant užtikrinti jūsų saugą, ši veikla nepasiekiama vairuojant.\nJei norite tęsti, palaukite, kol sustosite."</string>
     <string name="exit_button_message" msgid="8554690915924055685">"Jei norite pradėti iš naujo naudodami saugias programos funkcijas, pasirinkite <xliff:g id="EXIT_BUTTON">%s</xliff:g>."</string>
     <string name="exit_button" msgid="5829638404777671253">"Atgal"</string>
-    <string name="exit_button_close_application" msgid="8824289547809332460">"Uždaryti programą"</string>
-    <string name="exit_button_go_back" msgid="3469083862100560326">"Atgal"</string>
     <string name="car_permission_label_diag_read" msgid="7248894224877702604">"nuskaityti diagnostikos duomenis"</string>
     <string name="car_permission_desc_diag_read" msgid="1121426363040966178">"Nuskaityti automobilio diagnostikos duomenis."</string>
     <string name="car_permission_label_diag_clear" msgid="4783070510879698157">"išvalyti diagnostikos duomenis"</string>
@@ -89,6 +93,8 @@
     <string name="car_permission_desc_car_engine_detailed" msgid="1746863362811347700">"Pasiekti išsamią automobilio variklio informaciją."</string>
     <string name="car_permission_label_car_energy_ports" msgid="8548990315169219454">"pasiekti automobilio degalų bako dureles ir įkrovimo prievadą"</string>
     <string name="car_permission_desc_car_energy_ports" msgid="7771185999828794949">"Pasiekti automobilio degalų bako dureles ir įkrovimo prievadą."</string>
+    <string name="car_permission_label_control_car_energy_ports" msgid="4375137311026313475">"valdyti automobilio degalų bako dureles ir įkrovimo prievadą"</string>
+    <string name="car_permission_desc_control_car_energy_ports" msgid="7364633710492525387">"Valdyti automobilio degalų bako dureles ir įkrovimo prievadą."</string>
     <string name="car_permission_label_car_identification" msgid="5896712510164020478">"nuskaityti automobilio identifikavimo duomenis"</string>
     <string name="car_permission_desc_car_identification" msgid="4132040867171275059">"Pasiekti automobilio identifikavimo duomenis."</string>
     <string name="car_permission_label_control_car_doors" msgid="3032058819250195700">"valdyti automobilio dureles"</string>
@@ -101,6 +107,8 @@
     <string name="car_permission_desc_control_car_seats" msgid="2407536601226470563">"Valdyti automobilio sėdynes."</string>
     <string name="car_permission_label_car_info" msgid="4707513570676492315">"pasiekti pagrindinę automobilio informaciją"</string>
     <string name="car_permission_desc_car_info" msgid="2118081474543537653">"Pasiekti pagrindinę automobilio informaciją."</string>
+    <string name="car_permission_label_vendor_permission_info" msgid="4471260460536888654">"pasiekti automobilio paslaugų teikėjo leidimo informaciją"</string>
+    <string name="car_permission_desc_vendor_permission_info" msgid="8152113853528488398">"Pasiekti automobilio paslaugų teikėjo leidimo informaciją."</string>
     <string name="car_permission_label_car_exterior_lights" msgid="541304469604902110">"nuskaityti automobilio išorinių žibintų būseną"</string>
     <string name="car_permission_desc_car_exterior_lights" msgid="4038037584100849318">"Pasiekti automobilio išorinių žibintų būseną."</string>
     <string name="car_permission_label_control_car_exterior_lights" msgid="101357531386232141">"nuskaityti automobilio išorinių žibintų būseną"</string>
@@ -127,5 +135,9 @@
     <string name="car_permission_desc_enroll_trust" msgid="4148649994602185130">"Leisti užregistruoti patikimą įrenginį"</string>
     <string name="car_permission_label_car_test_service" msgid="9159328930558208708">"Valdyti automobilio bandomąjį režimą"</string>
     <string name="car_permission_desc_car_test_service" msgid="7426844534110145843">"Valdyti automobilio bandomąjį režimą"</string>
+    <string name="car_permission_label_control_car_features" msgid="3905791560378888286">"Įgalinti arba išjungti automobilio funkcijas"</string>
+    <string name="car_permission_desc_control_car_features" msgid="7646711104530599901">"Įgalinkite arba išjunkite automobilio funkcijas."</string>
+    <string name="car_permission_label_use_car_watchdog" msgid="6973938293170413475">"naudoti automobilio apsauginį laikmatį"</string>
+    <string name="car_permission_desc_use_car_watchdog" msgid="8244592601805516086">"Naudoti automobilio apsauginį laikmatį."</string>
     <string name="trust_device_default_name" msgid="4213625926070261253">"Mano įrenginys"</string>
 </resources>
diff --git a/service/res/values-lv/strings.xml b/service/res/values-lv/strings.xml
index e7c98c4..e3a8330 100644
--- a/service/res/values-lv/strings.xml
+++ b/service/res/values-lv/strings.xml
@@ -22,6 +22,8 @@
     <string name="car_permission_desc_camera" msgid="917024932164501426">"Piekļūt automašīnas kamerai(-ām)."</string>
     <string name="car_permission_label_energy" msgid="7409144323527821558">"piekļūt informācijai par automašīnas degvielas/uzlādes līmeni"</string>
     <string name="car_permission_desc_energy" msgid="3392963810053235407">"Piekļūt informācijai par automašīnas enerģiju."</string>
+    <string name="car_permission_label_adjust_range_remaining" msgid="839033553999920138">"pielāgot automašīnas atlikušo attālumu"</string>
+    <string name="car_permission_desc_adjust_range_remaining" msgid="2369321650437370673">"Pielāgojiet automašīnas atlikušā attāluma vērtību."</string>
     <string name="car_permission_label_hvac" msgid="1499454192558727843">"piekļūt automašīnas gaisa kondicionētājam"</string>
     <string name="car_permission_desc_hvac" msgid="3754229695589774195">"Piekļūt automašīnas gaisa kondicionēšanas sistēmai."</string>
     <string name="car_permission_label_mileage" msgid="4661317074631150551">"piekļūt informācijai par automašīnas nobraukumu"</string>
@@ -62,16 +64,17 @@
     <string name="car_permission_desc_car_ux_restrictions_configuration" msgid="5711926927484813777">"Konfigurēt lietotāja pieredzes ierobežojumus."</string>
     <string name="car_permission_label_car_handle_usb_aoap_device" msgid="72783989504378036">"Sazināties ar USB ierīci AOAP režīmā"</string>
     <string name="car_permission_desc_car_handle_usb_aoap_device" msgid="273505990971317034">"Ļauj lietotnei sazināties ar ierīci AOAP režīmā"</string>
+    <string name="car_permission_label_read_car_occupant_awareness_state" msgid="125517953575032758">"Lasīšanas piekļuve pasažieru uzraudzības sistēmai"</string>
+    <string name="car_permission_desc_read_car_occupant_awareness_state" msgid="188865882598414986">"Ļauj lasīt pasažieru uzraudzības sistēmas statusu un noteikšanas datus."</string>
+    <string name="car_permission_label_control_car_occupant_awareness_system" msgid="7163330266691094542">"Kontrolēt pasažieru uzraudzības sistēmas diagrammu"</string>
+    <string name="car_permission_desc_control_car_occupant_awareness_system" msgid="7123482622084531911">"Ļauj kontrolēt pasažieru uzraudzības sistēmas noteikšanas diagrammas palaišanu un apturēšanu."</string>
     <string name="car_permission_label_bind_input_service" msgid="6698489034024273750">"Automašīnas ievades pakalpojums"</string>
     <string name="car_permission_desc_bind_input_service" msgid="1670323419931890170">"Apstrādāt ievades notikumus."</string>
     <string name="car_can_bus_failure" msgid="2334035748788283914">"Radās atteice datu maģistrālē"</string>
     <string name="car_can_bus_failure_desc" msgid="4125516222786484733">"Datu maģistrāle nereaģē. Atvienojiet un atkal pievienojiet stereosistēmas paneļa kabeli un atkārtoti iedarbiniet automašīnu"</string>
-    <!-- no translation found for activity_blocked_text (8088902789540147995) -->
-    <skip />
+    <string name="activity_blocked_text" msgid="5342114426610711378">"Drošības apsvērumu dēļ šī darbība nav pieejama braukšanas laikā.\nTurpiniet, kad automašīna būs novietota stāvēšanai."</string>
     <string name="exit_button_message" msgid="8554690915924055685">"Lai atsāktu darbu ar drošām lietotnes funkcijām, atlasiet pogu <xliff:g id="EXIT_BUTTON">%s</xliff:g>."</string>
     <string name="exit_button" msgid="5829638404777671253">"Atpakaļ"</string>
-    <string name="exit_button_close_application" msgid="8824289547809332460">"Aizvērt lietotni"</string>
-    <string name="exit_button_go_back" msgid="3469083862100560326">"Atpakaļ"</string>
     <string name="car_permission_label_diag_read" msgid="7248894224877702604">"nolasīt diagnostikas datus"</string>
     <string name="car_permission_desc_diag_read" msgid="1121426363040966178">"Nolasīt diagnostikas datus no automašīnas."</string>
     <string name="car_permission_label_diag_clear" msgid="4783070510879698157">"notīrīt diagnostikas datus"</string>
@@ -90,6 +93,8 @@
     <string name="car_permission_desc_car_engine_detailed" msgid="1746863362811347700">"Piekļūt detalizētai informācijai par automašīnas dzinēju."</string>
     <string name="car_permission_label_car_energy_ports" msgid="8548990315169219454">"piekļūt automašīnas degvielas tvertnes vāciņam un uzlādes pieslēgvietai"</string>
     <string name="car_permission_desc_car_energy_ports" msgid="7771185999828794949">"Piekļūt automašīnas degvielas tvertnes vāciņam un uzlādes pieslēgvietai."</string>
+    <string name="car_permission_label_control_car_energy_ports" msgid="4375137311026313475">"kontrolēt automašīnas degvielas tvertnes vāciņu un uzlādes pieslēgvietu"</string>
+    <string name="car_permission_desc_control_car_energy_ports" msgid="7364633710492525387">"Kontrolēt automašīnas degvielas tvertnes vāciņu un uzlādes pieslēgvietu."</string>
     <string name="car_permission_label_car_identification" msgid="5896712510164020478">"nolasīt automašīnas identifikācijas numuru"</string>
     <string name="car_permission_desc_car_identification" msgid="4132040867171275059">"Piekļūt automašīnas identifikācijas numuram."</string>
     <string name="car_permission_label_control_car_doors" msgid="3032058819250195700">"kontrolēt automašīnas durvis"</string>
@@ -102,6 +107,8 @@
     <string name="car_permission_desc_control_car_seats" msgid="2407536601226470563">"Kontrolēt automašīnas sēdekļus."</string>
     <string name="car_permission_label_car_info" msgid="4707513570676492315">"piekļūt pamatinformācijai par automašīnu"</string>
     <string name="car_permission_desc_car_info" msgid="2118081474543537653">"Piekļūt pamatinformācijai par automašīnu."</string>
+    <string name="car_permission_label_vendor_permission_info" msgid="4471260460536888654">"piekļuve informācijai par automašīnas ražotāja atļaujām"</string>
+    <string name="car_permission_desc_vendor_permission_info" msgid="8152113853528488398">"Piekļūt informācijai par automašīnas ražotāja atļaujām."</string>
     <string name="car_permission_label_car_exterior_lights" msgid="541304469604902110">"nolasīt automašīnas lukturu stāvokli"</string>
     <string name="car_permission_desc_car_exterior_lights" msgid="4038037584100849318">"Piekļūt automašīnas lukturu stāvoklim."</string>
     <string name="car_permission_label_control_car_exterior_lights" msgid="101357531386232141">"nolasīt automašīnas lukturus"</string>
@@ -128,5 +135,9 @@
     <string name="car_permission_desc_enroll_trust" msgid="4148649994602185130">"Atļaut uzticamu ierīču reģistrāciju"</string>
     <string name="car_permission_label_car_test_service" msgid="9159328930558208708">"kontrolēt automašīnas testa režīmu"</string>
     <string name="car_permission_desc_car_test_service" msgid="7426844534110145843">"Kontrolēt automašīnas testa režīmu"</string>
+    <string name="car_permission_label_control_car_features" msgid="3905791560378888286">"Iespējot vai atspējot automašīnas funkcijas"</string>
+    <string name="car_permission_desc_control_car_features" msgid="7646711104530599901">"Iespējojiet vai atspējojiet automašīnas funkcijas."</string>
+    <string name="car_permission_label_use_car_watchdog" msgid="6973938293170413475">"izmantot automašīnas sargierīci"</string>
+    <string name="car_permission_desc_use_car_watchdog" msgid="8244592601805516086">"Izmantot automašīnas sargierīci."</string>
     <string name="trust_device_default_name" msgid="4213625926070261253">"Mana ierīce"</string>
 </resources>
diff --git a/service/res/values-mk/strings.xml b/service/res/values-mk/strings.xml
index 97f02a2..63d0282 100644
--- a/service/res/values-mk/strings.xml
+++ b/service/res/values-mk/strings.xml
@@ -22,6 +22,8 @@
     <string name="car_permission_desc_camera" msgid="917024932164501426">"Пристапува до камерите на автомобилот."</string>
     <string name="car_permission_label_energy" msgid="7409144323527821558">"пристапува до информациите за енергијата на автомобилот"</string>
     <string name="car_permission_desc_energy" msgid="3392963810053235407">"Пристапува до информациите за енергијата на автомобилот."</string>
+    <string name="car_permission_label_adjust_range_remaining" msgid="839033553999920138">"приспособете ја преостанатата вредност на опсегот на автомобилот"</string>
+    <string name="car_permission_desc_adjust_range_remaining" msgid="2369321650437370673">"Приспособете ја преостанатата вредност на опсегот на автомобилот."</string>
     <string name="car_permission_label_hvac" msgid="1499454192558727843">"пристапува до клима-уредот на автомобилот"</string>
     <string name="car_permission_desc_hvac" msgid="3754229695589774195">"Пристапува до клима-уредот на автомобилот."</string>
     <string name="car_permission_label_mileage" msgid="4661317074631150551">"пристапува до информациите за километража на автомобилот"</string>
@@ -62,16 +64,17 @@
     <string name="car_permission_desc_car_ux_restrictions_configuration" msgid="5711926927484813777">"Конфигурирајте ги ограничувањата на корисничкото искуство"</string>
     <string name="car_permission_label_car_handle_usb_aoap_device" msgid="72783989504378036">"Комуницирајте со USB-уред во режим на AOAP"</string>
     <string name="car_permission_desc_car_handle_usb_aoap_device" msgid="273505990971317034">"Дозволува апликацијата да комуницира со уред во режим на AOAP"</string>
+    <string name="car_permission_label_read_car_occupant_awareness_state" msgid="125517953575032758">"Пристап за читање на „Системот за откривање патници“"</string>
+    <string name="car_permission_desc_read_car_occupant_awareness_state" msgid="188865882598414986">"Овозможува читање на статусот и податоците на откривање за „Системот за откривање патници“"</string>
+    <string name="car_permission_label_control_car_occupant_awareness_system" msgid="7163330266691094542">"Го контролира графиконот на „Системот за откривање патници“"</string>
+    <string name="car_permission_desc_control_car_occupant_awareness_system" msgid="7123482622084531911">"Овозможува контролирање на стартувањето и сопирањето на графиконот за откривање на „Системот за откривање патници“"</string>
     <string name="car_permission_label_bind_input_service" msgid="6698489034024273750">"Влезна услуга на автомобилот"</string>
     <string name="car_permission_desc_bind_input_service" msgid="1670323419931890170">"Ракува со влезните настани"</string>
     <string name="car_can_bus_failure" msgid="2334035748788283914">"CAN-магистралата не успеа"</string>
     <string name="car_can_bus_failure_desc" msgid="4125516222786484733">"CAN-магистралата не реагира. Откачете ја и повторно прикачете ја кутијата на главната единица и рестартирајте го автомобилот"</string>
-    <!-- no translation found for activity_blocked_text (8088902789540147995) -->
-    <skip />
+    <string name="activity_blocked_text" msgid="5342114426610711378">"За ваша безбедност, оваа активност не е достапна додека возите.\nЗа да продолжите, почекајте да се паркирате."</string>
     <string name="exit_button_message" msgid="8554690915924055685">"За да започнете одново со безбедносните фунции на апликацијата, изберете <xliff:g id="EXIT_BUTTON">%s</xliff:g>."</string>
     <string name="exit_button" msgid="5829638404777671253">"Назад"</string>
-    <string name="exit_button_close_application" msgid="8824289547809332460">"Затвори апликација"</string>
-    <string name="exit_button_go_back" msgid="3469083862100560326">"Назад"</string>
     <string name="car_permission_label_diag_read" msgid="7248894224877702604">"ги чита дијагностичките податоци"</string>
     <string name="car_permission_desc_diag_read" msgid="1121426363040966178">"Чита дијагностички податоци од автомобилот."</string>
     <string name="car_permission_label_diag_clear" msgid="4783070510879698157">"чисти дијагностички податоци"</string>
@@ -90,6 +93,8 @@
     <string name="car_permission_desc_car_engine_detailed" msgid="1746863362811347700">"Пристапува до деталните информации за моторот на автомобилот."</string>
     <string name="car_permission_label_car_energy_ports" msgid="8548990315169219454">"пристапува до вратата за гориво и портата за полнење на автомобилот"</string>
     <string name="car_permission_desc_car_energy_ports" msgid="7771185999828794949">"Пристапува до вратата за гориво и портата за полнење на автомобилот."</string>
+    <string name="car_permission_label_control_car_energy_ports" msgid="4375137311026313475">"ја контролира вратата за гориво и портата за полнење на автомобилот"</string>
+    <string name="car_permission_desc_control_car_energy_ports" msgid="7364633710492525387">"Ја контролира вратата за гориво и портата за полнење на автомобилот."</string>
     <string name="car_permission_label_car_identification" msgid="5896712510164020478">"ја чита идентификацијата на автомобилот"</string>
     <string name="car_permission_desc_car_identification" msgid="4132040867171275059">"Пристапува до идентификацијата на автомобилот."</string>
     <string name="car_permission_label_control_car_doors" msgid="3032058819250195700">"ги контролира вратите на автомобилот"</string>
@@ -102,6 +107,8 @@
     <string name="car_permission_desc_control_car_seats" msgid="2407536601226470563">"Ги контролира седиштата на автомобилот."</string>
     <string name="car_permission_label_car_info" msgid="4707513570676492315">"пристапува до основните информации за автомобилот"</string>
     <string name="car_permission_desc_car_info" msgid="2118081474543537653">"Пристапува до основните информации за автомобилот."</string>
+    <string name="car_permission_label_vendor_permission_info" msgid="4471260460536888654">"пристап до информациите за дозволи на продавачот"</string>
+    <string name="car_permission_desc_vendor_permission_info" msgid="8152113853528488398">"Пристап до информациите за дозволи на продавачот."</string>
     <string name="car_permission_label_car_exterior_lights" msgid="541304469604902110">"ја чита состојбата на надворешните светла на автомобилот"</string>
     <string name="car_permission_desc_car_exterior_lights" msgid="4038037584100849318">"Пристапува до состојбата на надворешните светла на автомобилот."</string>
     <string name="car_permission_label_control_car_exterior_lights" msgid="101357531386232141">"ги чита надворешните светла на автомобилот"</string>
@@ -128,5 +135,9 @@
     <string name="car_permission_desc_enroll_trust" msgid="4148649994602185130">"Дозволете регистрација на доверлив уред"</string>
     <string name="car_permission_label_car_test_service" msgid="9159328930558208708">"Контролирање на режимот за тестирање на автомобилот"</string>
     <string name="car_permission_desc_car_test_service" msgid="7426844534110145843">"Контролирање на режимот за тестирање на автомобилот"</string>
+    <string name="car_permission_label_control_car_features" msgid="3905791560378888286">"Овозможување или оневозможување функции на автомобилот"</string>
+    <string name="car_permission_desc_control_car_features" msgid="7646711104530599901">"Овозможување или оневозможување функции на автомобилот."</string>
+    <string name="car_permission_label_use_car_watchdog" msgid="6973938293170413475">"ја користат „Заштитник за автомобилот“"</string>
+    <string name="car_permission_desc_use_car_watchdog" msgid="8244592601805516086">"Ја користат „Заштитник за автомобилот“."</string>
     <string name="trust_device_default_name" msgid="4213625926070261253">"Мојот уред"</string>
 </resources>
diff --git a/service/res/values-ml/strings.xml b/service/res/values-ml/strings.xml
index 48964b4..5646ef2 100644
--- a/service/res/values-ml/strings.xml
+++ b/service/res/values-ml/strings.xml
@@ -22,6 +22,8 @@
     <string name="car_permission_desc_camera" msgid="917024932164501426">"നിങ്ങളുടെ കാറിന്റെ ക്യാമറ(കൾ) ആക്‌സസ് ചെയ്യുക."</string>
     <string name="car_permission_label_energy" msgid="7409144323527821558">"കാറിൻ്റെ എനർജി വിവരങ്ങൾ ആക്‌സസ് ചെയ്യുക"</string>
     <string name="car_permission_desc_energy" msgid="3392963810053235407">"നിങ്ങളുടെ കാറിന്റെ എനർജി വിവരങ്ങൾ ആക്‌സസ് ചെയ്യുക."</string>
+    <string name="car_permission_label_adjust_range_remaining" msgid="839033553999920138">"കാറിൽ ശേഷിക്കുന്ന ഇന്ധനം ക്രമീകരിക്കുക"</string>
+    <string name="car_permission_desc_adjust_range_remaining" msgid="2369321650437370673">"കാറിൽ ശേഷിക്കുന്ന ഇന്ധനത്തിന്റെ മൂല്യം ക്രമീകരിക്കുക."</string>
     <string name="car_permission_label_hvac" msgid="1499454192558727843">"കാറിൻ്റെ hvac ആക്‌സസ് ചെയ്യുക"</string>
     <string name="car_permission_desc_hvac" msgid="3754229695589774195">"നിങ്ങളുടെ കാറിന്റെ hvac ആക്‌സസ് ചെയ്യുക."</string>
     <string name="car_permission_label_mileage" msgid="4661317074631150551">"കാറിൻ്റെ മൈലേജ് വിവരങ്ങൾ ആക്‌സസ് ചെയ്യുക"</string>
@@ -62,16 +64,17 @@
     <string name="car_permission_desc_car_ux_restrictions_configuration" msgid="5711926927484813777">"UX നിയന്ത്രണങ്ങൾ കോൺഫിഗർ ചെയ്യുക"</string>
     <string name="car_permission_label_car_handle_usb_aoap_device" msgid="72783989504378036">"AOAP മോഡിൽ USB ഉപകരണം ഉപയോഗിച്ച് ആശയവിനിമയം നടത്തുക"</string>
     <string name="car_permission_desc_car_handle_usb_aoap_device" msgid="273505990971317034">"AOAP മോഡിലുള്ള ഉപകരണവുമായി ബന്ധപ്പെടാൻ ആപ്പിനെ അനുവദിക്കുന്നു"</string>
+    <string name="car_permission_label_read_car_occupant_awareness_state" msgid="125517953575032758">"ഒക്യുപന്റ് അവയർനെസ് സിസ്റ്റം വായനാ ആക്‌സസ്"</string>
+    <string name="car_permission_desc_read_car_occupant_awareness_state" msgid="188865882598414986">"ഒക്യുപന്റ് അവയർനെസ് സിസ്റ്റത്തിനുള്ള വായനാ നിലയും കണ്ടെത്തൽ ഡാറ്റയും അനുവദിക്കുന്നു"</string>
+    <string name="car_permission_label_control_car_occupant_awareness_system" msgid="7163330266691094542">"ഒക്യുപന്റ് അവയർനെസ് സിസ്റ്റം ഗ്രാഫ് നിയന്ത്രിക്കുക"</string>
+    <string name="car_permission_desc_control_car_occupant_awareness_system" msgid="7123482622084531911">"ഒക്യുപന്റ് അവയർനെസ് സിസ്റ്റം കണ്ടെത്തൽ ഗ്രാഫിന്റെ ആരംഭവും നിർത്തലും നിയന്ത്രിക്കാൻ അനുവദിക്കുന്നു"</string>
     <string name="car_permission_label_bind_input_service" msgid="6698489034024273750">"കാറിന്റെ ഇൻപുട്ട് സേവനം"</string>
     <string name="car_permission_desc_bind_input_service" msgid="1670323419931890170">"ഇൻപുട്ട് ഇവന്റുകൾ കൈകാര്യം ചെയ്യുക"</string>
     <string name="car_can_bus_failure" msgid="2334035748788283914">"CAN ബസ് പരാജയപ്പെട്ടു"</string>
     <string name="car_can_bus_failure_desc" msgid="4125516222786484733">"CAN ബസ് പ്രതികരിക്കുന്നില്ല. ഹെഡ്‌യൂണിറ്റ് ബോക്‌സ്, അൺപ്ലഗ് ചെയ്‌ത്, വീണ്ടും പ്ലഗ് ചെയ്‌ത്, കാർ റീസ്‌റ്റാർട്ട് ചെയ്യുക"</string>
-    <!-- no translation found for activity_blocked_text (8088902789540147995) -->
-    <skip />
+    <string name="activity_blocked_text" msgid="5342114426610711378">"നിങ്ങളുടെ സുരക്ഷയ്ക്ക്, ഡ്രൈവ് ചെയ്യുമ്പോൾ ഈ ആക്റ്റിവിറ്റി ലഭ്യമല്ല.\nതുടരാൻ, നിങ്ങൾ പാർക്ക് ചെയ്യുന്നതുവരെ കാത്തിരിക്കുക."</string>
     <string name="exit_button_message" msgid="8554690915924055685">"സുരക്ഷിതമായ ആപ്പ് ഫീച്ചറുകൾ ഉപയോഗിച്ച് പുനരാരംഭിക്കാൻ, <xliff:g id="EXIT_BUTTON">%s</xliff:g> തിരഞ്ഞെടുക്കുക."</string>
     <string name="exit_button" msgid="5829638404777671253">"മടങ്ങുക"</string>
-    <string name="exit_button_close_application" msgid="8824289547809332460">"ആപ്പ് അടയ്‌ക്കുക"</string>
-    <string name="exit_button_go_back" msgid="3469083862100560326">"മടങ്ങുക"</string>
     <string name="car_permission_label_diag_read" msgid="7248894224877702604">"പ്രശ്‌നനിർണ്ണയ ഡാറ്റ വായിക്കുക"</string>
     <string name="car_permission_desc_diag_read" msgid="1121426363040966178">"കാറിൽ നിന്നുള്ള പ്രശ്‌നനിർണ്ണയ ഡാറ്റ വായിക്കുക."</string>
     <string name="car_permission_label_diag_clear" msgid="4783070510879698157">"പ്രശ്‌നനിർണ്ണയ ഡാറ്റ മായ്‌ക്കുക"</string>
@@ -90,6 +93,8 @@
     <string name="car_permission_desc_car_engine_detailed" msgid="1746863362811347700">"നിങ്ങളുടെ കാറിൻ്റെ വിശദമായ എഞ്ചിൻ വിവരങ്ങൾ ആക്‌സസ് ചെയ്യുക."</string>
     <string name="car_permission_label_car_energy_ports" msgid="8548990315169219454">"കാറിൻ്റെ ഇന്ധന വാതിലും ചാർജ് പോർട്ടും ആക്‌സസ് ചെയ്യുക"</string>
     <string name="car_permission_desc_car_energy_ports" msgid="7771185999828794949">"കാറിൻ്റെ ഇന്ധന വാതിലും ചാർജ് പോർട്ടും ആക്‌സസ് ചെയ്യുക."</string>
+    <string name="car_permission_label_control_car_energy_ports" msgid="4375137311026313475">"കാറിന്റെ ഇന്ധന വാതിലും ചാർജ് പോർട്ടും ആക്‌സസ് ചെയ്യുക"</string>
+    <string name="car_permission_desc_control_car_energy_ports" msgid="7364633710492525387">"കാറിന്റെ ഇന്ധന വാതിലും ചാർജ് പോർട്ടും ആക്‌സസ് ചെയ്യുക."</string>
     <string name="car_permission_label_car_identification" msgid="5896712510164020478">"കാറിൻ്റെ ഐഡൻ്റിഫിക്കേഷൻ വായിക്കുക"</string>
     <string name="car_permission_desc_car_identification" msgid="4132040867171275059">"കാറിൻ്റെ ഐഡൻ്റിഫിക്കേഷൻ ആക്‌സസ് ചെയ്യുക."</string>
     <string name="car_permission_label_control_car_doors" msgid="3032058819250195700">"കാറിൻ്റെ ഡോറുകൾ നിയന്ത്രിക്കുക"</string>
@@ -102,6 +107,8 @@
     <string name="car_permission_desc_control_car_seats" msgid="2407536601226470563">"കാറിൻ്റെ സീറ്റുകൾ നിയന്ത്രിക്കുക."</string>
     <string name="car_permission_label_car_info" msgid="4707513570676492315">"കാറിൻ്റെ അടിസ്ഥാന വിവരങ്ങൾ ആക്‌സസ് ചെയ്യുക"</string>
     <string name="car_permission_desc_car_info" msgid="2118081474543537653">"കാറിൻ്റെ അടിസ്ഥാന വിവരങ്ങൾ ആക്‌സസ് ചെയ്യുക."</string>
+    <string name="car_permission_label_vendor_permission_info" msgid="4471260460536888654">"കാറിന്റെ വെൻഡർ അനുമതി വിവരങ്ങൾ ആക്‌സസ് ചെയ്യുക"</string>
+    <string name="car_permission_desc_vendor_permission_info" msgid="8152113853528488398">"കാറിന്റെ വെൻഡർ അനുമതി വിവരങ്ങൾ ആക്‌സസ് ചെയ്യുക."</string>
     <string name="car_permission_label_car_exterior_lights" msgid="541304469604902110">"കാറിൻ്റെ പുറംഭാഗത്തെ ലൈറ്റുകളുടെ നില വായിക്കുക"</string>
     <string name="car_permission_desc_car_exterior_lights" msgid="4038037584100849318">"കാറിൻ്റെ പുറംഭാഗത്തെ ലൈറ്റുകളുടെ നില ആക്‌സസ് ചെയ്യുക."</string>
     <string name="car_permission_label_control_car_exterior_lights" msgid="101357531386232141">"കാറിൻ്റെ പുറംഭാഗത്തെ ലൈറ്റുകളുടെ നില വായിക്കുക"</string>
@@ -128,5 +135,9 @@
     <string name="car_permission_desc_enroll_trust" msgid="4148649994602185130">"വിശ്വസ്‌ത ഉപകരണ എൻറോൾമെന്റ് അനുവദിക്കുക"</string>
     <string name="car_permission_label_car_test_service" msgid="9159328930558208708">"കാറിന്റെ ടെസ്റ്റ് മോഡ് നിയന്ത്രിക്കുക"</string>
     <string name="car_permission_desc_car_test_service" msgid="7426844534110145843">"കാറിന്റെ ടെസ്റ്റ് മോഡ് നിയന്ത്രിക്കുക"</string>
+    <string name="car_permission_label_control_car_features" msgid="3905791560378888286">"കാറിന്റെ ഫീച്ചറുകൾ പ്രവർത്തനക്ഷമമോ പ്രവർത്തനരഹിതമോ ആക്കുക"</string>
+    <string name="car_permission_desc_control_car_features" msgid="7646711104530599901">"കാറിന്റെ ഫീച്ചറുകൾ പ്രവർത്തനക്ഷമമോ പ്രവർത്തനരഹിതമോ ആക്കുക."</string>
+    <string name="car_permission_label_use_car_watchdog" msgid="6973938293170413475">"കാർ പരിശോധനാ സിസ്‌റ്റം ഉപയോഗിക്കുക"</string>
+    <string name="car_permission_desc_use_car_watchdog" msgid="8244592601805516086">"കാർ പരിശോധനാ സിസ്‌റ്റം ഉപയോഗിക്കുക."</string>
     <string name="trust_device_default_name" msgid="4213625926070261253">"എന്റെ ഉപകരണം"</string>
 </resources>
diff --git a/service/res/values-mn/strings.xml b/service/res/values-mn/strings.xml
index 8314370..8028cab 100644
--- a/service/res/values-mn/strings.xml
+++ b/service/res/values-mn/strings.xml
@@ -22,6 +22,8 @@
     <string name="car_permission_desc_camera" msgid="917024932164501426">"Машиныхаа камерт хандана уу."</string>
     <string name="car_permission_label_energy" msgid="7409144323527821558">"машины эрчим хүчний мэдээлэлд хандах"</string>
     <string name="car_permission_desc_energy" msgid="3392963810053235407">"Машиныхаа эрчим хүчний мэдээлэлд хандана уу."</string>
+    <string name="car_permission_label_adjust_range_remaining" msgid="839033553999920138">"автомашины туулах замын үлдсэн утгыг тохируулах"</string>
+    <string name="car_permission_desc_adjust_range_remaining" msgid="2369321650437370673">"Автомашины туулах замын үлдсэн утгыг тохируулна."</string>
     <string name="car_permission_label_hvac" msgid="1499454192558727843">"машины халаалт, агааржуулалт болон aгаар цэвэршүүлэгчид хандах"</string>
     <string name="car_permission_desc_hvac" msgid="3754229695589774195">"Машиныхаа халаалт, агааржуулалт болон aгаар цэвэршүүлэгчид хандана уу."</string>
     <string name="car_permission_label_mileage" msgid="4661317074631150551">"машины милийн мэдээлэлд хандах"</string>
@@ -62,16 +64,17 @@
     <string name="car_permission_desc_car_ux_restrictions_configuration" msgid="5711926927484813777">"UX хязгаарлалтыг тохируулах"</string>
     <string name="car_permission_label_car_handle_usb_aoap_device" msgid="72783989504378036">"AOAP горимд байгаа USB төхөөрөмжтэй холбогдох"</string>
     <string name="car_permission_desc_car_handle_usb_aoap_device" msgid="273505990971317034">"Аппыг AOAP горимд байгаа төхөөрөмжтэй холбогдохыг зөвшөөрдөг"</string>
+    <string name="car_permission_label_read_car_occupant_awareness_state" msgid="125517953575032758">"Оршин суугчийн дохионы мэдээллийн системийг уншихад хандах"</string>
+    <string name="car_permission_desc_read_car_occupant_awareness_state" msgid="188865882598414986">"Оршин суугчийн дохионы мэдээллийн системийн статус болон илрүүлэлтийн өгөгдлийг унших боломж олгодог"</string>
+    <string name="car_permission_label_control_car_occupant_awareness_system" msgid="7163330266691094542">"Оршин суугчийн дохионы мэдээллийн системийн графикийг хянах"</string>
+    <string name="car_permission_desc_control_car_occupant_awareness_system" msgid="7123482622084531911">"Оршин суугчийн дохионы мэдээллийн системийн илрүүлэлтийн графикийг эхлүүлэх, зогсоохыг хянах боломж олгодог"</string>
     <string name="car_permission_label_bind_input_service" msgid="6698489034024273750">"Машины оролтын үйлчилгээ"</string>
     <string name="car_permission_desc_bind_input_service" msgid="1670323419931890170">"Оролтын арга хэмжээг боловсруулах"</string>
     <string name="car_can_bus_failure" msgid="2334035748788283914">"CAN bus амжилтгүй болсон"</string>
     <string name="car_can_bus_failure_desc" msgid="4125516222786484733">"CAN bus хариу өгөхгүй байна. Хөгжим тоглуулагчийн хайрцгийг салгаад, дахин залгаж, машиныг дахин эхлүүлнэ үү"</string>
-    <!-- no translation found for activity_blocked_text (8088902789540147995) -->
-    <skip />
+    <string name="activity_blocked_text" msgid="5342114426610711378">"Таны аюулгүй байдлын үүднээс жолоодох явцад энэ үйл ажиллагааг хийх боломжгүй.\nМашинаа зогсоолд байршуулаад үргэлжлүүлнэ үү."</string>
     <string name="exit_button_message" msgid="8554690915924055685">"Аппын аюулгүй онцлогуудтайгаар дахин эхлүүлэхийн тулд <xliff:g id="EXIT_BUTTON">%s</xliff:g>-г сонгоно уу."</string>
     <string name="exit_button" msgid="5829638404777671253">"Буцах"</string>
-    <string name="exit_button_close_application" msgid="8824289547809332460">"Аппыг хаах"</string>
-    <string name="exit_button_go_back" msgid="3469083862100560326">"Буцах"</string>
     <string name="car_permission_label_diag_read" msgid="7248894224877702604">"оношилгооны өгөгдлийг унших"</string>
     <string name="car_permission_desc_diag_read" msgid="1121426363040966178">"Машины оношилгооны өгөгдлийг унших."</string>
     <string name="car_permission_label_diag_clear" msgid="4783070510879698157">"оношилгооны өгөгдлийг устгах"</string>
@@ -90,6 +93,8 @@
     <string name="car_permission_desc_car_engine_detailed" msgid="1746863362811347700">"Таны машины хөдөлгүүрийн дэлгэрэнгүй мэдээлэлд хандах."</string>
     <string name="car_permission_label_car_energy_ports" msgid="8548990315169219454">"машины шатахууны савны таг болон цэнэглэх оролтод хандах"</string>
     <string name="car_permission_desc_car_energy_ports" msgid="7771185999828794949">"Машины шатахууны савны таг болон цэнэглэх оролтод хандах."</string>
+    <string name="car_permission_label_control_car_energy_ports" msgid="4375137311026313475">"машины шатахууны савны таг болон цэнэглэх оролтыг хянах"</string>
+    <string name="car_permission_desc_control_car_energy_ports" msgid="7364633710492525387">"Машины шатахууны савны таг болон цэнэглэх оролтыг хянаарай."</string>
     <string name="car_permission_label_car_identification" msgid="5896712510164020478">"машины тодорхойлолтыг унших"</string>
     <string name="car_permission_desc_car_identification" msgid="4132040867171275059">"Машины тодорхойлолтод хандах."</string>
     <string name="car_permission_label_control_car_doors" msgid="3032058819250195700">"машины хаалгыг хянах"</string>
@@ -102,6 +107,8 @@
     <string name="car_permission_desc_control_car_seats" msgid="2407536601226470563">"Машины суудлыг хянах."</string>
     <string name="car_permission_label_car_info" msgid="4707513570676492315">"машины ерөнхий мэдээлэлд хандах"</string>
     <string name="car_permission_desc_car_info" msgid="2118081474543537653">"Машины ерөнхий мэдээлэлд хандах."</string>
+    <string name="car_permission_label_vendor_permission_info" msgid="4471260460536888654">"машин нийлүүлэгчийн зөвшөөрлийн мэдээлэлд хандах"</string>
+    <string name="car_permission_desc_vendor_permission_info" msgid="8152113853528488398">"Машин нийлүүлэгчийн зөвшөөрлийн мэдээлэлд хандана уу."</string>
     <string name="car_permission_label_car_exterior_lights" msgid="541304469604902110">"машины гадна талын гэрлийн төлөвийг унших"</string>
     <string name="car_permission_desc_car_exterior_lights" msgid="4038037584100849318">"Машины гадна талын гэрлийн төлөвт хандах."</string>
     <string name="car_permission_label_control_car_exterior_lights" msgid="101357531386232141">"машины гадна талын гэрлийн төлөвийг унших"</string>
@@ -128,5 +135,9 @@
     <string name="car_permission_desc_enroll_trust" msgid="4148649994602185130">"Итгэмжлэгдсэн төхөөрөмж бүртгэхийг зөвшөөрөх"</string>
     <string name="car_permission_label_car_test_service" msgid="9159328930558208708">"Машины тест горимыг хянах"</string>
     <string name="car_permission_desc_car_test_service" msgid="7426844534110145843">"Машины тест горимыг хянах"</string>
+    <string name="car_permission_label_control_car_features" msgid="3905791560378888286">"Автомашины онцлогуудыг идэвхжүүлэх эсвэл идэвхгүй болгох"</string>
+    <string name="car_permission_desc_control_car_features" msgid="7646711104530599901">"Автомашины онцлогуудыг идэвхжүүлж эсвэл идэвхгүй болгоно уу."</string>
+    <string name="car_permission_label_use_car_watchdog" msgid="6973938293170413475">"машины watchdog-г ашиглана уу"</string>
+    <string name="car_permission_desc_use_car_watchdog" msgid="8244592601805516086">"Машины watchdog-г ашиглана уу."</string>
     <string name="trust_device_default_name" msgid="4213625926070261253">"Миний төхөөрөмж"</string>
 </resources>
diff --git a/service/res/values-mr/strings.xml b/service/res/values-mr/strings.xml
index 616d2d6..f929253 100644
--- a/service/res/values-mr/strings.xml
+++ b/service/res/values-mr/strings.xml
@@ -22,6 +22,8 @@
     <string name="car_permission_desc_camera" msgid="917024932164501426">"तुमच्या कारचा(चे) कॅमेरा(रे) अ‍ॅक्सेस करा."</string>
     <string name="car_permission_label_energy" msgid="7409144323527821558">"कारच्या ऊर्जेची माहिती अ‍ॅक्सेस करा"</string>
     <string name="car_permission_desc_energy" msgid="3392963810053235407">"तुमच्या कारची ऊर्जा माहिती अ‍ॅक्सेस करा."</string>
+    <string name="car_permission_label_adjust_range_remaining" msgid="839033553999920138">"कारची शिल्लक रेंज अ‍ॅडजस्ट करा"</string>
+    <string name="car_permission_desc_adjust_range_remaining" msgid="2369321650437370673">"कारच्या शिल्लक रेंजचे मूल्य अ‍ॅडजस्ट करा."</string>
     <string name="car_permission_label_hvac" msgid="1499454192558727843">"कारचे hvac अ‍ॅक्सेस करा"</string>
     <string name="car_permission_desc_hvac" msgid="3754229695589774195">"तुमच्या कारचे hvac अ‍ॅक्सेस करा."</string>
     <string name="car_permission_label_mileage" msgid="4661317074631150551">"कारच्या मायलेजची माहिती अ‍ॅक्सेस करा"</string>
@@ -62,16 +64,17 @@
     <string name="car_permission_desc_car_ux_restrictions_configuration" msgid="5711926927484813777">"UX निर्बंध कॉन्फिगर करा"</string>
     <string name="car_permission_label_car_handle_usb_aoap_device" msgid="72783989504378036">"USB डिव्हाइसशी AOAP मोडमध्ये कनेक्ट करा"</string>
     <string name="car_permission_desc_car_handle_usb_aoap_device" msgid="273505990971317034">"अ‍ॅपला डिव्हाइसशी AOAP मोडमध्ये कनेक्ट करण्याची अनुमती देते"</string>
+    <string name="car_permission_label_read_car_occupant_awareness_state" msgid="125517953575032758">"ऑक्युपंट अवेअरनेस सिस्टम वाचन अ‍ॅक्सेस"</string>
+    <string name="car_permission_desc_read_car_occupant_awareness_state" msgid="188865882598414986">"ऑक्युपंट अवेअरनेस सिस्टम यासाठी वाचन स्थिती आणि डिटेक्‍शन डेटाला अनुमती देते"</string>
+    <string name="car_permission_label_control_car_occupant_awareness_system" msgid="7163330266691094542">"ऑक्युपंट अवेअरनेस सिस्टम आलेख नियंत्रित करा"</string>
+    <string name="car_permission_desc_control_car_occupant_awareness_system" msgid="7123482622084531911">"ऑक्युपंट अवेअरनेस सिस्टम डिटेक्शन आलेख सुरू करणे आणि थांबवणे नियंत्रित करण्यासाठी अनुमती देते"</string>
     <string name="car_permission_label_bind_input_service" msgid="6698489034024273750">"कार इनपुट सेवा"</string>
     <string name="car_permission_desc_bind_input_service" msgid="1670323419931890170">"इनपुट इव्हेंट हाताळा"</string>
     <string name="car_can_bus_failure" msgid="2334035748788283914">"CAN बस अयशस्वी"</string>
     <string name="car_can_bus_failure_desc" msgid="4125516222786484733">"CAN बस प्रतिसाद देत नाही. हेडयुनिट बॉक्स अनप्लग करून पुन्हा प्लग करा आणि कार रीस्टार्ट करा"</string>
-    <!-- no translation found for activity_blocked_text (8088902789540147995) -->
-    <skip />
+    <string name="activity_blocked_text" msgid="5342114426610711378">"तुमच्या सुरक्षेसाठी, ड्रायव्हिंग करत असताना ही अ‍ॅक्टिव्हिटी उपलब्ध नाही.\nसुरू ठेवण्यासाठी, पार्क करेपर्यंत वाट पहा."</string>
     <string name="exit_button_message" msgid="8554690915924055685">"सुरक्षित अ‍ॅप वैशिष्ट्यांसोबत पुन्हा सुरुवात करण्यासाठी, <xliff:g id="EXIT_BUTTON">%s</xliff:g> निवडा."</string>
     <string name="exit_button" msgid="5829638404777671253">"मागे जा"</string>
-    <string name="exit_button_close_application" msgid="8824289547809332460">"अ‍ॅप बंद करा"</string>
-    <string name="exit_button_go_back" msgid="3469083862100560326">"मागे जा"</string>
     <string name="car_permission_label_diag_read" msgid="7248894224877702604">"निदान डेटा वाचा"</string>
     <string name="car_permission_desc_diag_read" msgid="1121426363040966178">"कारचा निदान डेटा वाचा."</string>
     <string name="car_permission_label_diag_clear" msgid="4783070510879698157">"निदान डेटा साफ करा"</string>
@@ -90,6 +93,8 @@
     <string name="car_permission_desc_car_engine_detailed" msgid="1746863362811347700">"तुमच्या कारच्या इंजिनची तपशीलवार माहिती अ‍ॅक्सेस करा."</string>
     <string name="car_permission_label_car_energy_ports" msgid="8548990315169219454">"कारचा इंधन भरण्याचा दरवाजा आणि चार्ज पोर्ट अ‍ॅक्सेस करा"</string>
     <string name="car_permission_desc_car_energy_ports" msgid="7771185999828794949">"कारचा इंधन भरण्याचा दरवाजा आणि चार्ज पोर्ट अ‍ॅक्सेस करा."</string>
+    <string name="car_permission_label_control_car_energy_ports" msgid="4375137311026313475">"कारचा इंधन भरण्याचा दरवाजा आणि चार्ज पोर्ट नियंत्रित करा"</string>
+    <string name="car_permission_desc_control_car_energy_ports" msgid="7364633710492525387">"कारचा इंधन भरण्याचा दरवाजा आणि चार्ज पोर्ट नियंत्रित करा"</string>
     <string name="car_permission_label_car_identification" msgid="5896712510164020478">"कारची ओळख वाचा"</string>
     <string name="car_permission_desc_car_identification" msgid="4132040867171275059">"कारची ओळख अ‍ॅक्सेस करा."</string>
     <string name="car_permission_label_control_car_doors" msgid="3032058819250195700">"कारचे दरवाजे नियंत्रित करा"</string>
@@ -102,6 +107,8 @@
     <string name="car_permission_desc_control_car_seats" msgid="2407536601226470563">"कारची आसने नियंत्रित करा."</string>
     <string name="car_permission_label_car_info" msgid="4707513570676492315">"कारची प्राथमिक माहिती अ‍ॅक्सेस करा"</string>
     <string name="car_permission_desc_car_info" msgid="2118081474543537653">"कारची प्राथमिक माहिती अ‍ॅक्सेस करा."</string>
+    <string name="car_permission_label_vendor_permission_info" msgid="4471260460536888654">"कारच्या विक्रेता परवानगी माहिती ॲक्सेस करा"</string>
+    <string name="car_permission_desc_vendor_permission_info" msgid="8152113853528488398">"कारच्या विक्रेता परवानगी माहिती ॲक्सेस करा."</string>
     <string name="car_permission_label_car_exterior_lights" msgid="541304469604902110">"कारच्या बाहेरील लाइटची स्थिती वाचा"</string>
     <string name="car_permission_desc_car_exterior_lights" msgid="4038037584100849318">"कारच्या बाहेरील लाइटची स्थिती अ‍ॅक्सेस करा."</string>
     <string name="car_permission_label_control_car_exterior_lights" msgid="101357531386232141">"कारच्या बाहेरच्या लाइटची स्थिती वाचा"</string>
@@ -128,5 +135,9 @@
     <string name="car_permission_desc_enroll_trust" msgid="4148649994602185130">"विश्वसनीय डिव्हाइसच्या नोंदणीला अनुमती द्या"</string>
     <string name="car_permission_label_car_test_service" msgid="9159328930558208708">"कारचा चाचणी मोड नियंत्रित करा"</string>
     <string name="car_permission_desc_car_test_service" msgid="7426844534110145843">"कारचा चाचणी मोड नियंत्रित करा"</string>
+    <string name="car_permission_label_control_car_features" msgid="3905791560378888286">"कारची वैशिष्ट्ये सुरू किंवा बंद करा"</string>
+    <string name="car_permission_desc_control_car_features" msgid="7646711104530599901">"कारची वैशिष्ट्ये सुरू किंवा बंद करा."</string>
+    <string name="car_permission_label_use_car_watchdog" msgid="6973938293170413475">"कार वॉचडॉग वापरा"</string>
+    <string name="car_permission_desc_use_car_watchdog" msgid="8244592601805516086">"कार वॉचडॉग वापरा."</string>
     <string name="trust_device_default_name" msgid="4213625926070261253">"माझे डिव्हाइस"</string>
 </resources>
diff --git a/service/res/values-ms/strings.xml b/service/res/values-ms/strings.xml
index 58c3f15..473e42b 100644
--- a/service/res/values-ms/strings.xml
+++ b/service/res/values-ms/strings.xml
@@ -22,6 +22,8 @@
     <string name="car_permission_desc_camera" msgid="917024932164501426">"Akses kamera kereta anda."</string>
     <string name="car_permission_label_energy" msgid="7409144323527821558">"akses maklumat tenaga kereta"</string>
     <string name="car_permission_desc_energy" msgid="3392963810053235407">"Akses maklumat tenaga kereta anda."</string>
+    <string name="car_permission_label_adjust_range_remaining" msgid="839033553999920138">"laraskan baki julat kereta"</string>
+    <string name="car_permission_desc_adjust_range_remaining" msgid="2369321650437370673">"Laraskan baki nilai julat kereta."</string>
     <string name="car_permission_label_hvac" msgid="1499454192558727843">"akses hvac kereta"</string>
     <string name="car_permission_desc_hvac" msgid="3754229695589774195">"Akses hvac kereta anda."</string>
     <string name="car_permission_label_mileage" msgid="4661317074631150551">"akses maklumat perbatuan kereta"</string>
@@ -62,16 +64,17 @@
     <string name="car_permission_desc_car_ux_restrictions_configuration" msgid="5711926927484813777">"Konfigurasikan Sekatan UX"</string>
     <string name="car_permission_label_car_handle_usb_aoap_device" msgid="72783989504378036">"Berkomunikasi dengan peranti USB dalam mod AOAP"</string>
     <string name="car_permission_desc_car_handle_usb_aoap_device" msgid="273505990971317034">"Membenarkan apl berkomunikasi dengan peranti dalam mod AOAP"</string>
+    <string name="car_permission_label_read_car_occupant_awareness_state" msgid="125517953575032758">"Akses Baca Sistem Kesedaran Penumpang"</string>
+    <string name="car_permission_desc_read_car_occupant_awareness_state" msgid="188865882598414986">"Membenarkan pembacaan status dan data pengesanan untuk Sistem Kesedaran Penumpang"</string>
+    <string name="car_permission_label_control_car_occupant_awareness_system" msgid="7163330266691094542">"Kawal Graf Sistem Kesedaran Penumpang"</string>
+    <string name="car_permission_desc_control_car_occupant_awareness_system" msgid="7123482622084531911">"Membenarkan pengawalan permulaan dan penamatan graf pengesanan Sistem Kesedaran Penumpang"</string>
     <string name="car_permission_label_bind_input_service" msgid="6698489034024273750">"Perkhidmatan Input Kereta"</string>
     <string name="car_permission_desc_bind_input_service" msgid="1670323419931890170">"Kendalikan peristiwa input"</string>
     <string name="car_can_bus_failure" msgid="2334035748788283914">"Bas CAN gagal"</string>
     <string name="car_can_bus_failure_desc" msgid="4125516222786484733">"Bas CAN tidak bertindak balas. Cabut dan palamkan kembali kotak unit stereo dan mulakan semula kereta"</string>
-    <!-- no translation found for activity_blocked_text (8088902789540147995) -->
-    <skip />
+    <string name="activity_blocked_text" msgid="5342114426610711378">"Untuk keselamatan anda, aktiviti ini tidak tersedia semasa memandu.\nUntuk meneruskan, tunggu kereta diberhentikan."</string>
     <string name="exit_button_message" msgid="8554690915924055685">"Untuk bermula semula dengan ciri apl selamat, pilih <xliff:g id="EXIT_BUTTON">%s</xliff:g>."</string>
     <string name="exit_button" msgid="5829638404777671253">"Kembali"</string>
-    <string name="exit_button_close_application" msgid="8824289547809332460">"Tutup apl"</string>
-    <string name="exit_button_go_back" msgid="3469083862100560326">"Kembali"</string>
     <string name="car_permission_label_diag_read" msgid="7248894224877702604">"baca data diagnostik"</string>
     <string name="car_permission_desc_diag_read" msgid="1121426363040966178">"Baca data diagnostik daripada kereta."</string>
     <string name="car_permission_label_diag_clear" msgid="4783070510879698157">"kosongkan data diagnostik"</string>
@@ -90,6 +93,8 @@
     <string name="car_permission_desc_car_engine_detailed" msgid="1746863362811347700">"Akses maklumat terperinci enjin kereta anda."</string>
     <string name="car_permission_label_car_energy_ports" msgid="8548990315169219454">"akses penutup tangki bahan api dan port pengecasan kereta"</string>
     <string name="car_permission_desc_car_energy_ports" msgid="7771185999828794949">"Akses penutup tangki bahan api dan port pengecasan kereta."</string>
+    <string name="car_permission_label_control_car_energy_ports" msgid="4375137311026313475">"kawal penutup tangki bahan api dan port pengecasan kereta"</string>
+    <string name="car_permission_desc_control_car_energy_ports" msgid="7364633710492525387">"Kawal penutup tangki bahan api dan port pengecasan kereta."</string>
     <string name="car_permission_label_car_identification" msgid="5896712510164020478">"baca pengenalan kereta"</string>
     <string name="car_permission_desc_car_identification" msgid="4132040867171275059">"Akses pengenalan kereta."</string>
     <string name="car_permission_label_control_car_doors" msgid="3032058819250195700">"kawal pintu kereta"</string>
@@ -102,6 +107,8 @@
     <string name="car_permission_desc_control_car_seats" msgid="2407536601226470563">"Kawal tempat duduk kereta."</string>
     <string name="car_permission_label_car_info" msgid="4707513570676492315">"akses maklumat asas kereta"</string>
     <string name="car_permission_desc_car_info" msgid="2118081474543537653">"Akses maklumat asas kereta."</string>
+    <string name="car_permission_label_vendor_permission_info" msgid="4471260460536888654">"akses maklumat kebenaran vendor kereta"</string>
+    <string name="car_permission_desc_vendor_permission_info" msgid="8152113853528488398">"Akses maklumat kebenaran vendor kereta"</string>
     <string name="car_permission_label_car_exterior_lights" msgid="541304469604902110">"baca keadaan lampu luar kereta"</string>
     <string name="car_permission_desc_car_exterior_lights" msgid="4038037584100849318">"Akses keadaan lampu luar kereta."</string>
     <string name="car_permission_label_control_car_exterior_lights" msgid="101357531386232141">"baca lampu luar kereta"</string>
@@ -128,5 +135,9 @@
     <string name="car_permission_desc_enroll_trust" msgid="4148649994602185130">"Benarkan Pendaftaran Peranti yang Dipercayai"</string>
     <string name="car_permission_label_car_test_service" msgid="9159328930558208708">"Mod ujian kereta kawalan"</string>
     <string name="car_permission_desc_car_test_service" msgid="7426844534110145843">"Mod ujian kereta kawalan"</string>
+    <string name="car_permission_label_control_car_features" msgid="3905791560378888286">"Dayakan atau lumpuhkan ciri kereta"</string>
+    <string name="car_permission_desc_control_car_features" msgid="7646711104530599901">"Dayakan atau lumpuhkan ciri kereta."</string>
+    <string name="car_permission_label_use_car_watchdog" msgid="6973938293170413475">"gunakan pengawas kereta"</string>
+    <string name="car_permission_desc_use_car_watchdog" msgid="8244592601805516086">"Gunakan pengawas kereta."</string>
     <string name="trust_device_default_name" msgid="4213625926070261253">"Peranti Saya"</string>
 </resources>
diff --git a/service/res/values-my/strings.xml b/service/res/values-my/strings.xml
index 880f470..d781575 100644
--- a/service/res/values-my/strings.xml
+++ b/service/res/values-my/strings.xml
@@ -22,6 +22,8 @@
     <string name="car_permission_desc_camera" msgid="917024932164501426">"သင့်ကား၏ ကင်မရာ(များ)ကို ဝင်ရောက်အသုံးပြုပါမည်။"</string>
     <string name="car_permission_label_energy" msgid="7409144323527821558">"ကား၏ စွမ်းအင်အချက်အလက်များကို ရယူပါမည်"</string>
     <string name="car_permission_desc_energy" msgid="3392963810053235407">"ကား၏ စွမ်းအင်ဆိုင်ရာ အချက်အလက်ကို အသုံးပြုပါမည်။"</string>
+    <string name="car_permission_label_adjust_range_remaining" msgid="839033553999920138">"ကား၏ အတိုင်းအတာ လက်ကျန်ကို ချိန်ညှိရန်"</string>
+    <string name="car_permission_desc_adjust_range_remaining" msgid="2369321650437370673">"ကား၏ အတိုင်းအတာ လက်ကျန်တန်ဖိုးကို ချိန်ညှိရန်"</string>
     <string name="car_permission_label_hvac" msgid="1499454192558727843">"ကား၏ hvac ကို အသုံးပြုပါမည်"</string>
     <string name="car_permission_desc_hvac" msgid="3754229695589774195">"သင့်ကား၏ \"havc စနစ်\" ကို အသုံးပြုပါမည်။"</string>
     <string name="car_permission_label_mileage" msgid="4661317074631150551">"ကား၏ ခရီးမိုင်အချက်အလက်များကို ရယူပါမည်"</string>
@@ -62,15 +64,17 @@
     <string name="car_permission_desc_car_ux_restrictions_configuration" msgid="5711926927484813777">"UX ကန့်သတ်ချက်များကို စီစဉ်သတ်မှတ်ရန်"</string>
     <string name="car_permission_label_car_handle_usb_aoap_device" msgid="72783989504378036">"AOAP မုဒ်တွင် USB ကိရိယာတစ်ခုနှင့် ဆက်သွယ်ခြင်း"</string>
     <string name="car_permission_desc_car_handle_usb_aoap_device" msgid="273505990971317034">"အက်ပ်တစ်ခုကို AOAP မုဒ်တွင် စက်တစ်ခုနှင့် ဆက်သွယ်ခွင့်ပြုသည်"</string>
+    <string name="car_permission_label_read_car_occupant_awareness_state" msgid="125517953575032758">"\'စီးနင်းသူ သတိရှိမှု စနစ်\' ဖတ်ရှုခွင့်"</string>
+    <string name="car_permission_desc_read_car_occupant_awareness_state" msgid="188865882598414986">"\'စီးနင်းသူ သတိရှိမှု စနစ်\' အတွက် အခြေအနေဖတ်ခြင်းနှင့် ဒေတာရှာဖွေခြင်းတို့ကို ခွင့်ပြုသည်"</string>
+    <string name="car_permission_label_control_car_occupant_awareness_system" msgid="7163330266691094542">"\'စီးနင်းသူ သတိရှိမှု စနစ် ဂရပ်ဖ်\' ကို ထိန်းချုပ်ရန်"</string>
+    <string name="car_permission_desc_control_car_occupant_awareness_system" msgid="7123482622084531911">"\'စီးနင်းသူ သတိရှိမှု စနစ်\' သိရှိသည့်ဂရပ်ဖ် စတင်ခြင်းနှင့် ရပ်တန့်ခြင်းကို ထိန်းချုပ်ရန် ခွင့်ပြုသည်"</string>
     <string name="car_permission_label_bind_input_service" msgid="6698489034024273750">"ကား၏ အချက်အလက်ထည့်သွင်းခြင်း ဝန်ဆောင်မှု"</string>
     <string name="car_permission_desc_bind_input_service" msgid="1670323419931890170">"အချက်အလက်ထည့်သွင်းခြင်း အစီအစဉ်များကို စီမံပါမည်"</string>
     <string name="car_can_bus_failure" msgid="2334035748788283914">"\"CAN bus\" စနစ် အသုံးပြုမှု မအောင်မြင်ပါ"</string>
     <string name="car_can_bus_failure_desc" msgid="4125516222786484733">"CAN bus စနစ်က တုန့်ပြန်မှုမရှိပါ။ စက်အထိုင်ဘောက်စ်ကို ပလတ်ဖြုတ်ပြီး ပြန်တပ်ကာ ကားကို ပြန်လည်စတင်ပါ"</string>
-    <string name="activity_blocked_text" msgid="8088902789540147995">"ကားမောင်းနေစဉ် ဤဝန်ဆောင်မှုကို သုံး၍မရပါ"</string>
+    <string name="activity_blocked_text" msgid="5342114426610711378">"သင့် လုံခြုံမှုအတွက် ဤလုပ်ဆောင်ချက်ကို ကားမောင်းနေစဉ် အသုံးမပြုနိုင်ပါ။\nဆက်လက်လုပ်ဆောင်ရန် ကားရပ်နားသည်အထိစောင့်ပါ။"</string>
     <string name="exit_button_message" msgid="8554690915924055685">"စိတ်ချရသော အက်ပ်လုပ်ဆောင်ချက်များနှင့်အတူ အစမှပြန်စရန် <xliff:g id="EXIT_BUTTON">%s</xliff:g> ကို ရွေးချယ်ပါ။"</string>
     <string name="exit_button" msgid="5829638404777671253">"နောက်သို့"</string>
-    <string name="exit_button_close_application" msgid="8824289547809332460">"အက်ပ်ကိုပိတ်ရန်"</string>
-    <string name="exit_button_go_back" msgid="3469083862100560326">"နောက်သို့"</string>
     <string name="car_permission_label_diag_read" msgid="7248894224877702604">"အမှားရှာပြင်ခြင်းဒေတာများကို ကြည့်ပါမည်"</string>
     <string name="car_permission_desc_diag_read" msgid="1121426363040966178">"ကားအတွင်း အမှားရှာပြင်ခြင်းဒေတာကို ကြည့်ပါမည်။"</string>
     <string name="car_permission_label_diag_clear" msgid="4783070510879698157">"အမှားရှာပြင်ခြင်းဒေတာများကို ရှင်းလင်းပါမည်"</string>
@@ -89,6 +93,8 @@
     <string name="car_permission_desc_car_engine_detailed" msgid="1746863362811347700">"သင့်ကား၏ အသေးစိတ် အင်ဂျင်အချက်အလက်ကို ရယူပါမည်။"</string>
     <string name="car_permission_label_car_energy_ports" msgid="8548990315169219454">"ကား၏ ဆီတိုင်ကီတံခါးပေါက်နှင့် အားသွင်းသည့်အပေါက်ကို အသုံးပြုပါမည်"</string>
     <string name="car_permission_desc_car_energy_ports" msgid="7771185999828794949">"ကား၏ ဆီတိုင်ကီတံခါးပေါက်နှင့် အားသွင်းသည့်အပေါက်ကို အသုံးပြုပါမည်။"</string>
+    <string name="car_permission_label_control_car_energy_ports" msgid="4375137311026313475">"ကား၏ ဆီတိုင်ကီတံခါးပေါက်နှင့် အားသွင်းသည့်အပေါက်ကို ထိန်းချုပ်ရန်"</string>
+    <string name="car_permission_desc_control_car_energy_ports" msgid="7364633710492525387">"ကား၏ ဆီတိုင်ကီတံခါးပေါက်နှင့် အားသွင်းသည့်အပေါက်ကို ထိန်းချုပ်ပါမည်။"</string>
     <string name="car_permission_label_car_identification" msgid="5896712510164020478">"ကား၏ အထောက်အထားကို ကြည့်ပါမည်"</string>
     <string name="car_permission_desc_car_identification" msgid="4132040867171275059">"ကား၏ အထောက်အထားကို ရယူပါမည်။"</string>
     <string name="car_permission_label_control_car_doors" msgid="3032058819250195700">"ကားတံခါးများကို ထိန်းချုပ်သည်"</string>
@@ -101,6 +107,8 @@
     <string name="car_permission_desc_control_car_seats" msgid="2407536601226470563">"ကားတွင်းထိုင်ခုံများကို ထိန်းချုပ်ပါမည်။"</string>
     <string name="car_permission_label_car_info" msgid="4707513570676492315">"ကား၏ အခြေခံအချက်အလက်များကို ရယူပါမည်"</string>
     <string name="car_permission_desc_car_info" msgid="2118081474543537653">"ကား၏ အခြေခံအချက်အလက်များကို ရယူပါမည်။"</string>
+    <string name="car_permission_label_vendor_permission_info" msgid="4471260460536888654">"ကားရောင်းချသူ ခွင့်ပြုချက်ဆိုင်ရာ အချက်အလက်များကို ကြည့်ရန်"</string>
+    <string name="car_permission_desc_vendor_permission_info" msgid="8152113853528488398">"ကားရောင်းချသူ ခွင့်ပြုချက်ဆိုင်ရာ အချက်အလက်များကို ကြည့်ရန်။"</string>
     <string name="car_permission_label_car_exterior_lights" msgid="541304469604902110">"ကားအပြင်ဘက်ရှိ မီးလုံးများ၏ အခြေအနေကို ကြည့်ပါမည်"</string>
     <string name="car_permission_desc_car_exterior_lights" msgid="4038037584100849318">"ကား၏အပြင်ဘက်ရှိ မီးလုံးများ၏ အခြေအနေကို ကြည့်ပါမည်။"</string>
     <string name="car_permission_label_control_car_exterior_lights" msgid="101357531386232141">"ကားအပြင်ဘက်ရှိ မီးလုံးများကို ကြည့်ပါမည်"</string>
@@ -127,5 +135,9 @@
     <string name="car_permission_desc_enroll_trust" msgid="4148649994602185130">"ယုံကြည်ရသည့် ကိရိယာအား စာရင်းသွင်းခြင်းကို ခွင့်ပြုရန်"</string>
     <string name="car_permission_label_car_test_service" msgid="9159328930558208708">"ကား၏ စမ်းသပ်မုဒ်ကို ထိန်းချုပ်ရန်"</string>
     <string name="car_permission_desc_car_test_service" msgid="7426844534110145843">"ကား၏ စမ်းသပ်မုဒ်ကို ထိန်းချုပ်ရန်"</string>
+    <string name="car_permission_label_control_car_features" msgid="3905791560378888286">"ကား၏ ဝန်ဆောင်မှုများကို ပိတ်ရန် သို့မဟုတ် ပိတ်ရန်"</string>
+    <string name="car_permission_desc_control_car_features" msgid="7646711104530599901">"ကား၏ ဝန်ဆောင်မှုများကို ဖွင့်ရန် သို့မဟုတ် ပိတ်ရန်။"</string>
+    <string name="car_permission_label_use_car_watchdog" msgid="6973938293170413475">"ကားကင်းစောင့်ကို အသုံးပြုခွင့်"</string>
+    <string name="car_permission_desc_use_car_watchdog" msgid="8244592601805516086">"ကားကင်းစောင့်ကို အသုံးပြုခွင့်။"</string>
     <string name="trust_device_default_name" msgid="4213625926070261253">"ကျွန်ုပ်၏စက်"</string>
 </resources>
diff --git a/service/res/values-nb/strings.xml b/service/res/values-nb/strings.xml
index 6d6d695..7957d0f 100644
--- a/service/res/values-nb/strings.xml
+++ b/service/res/values-nb/strings.xml
@@ -22,6 +22,8 @@
     <string name="car_permission_desc_camera" msgid="917024932164501426">"Tilgang til bilens kamera(er)."</string>
     <string name="car_permission_label_energy" msgid="7409144323527821558">"få tilgang til bilens energiinformasjon"</string>
     <string name="car_permission_desc_energy" msgid="3392963810053235407">"Tilgang til informasjon om bilens energibruk."</string>
+    <string name="car_permission_label_adjust_range_remaining" msgid="839033553999920138">"juster bilens gjenværende rekkevidde"</string>
+    <string name="car_permission_desc_adjust_range_remaining" msgid="2369321650437370673">"Juster verdien for bilens gjenværende rekkevidde."</string>
     <string name="car_permission_label_hvac" msgid="1499454192558727843">"få tilgang til bilens klimaanlegg"</string>
     <string name="car_permission_desc_hvac" msgid="3754229695589774195">"Tilgang til bilens klimaanlegg."</string>
     <string name="car_permission_label_mileage" msgid="4661317074631150551">"få informasjon om bilens kjørelengde"</string>
@@ -62,15 +64,17 @@
     <string name="car_permission_desc_car_ux_restrictions_configuration" msgid="5711926927484813777">"Konfigurering av begrensninger tilknyttet brukeropplevelsen"</string>
     <string name="car_permission_label_car_handle_usb_aoap_device" msgid="72783989504378036">"Kommuniser med USB-enhet i AOAP-modus"</string>
     <string name="car_permission_desc_car_handle_usb_aoap_device" msgid="273505990971317034">"Tillater at en app kommuniserer med en enhet i AOAP-modus"</string>
+    <string name="car_permission_label_read_car_occupant_awareness_state" msgid="125517953575032758">"Lesetilgang til Occupant Awareness System"</string>
+    <string name="car_permission_desc_read_car_occupant_awareness_state" msgid="188865882598414986">"Lar deg lese av status og registreringsdata for Occupant Awareness System"</string>
+    <string name="car_permission_label_control_car_occupant_awareness_system" msgid="7163330266691094542">"Kontrollér diagrammet for Occupant Awareness System"</string>
+    <string name="car_permission_desc_control_car_occupant_awareness_system" msgid="7123482622084531911">"Lar deg kontrollere start og stopp av registreringsdiagrammet for Occupant Awareness System"</string>
     <string name="car_permission_label_bind_input_service" msgid="6698489034024273750">"Bilens inndatatjeneste"</string>
     <string name="car_permission_desc_bind_input_service" msgid="1670323419931890170">"Behandling av inndatahendelser"</string>
     <string name="car_can_bus_failure" msgid="2334035748788283914">"CAN-bus mislyktes"</string>
     <string name="car_can_bus_failure_desc" msgid="4125516222786484733">"CAN-bus svarer ikke. Koble bilens hovedenhet ut og inn igjen, og start bilen på nytt"</string>
-    <string name="activity_blocked_text" msgid="8088902789540147995">"Du kan ikke bruke denne funksjonen når du kjører"</string>
+    <string name="activity_blocked_text" msgid="5342114426610711378">"Av sikkerhetshensyn er denne aktiviteten utilgjengelig når du kjører.\nDu kan ikke fortsette før du stopper bilen."</string>
     <string name="exit_button_message" msgid="8554690915924055685">"For å starte på nytt med sikre appfunksjoner, velg <xliff:g id="EXIT_BUTTON">%s</xliff:g>."</string>
     <string name="exit_button" msgid="5829638404777671253">"Tilbake"</string>
-    <string name="exit_button_close_application" msgid="8824289547809332460">"Lukk appen"</string>
-    <string name="exit_button_go_back" msgid="3469083862100560326">"Tilbake"</string>
     <string name="car_permission_label_diag_read" msgid="7248894224877702604">"lese diagnostikkdata"</string>
     <string name="car_permission_desc_diag_read" msgid="1121426363040966178">"Les diagnostikkdata fra bilen."</string>
     <string name="car_permission_label_diag_clear" msgid="4783070510879698157">"slette diagnostikkdata"</string>
@@ -89,6 +93,8 @@
     <string name="car_permission_desc_car_engine_detailed" msgid="1746863362811347700">"Tilgang til detaljert informasjon om bilmotoren."</string>
     <string name="car_permission_label_car_energy_ports" msgid="8548990315169219454">"få tilgang til tanklokket og ladeporten på bilen"</string>
     <string name="car_permission_desc_car_energy_ports" msgid="7771185999828794949">"Tilgang til tanklokket og ladeporten på bilen."</string>
+    <string name="car_permission_label_control_car_energy_ports" msgid="4375137311026313475">"kontrollere tanklokket og ladeporten på bilen"</string>
+    <string name="car_permission_desc_control_car_energy_ports" msgid="7364633710492525387">"Kontrollér tanklokket og ladeporten på bilen."</string>
     <string name="car_permission_label_car_identification" msgid="5896712510164020478">"lese bilens identifikator"</string>
     <string name="car_permission_desc_car_identification" msgid="4132040867171275059">"Tilgang til bilens identifikasjon."</string>
     <string name="car_permission_label_control_car_doors" msgid="3032058819250195700">"kontrollére bildørene"</string>
@@ -101,6 +107,8 @@
     <string name="car_permission_desc_control_car_seats" msgid="2407536601226470563">"Kontrollér bilsetene."</string>
     <string name="car_permission_label_car_info" msgid="4707513570676492315">"få tilgang til grunnleggende informasjon om bilen"</string>
     <string name="car_permission_desc_car_info" msgid="2118081474543537653">"Tilgang til grunnleggende informasjon om bilen."</string>
+    <string name="car_permission_label_vendor_permission_info" msgid="4471260460536888654">"få tilgang til bilens informasjon om leverandørtillatelser"</string>
+    <string name="car_permission_desc_vendor_permission_info" msgid="8152113853528488398">"Få tilgang til bilens informasjon om leverandørtillatelser."</string>
     <string name="car_permission_label_car_exterior_lights" msgid="541304469604902110">"lese tilstanden til bilens utvendige lys"</string>
     <string name="car_permission_desc_car_exterior_lights" msgid="4038037584100849318">"Tilgang til tilstanden til bilens utvendige lys."</string>
     <string name="car_permission_label_control_car_exterior_lights" msgid="101357531386232141">"lese bilens utvendige lys"</string>
@@ -127,5 +135,9 @@
     <string name="car_permission_desc_enroll_trust" msgid="4148649994602185130">"Tillat registrering av godkjente enheter"</string>
     <string name="car_permission_label_car_test_service" msgid="9159328930558208708">"Kontrollér bilens testmodus"</string>
     <string name="car_permission_desc_car_test_service" msgid="7426844534110145843">"Kontrollér bilens testmodus"</string>
+    <string name="car_permission_label_control_car_features" msgid="3905791560378888286">"Slå bilens funksjoner på eller av"</string>
+    <string name="car_permission_desc_control_car_features" msgid="7646711104530599901">"Slå bilens funksjoner på eller av."</string>
+    <string name="car_permission_label_use_car_watchdog" msgid="6973938293170413475">"bruk vakthund for bil"</string>
+    <string name="car_permission_desc_use_car_watchdog" msgid="8244592601805516086">"Bruk vakthund for bil."</string>
     <string name="trust_device_default_name" msgid="4213625926070261253">"Enheten min"</string>
 </resources>
diff --git a/service/res/values-ne/strings.xml b/service/res/values-ne/strings.xml
index eb310c9..57bec9b 100644
--- a/service/res/values-ne/strings.xml
+++ b/service/res/values-ne/strings.xml
@@ -22,6 +22,8 @@
     <string name="car_permission_desc_camera" msgid="917024932164501426">"तपाईंको कारका क्यामेरा(हरू) माथि पहुँच राख्ने।"</string>
     <string name="car_permission_label_energy" msgid="7409144323527821558">"कारको ऊर्जासम्बन्धी जानकारीमाथि पहुँच राख्ने"</string>
     <string name="car_permission_desc_energy" msgid="3392963810053235407">"तपाईंको कारको ऊर्जासम्बन्धी जानकारीमाथि पहुँच राख्ने।"</string>
+    <string name="car_permission_label_adjust_range_remaining" msgid="839033553999920138">"कारको दायराको बाँकी मान समायोजन गर्नुहोस्"</string>
+    <string name="car_permission_desc_adjust_range_remaining" msgid="2369321650437370673">"कारको दायराको बाँकी मान समायोजन गर्नुहोस्।"</string>
     <string name="car_permission_label_hvac" msgid="1499454192558727843">"कारको hvac प्रणालीमाथि पहुँच राख्ने"</string>
     <string name="car_permission_desc_hvac" msgid="3754229695589774195">"तपाईंको कारको hvac माथि पहुँच राख्ने।"</string>
     <string name="car_permission_label_mileage" msgid="4661317074631150551">"कारको माइलेजसम्बन्धी जानकारीमाथि पहुँच राख्ने"</string>
@@ -62,16 +64,17 @@
     <string name="car_permission_desc_car_ux_restrictions_configuration" msgid="5711926927484813777">"UX सम्बन्धी प्रतिबन्धहरू कन्फिगर गर्नुहोस्‌"</string>
     <string name="car_permission_label_car_handle_usb_aoap_device" msgid="72783989504378036">"AOAP मोडमा USB यन्त्रसँग सञ्चार गर्नुहोस्"</string>
     <string name="car_permission_desc_car_handle_usb_aoap_device" msgid="273505990971317034">"AOAP मोडमा अनुप्रयोगलाई कुनै यन्त्रसँग सञ्चार गर्न दिन्छ"</string>
+    <string name="car_permission_label_read_car_occupant_awareness_state" msgid="125517953575032758">"Occupant Awareness System रिड गर्ने पहुँच"</string>
+    <string name="car_permission_desc_read_car_occupant_awareness_state" msgid="188865882598414986">"Occupant Awareness System को स्थिति र पत्ता लगाउनेसम्बन्धी डेटा रिड गर्न दिन्छ"</string>
+    <string name="car_permission_label_control_car_occupant_awareness_system" msgid="7163330266691094542">"Occupant Awareness System सम्बन्धी ग्राफको नियन्त्रण गर्नुहोस्"</string>
+    <string name="car_permission_desc_control_car_occupant_awareness_system" msgid="7123482622084531911">"Occupant Awareness System पत्ता लगाउनेसम्बन्धी ग्राफको सुरु र समाप्त हुने कार्य नियन्त्रण गर्न अनुमति दिनुहोस्"</string>
     <string name="car_permission_label_bind_input_service" msgid="6698489034024273750">"कारको इनपुट सेवा"</string>
     <string name="car_permission_desc_bind_input_service" msgid="1670323419931890170">"इनपुट गरिएका कार्यक्रमहरू व्यवस्थापन गर्ने"</string>
     <string name="car_can_bus_failure" msgid="2334035748788283914">"CAN bus असफल भयो"</string>
     <string name="car_can_bus_failure_desc" msgid="4125516222786484733">"CAN bus ले प्रतिक्रिया जनाएन। हेडयुनिट बाकसलाई प्लगबाट निकालेर फेरि प्लगमा घुसाउनुहोस् र कार पुनःसुरु गर्नुहोस्‌"</string>
-    <!-- no translation found for activity_blocked_text (8088902789540147995) -->
-    <skip />
+    <string name="activity_blocked_text" msgid="5342114426610711378">"तपाईंको सुरक्षाका लागि, तपाईंले सवारी साधन चलाइरहेका बेलामा यो क्रियाकलाप उपलब्ध हुँदैन।\nजारी राख्न, पार्क नगरुन्जेल पर्खनुहोस्‌।"</string>
     <string name="exit_button_message" msgid="8554690915924055685">"अनुप्रयोगका सुरक्षित सुविधाहरूको प्रयोग गरी फेरि सुरु गर्न <xliff:g id="EXIT_BUTTON">%s</xliff:g> चयन गर्नुहोस्‌।"</string>
     <string name="exit_button" msgid="5829638404777671253">"पछाडि"</string>
-    <string name="exit_button_close_application" msgid="8824289547809332460">"अनुप्रयोग बन्द गर्नुहोस्"</string>
-    <string name="exit_button_go_back" msgid="3469083862100560326">"पछाडि"</string>
     <string name="car_permission_label_diag_read" msgid="7248894224877702604">"निदानसम्बन्धी डेटा पढ्ने"</string>
     <string name="car_permission_desc_diag_read" msgid="1121426363040966178">"कारको निदानसम्बन्धी डेटा पढ्ने।"</string>
     <string name="car_permission_label_diag_clear" msgid="4783070510879698157">"निदानसम्बन्धी डेटा हटाउने"</string>
@@ -90,6 +93,8 @@
     <string name="car_permission_desc_car_engine_detailed" msgid="1746863362811347700">"तपाईंको कारको इन्जिनको विस्तृत जानकारीमाथि पहुँच राख्ने।"</string>
     <string name="car_permission_label_car_energy_ports" msgid="8548990315169219454">"कारको इन्धन हाल्ने ट्याङ्कीको बिर्को तथा चार्ज गर्ने पोर्टमाथि पहुँच राख्ने"</string>
     <string name="car_permission_desc_car_energy_ports" msgid="7771185999828794949">"कारको इन्धन हाल्ने ट्याङ्कीको बिर्को तथा चार्ज गर्ने पोर्टमाथि पहुँच राख्ने।"</string>
+    <string name="car_permission_label_control_car_energy_ports" msgid="4375137311026313475">"कारको इन्धन हाल्ने ट्याङ्कीको बिर्को तथा चार्ज गर्ने पोर्टमाथि नियन्त्रण राख्नुहोस्"</string>
+    <string name="car_permission_desc_control_car_energy_ports" msgid="7364633710492525387">"कारको इन्धन हाल्ने ट्याङ्कीको बिर्को तथा चार्ज गर्ने पोर्ट नियन्त्रण गर्नुहोस्।"</string>
     <string name="car_permission_label_car_identification" msgid="5896712510164020478">"कारको पहिचानसम्बन्धी जानकारी पढ्ने"</string>
     <string name="car_permission_desc_car_identification" msgid="4132040867171275059">"कारको पहिचानसम्बन्धी जानकारीमाथि पहुँच राख्ने।"</string>
     <string name="car_permission_label_control_car_doors" msgid="3032058819250195700">"कारका ढोकाहरू नियन्त्रण गर्ने"</string>
@@ -102,6 +107,8 @@
     <string name="car_permission_desc_control_car_seats" msgid="2407536601226470563">"कारका सिटहरू नियन्त्रण गर्ने।"</string>
     <string name="car_permission_label_car_info" msgid="4707513570676492315">"कारको आधारभूत जानकारीमाथि पहुँच राख्ने"</string>
     <string name="car_permission_desc_car_info" msgid="2118081474543537653">"कारको आधारभूत जानकारीमाथि पहुँच राख्ने।"</string>
+    <string name="car_permission_label_vendor_permission_info" msgid="4471260460536888654">"कारको विक्रेतासम्बन्धी अनुमतिको जानकारीमाथि पहुँच राख्नुहोस्"</string>
+    <string name="car_permission_desc_vendor_permission_info" msgid="8152113853528488398">"कारको विक्रेतासम्बन्धी अनुमतिको जानकारीमाथि पहुँच राख्नुहोस्।"</string>
     <string name="car_permission_label_car_exterior_lights" msgid="541304469604902110">"कारका बाहिरी बत्तीहरूको स्थिति पढ्ने"</string>
     <string name="car_permission_desc_car_exterior_lights" msgid="4038037584100849318">"कारका बाहिरी बत्तीहरूको स्थितिमाथि पहुँच राख्ने।"</string>
     <string name="car_permission_label_control_car_exterior_lights" msgid="101357531386232141">"कारका बाहिरी बत्तीहरूको अवस्थाबारे जानकारी प्राप्त गर्ने"</string>
@@ -128,5 +135,9 @@
     <string name="car_permission_desc_enroll_trust" msgid="4148649994602185130">"विश्वसनीय यन्त्र दर्ता गर्ने अनुमति दिनुहोस्"</string>
     <string name="car_permission_label_car_test_service" msgid="9159328930558208708">"कारको परीक्षण मोड नियन्त्रण गर्नुहोस्"</string>
     <string name="car_permission_desc_car_test_service" msgid="7426844534110145843">"कारको परीक्षण मोड नियन्त्रण गर्नुहोस्"</string>
+    <string name="car_permission_label_control_car_features" msgid="3905791560378888286">"कारका सुविधाहरू सक्षम वा असक्षम पार्नुहोस्"</string>
+    <string name="car_permission_desc_control_car_features" msgid="7646711104530599901">"कारका सुविधाहरू सक्षम वा असक्षम पार्नुहोस्।"</string>
+    <string name="car_permission_label_use_car_watchdog" msgid="6973938293170413475">"कारको प्रणालीको निगरानी गर्ने सुविधा प्रयोग गर्नुहोस्"</string>
+    <string name="car_permission_desc_use_car_watchdog" msgid="8244592601805516086">"कारको प्रणालीको निगरानी गर्ने सुविधा प्रयोग गर्नुहोस्"</string>
     <string name="trust_device_default_name" msgid="4213625926070261253">"मेरो यन्त्र"</string>
 </resources>
diff --git a/service/res/values-nl/strings.xml b/service/res/values-nl/strings.xml
index 623ea8f..5d084df 100644
--- a/service/res/values-nl/strings.xml
+++ b/service/res/values-nl/strings.xml
@@ -16,12 +16,14 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="car_permission_label" msgid="741004755205554376">"Toegang tot gegevens van auto"</string>
+    <string name="car_permission_label" msgid="741004755205554376">"toegang tot gegevens van auto"</string>
     <string name="car_permission_desc" msgid="162499818870052725">"toegang krijgen tot je autogegevens."</string>
     <string name="car_permission_label_camera" msgid="3725702064841827180">"toegang tot camera van auto"</string>
     <string name="car_permission_desc_camera" msgid="917024932164501426">"Toegang tot de camera(\'s) van je auto."</string>
     <string name="car_permission_label_energy" msgid="7409144323527821558">"toegang tot informatie over energieniveau van auto"</string>
     <string name="car_permission_desc_energy" msgid="3392963810053235407">"Toegang tot informatie over het energieniveau van je auto."</string>
+    <string name="car_permission_label_adjust_range_remaining" msgid="839033553999920138">"resterende actieradius van auto aanpassen"</string>
+    <string name="car_permission_desc_adjust_range_remaining" msgid="2369321650437370673">"Waarde voor resterende actieradius van auto aanpassen."</string>
     <string name="car_permission_label_hvac" msgid="1499454192558727843">"toegang tot HVAC van auto"</string>
     <string name="car_permission_desc_hvac" msgid="3754229695589774195">"Toegang tot de HVAC van je auto."</string>
     <string name="car_permission_label_mileage" msgid="4661317074631150551">"toegang tot informatie over kilometerstand van auto"</string>
@@ -62,15 +64,17 @@
     <string name="car_permission_desc_car_ux_restrictions_configuration" msgid="5711926927484813777">"Beperkingen voor gebruikerservaring configureren"</string>
     <string name="car_permission_label_car_handle_usb_aoap_device" msgid="72783989504378036">"Communiceren met USB-apparaat in AOAP-modus"</string>
     <string name="car_permission_desc_car_handle_usb_aoap_device" msgid="273505990971317034">"Hiermee kan een app communiceren met een apparaat in AOAP-modus"</string>
+    <string name="car_permission_label_read_car_occupant_awareness_state" msgid="125517953575032758">"Leestoegang voor Occupant Awareness System"</string>
+    <string name="car_permission_desc_read_car_occupant_awareness_state" msgid="188865882598414986">"Hiermee kunnen status- en detectiegegevens van het Occupant Awareness System worden gelezen"</string>
+    <string name="car_permission_label_control_car_occupant_awareness_system" msgid="7163330266691094542">"Occupant Awareness System-diagram bedienen"</string>
+    <string name="car_permission_desc_control_car_occupant_awareness_system" msgid="7123482622084531911">"Hiermee kan het starten en stoppen van het Occupant Awareness System-detectiediagram worden bediend"</string>
     <string name="car_permission_label_bind_input_service" msgid="6698489034024273750">"Invoerservice van auto"</string>
     <string name="car_permission_desc_bind_input_service" msgid="1670323419931890170">"Invoergebeurtenissen verwerken"</string>
     <string name="car_can_bus_failure" msgid="2334035748788283914">"CAN-bus is mislukt"</string>
     <string name="car_can_bus_failure_desc" msgid="4125516222786484733">"CAN-bus reageert niet. Koppel de hoofdeenheid los en sluit deze vervolgens weer aan. Start de auto daarna opnieuw."</string>
-    <string name="activity_blocked_text" msgid="8088902789540147995">"Je kunt deze functie niet gebruiken tijdens het rijden"</string>
+    <string name="activity_blocked_text" msgid="5342114426610711378">"Voor jouw veiligheid is deze activiteit niet beschikbaar als je aan het rijden bent.\nJe moet parkeren om door te gaan."</string>
     <string name="exit_button_message" msgid="8554690915924055685">"Selecteer <xliff:g id="EXIT_BUTTON">%s</xliff:g> om opnieuw te beginnen met de veilige app-functies."</string>
     <string name="exit_button" msgid="5829638404777671253">"Vorige"</string>
-    <string name="exit_button_close_application" msgid="8824289547809332460">"App sluiten"</string>
-    <string name="exit_button_go_back" msgid="3469083862100560326">"Terug"</string>
     <string name="car_permission_label_diag_read" msgid="7248894224877702604">"diagnostische gegevens lezen"</string>
     <string name="car_permission_desc_diag_read" msgid="1121426363040966178">"Diagnostische gegevens van auto lezen."</string>
     <string name="car_permission_label_diag_clear" msgid="4783070510879698157">"diagnostische gegevens wissen"</string>
@@ -89,6 +93,8 @@
     <string name="car_permission_desc_car_engine_detailed" msgid="1746863362811347700">"Toegang tot gedetailleerde informatie over motor van auto."</string>
     <string name="car_permission_label_car_energy_ports" msgid="8548990315169219454">"toegang tot tankklep en oplaadpoort van auto"</string>
     <string name="car_permission_desc_car_energy_ports" msgid="7771185999828794949">"Toegang tot tankklep en oplaadpoort van auto."</string>
+    <string name="car_permission_label_control_car_energy_ports" msgid="4375137311026313475">"tankklep en oplaadpoort van auto bedienen"</string>
+    <string name="car_permission_desc_control_car_energy_ports" msgid="7364633710492525387">"Tankklep en oplaadpoort van auto bedienen."</string>
     <string name="car_permission_label_car_identification" msgid="5896712510164020478">"identificatie van auto lezen"</string>
     <string name="car_permission_desc_car_identification" msgid="4132040867171275059">"Toegang tot identificatie van auto."</string>
     <string name="car_permission_label_control_car_doors" msgid="3032058819250195700">"autoportieren bedienen"</string>
@@ -101,6 +107,8 @@
     <string name="car_permission_desc_control_car_seats" msgid="2407536601226470563">"Autostoelen bedienen."</string>
     <string name="car_permission_label_car_info" msgid="4707513570676492315">"toegang tot basisgegevens van auto"</string>
     <string name="car_permission_desc_car_info" msgid="2118081474543537653">"Toegang tot basisgegevens van auto."</string>
+    <string name="car_permission_label_vendor_permission_info" msgid="4471260460536888654">"toegang tot informatie over de leveranciersrechten van de auto"</string>
+    <string name="car_permission_desc_vendor_permission_info" msgid="8152113853528488398">"Toegang tot informatie over de leveranciersrechten van de auto."</string>
     <string name="car_permission_label_car_exterior_lights" msgid="541304469604902110">"status van buitenverlichting van auto lezen"</string>
     <string name="car_permission_desc_car_exterior_lights" msgid="4038037584100849318">"Toegang tot status van buitenverlichting van auto."</string>
     <string name="car_permission_label_control_car_exterior_lights" msgid="101357531386232141">"buitenverlichting van auto lezen"</string>
@@ -127,5 +135,9 @@
     <string name="car_permission_desc_enroll_trust" msgid="4148649994602185130">"Inschrijving van vertrouwd apparaat toestaan"</string>
     <string name="car_permission_label_car_test_service" msgid="9159328930558208708">"Testmodus van auto bedienen"</string>
     <string name="car_permission_desc_car_test_service" msgid="7426844534110145843">"Testmodus van auto bedienen"</string>
+    <string name="car_permission_label_control_car_features" msgid="3905791560378888286">"Functies van de auto in- of uitschakelen"</string>
+    <string name="car_permission_desc_control_car_features" msgid="7646711104530599901">"Functies van de auto in- of uitschakelen."</string>
+    <string name="car_permission_label_use_car_watchdog" msgid="6973938293170413475">"auto-watchdog gebruiken"</string>
+    <string name="car_permission_desc_use_car_watchdog" msgid="8244592601805516086">"Auto-watchdog gebruiken."</string>
     <string name="trust_device_default_name" msgid="4213625926070261253">"Mijn apparaat"</string>
 </resources>
diff --git a/service/res/values-or/strings.xml b/service/res/values-or/strings.xml
index 39ac91e..a671892 100644
--- a/service/res/values-or/strings.xml
+++ b/service/res/values-or/strings.xml
@@ -22,6 +22,8 @@
     <string name="car_permission_desc_camera" msgid="917024932164501426">"ଆପଣଙ୍କ କାର୍‍ର କ୍ୟାମେରା(ଗୁଡ଼ିକ) ଆକ୍ସେସ୍ କରିପାରେ।"</string>
     <string name="car_permission_label_energy" msgid="7409144323527821558">"କାର୍\'ର ଏନାର୍ଜି ସୂଚନାକୁ ଆକ୍‍‍ସେସ୍ କରନ୍ତୁ"</string>
     <string name="car_permission_desc_energy" msgid="3392963810053235407">"ଆପଣଙ୍କ କାର୍‍ର ଶକ୍ତି ସୂଚନା ଆକ୍ସେସ୍ କରିପାରେ।"</string>
+    <string name="car_permission_label_adjust_range_remaining" msgid="839033553999920138">"କାର୍‌ର ରେଞ୍ଜ୍ ପାଇଁ ଅବଶିଷ୍ଟ ମୂଲ୍ୟ ଆଡ୍‌ଜଷ୍ଟ୍ କରନ୍ତୁ"</string>
+    <string name="car_permission_desc_adjust_range_remaining" msgid="2369321650437370673">"କାର୍‌ର ରେଞ୍ଜ୍ ପାଇଁ ଅବଶିଷ୍ଟ ମୂଲ୍ୟ ଆଡ୍‌ଜଷ୍ଟ୍ କରନ୍ତୁ।"</string>
     <string name="car_permission_label_hvac" msgid="1499454192558727843">"କାର୍\'ର hvac ଆକ୍‍‍ସେସ୍ କରନ୍ତୁ"</string>
     <string name="car_permission_desc_hvac" msgid="3754229695589774195">"ଆପଣଙ୍କ କାର୍‍ର hvac ଆକ୍ସେସ୍ କରିପାରେ।"</string>
     <string name="car_permission_label_mileage" msgid="4661317074631150551">"କାର୍\'ର ମାଇଲେଜ୍ ସୂଚନାକୁ ଆକ୍‍‍ସେସ୍ କରନ୍ତୁ"</string>
@@ -62,15 +64,17 @@
     <string name="car_permission_desc_car_ux_restrictions_configuration" msgid="5711926927484813777">"UX ପ୍ରତିବନ୍ଧତା କନ୍‌ଫିଗର୍ କରନ୍ତୁ"</string>
     <string name="car_permission_label_car_handle_usb_aoap_device" msgid="72783989504378036">"AOAP ମୋଡ୍‌ରେ USB ଡିଭାଇସ୍ ସହ ଯୋଗାଯୋଗ କରନ୍ତୁ"</string>
     <string name="car_permission_desc_car_handle_usb_aoap_device" msgid="273505990971317034">"AOAP ମୋଡ୍‌ରେ ଏକ ଡିଭାଇସ୍ ସହ ଯୋଗାଯୋଗ କରିବା ପାଇଁ ଆପ୍‍କୁ ଅନୁମତି ଦେଇଥାଏ"</string>
+    <string name="car_permission_label_read_car_occupant_awareness_state" msgid="125517953575032758">"ଅକ୍ୟୁପେଣ୍ଟ ଆୱାରନେସ୍ ସିଷ୍ଟମ୍ ପଢ଼ିବା ଆକ୍ସେସ୍"</string>
+    <string name="car_permission_desc_read_car_occupant_awareness_state" msgid="188865882598414986">"ଏହା ଅକ୍ୟୁପେଣ୍ଟ ଆୱାରନେସ୍ ସିଷ୍ଟମ୍ ପାଇଁ ସ୍ଥିତି ଏବଂ ଚିହ୍ନଟକରଣ ଡାଟା ପଢ଼ିବାକୁ ଅନୁମତି ଦେଇଥାଏ"</string>
+    <string name="car_permission_label_control_car_occupant_awareness_system" msgid="7163330266691094542">"ଅକ୍ୟୁପେଣ୍ଟ ଆୱାରନେସ୍ ସିଷ୍ଟମ୍ ଗ୍ରାଫ୍ ନିୟନ୍ତ୍ରଣ କରନ୍ତୁ"</string>
+    <string name="car_permission_desc_control_car_occupant_awareness_system" msgid="7123482622084531911">"ଏହା ଅକ୍ୟୁପେଣ୍ଟ ଆୱାରନେସ୍ ସିଷ୍ଟମ୍ ଡିଟେକ୍ସନ୍ ଗ୍ରାଫକୁ ଚାଲୁ ଏବଂ ବନ୍ଦ କରିବା ନିୟନ୍ତ୍ରଣ ପାଇଁ ଅନୁମତି ଦେଇଥାଏ"</string>
     <string name="car_permission_label_bind_input_service" msgid="6698489034024273750">"କାର୍‍ର ଇନ୍‍ପୁଟ୍ ସେବା"</string>
     <string name="car_permission_desc_bind_input_service" msgid="1670323419931890170">"ଇନ୍‍ପୁଟ୍ ଇଭେଣ୍ଟଗୁଡ଼ିକ ପରିଚାଳନା କରିପାରେ"</string>
     <string name="car_can_bus_failure" msgid="2334035748788283914">"CAN ବସ୍ ବିଫଳ ହେଲା"</string>
     <string name="car_can_bus_failure_desc" msgid="4125516222786484733">"CAN ବସ୍ ପ୍ରତିକ୍ରିୟା ଦେଉନାହିଁ। ହେଡୟୁନିଟ୍ ବାକ୍ସର ପ୍ଲଗ୍ କାଢ଼ି ପୁଣି ଲଗାନ୍ତୁ ଏବଂ କାର୍‍କୁ ରିଷ୍ଟାର୍ଟ କରନ୍ତୁ"</string>
-    <string name="activity_blocked_text" msgid="8088902789540147995">"ଆପଣ ଡ୍ରାଇଭ୍‍ କରିବା ସମୟରେ ଏହି ଫିଚର୍‍ ବ୍ୟବହାର କରିପାରିବେ ନାହିଁ"</string>
+    <string name="activity_blocked_text" msgid="5342114426610711378">"ଆପଣଙ୍କ ସୁରକ୍ଷା ପାଇଁ, ଡ୍ରାଇଭିଂ କରିବା ସମୟରେ ଏହି ଗତିବିଧି ଉପଲବ୍ଧ ନାହିଁ।\nଜାରି ରଖିବା ପାଇଁ, ପାର୍କ କରିବା ପର୍ଯ୍ୟନ୍ତ ଅପେକ୍ଷା କରନ୍ତୁ।"</string>
     <string name="exit_button_message" msgid="8554690915924055685">"ସୁରକ୍ଷିତ ଆପ୍ ବୈଶିଷ୍ଟ୍ୟଗୁଡ଼ିକୁ ନେ‍ଇ ପୁଣି ଆରମ୍ଭ କରିବା ପାଇଁ, <xliff:g id="EXIT_BUTTON">%s</xliff:g> ଚୟନ କରନ୍ତୁ।"</string>
     <string name="exit_button" msgid="5829638404777671253">"ପଛକୁ ଫେରନ୍ତୁ"</string>
-    <string name="exit_button_close_application" msgid="8824289547809332460">"ଆପ୍‍ ବନ୍ଦ କରନ୍ତୁ"</string>
-    <string name="exit_button_go_back" msgid="3469083862100560326">"ପଛକୁ ଫେରନ୍ତୁ"</string>
     <string name="car_permission_label_diag_read" msgid="7248894224877702604">"ଡାଏଗ୍ନୋଷ୍ଟିକ୍ ଡାଟାକୁ ପଢ଼ିବ"</string>
     <string name="car_permission_desc_diag_read" msgid="1121426363040966178">"କାର୍‍ ମଧ୍ୟରୁ ଡାଏଗ୍ନୋଷ୍ଟିକ୍ ଡାଟାକୁ ପଢ଼ିବ।"</string>
     <string name="car_permission_label_diag_clear" msgid="4783070510879698157">"ଡାଏଗ୍ନୋଷ୍ଟିକ୍ ଡାଟା ଖାଲି କରନ୍ତୁ"</string>
@@ -89,6 +93,8 @@
     <string name="car_permission_desc_car_engine_detailed" msgid="1746863362811347700">"ଆପଣଙ୍କ କାର୍\'ର ବିସ୍ତୃତ ଇଞ୍ଜିନ୍ ସୂଚନାକୁ ଆକ୍‍‍ସେସ୍ କରନ୍ତୁ।"</string>
     <string name="car_permission_label_car_energy_ports" msgid="8548990315169219454">"କାର୍\'ର ଫୁଏଲ୍ ଡୋର୍ ଏବଂ ଚାର୍ଜ ପୋର୍ଟକୁ ଆକ୍‍‍ସେସ୍ କରନ୍ତୁ।"</string>
     <string name="car_permission_desc_car_energy_ports" msgid="7771185999828794949">"କାର୍\'ର ଫୁଏଲ୍‍ ଡୋର୍ ଏବଂ ଚାର୍ଜ ପୋର୍ଟକୁ ଆକ୍‍‍ସେସ୍ କରନ୍ତୁ।"</string>
+    <string name="car_permission_label_control_car_energy_ports" msgid="4375137311026313475">"କାରର ଫୁଏଲ୍ ଡୋର୍ ଏବଂ ଚାର୍ଜ ପୋର୍ଟକୁ ନିୟନ୍ତ୍ରଣ କରିପାରିବ"</string>
+    <string name="car_permission_desc_control_car_energy_ports" msgid="7364633710492525387">"କାରର ଫୁଏଲ୍ ଡୋର୍ ଏବଂ ଚାର୍ଜ ପୋର୍ଟକୁ ନିୟନ୍ତ୍ରଣ କରିପାରିବ।"</string>
     <string name="car_permission_label_car_identification" msgid="5896712510164020478">"କାର୍\'ର ଚିହ୍ନଟକରଣକୁ ପଢ଼ିବ"</string>
     <string name="car_permission_desc_car_identification" msgid="4132040867171275059">"କାର୍\'ର ଚିହ୍ନଟକରଣ ଆକ୍‍‍ସେସ୍ କରନ୍ତୁ।"</string>
     <string name="car_permission_label_control_car_doors" msgid="3032058819250195700">"କାର୍\'ର ଡୋର୍‌ଗୁଡ଼ିକୁ ନିୟନ୍ତ୍ରଣ କରନ୍ତୁ"</string>
@@ -101,6 +107,8 @@
     <string name="car_permission_desc_control_car_seats" msgid="2407536601226470563">"କାର୍\'ର ସିଟ୍‌ଗୁଡ଼ିକୁ ନିୟନ୍ତ୍ରଣ କରନ୍ତୁ।"</string>
     <string name="car_permission_label_car_info" msgid="4707513570676492315">"କାର୍\'ର ମୌଳିକ ସୂଚନା ଆକ୍‍‍ସେସ୍ କରନ୍ତୁ"</string>
     <string name="car_permission_desc_car_info" msgid="2118081474543537653">"କାର୍\'ର ମୌଳିକ ସୂଚନା ଆକ୍‍‍ସେସ୍‍ କରନ୍ତୁ।"</string>
+    <string name="car_permission_label_vendor_permission_info" msgid="4471260460536888654">"କାର୍\'ର ଭେଣ୍ଡର୍ ଅନୁମତି ସୂୂଚନା ଆକ୍‍‍ସେସ୍ କରନ୍ତୁ"</string>
+    <string name="car_permission_desc_vendor_permission_info" msgid="8152113853528488398">"କାର୍\'ର ଭେଣ୍ଡର୍ ଅନୁମତି ସୂୂଚନା ଆକ୍‍‍ସେସ୍ କରନ୍ତୁ।"</string>
     <string name="car_permission_label_car_exterior_lights" msgid="541304469604902110">"କାର୍\'ର ବାହାର ଲାଇଟ୍‌ଗୁଡ଼ିକର ଷ୍ଟେଟ୍‌କୁ ପଢ଼ିବ"</string>
     <string name="car_permission_desc_car_exterior_lights" msgid="4038037584100849318">"କାର୍\'ର ବାହାର ଲାଇଟ୍‌ଗୁଡ଼ିକର ଷ୍ଟେଟ୍‌କୁ ଆକ୍‍‍ସେସ୍ କରନ୍ତୁ।"</string>
     <string name="car_permission_label_control_car_exterior_lights" msgid="101357531386232141">"କାର୍\'ର ବାହାର ଲାଇଟ୍‌ଗୁଡ଼ିକର ସ୍ଥିତିକୁ ପଢ଼ିବ"</string>
@@ -127,5 +135,9 @@
     <string name="car_permission_desc_enroll_trust" msgid="4148649994602185130">"ଚିହ୍ନା ଡିଭାଇସ୍‌ ନାମାଙ୍କନକୁ ଅନୁମତି ଦିଅନ୍ତୁ"</string>
     <string name="car_permission_label_car_test_service" msgid="9159328930558208708">"କାର୍‍ର ପରୀକ୍ଷଣ ମୋଡ୍ ନିୟନ୍ତ୍ରଣ କରନ୍ତୁ"</string>
     <string name="car_permission_desc_car_test_service" msgid="7426844534110145843">"କାର୍‍ର ପରୀକ୍ଷଣ ମୋଡ୍ ନିୟନ୍ତ୍ରଣ କରନ୍ତୁ"</string>
+    <string name="car_permission_label_control_car_features" msgid="3905791560378888286">"କାରର ଫିଚର୍‌ଗୁଡ଼ିକୁ ସକ୍ଷମ କିମ୍ବା ଅକ୍ଷମ କରନ୍ତୁ"</string>
+    <string name="car_permission_desc_control_car_features" msgid="7646711104530599901">"କାରର ଫିଚର୍‌ଗୁଡ଼ିକୁ ସକ୍ଷମ କିମ୍ବା ଅକ୍ଷମ କରନ୍ତୁ।"</string>
+    <string name="car_permission_label_use_car_watchdog" msgid="6973938293170413475">"କାର ୱାଚଡଗ ବ୍ୟବହାର କରନ୍ତୁ"</string>
+    <string name="car_permission_desc_use_car_watchdog" msgid="8244592601805516086">"କାର ୱାଚଡଗ ବ୍ୟବହାର କରନ୍ତୁ।"</string>
     <string name="trust_device_default_name" msgid="4213625926070261253">"ମୋ ଡିଭାଇସ୍"</string>
 </resources>
diff --git a/service/res/values-pa/strings.xml b/service/res/values-pa/strings.xml
index 2d4bbd0..f4dee5d 100644
--- a/service/res/values-pa/strings.xml
+++ b/service/res/values-pa/strings.xml
@@ -22,6 +22,8 @@
     <string name="car_permission_desc_camera" msgid="917024932164501426">"ਤੁਹਾਡੀ ਕਾਰ ਦੇ ਕੈਮਰੇ ਤੱਕ ਪਹੁੰਚ।"</string>
     <string name="car_permission_label_energy" msgid="7409144323527821558">"ਕਾਰ ਦੀ ਊਰਜਾ ਸੰਬੰਧੀ ਜਾਣਕਾਰੀ ਤੱਕ ਪਹੁੰਚ"</string>
     <string name="car_permission_desc_energy" msgid="3392963810053235407">"ਤੁਹਾਡੀ ਕਾਰ ਦੀ ਊਰਜਾ ਸੰਬੰਧੀ ਜਾਣਕਾਰੀ ਤੱਕ ਪਹੁੰਚ।"</string>
+    <string name="car_permission_label_adjust_range_remaining" msgid="839033553999920138">"ਕਾਰ ਦੀ ਬਾਕੀ ਰੇਂਜ ਨੂੰ ਵਿਵਸਥਿਤ ਕਰੋ"</string>
+    <string name="car_permission_desc_adjust_range_remaining" msgid="2369321650437370673">"ਕਾਰ ਦੀ ਬਾਕੀ ਰੇਂਜ ਦੇ ਮੁੱਲ ਨੂੰ ਵਿਵਸਥਿਤ ਕਰੋ।"</string>
     <string name="car_permission_label_hvac" msgid="1499454192558727843">"ਕਾਰ ਦੇ hvac ਤੱਕ ਪਹੁੰਚ"</string>
     <string name="car_permission_desc_hvac" msgid="3754229695589774195">"ਤੁਹਾਡੀ ਕਾਰ ਦੇ hvac ਸਿਸਟਮ ਤੱਕ ਪਹੁੰਚ।"</string>
     <string name="car_permission_label_mileage" msgid="4661317074631150551">"ਕਾਰ ਦੀ ਮਾਈਲੇਜ ਜਾਣਕਾਰੀ ਤੱਕ ਪਹੁੰਚ"</string>
@@ -62,16 +64,17 @@
     <string name="car_permission_desc_car_ux_restrictions_configuration" msgid="5711926927484813777">"UX ਪਾਬੰਦੀਆਂ ਦਾ ਸੰਰੂਪਣ ਕਰੋ"</string>
     <string name="car_permission_label_car_handle_usb_aoap_device" msgid="72783989504378036">"AOAP ਮੋਡ ਵਿੱਚ USB ਡੀਵਾਈਸ ਨਾਲ ਸੰਚਾਰ ਕਰੋ"</string>
     <string name="car_permission_desc_car_handle_usb_aoap_device" msgid="273505990971317034">"ਕਿਸੇ ਐਪ ਨੂੰ AOAP ਮੋਡ ਵਿੱਚ ਡੀਵਾਈਸ ਨਾਲ ਸੰਚਾਰ ਕਰਨ ਦਿਓ"</string>
+    <string name="car_permission_label_read_car_occupant_awareness_state" msgid="125517953575032758">"ਕਾਰ ਚਲਾਉਣ ਵਾਲੇ ਲਈ ਜਾਗਰੂਕਤਾ ਸਿਸਟਮ ਨੂੰ ਪੜ੍ਹਨ ਦੀ ਪਹੁੰਚ"</string>
+    <string name="car_permission_desc_read_car_occupant_awareness_state" msgid="188865882598414986">"ਕਾਰ ਚਲਾਉਣ ਵਾਲੇ ਲਈ ਜਾਗਰੂਕਤਾ ਸਿਸਟਮ ਦੀ ਸਥਿਤੀ ਨੂੰ ਪੜ੍ਹਨ ਅਤੇ ਇਸਦੇ ਡਾਟੇ ਦਾ ਪਤਾ ਲਗਾਉਣ ਦੀ ਆਗਿਆ ਦਿੰਦਾ ਹੈ"</string>
+    <string name="car_permission_label_control_car_occupant_awareness_system" msgid="7163330266691094542">"ਕਾਰ ਚਲਾਉਣ ਵਾਲੇ ਲਈ ਜਾਗਰੂਕਤਾ ਸਿਸਟਮ ਦੇ ਗ੍ਰਾਫ਼ ਨੂੰ ਕੰਟਰੋਲ ਕਰੋ"</string>
+    <string name="car_permission_desc_control_car_occupant_awareness_system" msgid="7123482622084531911">"ਕਾਰ ਚਲਾਉਣ ਵਾਲੇ ਲਈ ਜਾਗਰੂਕਤਾ ਸਿਸਟਮ ਦੇ ਪਤਾ ਲਗਾਉਣ ਵਾਲੇ ਗ੍ਰਾਫ਼ ਨੂੰ ਸ਼ੁਰੂ ਕਰਨ ਅਤੇ ਰੋਕਣ ਦੀ ਆਗਿਆ ਦਿੰਦਾ ਹੈ"</string>
     <string name="car_permission_label_bind_input_service" msgid="6698489034024273750">"ਕਾਰ ਇਨਪੁੱਟ ਸਰਵਿਸ"</string>
     <string name="car_permission_desc_bind_input_service" msgid="1670323419931890170">"ਇਨਪੁੱਟ ਇਵੈਂਟਾਂ ਦੀ ਸੰਭਾਲ"</string>
     <string name="car_can_bus_failure" msgid="2334035748788283914">"CAN ਬੱਸ ਅਸਫਲ ਰਹੀ"</string>
     <string name="car_can_bus_failure_desc" msgid="4125516222786484733">"CAN ਬੱਸ ਕੰਮ ਨਹੀਂ ਕਰਦੀ। ਹੈੱਡ ਯੂਨਿਟ ਬਾਕਸ ਨੂੰ ਅਨਪਲੱਗ ਕਰੋ ਅਤੇ ਦੁਬਾਰਾ ਪਲੱਗ ਲਗਾ ਕੇ ਕਾਰ ਨੂੰ ਮੁੜ-ਚਾਲੂ ਕਰੋ"</string>
-    <!-- no translation found for activity_blocked_text (8088902789540147995) -->
-    <skip />
+    <string name="activity_blocked_text" msgid="5342114426610711378">"ਤੁਹਾਡੀ ਸੁਰੱਖਿਆ ਲਈ, ਗੱਡੀ ਚਲਾਉਣ ਵੇਲੇ ਇਹ ਸਰਗਰਮੀ ਉਪਲਬਧ ਨਹੀਂ ਹੈ।\nਜਾਰੀ ਰੱਖਣ ਲਈ, ਗੱਡੀ ਰੋਕਣ ਤੱਕ ਉਡੀਕ ਕਰੋ।"</string>
     <string name="exit_button_message" msgid="8554690915924055685">"ਸੁਰੱਖਿਅਤ ਐਪ ਵਿਸ਼ੇਸ਼ਤਾਵਾਂ ਨਾਲ ਮੁੜ ਤੋਂ ਸ਼ੁਰੂ ਕਰਨ ਲਈ, <xliff:g id="EXIT_BUTTON">%s</xliff:g> ਚੁਣੋ।"</string>
     <string name="exit_button" msgid="5829638404777671253">"ਪਿੱਛੇ"</string>
-    <string name="exit_button_close_application" msgid="8824289547809332460">"ਐਪ ਬੰਦ ਕਰੋ"</string>
-    <string name="exit_button_go_back" msgid="3469083862100560326">"ਪਿੱਛੇ"</string>
     <string name="car_permission_label_diag_read" msgid="7248894224877702604">"ਤਸ਼ਖੀਸੀ ਡਾਟੇ ਨੂੰ ਪੜ੍ਹਨਾ"</string>
     <string name="car_permission_desc_diag_read" msgid="1121426363040966178">"ਕਾਰ ਦਾ ਤਸ਼ਖੀਸੀ ਡਾਟਾ ਪੜ੍ਹਨਾ।"</string>
     <string name="car_permission_label_diag_clear" msgid="4783070510879698157">"ਤਸ਼ਖੀਸੀ ਡਾਟਾ ਕਲੀਅਰ ਕਰਨਾ"</string>
@@ -90,6 +93,8 @@
     <string name="car_permission_desc_car_engine_detailed" msgid="1746863362811347700">"ਤੁਹਾਡੀ ਕਾਰ ਦੇ ਇੰਜਣ ਦੀ ਵੇਰਵੇ ਸਹਿਤ ਜਾਣਕਾਰੀ ਤੱਕ ਪਹੁੰਚ।"</string>
     <string name="car_permission_label_car_energy_ports" msgid="8548990315169219454">"ਕਾਰ ਦੀ ਈਂਧਣ ਵਾਲੀ ਜਗ੍ਹਾ ਅਤੇ ਚਾਰਜ ਪੋਰਟ ਤੱਕ ਪਹੁੰਚ"</string>
     <string name="car_permission_desc_car_energy_ports" msgid="7771185999828794949">"ਕਾਰ ਦੀ ਈਂਧਣ ਵਾਲੀ ਜਗ੍ਹਾ ਅਤੇ ਚਾਰਜ ਪੋਰਟ ਤੱਕ ਪਹੁੰਚ।"</string>
+    <string name="car_permission_label_control_car_energy_ports" msgid="4375137311026313475">"ਕਾਰ ਦੀ ਈਂਧਣ ਭਰਨ ਵਾਲੀ ਜਗ੍ਹਾ ਦੇ ਢੱਕਣ ਅਤੇ ਚਾਰਜ ਪੋਰਟ ਨੂੰ ਕੰਟਰੋਲ ਕਰੋ"</string>
+    <string name="car_permission_desc_control_car_energy_ports" msgid="7364633710492525387">"ਕਾਰ ਦੀ ਈਂਧਣ ਭਰਨ ਵਾਲੀ ਜਗ੍ਹਾ ਦੇ ਢੱਕਣ ਅਤੇ ਚਾਰਜ ਪੋਰਟ ਨੂੰ ਕੰਟਰੋਲ ਕਰੋ।"</string>
     <string name="car_permission_label_car_identification" msgid="5896712510164020478">"ਕਾਰ ਦੀ ਪਛਾਣ ਤੱਕ ਪਹੁੰਚ"</string>
     <string name="car_permission_desc_car_identification" msgid="4132040867171275059">"ਕਾਰ ਦੀ ਪਛਾਣ ਤੱਕ ਪਹੁੰਚ।"</string>
     <string name="car_permission_label_control_car_doors" msgid="3032058819250195700">"ਕਾਰ ਦੀਆਂ ਤਾਕੀਆਂ \'ਤੇ ਕੰਟਰੋਲ"</string>
@@ -102,6 +107,8 @@
     <string name="car_permission_desc_control_car_seats" msgid="2407536601226470563">"ਕਾਰ ਦੀਆਂ ਸੀਟਾਂ \'ਤੇ ਕੰਟਰੋਲ।"</string>
     <string name="car_permission_label_car_info" msgid="4707513570676492315">"ਕਾਰ ਦੀ ਮੂਲ ਜਾਣਕਾਰੀ ਤੱਕ ਪਹੁੰਚ"</string>
     <string name="car_permission_desc_car_info" msgid="2118081474543537653">"ਕਾਰ ਦੀ ਮੂਲ ਜਾਣਕਾਰੀ ਤੱਕ ਪਹੁੰਚ।"</string>
+    <string name="car_permission_label_vendor_permission_info" msgid="4471260460536888654">"ਕਾਰ ਵਿਕਰੇਤਾ ਦੀ ਇਜਾਜ਼ਤ ਸੰਬੰਧੀ ਜਾਣਕਾਰੀ ਤੱਕ ਪਹੁੰਚ"</string>
+    <string name="car_permission_desc_vendor_permission_info" msgid="8152113853528488398">"ਕਾਰ ਵਿਕਰੇਤਾ ਦੀ ਇਜਾਜ਼ਤ ਸੰਬੰਧੀ ਜਾਣਕਾਰੀ ਤੱਕ ਪਹੁੰਚ।"</string>
     <string name="car_permission_label_car_exterior_lights" msgid="541304469604902110">"ਕਾਰ ਦੀਆਂ ਬਾਹਰੀ ਲਾਈਟਾਂ ਦੀ ਸਥਿਤੀ ਨੂੰ ਪੜ੍ਹਨਾ"</string>
     <string name="car_permission_desc_car_exterior_lights" msgid="4038037584100849318">"ਕਾਰ ਦੀਆਂ ਬਾਹਰੀ ਲਾਈਟਾਂ ਦੀ ਸਥਿਤੀ ਤੱਕ ਪਹੁੰਚ।"</string>
     <string name="car_permission_label_control_car_exterior_lights" msgid="101357531386232141">"ਕਾਰ ਦੀਆਂ ਬਾਹਰੀ ਲਾਈਟਾਂ \'ਤੇ ਕੰਟਰੋਲ"</string>
@@ -128,5 +135,9 @@
     <string name="car_permission_desc_enroll_trust" msgid="4148649994602185130">"ਭਰੋਸੇਯੋਗ ਡੀਵਾਈਸਾਂ ਨੂੰ ਦਰਜਾਬੰਦੀ ਕਰਨ ਦਿਓ"</string>
     <string name="car_permission_label_car_test_service" msgid="9159328930558208708">"ਕਾਰ ਦੇ ਜਾਂਚ ਮੋਡ \'ਤੇ ਕੰਟਰੋਲ"</string>
     <string name="car_permission_desc_car_test_service" msgid="7426844534110145843">"ਕਾਰ ਦੇ ਜਾਂਚ ਮੋਡ \'ਤੇ ਕੰਟਰੋਲ"</string>
+    <string name="car_permission_label_control_car_features" msgid="3905791560378888286">"ਕਾਰ ਦੀਆਂ ਵਿਸ਼ੇਸ਼ਤਾਵਾਂ ਨੂੰ ਚਾਲੂ ਜਾਂ ਬੰਦ ਕਰੋ"</string>
+    <string name="car_permission_desc_control_car_features" msgid="7646711104530599901">"ਕਾਰ ਦੀਆਂ ਵਿਸ਼ੇਸ਼ਤਾਵਾਂ ਨੂੰ ਚਾਲੂ ਜਾਂ ਬੰਦ ਕਰੋ।"</string>
+    <string name="car_permission_label_use_car_watchdog" msgid="6973938293170413475">"ਕਾਰ ਵਾਚਡੌਗ ਵਰਤੋ"</string>
+    <string name="car_permission_desc_use_car_watchdog" msgid="8244592601805516086">"ਕਾਰ ਵਾਚਡੌਗ ਵਰਤੋ।"</string>
     <string name="trust_device_default_name" msgid="4213625926070261253">"ਮੇਰਾ ਡੀਵਾਈਸ"</string>
 </resources>
diff --git a/service/res/values-pl/strings.xml b/service/res/values-pl/strings.xml
index e636a4a..0c21c9b 100644
--- a/service/res/values-pl/strings.xml
+++ b/service/res/values-pl/strings.xml
@@ -22,6 +22,8 @@
     <string name="car_permission_desc_camera" msgid="917024932164501426">"Dostęp do kamer samochodu."</string>
     <string name="car_permission_label_energy" msgid="7409144323527821558">"dostęp do informacji o zasilaniu w samochodzie"</string>
     <string name="car_permission_desc_energy" msgid="3392963810053235407">"Dostęp do informacji o zasilaniu w samochodzie."</string>
+    <string name="car_permission_label_adjust_range_remaining" msgid="839033553999920138">"korekta pozostałego zasięgu samochodu"</string>
+    <string name="car_permission_desc_adjust_range_remaining" msgid="2369321650437370673">"Korekta wartości pozostałego zasięgu samochodu."</string>
     <string name="car_permission_label_hvac" msgid="1499454192558727843">"dostęp do systemu sterowania temperaturą w samochodzie"</string>
     <string name="car_permission_desc_hvac" msgid="3754229695589774195">"Dostęp do systemu sterowania temperaturą w samochodzie."</string>
     <string name="car_permission_label_mileage" msgid="4661317074631150551">"dostęp do informacji o przebiegu samochodu"</string>
@@ -62,15 +64,17 @@
     <string name="car_permission_desc_car_ux_restrictions_configuration" msgid="5711926927484813777">"Konfigurowanie ograniczeń UX"</string>
     <string name="car_permission_label_car_handle_usb_aoap_device" msgid="72783989504378036">"Komunikowanie się z urządzeniem USB w trybie AOAP"</string>
     <string name="car_permission_desc_car_handle_usb_aoap_device" msgid="273505990971317034">"Zezwala aplikacji na komunikowanie się z urządzeniem w trybie AOAP"</string>
+    <string name="car_permission_label_read_car_occupant_awareness_state" msgid="125517953575032758">"Odczytywanie danych z systemu wykrywania osób"</string>
+    <string name="car_permission_desc_read_car_occupant_awareness_state" msgid="188865882598414986">"Umożliwia odczytywanie danych o stanie i działaniu systemu wykrywania osób"</string>
+    <string name="car_permission_label_control_car_occupant_awareness_system" msgid="7163330266691094542">"Sterowanie wykresem z systemu wykrywania osób"</string>
+    <string name="car_permission_desc_control_car_occupant_awareness_system" msgid="7123482622084531911">"Umożliwia włączanie i wyłączanie wykresu z systemu wykrywania osób"</string>
     <string name="car_permission_label_bind_input_service" msgid="6698489034024273750">"Usługa wprowadzania danych w samochodzie"</string>
     <string name="car_permission_desc_bind_input_service" msgid="1670323419931890170">"Obsługa zdarzeń wprowadzania danych"</string>
     <string name="car_can_bus_failure" msgid="2334035748788283914">"Błąd magistrali CAN"</string>
     <string name="car_can_bus_failure_desc" msgid="4125516222786484733">"Magistrala CAN nie odpowiada. Odłącz i jeszcze raz podłącz moduł główny i ponownie uruchom samochód."</string>
-    <string name="activity_blocked_text" msgid="8088902789540147995">"Podczas jazdy nie można korzystać z tej funkcji"</string>
+    <string name="activity_blocked_text" msgid="5342114426610711378">"Dla Twojego bezpieczeństwa ta funkcja jest wyłączona podczas jazdy.\nAby jej użyć, zaparkuj samochód."</string>
     <string name="exit_button_message" msgid="8554690915924055685">"Aby jeszcze raz przejść do funkcji bezpieczeństwa w aplikacji, wybierz <xliff:g id="EXIT_BUTTON">%s</xliff:g>."</string>
     <string name="exit_button" msgid="5829638404777671253">"Wstecz"</string>
-    <string name="exit_button_close_application" msgid="8824289547809332460">"Zamknij aplikację"</string>
-    <string name="exit_button_go_back" msgid="3469083862100560326">"Wstecz"</string>
     <string name="car_permission_label_diag_read" msgid="7248894224877702604">"odczytywanie danych diagnostycznych"</string>
     <string name="car_permission_desc_diag_read" msgid="1121426363040966178">"Odczytywanie danych diagnostycznych samochodu."</string>
     <string name="car_permission_label_diag_clear" msgid="4783070510879698157">"czyszczenie danych diagnostycznych"</string>
@@ -89,6 +93,8 @@
     <string name="car_permission_desc_car_engine_detailed" msgid="1746863362811347700">"Dostęp do szczegółowych informacji o silniku."</string>
     <string name="car_permission_label_car_energy_ports" msgid="8548990315169219454">"dostęp do drzwiczek wlewu paliwa i portu ładowania samochodu"</string>
     <string name="car_permission_desc_car_energy_ports" msgid="7771185999828794949">"Dostęp do drzwiczek wlewu paliwa i portu ładowania samochodu."</string>
+    <string name="car_permission_label_control_car_energy_ports" msgid="4375137311026313475">"sterowanie drzwiczkami wlewu paliwa i portu ładowania samochodu"</string>
+    <string name="car_permission_desc_control_car_energy_ports" msgid="7364633710492525387">"Sterowanie drzwiczkami wlewu paliwa i portu ładowania samochodu."</string>
     <string name="car_permission_label_car_identification" msgid="5896712510164020478">"odczytywanie danych identyfikacyjnych samochodu"</string>
     <string name="car_permission_desc_car_identification" msgid="4132040867171275059">"Dostęp do danych identyfikacyjnych samochodu."</string>
     <string name="car_permission_label_control_car_doors" msgid="3032058819250195700">"sterowanie drzwiami samochodu"</string>
@@ -101,6 +107,8 @@
     <string name="car_permission_desc_control_car_seats" msgid="2407536601226470563">"Sterowanie fotelami samochodowymi."</string>
     <string name="car_permission_label_car_info" msgid="4707513570676492315">"dostęp do podstawowych informacji o samochodzie"</string>
     <string name="car_permission_desc_car_info" msgid="2118081474543537653">"Dostęp do podstawowych informacji o samochodzie."</string>
+    <string name="car_permission_label_vendor_permission_info" msgid="4471260460536888654">"dostęp do informacji o uprawnieniach producenta samochodu"</string>
+    <string name="car_permission_desc_vendor_permission_info" msgid="8152113853528488398">"Dostęp do informacji o uprawnieniach producenta samochodu."</string>
     <string name="car_permission_label_car_exterior_lights" msgid="541304469604902110">"odczytywanie stanu zewnętrznych świateł samochodu"</string>
     <string name="car_permission_desc_car_exterior_lights" msgid="4038037584100849318">"Dostęp do stanu zewnętrznych świateł samochodu."</string>
     <string name="car_permission_label_control_car_exterior_lights" msgid="101357531386232141">"odczytywanie informacji o zewnętrznych światłach samochodu"</string>
@@ -127,5 +135,9 @@
     <string name="car_permission_desc_enroll_trust" msgid="4148649994602185130">"Zezwalaj na rejestrowanie zaufanych urządzeń"</string>
     <string name="car_permission_label_car_test_service" msgid="9159328930558208708">"Sterowanie trybem testowym w samochodzie"</string>
     <string name="car_permission_desc_car_test_service" msgid="7426844534110145843">"Sterowanie trybem testowym w samochodzie"</string>
+    <string name="car_permission_label_control_car_features" msgid="3905791560378888286">"Włączanie i wyłączanie funkcji samochodu"</string>
+    <string name="car_permission_desc_control_car_features" msgid="7646711104530599901">"Włączanie i wyłączanie funkcji samochodu."</string>
+    <string name="car_permission_label_use_car_watchdog" msgid="6973938293170413475">"używaj watchdoga w samochodzie"</string>
+    <string name="car_permission_desc_use_car_watchdog" msgid="8244592601805516086">"Używaj watchdoga w samochodzie."</string>
     <string name="trust_device_default_name" msgid="4213625926070261253">"Moje urządzenie"</string>
 </resources>
diff --git a/service/res/values-pt-rPT/strings.xml b/service/res/values-pt-rPT/strings.xml
index 5e99c31..43f9521 100644
--- a/service/res/values-pt-rPT/strings.xml
+++ b/service/res/values-pt-rPT/strings.xml
@@ -22,6 +22,8 @@
     <string name="car_permission_desc_camera" msgid="917024932164501426">"Aceda à(s) câmara(s) do automóvel."</string>
     <string name="car_permission_label_energy" msgid="7409144323527821558">"aceder às informações de energia do automóvel"</string>
     <string name="car_permission_desc_energy" msgid="3392963810053235407">"Aceda às informações de energia do automóvel."</string>
+    <string name="car_permission_label_adjust_range_remaining" msgid="839033553999920138">"ajustar o funcionamento restante do automóvel"</string>
+    <string name="car_permission_desc_adjust_range_remaining" msgid="2369321650437370673">"Ajuste o valor restante do funcionamento do automóvel."</string>
     <string name="car_permission_label_hvac" msgid="1499454192558727843">"aceder ao AVAC do automóvel"</string>
     <string name="car_permission_desc_hvac" msgid="3754229695589774195">"Aceda ao AVAC do automóvel."</string>
     <string name="car_permission_label_mileage" msgid="4661317074631150551">"aceder às informações de quilometragem do automóvel"</string>
@@ -62,15 +64,17 @@
     <string name="car_permission_desc_car_ux_restrictions_configuration" msgid="5711926927484813777">"Configure restrições da experiência do utilizador."</string>
     <string name="car_permission_label_car_handle_usb_aoap_device" msgid="72783989504378036">"Comunicar com o dispositivo USB no modo AOAP"</string>
     <string name="car_permission_desc_car_handle_usb_aoap_device" msgid="273505990971317034">"Permite que uma aplicação comunique com um dispositivo no modo AOAP."</string>
+    <string name="car_permission_label_read_car_occupant_awareness_state" msgid="125517953575032758">"Acesso de leitura do Sistema de deteção de ocupantes"</string>
+    <string name="car_permission_desc_read_car_occupant_awareness_state" msgid="188865882598414986">"Permite a leitura do estado e dos dados de deteção do Sistema de deteção de ocupantes."</string>
+    <string name="car_permission_label_control_car_occupant_awareness_system" msgid="7163330266691094542">"Controlar o gráfico do Sistema de deteção de ocupantes"</string>
+    <string name="car_permission_desc_control_car_occupant_awareness_system" msgid="7123482622084531911">"Permite controlar o início e a interrupção do gráfico de deteção do Sistema de deteção de ocupantes."</string>
     <string name="car_permission_label_bind_input_service" msgid="6698489034024273750">"Serviço de entrada do automóvel"</string>
     <string name="car_permission_desc_bind_input_service" msgid="1670323419931890170">"Processe eventos de entrada."</string>
     <string name="car_can_bus_failure" msgid="2334035748788283914">"Falha no CAN bus."</string>
     <string name="car_can_bus_failure_desc" msgid="4125516222786484733">"O CAN bus não responde. Desligue e volte a ligar a caixa da unidade principal e reinicie o automóvel."</string>
-    <string name="activity_blocked_text" msgid="8088902789540147995">"Não pode utilizar esta funcionalidade enquanto conduz."</string>
+    <string name="activity_blocked_text" msgid="5342114426610711378">"Para sua segurança, esta atividade não está disponível enquanto estiver a conduzir.\nPode continuar após estacionar."</string>
     <string name="exit_button_message" msgid="8554690915924055685">"Para começar de novo com funcionalidades de aplicações seguras, selecione <xliff:g id="EXIT_BUTTON">%s</xliff:g>."</string>
     <string name="exit_button" msgid="5829638404777671253">"Anterior"</string>
-    <string name="exit_button_close_application" msgid="8824289547809332460">"Fechar app"</string>
-    <string name="exit_button_go_back" msgid="3469083862100560326">"Anterior"</string>
     <string name="car_permission_label_diag_read" msgid="7248894224877702604">"ler os dados de diagnóstico"</string>
     <string name="car_permission_desc_diag_read" msgid="1121426363040966178">"Ler os dados de diagnóstico do automóvel."</string>
     <string name="car_permission_label_diag_clear" msgid="4783070510879698157">"limpar os dados de diagnóstico"</string>
@@ -89,6 +93,8 @@
     <string name="car_permission_desc_car_engine_detailed" msgid="1746863362811347700">"Aceder às informações detalhadas do motor do automóvel."</string>
     <string name="car_permission_label_car_energy_ports" msgid="8548990315169219454">"aceder à porta de carregamento e à tampa do depósito de combustível do automóvel"</string>
     <string name="car_permission_desc_car_energy_ports" msgid="7771185999828794949">"Aceder à porta de carregamento e à tampa do depósito de combustível do automóvel."</string>
+    <string name="car_permission_label_control_car_energy_ports" msgid="4375137311026313475">"controlar a porta de carregamento e a tampa do depósito de combustível do automóvel."</string>
+    <string name="car_permission_desc_control_car_energy_ports" msgid="7364633710492525387">"Controlar a porta de carregamento e a tampa do depósito de combustível do automóvel."</string>
     <string name="car_permission_label_car_identification" msgid="5896712510164020478">"ler a identificação do automóvel"</string>
     <string name="car_permission_desc_car_identification" msgid="4132040867171275059">"Aceder à identificação do automóvel."</string>
     <string name="car_permission_label_control_car_doors" msgid="3032058819250195700">"controlar as portas do automóvel"</string>
@@ -101,6 +107,8 @@
     <string name="car_permission_desc_control_car_seats" msgid="2407536601226470563">"Controlar os assentos do automóvel."</string>
     <string name="car_permission_label_car_info" msgid="4707513570676492315">"aceder às informações básicas acerca do automóvel"</string>
     <string name="car_permission_desc_car_info" msgid="2118081474543537653">"Aceder às informações básicas acerca do automóvel."</string>
+    <string name="car_permission_label_vendor_permission_info" msgid="4471260460536888654">"aceda às informações de autorização do fornecedor do automóvel."</string>
+    <string name="car_permission_desc_vendor_permission_info" msgid="8152113853528488398">"Aceda às informações de autorização do fornecedor do automóvel."</string>
     <string name="car_permission_label_car_exterior_lights" msgid="541304469604902110">"ler o estado das luzes exteriores do automóvel"</string>
     <string name="car_permission_desc_car_exterior_lights" msgid="4038037584100849318">"Aceder ao estado das luzes exteriores do automóvel."</string>
     <string name="car_permission_label_control_car_exterior_lights" msgid="101357531386232141">"ler as luzes exteriores do automóvel"</string>
@@ -127,5 +135,9 @@
     <string name="car_permission_desc_enroll_trust" msgid="4148649994602185130">"Permitir a inscrição de dispositivos fidedignos"</string>
     <string name="car_permission_label_car_test_service" msgid="9159328930558208708">"Controlar o modo de teste do automóvel"</string>
     <string name="car_permission_desc_car_test_service" msgid="7426844534110145843">"Controlar o modo de teste do automóvel"</string>
+    <string name="car_permission_label_control_car_features" msgid="3905791560378888286">"Ativar ou desativar as funcionalidades do automóvel"</string>
+    <string name="car_permission_desc_control_car_features" msgid="7646711104530599901">"Ative ou desative as funcionalidades do automóvel."</string>
+    <string name="car_permission_label_use_car_watchdog" msgid="6973938293170413475">"utilizar o watchdog do automóvel"</string>
+    <string name="car_permission_desc_use_car_watchdog" msgid="8244592601805516086">"Utilize o watchdog do automóvel."</string>
     <string name="trust_device_default_name" msgid="4213625926070261253">"Dispositivo"</string>
 </resources>
diff --git a/service/res/values-pt/strings.xml b/service/res/values-pt/strings.xml
index 89bc682..fa23098 100644
--- a/service/res/values-pt/strings.xml
+++ b/service/res/values-pt/strings.xml
@@ -22,6 +22,8 @@
     <string name="car_permission_desc_camera" msgid="917024932164501426">"Acessar câmeras do carro."</string>
     <string name="car_permission_label_energy" msgid="7409144323527821558">"acessar as informações de energia do carro"</string>
     <string name="car_permission_desc_energy" msgid="3392963810053235407">"Acessar informações de abastecimento do carro."</string>
+    <string name="car_permission_label_adjust_range_remaining" msgid="839033553999920138">"ajustar a autonomia restante do carro"</string>
+    <string name="car_permission_desc_adjust_range_remaining" msgid="2369321650437370673">"Ajustar o valor restante de autonomia do carro."</string>
     <string name="car_permission_label_hvac" msgid="1499454192558727843">"acessar o HVAC (Aquecimento, ventilação e ar-condicionado) do carro"</string>
     <string name="car_permission_desc_hvac" msgid="3754229695589774195">"Acessar o HVAC (Aquecimento, ventilação e ar-condicionado) do carro."</string>
     <string name="car_permission_label_mileage" msgid="4661317074631150551">"acessar informações de quilometragem do carro"</string>
@@ -62,15 +64,17 @@
     <string name="car_permission_desc_car_ux_restrictions_configuration" msgid="5711926927484813777">"Configurar restrições de UX"</string>
     <string name="car_permission_label_car_handle_usb_aoap_device" msgid="72783989504378036">"Comunicar com um dispositivo USB no modo AOAP"</string>
     <string name="car_permission_desc_car_handle_usb_aoap_device" msgid="273505990971317034">"Permite que um app se comunique com um dispositivo no modo AOAP"</string>
+    <string name="car_permission_label_read_car_occupant_awareness_state" msgid="125517953575032758">"Acesso de leitura ao sistema de detecção de ocupantes"</string>
+    <string name="car_permission_desc_read_car_occupant_awareness_state" msgid="188865882598414986">"Permite ler o status e os dados de detecção do sistema de detecção de ocupantes"</string>
+    <string name="car_permission_label_control_car_occupant_awareness_system" msgid="7163330266691094542">"Controlar o gráfico do sistema de detecção de ocupantes"</string>
+    <string name="car_permission_desc_control_car_occupant_awareness_system" msgid="7123482622084531911">"Permite controlar o início e fim do gráfico do sistema de detecção de ocupantes"</string>
     <string name="car_permission_label_bind_input_service" msgid="6698489034024273750">"Serviço de entrada do carro"</string>
     <string name="car_permission_desc_bind_input_service" msgid="1670323419931890170">"Gerenciar eventos de entrada"</string>
     <string name="car_can_bus_failure" msgid="2334035748788283914">"Falha no barramento CAN"</string>
     <string name="car_can_bus_failure_desc" msgid="4125516222786484733">"O barramento CAN parou de responder. Desconecte e conecte novamente a caixa da unidade principal, depois ligue o carro"</string>
-    <string name="activity_blocked_text" msgid="8088902789540147995">"Não é possível usar esse recurso enquanto você dirige"</string>
+    <string name="activity_blocked_text" msgid="5342114426610711378">"Para sua segurança, essa atividade não está disponível enquanto você dirige.\nPara continuar, aguarde até estacionar."</string>
     <string name="exit_button_message" msgid="8554690915924055685">"Para reiniciar o app com recursos de segurança, selecione <xliff:g id="EXIT_BUTTON">%s</xliff:g>."</string>
     <string name="exit_button" msgid="5829638404777671253">"Voltar"</string>
-    <string name="exit_button_close_application" msgid="8824289547809332460">"Fechar app"</string>
-    <string name="exit_button_go_back" msgid="3469083862100560326">"Voltar"</string>
     <string name="car_permission_label_diag_read" msgid="7248894224877702604">"ler dados de diagnóstico"</string>
     <string name="car_permission_desc_diag_read" msgid="1121426363040966178">"Ler dados de diagnóstico do carro."</string>
     <string name="car_permission_label_diag_clear" msgid="4783070510879698157">"limpar dados de diagnóstico"</string>
@@ -89,6 +93,8 @@
     <string name="car_permission_desc_car_engine_detailed" msgid="1746863362811347700">"Acessar informações detalhadas do motor do carro."</string>
     <string name="car_permission_label_car_energy_ports" msgid="8548990315169219454">"acessar as entradas de combustível e carregamento do carro"</string>
     <string name="car_permission_desc_car_energy_ports" msgid="7771185999828794949">"Acessar as entradas de combustível e carregamento do carro."</string>
+    <string name="car_permission_label_control_car_energy_ports" msgid="4375137311026313475">"controlar as entradas de combustível e carregamento do carro"</string>
+    <string name="car_permission_desc_control_car_energy_ports" msgid="7364633710492525387">"Controlar as entradas de combustível e carregamento do carro."</string>
     <string name="car_permission_label_car_identification" msgid="5896712510164020478">"ler identificação do carro"</string>
     <string name="car_permission_desc_car_identification" msgid="4132040867171275059">"Acessar identificação do carro."</string>
     <string name="car_permission_label_control_car_doors" msgid="3032058819250195700">"controlar portas do carro"</string>
@@ -101,6 +107,8 @@
     <string name="car_permission_desc_control_car_seats" msgid="2407536601226470563">"Controlar bancos do carro."</string>
     <string name="car_permission_label_car_info" msgid="4707513570676492315">"acessar informações básicas do carro"</string>
     <string name="car_permission_desc_car_info" msgid="2118081474543537653">"Acessar informações básicas do carro."</string>
+    <string name="car_permission_label_vendor_permission_info" msgid="4471260460536888654">"acessar informações de permissão do fornecedor do carro"</string>
+    <string name="car_permission_desc_vendor_permission_info" msgid="8152113853528488398">"Acessar informações de permissão do fornecedor do carro."</string>
     <string name="car_permission_label_car_exterior_lights" msgid="541304469604902110">"ler o estado das luzes externas do carro"</string>
     <string name="car_permission_desc_car_exterior_lights" msgid="4038037584100849318">"Acessar o estado das luzes externas do carro."</string>
     <string name="car_permission_label_control_car_exterior_lights" msgid="101357531386232141">"ler informações sobre as luzes externas do carro"</string>
@@ -127,5 +135,9 @@
     <string name="car_permission_desc_enroll_trust" msgid="4148649994602185130">"Permitir inscrição de dispositivo confiável"</string>
     <string name="car_permission_label_car_test_service" msgid="9159328930558208708">"Controlar modo de teste do carro"</string>
     <string name="car_permission_desc_car_test_service" msgid="7426844534110145843">"Controlar modo de teste do carro"</string>
+    <string name="car_permission_label_control_car_features" msgid="3905791560378888286">"Ativar ou desativar os recursos do carro"</string>
+    <string name="car_permission_desc_control_car_features" msgid="7646711104530599901">"Ativar ou desativar os recursos do carro."</string>
+    <string name="car_permission_label_use_car_watchdog" msgid="6973938293170413475">"usar watchdog do carro"</string>
+    <string name="car_permission_desc_use_car_watchdog" msgid="8244592601805516086">"Usar watchdog do carro."</string>
     <string name="trust_device_default_name" msgid="4213625926070261253">"Meu dispositivo"</string>
 </resources>
diff --git a/service/res/values-ro/strings.xml b/service/res/values-ro/strings.xml
index a77e92f..0b52ec8 100644
--- a/service/res/values-ro/strings.xml
+++ b/service/res/values-ro/strings.xml
@@ -22,6 +22,8 @@
     <string name="car_permission_desc_camera" msgid="917024932164501426">"Accesează camerele mașinii."</string>
     <string name="car_permission_label_energy" msgid="7409144323527821558">"Accesează informațiile despre nivelul de energie al mașinii"</string>
     <string name="car_permission_desc_energy" msgid="3392963810053235407">"Accesează informațiile despre energie ale mașinii."</string>
+    <string name="car_permission_label_adjust_range_remaining" msgid="839033553999920138">"ajustați restul distanței parcurse de mașină"</string>
+    <string name="car_permission_desc_adjust_range_remaining" msgid="2369321650437370673">"Ajustați restul distanței parcurse de mașină."</string>
     <string name="car_permission_label_hvac" msgid="1499454192558727843">"Accesează sistemul hvac al mașinii"</string>
     <string name="car_permission_desc_hvac" msgid="3754229695589774195">"Accesează sistemul hvac al mașinii."</string>
     <string name="car_permission_label_mileage" msgid="4661317074631150551">"Accesează informațiile despre kilometrajul mașinii"</string>
@@ -62,16 +64,17 @@
     <string name="car_permission_desc_car_ux_restrictions_configuration" msgid="5711926927484813777">"Configurați restricțiile UX"</string>
     <string name="car_permission_label_car_handle_usb_aoap_device" msgid="72783989504378036">"Comunică cu dispozitivul USB în modul AOAP"</string>
     <string name="car_permission_desc_car_handle_usb_aoap_device" msgid="273505990971317034">"Permite unei aplicații să comunice cu un dispozitiv în modul AOAP"</string>
+    <string name="car_permission_label_read_car_occupant_awareness_state" msgid="125517953575032758">"Acces de citire la Sistemul de avertizare privind ocupanții"</string>
+    <string name="car_permission_desc_read_car_occupant_awareness_state" msgid="188865882598414986">"Permite citirea stării și a datelor de detectare pentru Sistemul de avertizare privind ocupanții"</string>
+    <string name="car_permission_label_control_car_occupant_awareness_system" msgid="7163330266691094542">"Controlează graficul Sistemului de avertizare privind ocupanții"</string>
+    <string name="car_permission_desc_control_car_occupant_awareness_system" msgid="7123482622084531911">"Permite controlarea începerii și opririi graficului de detectare a Sistemului de avertizare privind ocupanții"</string>
     <string name="car_permission_label_bind_input_service" msgid="6698489034024273750">"Gestionează serviciul de intrare pentru mașină"</string>
     <string name="car_permission_desc_bind_input_service" msgid="1670323419931890170">"Gestionează evenimentele de intrare"</string>
     <string name="car_can_bus_failure" msgid="2334035748788283914">"Magistrala CAN nu a reușit"</string>
     <string name="car_can_bus_failure_desc" msgid="4125516222786484733">"Magistrala CAN nu răspunde. Deconectați și reconectați unitatea radio, apoi reporniți mașina"</string>
-    <!-- no translation found for activity_blocked_text (8088902789540147995) -->
-    <skip />
+    <string name="activity_blocked_text" msgid="5342114426610711378">"Pentru siguranța dvs., activitatea nu este disponibilă în timp ce conduceți.\nCa să continuați, așteptați să parcați."</string>
     <string name="exit_button_message" msgid="8554690915924055685">"Pentru a începe din nou cu funcțiile pentru aplicații sigure, selectați <xliff:g id="EXIT_BUTTON">%s</xliff:g>."</string>
     <string name="exit_button" msgid="5829638404777671253">"Înapoi"</string>
-    <string name="exit_button_close_application" msgid="8824289547809332460">"Închideți aplicația"</string>
-    <string name="exit_button_go_back" msgid="3469083862100560326">"Înapoi"</string>
     <string name="car_permission_label_diag_read" msgid="7248894224877702604">"Citește datele de diagnosticare"</string>
     <string name="car_permission_desc_diag_read" msgid="1121426363040966178">"Citește datele de diagnosticare de la mașină."</string>
     <string name="car_permission_label_diag_clear" msgid="4783070510879698157">"Șterge datele de diagnosticare"</string>
@@ -90,6 +93,8 @@
     <string name="car_permission_desc_car_engine_detailed" msgid="1746863362811347700">"Accesează informațiile detaliate despre motorul mașinii"</string>
     <string name="car_permission_label_car_energy_ports" msgid="8548990315169219454">"Accesează ușa de alimentare a mașinii și portul de încărcare"</string>
     <string name="car_permission_desc_car_energy_ports" msgid="7771185999828794949">"Accesează ușa de alimentare a mașinii și portul de încărcare."</string>
+    <string name="car_permission_label_control_car_energy_ports" msgid="4375137311026313475">"controlează ușa de alimentare a mașinii și portul de încărcare"</string>
+    <string name="car_permission_desc_control_car_energy_ports" msgid="7364633710492525387">"Controlează ușa de alimentare a mașinii și portul de încărcare."</string>
     <string name="car_permission_label_car_identification" msgid="5896712510164020478">"Citește informațiile de identificare a mașinii"</string>
     <string name="car_permission_desc_car_identification" msgid="4132040867171275059">"Accesează informațiile de identificare a mașinii."</string>
     <string name="car_permission_label_control_car_doors" msgid="3032058819250195700">"Controlează portierele mașinii"</string>
@@ -102,6 +107,8 @@
     <string name="car_permission_desc_control_car_seats" msgid="2407536601226470563">"Controlează locurile din mașină."</string>
     <string name="car_permission_label_car_info" msgid="4707513570676492315">"Accesează informațiile de bază despre mașină"</string>
     <string name="car_permission_desc_car_info" msgid="2118081474543537653">"Accesează informațiile de bază despre mașină."</string>
+    <string name="car_permission_label_vendor_permission_info" msgid="4471260460536888654">"accesați informațiile despre permisiuni ale furnizorului mașinii"</string>
+    <string name="car_permission_desc_vendor_permission_info" msgid="8152113853528488398">"Accesați informațiile despre permisiuni ale furnizorului mașinii."</string>
     <string name="car_permission_label_car_exterior_lights" msgid="541304469604902110">"Citește starea luminilor exterioare ale mașinii"</string>
     <string name="car_permission_desc_car_exterior_lights" msgid="4038037584100849318">"Accesează starea luminilor exterioare ale mașinii."</string>
     <string name="car_permission_label_control_car_exterior_lights" msgid="101357531386232141">"Citește starea luminilor exterioare ale mașinii"</string>
@@ -128,5 +135,9 @@
     <string name="car_permission_desc_enroll_trust" msgid="4148649994602185130">"Permiteți înscrierea unui dispozitiv de încredere"</string>
     <string name="car_permission_label_car_test_service" msgid="9159328930558208708">"Controlați modul de testare a mașinii"</string>
     <string name="car_permission_desc_car_test_service" msgid="7426844534110145843">"Controlați modul de testare a mașinii"</string>
+    <string name="car_permission_label_control_car_features" msgid="3905791560378888286">"Activați sau dezactivați funcțiile mașinii"</string>
+    <string name="car_permission_desc_control_car_features" msgid="7646711104530599901">"Activați sau dezactivați funcțiile mașinii."</string>
+    <string name="car_permission_label_use_car_watchdog" msgid="6973938293170413475">"folosiți ceasul de gardă al mașinii"</string>
+    <string name="car_permission_desc_use_car_watchdog" msgid="8244592601805516086">"Folosiți ceasul de gardă al mașinii."</string>
     <string name="trust_device_default_name" msgid="4213625926070261253">"Dispozitivul meu"</string>
 </resources>
diff --git a/service/res/values-ru/strings.xml b/service/res/values-ru/strings.xml
index 2005130..913be79 100644
--- a/service/res/values-ru/strings.xml
+++ b/service/res/values-ru/strings.xml
@@ -22,6 +22,8 @@
     <string name="car_permission_desc_camera" msgid="917024932164501426">"Доступ к камерам автомобиля"</string>
     <string name="car_permission_label_energy" msgid="7409144323527821558">"доступ к данным об энергоресурсах автомобиля"</string>
     <string name="car_permission_desc_energy" msgid="3392963810053235407">"Доступ к данным об энергоресурсах автомобиля"</string>
+    <string name="car_permission_label_adjust_range_remaining" msgid="839033553999920138">"Изменение расстояния, которое проедет автомобиль без дозаправки"</string>
+    <string name="car_permission_desc_adjust_range_remaining" msgid="2369321650437370673">"Изменение расстояния, которое проедет автомобиль без дозаправки"</string>
     <string name="car_permission_label_hvac" msgid="1499454192558727843">"доступ к системе ОВиК"</string>
     <string name="car_permission_desc_hvac" msgid="3754229695589774195">"Доступ к системе ОВиК"</string>
     <string name="car_permission_label_mileage" msgid="4661317074631150551">"доступ к данным о пробеге"</string>
@@ -62,16 +64,17 @@
     <string name="car_permission_desc_car_ux_restrictions_configuration" msgid="5711926927484813777">"Возможность ограничивать использование функций"</string>
     <string name="car_permission_label_car_handle_usb_aoap_device" msgid="72783989504378036">"Обмен данными с USB-устройством в режиме AOAP"</string>
     <string name="car_permission_desc_car_handle_usb_aoap_device" msgid="273505990971317034">"Приложение сможет обмениваться данными с устройством в режиме AOAP."</string>
+    <string name="car_permission_label_read_car_occupant_awareness_state" msgid="125517953575032758">"Доступ к считыванию состояния функции Occupant Awareness System"</string>
+    <string name="car_permission_desc_read_car_occupant_awareness_state" msgid="188865882598414986">"Считывание статуса и данных функции Occupant Awareness System"</string>
+    <string name="car_permission_label_control_car_occupant_awareness_system" msgid="7163330266691094542">"Управление графиком функции Occupant Awareness System"</string>
+    <string name="car_permission_desc_control_car_occupant_awareness_system" msgid="7123482622084531911">"Управление началом и окончанием работы графика функции Occupant Awareness System"</string>
     <string name="car_permission_label_bind_input_service" msgid="6698489034024273750">"Автомобильная служба ввода"</string>
     <string name="car_permission_desc_bind_input_service" msgid="1670323419931890170">"Возможность обрабатывать события ввода"</string>
     <string name="car_can_bus_failure" msgid="2334035748788283914">"Произошла ошибка шины CAN."</string>
     <string name="car_can_bus_failure_desc" msgid="4125516222786484733">"Шина CAN не отвечает. Переподключите коннектор, а затем выключите зажигание и заведите машину снова."</string>
-    <!-- no translation found for activity_blocked_text (8088902789540147995) -->
-    <skip />
+    <string name="activity_blocked_text" msgid="5342114426610711378">"Во время вождения это действие недоступно.\nСначала вам нужно припарковаться."</string>
     <string name="exit_button_message" msgid="8554690915924055685">"Чтобы перезапустить приложение в безопасном режиме, нажмите кнопку \"<xliff:g id="EXIT_BUTTON">%s</xliff:g>\"."</string>
     <string name="exit_button" msgid="5829638404777671253">"Назад"</string>
-    <string name="exit_button_close_application" msgid="8824289547809332460">"Закрыть приложение"</string>
-    <string name="exit_button_go_back" msgid="3469083862100560326">"Назад"</string>
     <string name="car_permission_label_diag_read" msgid="7248894224877702604">"чтение диагностических данных"</string>
     <string name="car_permission_desc_diag_read" msgid="1121426363040966178">"Чтение диагностических данных автомобиля."</string>
     <string name="car_permission_label_diag_clear" msgid="4783070510879698157">"удаление диагностических данных"</string>
@@ -90,6 +93,8 @@
     <string name="car_permission_desc_car_engine_detailed" msgid="1746863362811347700">"Доступ к подробным данным о двигателе."</string>
     <string name="car_permission_label_car_energy_ports" msgid="8548990315169219454">"доступ к лючку топливного бака и порту зарядки"</string>
     <string name="car_permission_desc_car_energy_ports" msgid="7771185999828794949">"Доступ к лючку топливного бака и порту зарядки."</string>
+    <string name="car_permission_label_control_car_energy_ports" msgid="4375137311026313475">"управление лючком топливного бака и портом зарядки"</string>
+    <string name="car_permission_desc_control_car_energy_ports" msgid="7364633710492525387">"Управление лючком топливного бака и портом зарядки."</string>
     <string name="car_permission_label_car_identification" msgid="5896712510164020478">"чтение идентификационных данных автомобиля"</string>
     <string name="car_permission_desc_car_identification" msgid="4132040867171275059">"Доступ к идентификационным данным автомобиля."</string>
     <string name="car_permission_label_control_car_doors" msgid="3032058819250195700">"управление дверями"</string>
@@ -102,6 +107,8 @@
     <string name="car_permission_desc_control_car_seats" msgid="2407536601226470563">"Управление сиденьями."</string>
     <string name="car_permission_label_car_info" msgid="4707513570676492315">"Доступ к общим данным об автомобиле"</string>
     <string name="car_permission_desc_car_info" msgid="2118081474543537653">"Доступ к общим данным об автомобиле."</string>
+    <string name="car_permission_label_vendor_permission_info" msgid="4471260460536888654">"доступ к информации о фирменных разрешениях"</string>
+    <string name="car_permission_desc_vendor_permission_info" msgid="8152113853528488398">"Доступ к информации о фирменных разрешениях."</string>
     <string name="car_permission_label_car_exterior_lights" msgid="541304469604902110">"чтение данных о состоянии внешних осветительных приборов"</string>
     <string name="car_permission_desc_car_exterior_lights" msgid="4038037584100849318">"Доступ к данным о состоянии внешних осветительных приборов."</string>
     <string name="car_permission_label_control_car_exterior_lights" msgid="101357531386232141">"чтение данных о состоянии внешних осветительных приборов"</string>
@@ -128,5 +135,9 @@
     <string name="car_permission_desc_enroll_trust" msgid="4148649994602185130">"Разрешить регистрацию надежных устройств."</string>
     <string name="car_permission_label_car_test_service" msgid="9159328930558208708">"Управление тестовым режимом"</string>
     <string name="car_permission_desc_car_test_service" msgid="7426844534110145843">"Управление тестовым режимом"</string>
+    <string name="car_permission_label_control_car_features" msgid="3905791560378888286">"Включение и отключение функций автомобиля"</string>
+    <string name="car_permission_desc_control_car_features" msgid="7646711104530599901">"Включение и отключение функций автомобиля."</string>
+    <string name="car_permission_label_use_car_watchdog" msgid="6973938293170413475">"использование системы мониторинга автомобиля"</string>
+    <string name="car_permission_desc_use_car_watchdog" msgid="8244592601805516086">"Использование системы мониторинга автомобиля."</string>
     <string name="trust_device_default_name" msgid="4213625926070261253">"Мое устройство"</string>
 </resources>
diff --git a/service/res/values-si/strings.xml b/service/res/values-si/strings.xml
index 28ef417..a762579 100644
--- a/service/res/values-si/strings.xml
+++ b/service/res/values-si/strings.xml
@@ -22,6 +22,8 @@
     <string name="car_permission_desc_camera" msgid="917024932164501426">"ඔබේ මෝටර් රථයේ කැමරා(ව) වෙත ප්‍රවේශ වන්න."</string>
     <string name="car_permission_label_energy" msgid="7409144323527821558">"මෝටර් රථයේ බලශක්ති තොරතුරුවලට ප්‍රවේශ වන්න"</string>
     <string name="car_permission_desc_energy" msgid="3392963810053235407">"ඔබේ මෝටර් රථයේ බල ශක්ති තොරතුරු වෙත ප්‍රවේශ වන්න."</string>
+    <string name="car_permission_label_adjust_range_remaining" msgid="839033553999920138">"මෝටර් රථ පරාසයේ ඉතිරිය ගළපන්න"</string>
+    <string name="car_permission_desc_adjust_range_remaining" msgid="2369321650437370673">"මෝටර් රථ පරාසයේ ඉතිරිව ඇති අගය ගළපන්න."</string>
     <string name="car_permission_label_hvac" msgid="1499454192558727843">"මෝටර් රථයේ hvac වෙත ප්‍රවේශ වන්න"</string>
     <string name="car_permission_desc_hvac" msgid="3754229695589774195">"ඔබේ මෝටර් රථයේ hvac වෙත ප්‍රවේශ වන්න."</string>
     <string name="car_permission_label_mileage" msgid="4661317074631150551">"මෝටර් රථයේ ධාවන දුර තොරතුරුවලට ප්‍රවේශ වන්න"</string>
@@ -62,16 +64,17 @@
     <string name="car_permission_desc_car_ux_restrictions_configuration" msgid="5711926927484813777">"UX සීමා කිරීම් වින්‍යාස කරන්න"</string>
     <string name="car_permission_label_car_handle_usb_aoap_device" msgid="72783989504378036">"AOAP ප්‍රකාරයේ USB උපාංගය සමඟ සන්නිවේදනය කරන්න"</string>
     <string name="car_permission_desc_car_handle_usb_aoap_device" msgid="273505990971317034">"යෙදුමකට AOAP ප්‍රකාරය තුළ උපාංගයක් සමඟ සන්නිවේදනය කිරීමට ඉඩ දෙයි"</string>
+    <string name="car_permission_label_read_car_occupant_awareness_state" msgid="125517953575032758">"පදිංචිකරුවන් දැනුවත් කිරීමේ පද්ධති කියවීම් ප්‍රවේශය"</string>
+    <string name="car_permission_desc_read_car_occupant_awareness_state" msgid="188865882598414986">"පදිංචිකරුවන් දැනුවත් කිරීමේ පද්ධතිය සඳහා තත්ත්වය සහ අනාවරණ දත්ත කියවීමට ඉඩ දෙයි"</string>
+    <string name="car_permission_label_control_car_occupant_awareness_system" msgid="7163330266691094542">"පදිංචිකරුවන් දැනුවත් කිරීමේ පද්ධති ප්‍රස්ථාරය"</string>
+    <string name="car_permission_desc_control_car_occupant_awareness_system" msgid="7123482622084531911">"පදිංචිකරුවන් දැනුවත් කිරීමේ පද්ධති අනාවරණ ප්‍රස්ථාරයෙහි ආරම්භය සහ නැවැත්වීම පාලනයට ඉඩ දෙයි"</string>
     <string name="car_permission_label_bind_input_service" msgid="6698489034024273750">"මෝටර් රථ ආදාන සේවය"</string>
     <string name="car_permission_desc_bind_input_service" msgid="1670323419931890170">"ආදාන සිදුවීම් පරිහරණ කරන්න"</string>
     <string name="car_can_bus_failure" msgid="2334035748788283914">"CAN බස් අසාර්ථකයි"</string>
     <string name="car_can_bus_failure_desc" msgid="4125516222786484733">"CAN බස් ප්‍රතිචාර නොදක්වයි. හෙඩ්යුනිට් පෙට්ටිය පේනු ඉවත් කර ආපසු පේනුගත කර, මෝටර් රථය යළි අරඹන්න"</string>
-    <!-- no translation found for activity_blocked_text (8088902789540147995) -->
-    <skip />
+    <string name="activity_blocked_text" msgid="5342114426610711378">"ඔබේ ආරක්‍ෂාව සඳහා, ඔබ රිය පදවන විට මෙම ක්‍රියාකාරකම නොලැබේ.\nඉදිරියට යාමට, ඔබ ගාල් කරන තෙක් රැඳී සිටින්න."</string>
     <string name="exit_button_message" msgid="8554690915924055685">"ආරක්‍ෂිත යෙදුම් විශේෂාංග සමඟ පටන් ගැනීමට, <xliff:g id="EXIT_BUTTON">%s</xliff:g> තෝරන්න."</string>
     <string name="exit_button" msgid="5829638404777671253">"ආපසු"</string>
-    <string name="exit_button_close_application" msgid="8824289547809332460">"යෙදුම වසන්න"</string>
-    <string name="exit_button_go_back" msgid="3469083862100560326">"ආපසු"</string>
     <string name="car_permission_label_diag_read" msgid="7248894224877702604">"දෝෂනිර්ණ දත්ත කියවන්න"</string>
     <string name="car_permission_desc_diag_read" msgid="1121426363040966178">"මෝටර් රථයෙන් දෝෂනිර්ණ දත්ත කියවන්න."</string>
     <string name="car_permission_label_diag_clear" msgid="4783070510879698157">"දෝෂනිර්ණ දත්ත හිස් කරන්න"</string>
@@ -90,6 +93,8 @@
     <string name="car_permission_desc_car_engine_detailed" msgid="1746863362811347700">"ඔබේ මෝටර් රථයේ විස්තරාත්මක එන්ජින් තොරතුරුවලට ප්‍රවේශ වන්න."</string>
     <string name="car_permission_label_car_energy_ports" msgid="8548990315169219454">"මෝටර් රථයේ ඉන්ධන දොර සහ ආරෝපණ කවුළුවට ප්‍රවේශ වන්න"</string>
     <string name="car_permission_desc_car_energy_ports" msgid="7771185999828794949">"මෝටර් රථයේ ඉන්ධන දොර සහ ආරෝපණ කවුළුවට ප්‍රවේශ වන්න."</string>
+    <string name="car_permission_label_control_car_energy_ports" msgid="4375137311026313475">"මෝටර් රථයේ ඉන්ධන දොර සහ ආරෝපණ කවුළුව පාලනය"</string>
+    <string name="car_permission_desc_control_car_energy_ports" msgid="7364633710492525387">"මෝටර් රථයේ ඉන්ධන දොර සහ ආරෝපණ කවුළුව පාලනය කරන්න."</string>
     <string name="car_permission_label_car_identification" msgid="5896712510164020478">"මෝටර් රථයේ අනන්‍යතාවය කියවන්න"</string>
     <string name="car_permission_desc_car_identification" msgid="4132040867171275059">"මෝටර් රථයේ අනන්‍යතාවයට ප්‍රවේශ වන්න."</string>
     <string name="car_permission_label_control_car_doors" msgid="3032058819250195700">"මෝටර් රථයේ දොරවල් පාලනය කරන්න"</string>
@@ -102,6 +107,8 @@
     <string name="car_permission_desc_control_car_seats" msgid="2407536601226470563">"මෝටර් රථයේ ආසන පාලනය කරන්න."</string>
     <string name="car_permission_label_car_info" msgid="4707513570676492315">"මෝටර් රථයේ මූලික තොරතුරුවලට ප්‍රවේශ වන්න"</string>
     <string name="car_permission_desc_car_info" msgid="2118081474543537653">"මෝටර් රථයේ මූලික තොරතුරුවලට ප්‍රවේශ වන්න."</string>
+    <string name="car_permission_label_vendor_permission_info" msgid="4471260460536888654">"මෝටර් රථයේ විකුණුම්කරුගේ අවසර තොරතුරු වෙත ප්‍රවේශ වන්න"</string>
+    <string name="car_permission_desc_vendor_permission_info" msgid="8152113853528488398">"මෝටර් රථයේ විකුණුම්කරුගේ අවසර තොරතුරු වෙත ප්‍රවේශ වන්න."</string>
     <string name="car_permission_label_car_exterior_lights" msgid="541304469604902110">"මෝටර් රථයේ බාහිර ආලෝක තත්ත්වය කියවන්න"</string>
     <string name="car_permission_desc_car_exterior_lights" msgid="4038037584100849318">"මෝටර් රථයේ බාහිර ආලෝක තත්ත්වයට ප්‍රවේශ වන්න."</string>
     <string name="car_permission_label_control_car_exterior_lights" msgid="101357531386232141">"මෝටර් රථයේ බාහිර ආලෝකයන් කියවන්න"</string>
@@ -128,5 +135,9 @@
     <string name="car_permission_desc_enroll_trust" msgid="4148649994602185130">"විශ්වාසී උපාංග ඇතුළත් කිරීමට ඉඩ දෙන්න"</string>
     <string name="car_permission_label_car_test_service" msgid="9159328930558208708">"මෝටර් රථයේ පරීක්ෂණ ප්‍රකාරය පාලනය"</string>
     <string name="car_permission_desc_car_test_service" msgid="7426844534110145843">"මෝටර් රථයේ පරීක්ෂණ ප්‍රකාරය පාලනය"</string>
+    <string name="car_permission_label_control_car_features" msgid="3905791560378888286">"මෝටර් රථයේ විශේෂාංග සබල හෝ අබල කරන්න"</string>
+    <string name="car_permission_desc_control_car_features" msgid="7646711104530599901">"මෝටර් රථයේ විශේෂාංග සබල හෝ අබල කරන්න."</string>
+    <string name="car_permission_label_use_car_watchdog" msgid="6973938293170413475">"රිය මුරබල්ලා භාවිතා කරන්න"</string>
+    <string name="car_permission_desc_use_car_watchdog" msgid="8244592601805516086">"රිය මුරබල්ලා භාවිතා කරන්න."</string>
     <string name="trust_device_default_name" msgid="4213625926070261253">"මගේ උපාංගය"</string>
 </resources>
diff --git a/service/res/values-sk/strings.xml b/service/res/values-sk/strings.xml
index 609a09a..20a14b6 100644
--- a/service/res/values-sk/strings.xml
+++ b/service/res/values-sk/strings.xml
@@ -22,6 +22,8 @@
     <string name="car_permission_desc_camera" msgid="917024932164501426">"Získajte prístup ku kamerám auta."</string>
     <string name="car_permission_label_energy" msgid="7409144323527821558">"získať prístup k informáciám o energii auta"</string>
     <string name="car_permission_desc_energy" msgid="3392963810053235407">"Získajte prístup k informáciám o palive a energii auta."</string>
+    <string name="car_permission_label_adjust_range_remaining" msgid="839033553999920138">"úprava dojazdu auta – zostatok"</string>
+    <string name="car_permission_desc_adjust_range_remaining" msgid="2369321650437370673">"Úprava zostávajúcej hodnoty dojazdu auta."</string>
     <string name="car_permission_label_hvac" msgid="1499454192558727843">"získať prístup ku kúreniu, vzduchotechnike a klimatizácii auta"</string>
     <string name="car_permission_desc_hvac" msgid="3754229695589774195">"Získajte prístup k vykurovaniu, ventilácii a klimatizácii auta."</string>
     <string name="car_permission_label_mileage" msgid="4661317074631150551">"získať prístup k informáciám o spotrebe auta"</string>
@@ -62,16 +64,17 @@
     <string name="car_permission_desc_car_ux_restrictions_configuration" msgid="5711926927484813777">"Konfigurovať obmedzenia dojmu používateľa"</string>
     <string name="car_permission_label_car_handle_usb_aoap_device" msgid="72783989504378036">"Komunikovať so zariadením USB v režime AOAP"</string>
     <string name="car_permission_desc_car_handle_usb_aoap_device" msgid="273505990971317034">"Umožňuje aplikácii komunikovať so zariadením v režime AOAP"</string>
+    <string name="car_permission_label_read_car_occupant_awareness_state" msgid="125517953575032758">"Prístup na čítanie do systému detekcie posádky"</string>
+    <string name="car_permission_desc_read_car_occupant_awareness_state" msgid="188865882598414986">"Umožňuje čítať stav a údaje o detekcii zo systému detekcie posádky"</string>
+    <string name="car_permission_label_control_car_occupant_awareness_system" msgid="7163330266691094542">"Ovládanie grafu systému detekcie posádky"</string>
+    <string name="car_permission_desc_control_car_occupant_awareness_system" msgid="7123482622084531911">"Umožňuje ovládať spustenie a zastavenie grafu detekcie systému detekcie posádky"</string>
     <string name="car_permission_label_bind_input_service" msgid="6698489034024273750">"Služba vstupov auta"</string>
     <string name="car_permission_desc_bind_input_service" msgid="1670323419931890170">"Spravovať udalosti vstupu"</string>
     <string name="car_can_bus_failure" msgid="2334035748788283914">"Zbernica CAN zlyhala"</string>
     <string name="car_can_bus_failure_desc" msgid="4125516222786484733">"Zbernica CAN nereaguje. Odpojte autorádio a znova ho pripojte. Potom auto znova naštartujte."</string>
-    <!-- no translation found for activity_blocked_text (8088902789540147995) -->
-    <skip />
+    <string name="activity_blocked_text" msgid="5342114426610711378">"Z bezpečnostných dôvodov nie je táto aktivita k dispozícii počas jazdy.\nAk chcete pokračovať, počkajte, kým budete parkovať."</string>
     <string name="exit_button_message" msgid="8554690915924055685">"Ak chcete začať odznova s bezpečnými funkciami aplikácie, vyberte tlačidlo <xliff:g id="EXIT_BUTTON">%s</xliff:g>."</string>
     <string name="exit_button" msgid="5829638404777671253">"Späť"</string>
-    <string name="exit_button_close_application" msgid="8824289547809332460">"Zavrieť aplikáciu"</string>
-    <string name="exit_button_go_back" msgid="3469083862100560326">"Späť"</string>
     <string name="car_permission_label_diag_read" msgid="7248894224877702604">"čítať diagnostické údaje"</string>
     <string name="car_permission_desc_diag_read" msgid="1121426363040966178">"Čítanie diagnostických údajov z auta."</string>
     <string name="car_permission_label_diag_clear" msgid="4783070510879698157">"vymazať diagnostické údaje"</string>
@@ -90,6 +93,8 @@
     <string name="car_permission_desc_car_engine_detailed" msgid="1746863362811347700">"Získajte prístup k podrobným informáciám o motore auta."</string>
     <string name="car_permission_label_car_energy_ports" msgid="8548990315169219454">"získať prístup k dvierkam palivovej nádrže a nabíjaciemu portu"</string>
     <string name="car_permission_desc_car_energy_ports" msgid="7771185999828794949">"Prístup k dvierkam palivovej nádrže a nabíjaciemu portu."</string>
+    <string name="car_permission_label_control_car_energy_ports" msgid="4375137311026313475">"ovládať dvierka palivovej nádrže a nabíjací port"</string>
+    <string name="car_permission_desc_control_car_energy_ports" msgid="7364633710492525387">"Ovládanie dvierok palivovej nádrže a nabíjacieho portu"</string>
     <string name="car_permission_label_car_identification" msgid="5896712510164020478">"čítať identifikačné číslo auta"</string>
     <string name="car_permission_desc_car_identification" msgid="4132040867171275059">"Prístup k identifikačnému číslu auta."</string>
     <string name="car_permission_label_control_car_doors" msgid="3032058819250195700">"ovládať dvere auta"</string>
@@ -102,6 +107,8 @@
     <string name="car_permission_desc_control_car_seats" msgid="2407536601226470563">"Ovládanie sedadiel auta."</string>
     <string name="car_permission_label_car_info" msgid="4707513570676492315">"získať prístup k základným informáciám o aute"</string>
     <string name="car_permission_desc_car_info" msgid="2118081474543537653">"Prístup k základným informáciám o aute."</string>
+    <string name="car_permission_label_vendor_permission_info" msgid="4471260460536888654">"prístup k informáciám o povoleniach dodávateľa auta"</string>
+    <string name="car_permission_desc_vendor_permission_info" msgid="8152113853528488398">"Prístup k informáciám o povoleniach dodávateľa auta."</string>
     <string name="car_permission_label_car_exterior_lights" msgid="541304469604902110">"čítať stav vonkajších svetiel auta"</string>
     <string name="car_permission_desc_car_exterior_lights" msgid="4038037584100849318">"Prístup k stavu vonkajších svetiel auta."</string>
     <string name="car_permission_label_control_car_exterior_lights" msgid="101357531386232141">"čítať vonkajšie svetlá auta"</string>
@@ -128,5 +135,9 @@
     <string name="car_permission_desc_enroll_trust" msgid="4148649994602185130">"Povoliť registráciu dôveryhodného zariadenia"</string>
     <string name="car_permission_label_car_test_service" msgid="9159328930558208708">"Ovládanie testovacieho režimu auta"</string>
     <string name="car_permission_desc_car_test_service" msgid="7426844534110145843">"Ovládanie testovacieho režimu auta"</string>
+    <string name="car_permission_label_control_car_features" msgid="3905791560378888286">"Povoliť alebo zakázať funkcie auta"</string>
+    <string name="car_permission_desc_control_car_features" msgid="7646711104530599901">"Povoľte alebo zakážte funkcie auta."</string>
+    <string name="car_permission_label_use_car_watchdog" msgid="6973938293170413475">"používať strážcu prevádzky auta"</string>
+    <string name="car_permission_desc_use_car_watchdog" msgid="8244592601805516086">"Používať strážcu prevádzky auta."</string>
     <string name="trust_device_default_name" msgid="4213625926070261253">"Moje zariadenie"</string>
 </resources>
diff --git a/service/res/values-sl/strings.xml b/service/res/values-sl/strings.xml
index 9d7b0d7..f9c8e09 100644
--- a/service/res/values-sl/strings.xml
+++ b/service/res/values-sl/strings.xml
@@ -22,6 +22,8 @@
     <string name="car_permission_desc_camera" msgid="917024932164501426">"Dostop do kamer avtomobila."</string>
     <string name="car_permission_label_energy" msgid="7409144323527821558">"dostop do podatkov o energiji avtomobila"</string>
     <string name="car_permission_desc_energy" msgid="3392963810053235407">"Dostop do podatkov o energiji avtomobila."</string>
+    <string name="car_permission_label_adjust_range_remaining" msgid="839033553999920138">"prilagajanje preostalega dosega avtomobila"</string>
+    <string name="car_permission_desc_adjust_range_remaining" msgid="2369321650437370673">"Prilagajanje preostale vrednosti dosega avtomobila."</string>
     <string name="car_permission_label_hvac" msgid="1499454192558727843">"dostop do sistema za ogrevanje, hlajenje in prezračevanje avtomobila"</string>
     <string name="car_permission_desc_hvac" msgid="3754229695589774195">"Dostop do sistema za ogrevanje, hlajenje in prezračevanje avtomobila."</string>
     <string name="car_permission_label_mileage" msgid="4661317074631150551">"dostop o podatkov o prevoženih kilometrih avtomobila"</string>
@@ -62,16 +64,17 @@
     <string name="car_permission_desc_car_ux_restrictions_configuration" msgid="5711926927484813777">"Konfiguriranje omejitev uporabniške izkušnje"</string>
     <string name="car_permission_label_car_handle_usb_aoap_device" msgid="72783989504378036">"Komuniciranje z napravo USB v načinu AOAP"</string>
     <string name="car_permission_desc_car_handle_usb_aoap_device" msgid="273505990971317034">"Aplikaciji omogoča komuniciranje z napravo v načinu AOAP"</string>
+    <string name="car_permission_label_read_car_occupant_awareness_state" msgid="125517953575032758">"Dostop za branje za sistem za zavedanje potnikov"</string>
+    <string name="car_permission_desc_read_car_occupant_awareness_state" msgid="188865882598414986">"Omogoča branje stanja in podatkov o zaznavanju sistema za zavedanje potnikov"</string>
+    <string name="car_permission_label_control_car_occupant_awareness_system" msgid="7163330266691094542">"Upravljanje grafikona sistema za zavedanje potnikov"</string>
+    <string name="car_permission_desc_control_car_occupant_awareness_system" msgid="7123482622084531911">"Omogoča upravljanje grafikona začetka in ustavitve zaznavanja sistema za zavedanje potnikov"</string>
     <string name="car_permission_label_bind_input_service" msgid="6698489034024273750">"Storitev za vhode avtomobila"</string>
     <string name="car_permission_desc_bind_input_service" msgid="1670323419931890170">"Obravnava dogodkov vnosa"</string>
     <string name="car_can_bus_failure" msgid="2334035748788283914">"Napaka vodila CAN"</string>
     <string name="car_can_bus_failure_desc" msgid="4125516222786484733">"Vodilo CAN se ne odziva. Odklopite in znova priklopite ohišje avtomobilskega vmesnika ter znova zaženite avtomobil"</string>
-    <!-- no translation found for activity_blocked_text (8088902789540147995) -->
-    <skip />
+    <string name="activity_blocked_text" msgid="5342114426610711378">"Zaradi varnosti ta dejavnost med vožnjo ni na voljo.\nČe želite nadaljevati, počakajte, da bo vozilo parkirano."</string>
     <string name="exit_button_message" msgid="8554690915924055685">"Če želite začeti znova z varnimi funkcijami aplikacij, izberite <xliff:g id="EXIT_BUTTON">%s</xliff:g>."</string>
     <string name="exit_button" msgid="5829638404777671253">"Nazaj"</string>
-    <string name="exit_button_close_application" msgid="8824289547809332460">"Zapri aplikacijo"</string>
-    <string name="exit_button_go_back" msgid="3469083862100560326">"Nazaj"</string>
     <string name="car_permission_label_diag_read" msgid="7248894224877702604">"branje diagnostičnih podatkov"</string>
     <string name="car_permission_desc_diag_read" msgid="1121426363040966178">"Branje diagnostičnih podatkov avtomobila."</string>
     <string name="car_permission_label_diag_clear" msgid="4783070510879698157">"brisanje diagnostičnih podatkov"</string>
@@ -90,6 +93,8 @@
     <string name="car_permission_desc_car_engine_detailed" msgid="1746863362811347700">"Dostop do podrobnih podatkov o motorju avtomobila."</string>
     <string name="car_permission_label_car_energy_ports" msgid="8548990315169219454">"dostop do pokrova rezervoarja in polnilnih vrat avtomobila"</string>
     <string name="car_permission_desc_car_energy_ports" msgid="7771185999828794949">"Dostop do pokrova rezervoarja in polnilnih vrat avtomobila."</string>
+    <string name="car_permission_label_control_car_energy_ports" msgid="4375137311026313475">"upravljanje pokrova rezervoarja in polnilnih vrat avtomobila"</string>
+    <string name="car_permission_desc_control_car_energy_ports" msgid="7364633710492525387">"Upravljanje pokrova rezervoarja in polnilnih vrat avtomobila."</string>
     <string name="car_permission_label_car_identification" msgid="5896712510164020478">"branje identifikacijskih podatkov avtomobila"</string>
     <string name="car_permission_desc_car_identification" msgid="4132040867171275059">"Dostop do identifikacijskih podatkov avtomobila."</string>
     <string name="car_permission_label_control_car_doors" msgid="3032058819250195700">"nadziranje vrat avtomobila"</string>
@@ -102,6 +107,8 @@
     <string name="car_permission_desc_control_car_seats" msgid="2407536601226470563">"Nadziranje sedežev avtomobila."</string>
     <string name="car_permission_label_car_info" msgid="4707513570676492315">"dostop do osnovnih podatkov avtomobila"</string>
     <string name="car_permission_desc_car_info" msgid="2118081474543537653">"Dostop do osnovnih podatkov avtomobila."</string>
+    <string name="car_permission_label_vendor_permission_info" msgid="4471260460536888654">"dostop do podatkov o dovoljenjih prodajalca avtomobila"</string>
+    <string name="car_permission_desc_vendor_permission_info" msgid="8152113853528488398">"Dostop do podatkov o dovoljenjih prodajalca avtomobila."</string>
     <string name="car_permission_label_car_exterior_lights" msgid="541304469604902110">"branje stanja zunanjih luči avtomobila"</string>
     <string name="car_permission_desc_car_exterior_lights" msgid="4038037584100849318">"Dostop do stanja zunanjih luči avtomobila."</string>
     <string name="car_permission_label_control_car_exterior_lights" msgid="101357531386232141">"branje zunanjih luči avtomobila"</string>
@@ -128,5 +135,9 @@
     <string name="car_permission_desc_enroll_trust" msgid="4148649994602185130">"Omogočanje včlanitve zaupanja vredne naprave"</string>
     <string name="car_permission_label_car_test_service" msgid="9159328930558208708">"Nadziranje preizkusnega načina avtomobila"</string>
     <string name="car_permission_desc_car_test_service" msgid="7426844534110145843">"Nadziranje preizkusnega načina avtomobila"</string>
+    <string name="car_permission_label_control_car_features" msgid="3905791560378888286">"Omogočanje ali onemogočanje funkcij avtomobila."</string>
+    <string name="car_permission_desc_control_car_features" msgid="7646711104530599901">"Omogočanje ali onemogočanje funkcij avtomobila."</string>
+    <string name="car_permission_label_use_car_watchdog" msgid="6973938293170413475">"uporaba avtomobilskega nadzornika"</string>
+    <string name="car_permission_desc_use_car_watchdog" msgid="8244592601805516086">"Uporaba avtomobilskega nadzornika."</string>
     <string name="trust_device_default_name" msgid="4213625926070261253">"Moja naprava"</string>
 </resources>
diff --git a/service/res/values-sq/strings.xml b/service/res/values-sq/strings.xml
index 742401e..4229c94 100644
--- a/service/res/values-sq/strings.xml
+++ b/service/res/values-sq/strings.xml
@@ -22,6 +22,8 @@
     <string name="car_permission_desc_camera" msgid="917024932164501426">"Qasu te kamera(t) e makinës."</string>
     <string name="car_permission_label_energy" msgid="7409144323527821558">"qasu tek informacionet e energjisë së makinës"</string>
     <string name="car_permission_desc_energy" msgid="3392963810053235407">"Qasu tek informacionet e energjisë së makinës."</string>
+    <string name="car_permission_label_adjust_range_remaining" msgid="839033553999920138">"rregullo vlerën e mbetur të gamës së makinës"</string>
+    <string name="car_permission_desc_adjust_range_remaining" msgid="2369321650437370673">"Rregullo vlerën e mbetur të gamës së makinës."</string>
     <string name="car_permission_label_hvac" msgid="1499454192558727843">"qasu në sistemin HVAC të makinës"</string>
     <string name="car_permission_desc_hvac" msgid="3754229695589774195">"Qasu në sistemin HVAC të makinës."</string>
     <string name="car_permission_label_mileage" msgid="4661317074631150551">"qasu tek informacionet e kilometrazhit të makinës"</string>
@@ -62,15 +64,17 @@
     <string name="car_permission_desc_car_ux_restrictions_configuration" msgid="5711926927484813777">"Konfiguro kufizimet për eksperiencën e përdoruesit"</string>
     <string name="car_permission_label_car_handle_usb_aoap_device" msgid="72783989504378036">"Komuniko me pajisjen USB në modalitetin AOAP"</string>
     <string name="car_permission_desc_car_handle_usb_aoap_device" msgid="273505990971317034">"Lejon që një aplikacion të komunikojë me një pajisje në modalitetin AOAP"</string>
+    <string name="car_permission_label_read_car_occupant_awareness_state" msgid="125517953575032758">"Qasja për leximin e Occupant Awareness System"</string>
+    <string name="car_permission_desc_read_car_occupant_awareness_state" msgid="188865882598414986">"Lejon leximin e të dhënave për statusin dhe zbulimin për Occupant Awareness System"</string>
+    <string name="car_permission_label_control_car_occupant_awareness_system" msgid="7163330266691094542">"Kontrollo Occupant Awareness System Graph"</string>
+    <string name="car_permission_desc_control_car_occupant_awareness_system" msgid="7123482622084531911">"Lejon kontrollin e nisjes dhe ndalimit të grafikut të zbulimit të Occupant Awareness System"</string>
     <string name="car_permission_label_bind_input_service" msgid="6698489034024273750">"Shërbimi i hyrjes së makinës"</string>
     <string name="car_permission_desc_bind_input_service" msgid="1670323419931890170">"Menaxho ngjarjet e hyrjes"</string>
     <string name="car_can_bus_failure" msgid="2334035748788283914">"Kanali i komunikimit CAN dështoi"</string>
     <string name="car_can_bus_failure_desc" msgid="4125516222786484733">"Kanali i komunikimit CAN nuk përgjigjet. Shkëput dhe lidh përsëri kutinë e njësisë kryesore dhe rindiz makinës"</string>
-    <string name="activity_blocked_text" msgid="8088902789540147995">"Nuk mund ta përdorësh këtë veçori gjatë drejtimit të makinës"</string>
+    <string name="activity_blocked_text" msgid="5342114426610711378">"Për sigurinë tënde, ky aktivitet nuk ofrohet kur je duke drejtuar makinën.\nPër të vazhduar, prit deri sa të parkosh."</string>
     <string name="exit_button_message" msgid="8554690915924055685">"Për të filluar nga e para me funksionet e sigurta të aplikacionit, zgjidh <xliff:g id="EXIT_BUTTON">%s</xliff:g>."</string>
     <string name="exit_button" msgid="5829638404777671253">"Prapa"</string>
-    <string name="exit_button_close_application" msgid="8824289547809332460">"Mbyll aplikacionin"</string>
-    <string name="exit_button_go_back" msgid="3469083862100560326">"Pas"</string>
     <string name="car_permission_label_diag_read" msgid="7248894224877702604">"lexo të dhënat diagnostikuese"</string>
     <string name="car_permission_desc_diag_read" msgid="1121426363040966178">"Lexo të dhënat diagnostikuese nga makina."</string>
     <string name="car_permission_label_diag_clear" msgid="4783070510879698157">"pastro të dhënat diagnostikuese"</string>
@@ -89,6 +93,8 @@
     <string name="car_permission_desc_car_engine_detailed" msgid="1746863362811347700">"Qasu tek informacionet e detajuara të motorit të makinës."</string>
     <string name="car_permission_label_car_energy_ports" msgid="8548990315169219454">"qasu te porta e karburantit të makinës dhe te porta e karikimit"</string>
     <string name="car_permission_desc_car_energy_ports" msgid="7771185999828794949">"Qasu te porta e karburantit të makinës dhe te porta e karikimit."</string>
+    <string name="car_permission_label_control_car_energy_ports" msgid="4375137311026313475">"kontrollo kapakun e karburantit të makinës dhe portën e karikimit"</string>
+    <string name="car_permission_desc_control_car_energy_ports" msgid="7364633710492525387">"Kontrollo kapakun e karburantit të makinës dhe portën e karikimit."</string>
     <string name="car_permission_label_car_identification" msgid="5896712510164020478">"lexo identifikimin e makinës"</string>
     <string name="car_permission_desc_car_identification" msgid="4132040867171275059">"Qasu tek identifikimi i makinës."</string>
     <string name="car_permission_label_control_car_doors" msgid="3032058819250195700">"kontrollo dyert e makinës"</string>
@@ -101,6 +107,8 @@
     <string name="car_permission_desc_control_car_seats" msgid="2407536601226470563">"Kontrollo ndenjëset e makinës."</string>
     <string name="car_permission_label_car_info" msgid="4707513570676492315">"qasu tek informacionet bazë të makinës"</string>
     <string name="car_permission_desc_car_info" msgid="2118081474543537653">"Qasu tek informacionet bazë të makinës."</string>
+    <string name="car_permission_label_vendor_permission_info" msgid="4471260460536888654">"qasu tek informacionet për lejet e shitësit të makinës"</string>
+    <string name="car_permission_desc_vendor_permission_info" msgid="8152113853528488398">"Qasu tek informacionet për lejet e shitësit të makinës."</string>
     <string name="car_permission_label_car_exterior_lights" msgid="541304469604902110">"lexo gjendjen e dritave të jashtme të makinës"</string>
     <string name="car_permission_desc_car_exterior_lights" msgid="4038037584100849318">"Qasu te gjendja e dritave të jashtme të makinës."</string>
     <string name="car_permission_label_control_car_exterior_lights" msgid="101357531386232141">"lexo dritat e jashtme të makinës"</string>
@@ -127,5 +135,9 @@
     <string name="car_permission_desc_enroll_trust" msgid="4148649994602185130">"Lejo regjistrimin e pajisjes së besuar"</string>
     <string name="car_permission_label_car_test_service" msgid="9159328930558208708">"Kontrollo modalitetin e testimit të makinës"</string>
     <string name="car_permission_desc_car_test_service" msgid="7426844534110145843">"Kontrollo modalitetin e testimit të makinës"</string>
+    <string name="car_permission_label_control_car_features" msgid="3905791560378888286">"Aktivizo ose çaktivizo veçoritë e makinës"</string>
+    <string name="car_permission_desc_control_car_features" msgid="7646711104530599901">"Aktivizo ose çaktivizo veçoritë e makinës."</string>
+    <string name="car_permission_label_use_car_watchdog" msgid="6973938293170413475">"përdor monitoruesin e makinës"</string>
+    <string name="car_permission_desc_use_car_watchdog" msgid="8244592601805516086">"Përdor monitoruesin e makinës."</string>
     <string name="trust_device_default_name" msgid="4213625926070261253">"Pajisja ime"</string>
 </resources>
diff --git a/service/res/values-sr/strings.xml b/service/res/values-sr/strings.xml
index e5610b6..cdefd5b 100644
--- a/service/res/values-sr/strings.xml
+++ b/service/res/values-sr/strings.xml
@@ -22,6 +22,8 @@
     <string name="car_permission_desc_camera" msgid="917024932164501426">"Приступи камерама аутомобила."</string>
     <string name="car_permission_label_energy" msgid="7409144323527821558">"приступ подацима о енергији аутомобила"</string>
     <string name="car_permission_desc_energy" msgid="3392963810053235407">"Приступи информацијама о енергији аутомобила."</string>
+    <string name="car_permission_label_adjust_range_remaining" msgid="839033553999920138">"прилагођавање преосталог домета аутомобила"</string>
+    <string name="car_permission_desc_adjust_range_remaining" msgid="2369321650437370673">"Прилагођавање вредности преосталог домета аутомобила."</string>
     <string name="car_permission_label_hvac" msgid="1499454192558727843">"приступ грејању, вентилацији и климатизацији аутомобила"</string>
     <string name="car_permission_desc_hvac" msgid="3754229695589774195">"Приступи грејању, вентилацији и климатизацији аутомобила"</string>
     <string name="car_permission_label_mileage" msgid="4661317074631150551">"приступ подацима о километражи аутомобила"</string>
@@ -62,15 +64,17 @@
     <string name="car_permission_desc_car_ux_restrictions_configuration" msgid="5711926927484813777">"Конфигурише ограничења КД-а"</string>
     <string name="car_permission_label_car_handle_usb_aoap_device" msgid="72783989504378036">"Комуницира са USB уређајем у режиму AOAP"</string>
     <string name="car_permission_desc_car_handle_usb_aoap_device" msgid="273505990971317034">"Дозвољава апликацији комуникацију са уређајем у режиму AOAP"</string>
+    <string name="car_permission_label_read_car_occupant_awareness_state" msgid="125517953575032758">"Приступ за читање за Occupant Awareness System"</string>
+    <string name="car_permission_desc_read_car_occupant_awareness_state" msgid="188865882598414986">"Омогућава читање података о статусу и откривању за Occupant Awareness System"</string>
+    <string name="car_permission_label_control_car_occupant_awareness_system" msgid="7163330266691094542">"Управљање графиконом за Occupant Awareness System"</string>
+    <string name="car_permission_desc_control_car_occupant_awareness_system" msgid="7123482622084531911">"Омогућава покретање и заустављање графикона откривања за Occupant Awareness System"</string>
     <string name="car_permission_label_bind_input_service" msgid="6698489034024273750">"Услуга аутомобилског уноса"</string>
     <string name="car_permission_desc_bind_input_service" msgid="1670323419931890170">"Управља догађајима уноса"</string>
     <string name="car_can_bus_failure" msgid="2334035748788283914">"Грешка CAN магистрале"</string>
     <string name="car_can_bus_failure_desc" msgid="4125516222786484733">"CAN магистрала не реагује. Искључите и поново укључите главну јединицу и поново покрените аутомобил"</string>
-    <string name="activity_blocked_text" msgid="8088902789540147995">"Не можете да користите ову функцију док возите"</string>
+    <string name="activity_blocked_text" msgid="5342114426610711378">"Ова активност није доступна док возите ради ваше безбедности.\nДа бисте наставили, прво се паркирајте."</string>
     <string name="exit_button_message" msgid="8554690915924055685">"Да бисте поново почели са безбедним функцијама апликације, изаберите <xliff:g id="EXIT_BUTTON">%s</xliff:g>."</string>
     <string name="exit_button" msgid="5829638404777671253">"Назад"</string>
-    <string name="exit_button_close_application" msgid="8824289547809332460">"Затвори апликацију"</string>
-    <string name="exit_button_go_back" msgid="3469083862100560326">"Назад"</string>
     <string name="car_permission_label_diag_read" msgid="7248894224877702604">"читање дијагностичких података"</string>
     <string name="car_permission_desc_diag_read" msgid="1121426363040966178">"Читање дијагностичких података из аутомобила."</string>
     <string name="car_permission_label_diag_clear" msgid="4783070510879698157">"брисање дијагностичких података"</string>
@@ -89,6 +93,8 @@
     <string name="car_permission_desc_car_engine_detailed" msgid="1746863362811347700">"Приступ детаљним подацима о мотору аутомобила."</string>
     <string name="car_permission_label_car_energy_ports" msgid="8548990315169219454">"приступ поклопцу резервоара за гориво и порту за пуњење"</string>
     <string name="car_permission_desc_car_energy_ports" msgid="7771185999828794949">"Приступ поклопцу резервоара за гориво и порту за пуњење."</string>
+    <string name="car_permission_label_control_car_energy_ports" msgid="4375137311026313475">"управљање поклопцем резервоара за гориво и портом за пуњење"</string>
+    <string name="car_permission_desc_control_car_energy_ports" msgid="7364633710492525387">"Управљање поклопцем резервоара за гориво и портом за пуњење."</string>
     <string name="car_permission_label_car_identification" msgid="5896712510164020478">"читање података за идентификацију аутомобила"</string>
     <string name="car_permission_desc_car_identification" msgid="4132040867171275059">"Приступ подацима за идентификацију аутомобила."</string>
     <string name="car_permission_label_control_car_doors" msgid="3032058819250195700">"Контролисање врата аутомобила"</string>
@@ -101,6 +107,8 @@
     <string name="car_permission_desc_control_car_seats" msgid="2407536601226470563">"Контролисање седишта у аутомобилу."</string>
     <string name="car_permission_label_car_info" msgid="4707513570676492315">"приступ основним подацима о аутомобилу"</string>
     <string name="car_permission_desc_car_info" msgid="2118081474543537653">"Приступ основним подацима о аутомобилу."</string>
+    <string name="car_permission_label_vendor_permission_info" msgid="4471260460536888654">"приступ информацијама о дозволама продавца аутомобила"</string>
+    <string name="car_permission_desc_vendor_permission_info" msgid="8152113853528488398">"Приступа информацијама о дозволама продавца аутомобила."</string>
     <string name="car_permission_label_car_exterior_lights" msgid="541304469604902110">"читање стања спољних светла аутомобила"</string>
     <string name="car_permission_desc_car_exterior_lights" msgid="4038037584100849318">"Приступ стању спољних светла аутомобила."</string>
     <string name="car_permission_label_control_car_exterior_lights" msgid="101357531386232141">"читање статуса спољних светла аутомобила"</string>
@@ -127,5 +135,9 @@
     <string name="car_permission_desc_enroll_trust" msgid="4148649994602185130">"Омогући регистровање поузданих уређаја"</string>
     <string name="car_permission_label_car_test_service" msgid="9159328930558208708">"Контрола режима за тестирање аутомобила"</string>
     <string name="car_permission_desc_car_test_service" msgid="7426844534110145843">"Контрола режима за тестирање аутомобила"</string>
+    <string name="car_permission_label_control_car_features" msgid="3905791560378888286">"Омогућавање или онемогућавање функција аутомобила"</string>
+    <string name="car_permission_desc_control_car_features" msgid="7646711104530599901">"Омогућавање или онемогућавање функција аутомобила."</string>
+    <string name="car_permission_label_use_car_watchdog" msgid="6973938293170413475">"користи надзорни тајмер аутомобила"</string>
+    <string name="car_permission_desc_use_car_watchdog" msgid="8244592601805516086">"Користи надзорни тајмер аутомобила."</string>
     <string name="trust_device_default_name" msgid="4213625926070261253">"Мој уређај"</string>
 </resources>
diff --git a/service/res/values-sv/strings.xml b/service/res/values-sv/strings.xml
index 79ef61e..cb45be3 100644
--- a/service/res/values-sv/strings.xml
+++ b/service/res/values-sv/strings.xml
@@ -22,6 +22,8 @@
     <string name="car_permission_desc_camera" msgid="917024932164501426">"Åtkomst till bilens kamera eller kameror."</string>
     <string name="car_permission_label_energy" msgid="7409144323527821558">"åtkomst till information om bilens drivmedel"</string>
     <string name="car_permission_desc_energy" msgid="3392963810053235407">"Åtkomst till information om bilens drivmedel."</string>
+    <string name="car_permission_label_adjust_range_remaining" msgid="839033553999920138">"justera värdet på bilens återstående körsträcka"</string>
+    <string name="car_permission_desc_adjust_range_remaining" msgid="2369321650437370673">"Får justera värdet på bilens återstående körsträcka."</string>
     <string name="car_permission_label_hvac" msgid="1499454192558727843">"åtkomst till bilens värme, ventilation och luftkonditionering"</string>
     <string name="car_permission_desc_hvac" msgid="3754229695589774195">"Åtkomst till bilens värme-, ventilations- och AC-system."</string>
     <string name="car_permission_label_mileage" msgid="4661317074631150551">"åtkomst till information om bilens bränsleförbrukning"</string>
@@ -62,15 +64,17 @@
     <string name="car_permission_desc_car_ux_restrictions_configuration" msgid="5711926927484813777">"Konfigurera användningsbegränsningar"</string>
     <string name="car_permission_label_car_handle_usb_aoap_device" msgid="72783989504378036">"Kommunicera med en USB-enhet i AOAP-läge"</string>
     <string name="car_permission_desc_car_handle_usb_aoap_device" msgid="273505990971317034">"Tillåt att en app kommunicerar med en enhet i AOAP-läge"</string>
+    <string name="car_permission_label_read_car_occupant_awareness_state" msgid="125517953575032758">"Läsbehörighet för Occupant Awareness System"</string>
+    <string name="car_permission_desc_read_car_occupant_awareness_state" msgid="188865882598414986">"Tillåter läsning av status och detektionsdata för Occupant Awareness System"</string>
+    <string name="car_permission_label_control_car_occupant_awareness_system" msgid="7163330266691094542">"Styra diagrammet för Occupant Awareness System"</string>
+    <string name="car_permission_desc_control_car_occupant_awareness_system" msgid="7123482622084531911">"Tillåter styrning av start och stopp av detektionsdiagrammet för Occupant Awareness System"</string>
     <string name="car_permission_label_bind_input_service" msgid="6698489034024273750">"Indatatjänst för bilen"</string>
     <string name="car_permission_desc_bind_input_service" msgid="1670323419931890170">"Hantera indatahändelser"</string>
     <string name="car_can_bus_failure" msgid="2334035748788283914">"Fel i CAN-bussen"</string>
     <string name="car_can_bus_failure_desc" msgid="4125516222786484733">"CAN-bussen svarar inte. Koppla från huvudenheten och koppla in den igen. Starta sedan om bilen"</string>
-    <string name="activity_blocked_text" msgid="8088902789540147995">"Du kan inte använda funktionen medan du kör"</string>
+    <string name="activity_blocked_text" msgid="5342114426610711378">"Av säkerhetsskäl är den här aktiviteten inte tillgänglig under körning.\nDu kan fortsätta med detta när du har parkerat."</string>
     <string name="exit_button_message" msgid="8554690915924055685">"Välj <xliff:g id="EXIT_BUTTON">%s</xliff:g> om du vill starta om appen med säkra funktioner."</string>
     <string name="exit_button" msgid="5829638404777671253">"Tillbaka"</string>
-    <string name="exit_button_close_application" msgid="8824289547809332460">"Stäng appen"</string>
-    <string name="exit_button_go_back" msgid="3469083862100560326">"Tillbaka"</string>
     <string name="car_permission_label_diag_read" msgid="7248894224877702604">"läsa diagnostikdata"</string>
     <string name="car_permission_desc_diag_read" msgid="1121426363040966178">"Läsa diagnostisk data om bilen."</string>
     <string name="car_permission_label_diag_clear" msgid="4783070510879698157">"rensa diagnostikdata"</string>
@@ -89,6 +93,8 @@
     <string name="car_permission_desc_car_engine_detailed" msgid="1746863362811347700">"Åtkomst till detaljerad information om bilens motor."</string>
     <string name="car_permission_label_car_energy_ports" msgid="8548990315169219454">"åtkomst till bilens tanklucka och laddningsport"</string>
     <string name="car_permission_desc_car_energy_ports" msgid="7771185999828794949">"Åtkomst till bilens tanklucka och laddningsport."</string>
+    <string name="car_permission_label_control_car_energy_ports" msgid="4375137311026313475">"styra bilens tanklucka och laddningsport"</string>
+    <string name="car_permission_desc_control_car_energy_ports" msgid="7364633710492525387">"Styra bilens tanklucka och laddningsport."</string>
     <string name="car_permission_label_car_identification" msgid="5896712510164020478">"läsa av bilens id-information"</string>
     <string name="car_permission_desc_car_identification" msgid="4132040867171275059">"Åtkomst till bilens id-information."</string>
     <string name="car_permission_label_control_car_doors" msgid="3032058819250195700">"styra bilens dörrar och luckor"</string>
@@ -101,6 +107,8 @@
     <string name="car_permission_desc_control_car_seats" msgid="2407536601226470563">"Styra bilens säten."</string>
     <string name="car_permission_label_car_info" msgid="4707513570676492315">"åtkomst till grundläggande uppgifter om bilen"</string>
     <string name="car_permission_desc_car_info" msgid="2118081474543537653">"Åtkomst till grundläggande uppgifter om bilen."</string>
+    <string name="car_permission_label_vendor_permission_info" msgid="4471260460536888654">"åtkomst till information om behörighet från bilens tillverkare"</string>
+    <string name="car_permission_desc_vendor_permission_info" msgid="8152113853528488398">"Åtkomst till information om behörighet från bilens tillverkare."</string>
     <string name="car_permission_label_car_exterior_lights" msgid="541304469604902110">"läsa av status för bilens exteriörbelysning"</string>
     <string name="car_permission_desc_car_exterior_lights" msgid="4038037584100849318">"Åtkomst till status för bilens exteriörbelysning."</string>
     <string name="car_permission_label_control_car_exterior_lights" msgid="101357531386232141">"läsa av bilens exteriörbelysning"</string>
@@ -127,5 +135,9 @@
     <string name="car_permission_desc_enroll_trust" msgid="4148649994602185130">"Tillåt registrering av betrodda enheter"</string>
     <string name="car_permission_label_car_test_service" msgid="9159328930558208708">"Styra bilens testläge"</string>
     <string name="car_permission_desc_car_test_service" msgid="7426844534110145843">"Styra bilens testläge"</string>
+    <string name="car_permission_label_control_car_features" msgid="3905791560378888286">"Aktivera eller inaktivera funktioner i bilen"</string>
+    <string name="car_permission_desc_control_car_features" msgid="7646711104530599901">"Aktivera eller inaktivera funktioner i bilen."</string>
+    <string name="car_permission_label_use_car_watchdog" msgid="6973938293170413475">"använd vakthund för bilen"</string>
+    <string name="car_permission_desc_use_car_watchdog" msgid="8244592601805516086">"Använd vakthund för bilen."</string>
     <string name="trust_device_default_name" msgid="4213625926070261253">"Min enhet"</string>
 </resources>
diff --git a/service/res/values-sw/strings.xml b/service/res/values-sw/strings.xml
index caf211a..8f390bb 100644
--- a/service/res/values-sw/strings.xml
+++ b/service/res/values-sw/strings.xml
@@ -22,6 +22,8 @@
     <string name="car_permission_desc_camera" msgid="917024932164501426">"Kufikia kamera ya gari lako."</string>
     <string name="car_permission_label_energy" msgid="7409144323527821558">"kufikia maelezo ya nishati ya gari"</string>
     <string name="car_permission_desc_energy" msgid="3392963810053235407">"Kufikia maelezo ya nishati ya gari lako."</string>
+    <string name="car_permission_label_adjust_range_remaining" msgid="839033553999920138">"kurekebisha umbali unaosalia wa kusafiri wa gari"</string>
+    <string name="car_permission_desc_adjust_range_remaining" msgid="2369321650437370673">"Kurekebisha thamani inayosalia ya umbali wa kusafiri wa gari."</string>
     <string name="car_permission_label_hvac" msgid="1499454192558727843">"kufikia hali ya joto, hewa na kiyoyozi (hvac) katika gari"</string>
     <string name="car_permission_desc_hvac" msgid="3754229695589774195">"Kufikia hali ya joto, hewa na kiyoyozi (hvac) ya gari lako."</string>
     <string name="car_permission_label_mileage" msgid="4661317074631150551">"kufikia maelezo ya maili za gari"</string>
@@ -62,16 +64,17 @@
     <string name="car_permission_desc_car_ux_restrictions_configuration" msgid="5711926927484813777">"Weka Mipangilio ya Masharti ya UX"</string>
     <string name="car_permission_label_car_handle_usb_aoap_device" msgid="72783989504378036">"Iwasiliane na kifaa cha USB katika hali ya AOAP"</string>
     <string name="car_permission_desc_car_handle_usb_aoap_device" msgid="273505990971317034">"Inaruhusu programu iwasiliane na kifaa katika hali ya AOAP"</string>
+    <string name="car_permission_label_read_car_occupant_awareness_state" msgid="125517953575032758">"Ufikiaji wa Kusoma wa Mfumo wa Kutambua Waliomo"</string>
+    <string name="car_permission_desc_read_car_occupant_awareness_state" msgid="188865882598414986">"Huruhusu kusoma data ya hali na ya utambuzi ya Mfumo wa Kutambua Waliomo"</string>
+    <string name="car_permission_label_control_car_occupant_awareness_system" msgid="7163330266691094542">"Kudhibiti Grafu ya Mfumo wa Kutambua Waliomo"</string>
+    <string name="car_permission_desc_control_car_occupant_awareness_system" msgid="7123482622084531911">"Huruhusu udhibiti wa kuanzisha na kusimamisha grafu ya utambuzi ya Mfumo wa Kutambua Waliomo"</string>
     <string name="car_permission_label_bind_input_service" msgid="6698489034024273750">"Huduma ya Kuweka Data ya Gari"</string>
     <string name="car_permission_desc_bind_input_service" msgid="1670323419931890170">"Kudhibiti matukio ya kuweka data"</string>
     <string name="car_can_bus_failure" msgid="2334035748788283914">"Imeshindwa kuleta maelezo ya kebo CAN"</string>
     <string name="car_can_bus_failure_desc" msgid="4125516222786484733">"Kebo ya CAN haifanyi kazi. Ondoa kisha urudishe tena kisanduku cha sehemu kuu na uzime kisha uwashe gari"</string>
-    <!-- no translation found for activity_blocked_text (8088902789540147995) -->
-    <skip />
+    <string name="activity_blocked_text" msgid="5342114426610711378">"Kwa usalama wako, shughuli haipatikani wakati unaendesha gari. \nIli uendelee, subiri hadi utakapoegesha gari."</string>
     <string name="exit_button_message" msgid="8554690915924055685">"Ili uanzishe tena ukitumia vipengele salama vya programu, chagua <xliff:g id="EXIT_BUTTON">%s</xliff:g>."</string>
     <string name="exit_button" msgid="5829638404777671253">"Rudi Nyuma"</string>
-    <string name="exit_button_close_application" msgid="8824289547809332460">"Funga programu"</string>
-    <string name="exit_button_go_back" msgid="3469083862100560326">"Nyuma"</string>
     <string name="car_permission_label_diag_read" msgid="7248894224877702604">"kusoma data ya uchunguzi"</string>
     <string name="car_permission_desc_diag_read" msgid="1121426363040966178">"Kusoma data ya uchunguzi kwenye gari."</string>
     <string name="car_permission_label_diag_clear" msgid="4783070510879698157">"kufuta data ya uchunguzi wa gari"</string>
@@ -90,6 +93,8 @@
     <string name="car_permission_desc_car_engine_detailed" msgid="1746863362811347700">"Kufikia maelezo ya kina ya injini ya gari lako."</string>
     <string name="car_permission_label_car_energy_ports" msgid="8548990315169219454">"kufikia kifuniko cha sehemu ya kuwekea mafuta ya gari na mlango wa kuchaji"</string>
     <string name="car_permission_desc_car_energy_ports" msgid="7771185999828794949">"Kufikia maelezo ya kifuniko cha sehemu ya kuwekea mafuta ya gari na mlango wa kuchaji."</string>
+    <string name="car_permission_label_control_car_energy_ports" msgid="4375137311026313475">"kudhibiti kifuniko cha sehemu ya kuwekea mafuta ya gari na mlango wa kuchaji"</string>
+    <string name="car_permission_desc_control_car_energy_ports" msgid="7364633710492525387">"Kudhibiti kifuniko cha sehemu ya kuwekea mafuta ya gari na mlango wa kuchaji."</string>
     <string name="car_permission_label_car_identification" msgid="5896712510164020478">"kusoma maelezo ya utambulisho wa gari"</string>
     <string name="car_permission_desc_car_identification" msgid="4132040867171275059">"Kufikia maelezo ya utambulisho wa gari."</string>
     <string name="car_permission_label_control_car_doors" msgid="3032058819250195700">"kudhibiti milango ya gari"</string>
@@ -102,6 +107,8 @@
     <string name="car_permission_desc_control_car_seats" msgid="2407536601226470563">"Kudhibiti viti vya gari."</string>
     <string name="car_permission_label_car_info" msgid="4707513570676492315">"kufikia maelezo ya msingi ya gari"</string>
     <string name="car_permission_desc_car_info" msgid="2118081474543537653">"Kufikia maelezo ya msingi ya gari."</string>
+    <string name="car_permission_label_vendor_permission_info" msgid="4471260460536888654">"kufikia maelezo ya ruhusa ya muuzaji wa gari"</string>
+    <string name="car_permission_desc_vendor_permission_info" msgid="8152113853528488398">"Kufikia maelezo ya ruhusa ya muuzaji wa gari"</string>
     <string name="car_permission_label_car_exterior_lights" msgid="541304469604902110">"kusoma hali ya taa za nje ya gari"</string>
     <string name="car_permission_desc_car_exterior_lights" msgid="4038037584100849318">"Kufikia hali ya taa za nje ya gari."</string>
     <string name="car_permission_label_control_car_exterior_lights" msgid="101357531386232141">"kusoma taa za nje ya gari"</string>
@@ -128,5 +135,9 @@
     <string name="car_permission_desc_enroll_trust" msgid="4148649994602185130">"Ruhusu Usajili wa Vifaa Unavyoviamini"</string>
     <string name="car_permission_label_car_test_service" msgid="9159328930558208708">"Kudhibiti hali ya jaribio la gari"</string>
     <string name="car_permission_desc_car_test_service" msgid="7426844534110145843">"Kudhibiti hali ya jaribio la gari"</string>
+    <string name="car_permission_label_control_car_features" msgid="3905791560378888286">"Washa au uzime vipengele vya gari"</string>
+    <string name="car_permission_desc_control_car_features" msgid="7646711104530599901">"Washa au uzime vipengele vya gari."</string>
+    <string name="car_permission_label_use_car_watchdog" msgid="6973938293170413475">"tumia kipengele cha kulinda gari"</string>
+    <string name="car_permission_desc_use_car_watchdog" msgid="8244592601805516086">"Tumia kipengele cha kulinda gari."</string>
     <string name="trust_device_default_name" msgid="4213625926070261253">"Kifaa Changu"</string>
 </resources>
diff --git a/service/res/values-ta/strings.xml b/service/res/values-ta/strings.xml
index 6128723..85837ac 100644
--- a/service/res/values-ta/strings.xml
+++ b/service/res/values-ta/strings.xml
@@ -22,6 +22,8 @@
     <string name="car_permission_desc_camera" msgid="917024932164501426">"காரின் கேமராவை அணுகுதல்."</string>
     <string name="car_permission_label_energy" msgid="7409144323527821558">"காரின் எரிபொருள் விவரத்தை அணுக வேண்டும்"</string>
     <string name="car_permission_desc_energy" msgid="3392963810053235407">"காரின் எரிபொருள் தகவலை அணுகுதல்."</string>
+    <string name="car_permission_label_adjust_range_remaining" msgid="839033553999920138">"கார் சென்றடைய மீதமுள்ள மைலேஜின் மதிப்பை மாற்றும்"</string>
+    <string name="car_permission_desc_adjust_range_remaining" msgid="2369321650437370673">"கார் சென்றடைய மீதமுள்ள மைலேஜின் மதிப்பை மாற்றும்."</string>
     <string name="car_permission_label_hvac" msgid="1499454192558727843">"காரின் hvac சிஸ்டத்தை அணுக வேண்டும்"</string>
     <string name="car_permission_desc_hvac" msgid="3754229695589774195">"காரில் வெப்பம், காற்றோட்டம், குளிர்சாதன வசதி ஆகியவற்றை உள்ளடக்கிய அமைப்பை (hvac) அணுகுதல்."</string>
     <string name="car_permission_label_mileage" msgid="4661317074631150551">"காரின் மைலேஜ் பற்றிய தகவலை அணுக வேண்டும்"</string>
@@ -62,16 +64,17 @@
     <string name="car_permission_desc_car_ux_restrictions_configuration" msgid="5711926927484813777">"UX கட்டுப்பாடுகளை உள்ளமைத்தல்"</string>
     <string name="car_permission_label_car_handle_usb_aoap_device" msgid="72783989504378036">"AOAP பயன்முறையில் USB சாதனத்தை தொடர்புகொள்ளும்"</string>
     <string name="car_permission_desc_car_handle_usb_aoap_device" msgid="273505990971317034">"AOAP பயன்முறையில் சாதனத்துடன் ஆப்ஸைத் தொடர்புகொள்ள அனுமதிக்கிறது"</string>
+    <string name="car_permission_label_read_car_occupant_awareness_state" msgid="125517953575032758">"உள்ளிருப்போருக்கான விழிப்புணர்வு சிஸ்டத்தின் வாசிக்கும் அணுகல்"</string>
+    <string name="car_permission_desc_read_car_occupant_awareness_state" msgid="188865882598414986">"உள்ளிருப்போருக்கான விழிப்புணர்வு சிஸ்டத்திற்கு வாசிக்கும் நிலையையும் கண்டறிதல் தரவையும் அனுமதிக்கிறது"</string>
+    <string name="car_permission_label_control_car_occupant_awareness_system" msgid="7163330266691094542">"உள்ளிருப்போருக்கான விழிப்புணர்வு சிஸ்டத்தின் வரைபடத்தைக் கட்டுப்படுத்த வேண்டும்"</string>
+    <string name="car_permission_desc_control_car_occupant_awareness_system" msgid="7123482622084531911">"உள்ளிருப்போருக்கான விழிப்புணர்வு சிஸ்டத்தின் கண்டறிதல் வரைபடத்தை தொடங்குவதற்கும் நிறுத்துவதற்கும் கட்டுப்படுத்த அனுமதிக்கிறது"</string>
     <string name="car_permission_label_bind_input_service" msgid="6698489034024273750">"காருக்கு உற்பத்தியாளர் வழங்கும் சேவை"</string>
     <string name="car_permission_desc_bind_input_service" msgid="1670323419931890170">"உற்பத்தியாளர் வழங்கும் சேவைகளைக் கையாளுதல்"</string>
     <string name="car_can_bus_failure" msgid="2334035748788283914">"CAN bus அமைப்பு தோல்வியடைந்தது"</string>
     <string name="car_can_bus_failure_desc" msgid="4125516222786484733">"CAN bus அமைப்பு இயங்கவில்லை. ஹெட்யூனிட் பாக்ஸைப் பிளக்கில் இருந்து அகற்றிச் செருகியபின் காரை மீண்டும் தொடங்கவும்"</string>
-    <!-- no translation found for activity_blocked_text (8088902789540147995) -->
-    <skip />
+    <string name="activity_blocked_text" msgid="5342114426610711378">"உங்கள் பாதுகாப்பை முன்னிட்டு இந்தச் செயல்பாடு, வாகனம் ஓட்டும்போது இயங்காது.\nதொடர வாகனத்தை நிறுத்தும்வரை காத்திருக்கவும்."</string>
     <string name="exit_button_message" msgid="8554690915924055685">"ஆப்ஸைப் பாதுகாப்பான அம்சங்களுடன் மீண்டும் தொடங்க <xliff:g id="EXIT_BUTTON">%s</xliff:g>ஐத் தேர்ந்தெடுக்கவும்."</string>
     <string name="exit_button" msgid="5829638404777671253">"பின்செல்"</string>
-    <string name="exit_button_close_application" msgid="8824289547809332460">"ஆப்ஸை மூடுக"</string>
-    <string name="exit_button_go_back" msgid="3469083862100560326">"முந்தையது"</string>
     <string name="car_permission_label_diag_read" msgid="7248894224877702604">"ஆய்வுத் தரவை அறிய வேண்டும்"</string>
     <string name="car_permission_desc_diag_read" msgid="1121426363040966178">"கார் தொடர்பான ஆய்வுத் தரவை அறிய வேண்டும்."</string>
     <string name="car_permission_label_diag_clear" msgid="4783070510879698157">"ஆய்வுத் தரவை அழிக்க வேண்டும்"</string>
@@ -90,6 +93,8 @@
     <string name="car_permission_desc_car_engine_detailed" msgid="1746863362811347700">"காரின் இன்ஜின் குறித்த முழுமையான தகவலை அணுக வேண்டும்."</string>
     <string name="car_permission_label_car_energy_ports" msgid="8548990315169219454">"காரின் எரிபொருள் மூடியையும் சார்ஜ் போர்ட்டையும் அணுக வேண்டும்"</string>
     <string name="car_permission_desc_car_energy_ports" msgid="7771185999828794949">"காரின் எரிபொருள் மூடியையும் சார்ஜ் போர்ட்டையும் அணுக வேண்டும்."</string>
+    <string name="car_permission_label_control_car_energy_ports" msgid="4375137311026313475">"காரின் எரிபொருள் மூடியையும் சார்ஜ் போர்ட்டையும் கட்டுப்படுத்த வேண்டும்"</string>
+    <string name="car_permission_desc_control_car_energy_ports" msgid="7364633710492525387">"காரின் எரிபொருள் மூடியையும் சார்ஜ் போர்ட்டையும் கட்டுப்படுத்த வேண்டும்."</string>
     <string name="car_permission_label_car_identification" msgid="5896712510164020478">"காரின் VIN தகவலை அறிய வேண்டும்"</string>
     <string name="car_permission_desc_car_identification" msgid="4132040867171275059">"காரின் VIN தகவலை அணுக வேண்டும்."</string>
     <string name="car_permission_label_control_car_doors" msgid="3032058819250195700">"காரின் கதவுகளை நிர்வகிக்க வேண்டும்"</string>
@@ -102,6 +107,8 @@
     <string name="car_permission_desc_control_car_seats" msgid="2407536601226470563">"காரின் இருக்கைகளை நிர்வகிக்க வேண்டும்."</string>
     <string name="car_permission_label_car_info" msgid="4707513570676492315">"காரின் அடிப்படைத் தகவலை அணுக வேண்டும்"</string>
     <string name="car_permission_desc_car_info" msgid="2118081474543537653">"காரின் அடிப்படைத் தகவலை அணுக வேண்டும்."</string>
+    <string name="car_permission_label_vendor_permission_info" msgid="4471260460536888654">"காரின் தயாரிப்பாளர் அனுமதி குறித்த விவரங்களை அணுகுதல்"</string>
+    <string name="car_permission_desc_vendor_permission_info" msgid="8152113853528488398">"காரின் தயாரிப்பாளர் அனுமதி குறித்த விவரங்களை அணுகுதல்."</string>
     <string name="car_permission_label_car_exterior_lights" msgid="541304469604902110">"காரின் வெளிப்புற விளக்குகளின் நிலையை அறிய வேண்டும்"</string>
     <string name="car_permission_desc_car_exterior_lights" msgid="4038037584100849318">"காரின் வெளிப்புற விளக்குகளின் நிலையை அணுக வேண்டும்."</string>
     <string name="car_permission_label_control_car_exterior_lights" msgid="101357531386232141">"காரின் வெளிப்புற விளக்குகளை அறிய வேண்டும்"</string>
@@ -128,5 +135,9 @@
     <string name="car_permission_desc_enroll_trust" msgid="4148649994602185130">"’நம்பகமான சாதனம்’ என்று பதிவு செய்வதை அனுமதிக்கும்"</string>
     <string name="car_permission_label_car_test_service" msgid="9159328930558208708">"காரின் சோதனைப் பயன்முறையைக் கட்டுப்படுத்த வேண்டும்"</string>
     <string name="car_permission_desc_car_test_service" msgid="7426844534110145843">"காரின் சோதனைப் பயன்முறையைக் கட்டுப்படுத்த வேண்டும்"</string>
+    <string name="car_permission_label_control_car_features" msgid="3905791560378888286">"காரின் அம்சங்களை இயக்கும் அல்லது முடக்கும்"</string>
+    <string name="car_permission_desc_control_car_features" msgid="7646711104530599901">"காரின் அம்சங்களை இயக்கும் அல்லது முடக்கும்."</string>
+    <string name="car_permission_label_use_car_watchdog" msgid="6973938293170413475">"பயன்படுத்திய காருக்கான ஒழுங்குமுறை ஆணையம்"</string>
+    <string name="car_permission_desc_use_car_watchdog" msgid="8244592601805516086">"பயன்படுத்திய காருக்கான ஒழுங்குமுறை ஆணையம்."</string>
     <string name="trust_device_default_name" msgid="4213625926070261253">"எனது சாதனம்"</string>
 </resources>
diff --git a/service/res/values-te/strings.xml b/service/res/values-te/strings.xml
index a63790b..d7872c4 100644
--- a/service/res/values-te/strings.xml
+++ b/service/res/values-te/strings.xml
@@ -22,6 +22,8 @@
     <string name="car_permission_desc_camera" msgid="917024932164501426">"మీ కారు యొక్క కామెరా(లు)ని యాక్సెస్ చేయండి."</string>
     <string name="car_permission_label_energy" msgid="7409144323527821558">"కారు శక్తి సమాచారాన్ని యాక్సెస్ చేయగలవు"</string>
     <string name="car_permission_desc_energy" msgid="3392963810053235407">"మీ కారు శక్తి సమాచారాన్ని యాక్సెస్ చేయండి."</string>
+    <string name="car_permission_label_adjust_range_remaining" msgid="839033553999920138">"కారు యొక్క మిగిలిన ప్రయాణ దూరాన్ని సర్దుబాటు చేయండి"</string>
+    <string name="car_permission_desc_adjust_range_remaining" msgid="2369321650437370673">"కారు యొక్క మిగిలిన ప్రయాణ దూర విలువను సర్దుబాటు చేయండి."</string>
     <string name="car_permission_label_hvac" msgid="1499454192558727843">"కారు hvacని యాక్సెస్ చేయగలవు"</string>
     <string name="car_permission_desc_hvac" msgid="3754229695589774195">"మీ కారు యొక్క hvacని యాక్సెస్ చేయండి"</string>
     <string name="car_permission_label_mileage" msgid="4661317074631150551">"కారు మైలేజీ సమాచారాన్ని యాక్సెస్ చేయగలవు"</string>
@@ -62,16 +64,17 @@
     <string name="car_permission_desc_car_ux_restrictions_configuration" msgid="5711926927484813777">"UX పరిమితులను కాన్ఫిగర్ చెయ్యండి"</string>
     <string name="car_permission_label_car_handle_usb_aoap_device" msgid="72783989504378036">"AOAP మోడ్‌లో USB పరికరాన్ని కమ్యూనికేట్ చేయండి"</string>
     <string name="car_permission_desc_car_handle_usb_aoap_device" msgid="273505990971317034">"AOAP మోడ్‌లో పరికరంతో కమ్యూనికేట్ చేయడానికి యాప్‌ను అనుమతిస్తుంది"</string>
+    <string name="car_permission_label_read_car_occupant_awareness_state" msgid="125517953575032758">"Occupant Awareness Systemను చదవే యాక్సెస్"</string>
+    <string name="car_permission_desc_read_car_occupant_awareness_state" msgid="188865882598414986">"Occupant Awareness Systemకు చదివే స్థితిని, డేటాను గుర్తించడాన్ని అనుమతిస్తుంది"</string>
+    <string name="car_permission_label_control_car_occupant_awareness_system" msgid="7163330266691094542">"Occupant Awareness System గ్రాఫ్‌ను నియంత్రించు"</string>
+    <string name="car_permission_desc_control_car_occupant_awareness_system" msgid="7123482622084531911">"Occupant Awareness Systemను గుర్తించే గ్రాఫ్‌ను ప్రారంభించడాన్ని, ఆపివేయడాన్ని నియంత్రించడానికి అనుమతిస్తుంది"</string>
     <string name="car_permission_label_bind_input_service" msgid="6698489034024273750">"కారు ఇన్‌పుట్ సేవ"</string>
     <string name="car_permission_desc_bind_input_service" msgid="1670323419931890170">"ఇన్‌పుట్ ఈవెంట్‌లను హ్యాండిల్ చేయండి"</string>
     <string name="car_can_bus_failure" msgid="2334035748788283914">"CAN బస్సు విఫలమైంది"</string>
     <string name="car_can_bus_failure_desc" msgid="4125516222786484733">"CAN బస్సు స్పందించలేదు. హెడ్ యూనిట్ బాక్స్‌‍‌ని ప్లగ్ మరియు అన్‌ప్లగ్ చేసి కారుని పునఃప్రారంభించుము"</string>
-    <!-- no translation found for activity_blocked_text (8088902789540147995) -->
-    <skip />
+    <string name="activity_blocked_text" msgid="5342114426610711378">"మీ భద్రత విషయమై, ఈ కార్యాచరణ మీరు డ్రైవింగ్‌లో ఉన్నప్పుడు అందుబాటులో లేదు.\n కొనసాగించడానికి, మీరు పార్క్ చేయబడేవరకు వేచి ఉండండి."</string>
     <string name="exit_button_message" msgid="8554690915924055685">"సురక్షిత యాప్ లక్షణాలతో ప్రారంభించడానికి, <xliff:g id="EXIT_BUTTON">%s</xliff:g>ని ఎంచుకోండి."</string>
     <string name="exit_button" msgid="5829638404777671253">"వెనుకకు"</string>
-    <string name="exit_button_close_application" msgid="8824289547809332460">"యాప్‌ను మూసివేయండి"</string>
-    <string name="exit_button_go_back" msgid="3469083862100560326">"వెనుకకు"</string>
     <string name="car_permission_label_diag_read" msgid="7248894224877702604">"సమస్య విశ్లేషణ డేటాను తెలుసుకోగలవు"</string>
     <string name="car_permission_desc_diag_read" msgid="1121426363040966178">"కారు నుండి విశ్లేషణ డేటాను తెలుసుకోగలవు."</string>
     <string name="car_permission_label_diag_clear" msgid="4783070510879698157">"సమస్య విశ్లేషణ డేటాను క్లియర్ చేయగలవు"</string>
@@ -90,6 +93,8 @@
     <string name="car_permission_desc_car_engine_detailed" msgid="1746863362811347700">"మీ కారు యొక్క సమగ్ర ఇంజిన్ సమాచారాన్ని యాక్సెస్ చేయగలవు."</string>
     <string name="car_permission_label_car_energy_ports" msgid="8548990315169219454">"కారు ఇంధన డోర్ మరియు ఛార్జ్ పోర్ట్‌ను యాక్సెస్ చేయగలవు"</string>
     <string name="car_permission_desc_car_energy_ports" msgid="7771185999828794949">"కారు ఇంధన తలుపు మరియు ఛార్జ్ పోర్ట్‌ను యాక్సెస్ చేయగలవు."</string>
+    <string name="car_permission_label_control_car_energy_ports" msgid="4375137311026313475">"కారు ఇంధన డోర్, ఛార్జ్ పోర్ట్‌ను నియంత్రించు"</string>
+    <string name="car_permission_desc_control_car_energy_ports" msgid="7364633710492525387">"కారు ఇంధన డోర్, ఛార్జ్ పోర్ట్‌ను నియంత్రించు"</string>
     <string name="car_permission_label_car_identification" msgid="5896712510164020478">"కారు గుర్తింపును చూడగలవు"</string>
     <string name="car_permission_desc_car_identification" msgid="4132040867171275059">"కారు గుర్తింపును యాక్సెస్ చేయగలవు."</string>
     <string name="car_permission_label_control_car_doors" msgid="3032058819250195700">"కారు డోర్‌లను నియంత్రించగలవు"</string>
@@ -102,6 +107,8 @@
     <string name="car_permission_desc_control_car_seats" msgid="2407536601226470563">"కారు సీట్లను నియంత్రించగలవు."</string>
     <string name="car_permission_label_car_info" msgid="4707513570676492315">"కారు ప్రాథమిక సమాచారాన్ని యాక్సెస్ చేయగలవు"</string>
     <string name="car_permission_desc_car_info" msgid="2118081474543537653">"కారు యొక్క ప్రాథమిక సమాచారాన్ని యాక్సెస్ చేయగలవు."</string>
+    <string name="car_permission_label_vendor_permission_info" msgid="4471260460536888654">"కారు విక్రేత అనుమతి సమాచారాన్ని యాక్సెస్ చేయగలవు"</string>
+    <string name="car_permission_desc_vendor_permission_info" msgid="8152113853528488398">"కారు విక్రేత అనుమతి సమాచారాన్ని యాక్సెస్ చేయగలవు."</string>
     <string name="car_permission_label_car_exterior_lights" msgid="541304469604902110">"కారు బయటి లైట్‌ల స్థితిని తెలుసుకోగలవు"</string>
     <string name="car_permission_desc_car_exterior_lights" msgid="4038037584100849318">"కారు బయటి లైట్‌ల స్థితిని యాక్సెస్ చేయగలవు."</string>
     <string name="car_permission_label_control_car_exterior_lights" msgid="101357531386232141">"కారు బయటి లైట్‌ల స్థితిని తెలుసుకోగలవు"</string>
@@ -128,5 +135,9 @@
     <string name="car_permission_desc_enroll_trust" msgid="4148649994602185130">"విశ్వసనీయ పరికర నమోదును అనుమతించండి"</string>
     <string name="car_permission_label_car_test_service" msgid="9159328930558208708">"కారు యొక్క పరీక్ష మోడ్‌ను నియంత్రించండి"</string>
     <string name="car_permission_desc_car_test_service" msgid="7426844534110145843">"కారు యొక్క పరీక్ష మోడ్‌ను నియంత్రించండి"</string>
+    <string name="car_permission_label_control_car_features" msgid="3905791560378888286">"కార్ ఫీచర్‌లను ఎనేబుల్ లేదా డిజేబుల్ చేయడం"</string>
+    <string name="car_permission_desc_control_car_features" msgid="7646711104530599901">"కార్ ఫీచర్‌లను ఎనేబుల్ లేదా డిజేబుల్ చేయడం."</string>
+    <string name="car_permission_label_use_car_watchdog" msgid="6973938293170413475">"కార్ వాచ్‌డాగ్‌ను ఉపయోగించండి"</string>
+    <string name="car_permission_desc_use_car_watchdog" msgid="8244592601805516086">"కార్ వాచ్‌డాగ్‌ను ఉపయోగించండి."</string>
     <string name="trust_device_default_name" msgid="4213625926070261253">"నా పరికరం"</string>
 </resources>
diff --git a/service/res/values-th/strings.xml b/service/res/values-th/strings.xml
index 30b34f0..e0c3bdf 100644
--- a/service/res/values-th/strings.xml
+++ b/service/res/values-th/strings.xml
@@ -22,6 +22,8 @@
     <string name="car_permission_desc_camera" msgid="917024932164501426">"เข้าถึงกล้องของรถ"</string>
     <string name="car_permission_label_energy" msgid="7409144323527821558">"เข้าถึงข้อมูลพลังงานของรถ"</string>
     <string name="car_permission_desc_energy" msgid="3392963810053235407">"เข้าถึงข้อมูลพลังงานของรถ"</string>
+    <string name="car_permission_label_adjust_range_remaining" msgid="839033553999920138">"ปรับระยะวิ่งที่เหลืออยู่ของรถ"</string>
+    <string name="car_permission_desc_adjust_range_remaining" msgid="2369321650437370673">"ปรับค่าระยะวิ่งที่เหลืออยู่ของรถ"</string>
     <string name="car_permission_label_hvac" msgid="1499454192558727843">"เข้าถึง HVAC ของรถ"</string>
     <string name="car_permission_desc_hvac" msgid="3754229695589774195">"เข้าถึง HVAC ของรถ"</string>
     <string name="car_permission_label_mileage" msgid="4661317074631150551">"เข้าถึงข้อมูลระยะไมล์ของรถ"</string>
@@ -62,16 +64,17 @@
     <string name="car_permission_desc_car_ux_restrictions_configuration" msgid="5711926927484813777">"กำหนดค่าข้อจำกัด UX"</string>
     <string name="car_permission_label_car_handle_usb_aoap_device" msgid="72783989504378036">"สื่อสารกับอุปกรณ์ USB ในโหมด AOAP"</string>
     <string name="car_permission_desc_car_handle_usb_aoap_device" msgid="273505990971317034">"อนุญาตให้แอปสื่อสารกับอุปกรณ์ในโหมด AOAP"</string>
+    <string name="car_permission_label_read_car_occupant_awareness_state" msgid="125517953575032758">"การเข้าถึงการอ่านระบบการรับรู้ว่ามีคนอยู่"</string>
+    <string name="car_permission_desc_read_car_occupant_awareness_state" msgid="188865882598414986">"อนุญาตการอ่านข้อมูลสถานะและการตรวจจับของระบบการรับรู้ว่ามีคนอยู่"</string>
+    <string name="car_permission_label_control_car_occupant_awareness_system" msgid="7163330266691094542">"ควบคุมกราฟระบบการรับรู้ว่ามีคนอยู่"</string>
+    <string name="car_permission_desc_control_car_occupant_awareness_system" msgid="7123482622084531911">"อนุญาตการเริ่มต้นและหยุดกราฟการตรวจจับของระบบการรับรู้ว่ามีคนอยู่"</string>
     <string name="car_permission_label_bind_input_service" msgid="6698489034024273750">"บริการป้อนข้อมูลของรถ"</string>
     <string name="car_permission_desc_bind_input_service" msgid="1670323419931890170">"จัดการเหตุการณ์การป้อนข้อมูล"</string>
     <string name="car_can_bus_failure" msgid="2334035748788283914">"CAN Bus ล้มเหลว"</string>
     <string name="car_can_bus_failure_desc" msgid="4125516222786484733">"CAN Bus ไม่ตอบสนอง ถอดปลั๊กกล่องเครื่องเล่นวิทยุ (Headunit) แล้วเสียบกลับเข้าไป สตาร์ทรถอีกครั้ง"</string>
-    <!-- no translation found for activity_blocked_text (8088902789540147995) -->
-    <skip />
+    <string name="activity_blocked_text" msgid="5342114426610711378">"เพื่อความปลอดภัย กิจกรรมนี้จะไม่เปิดให้ใช้งานขณะขับรถ\nคุณต้องจอดรถให้เรียบร้อยก่อน จึงจะดำเนินการต่อได้"</string>
     <string name="exit_button_message" msgid="8554690915924055685">"เลือก <xliff:g id="EXIT_BUTTON">%s</xliff:g> เพื่อเริ่มต้นใหม่โดยใช้ฟีเจอร์แอปที่ปลอดภัย"</string>
     <string name="exit_button" msgid="5829638404777671253">"กลับ"</string>
-    <string name="exit_button_close_application" msgid="8824289547809332460">"ปิดแอป"</string>
-    <string name="exit_button_go_back" msgid="3469083862100560326">"กลับ"</string>
     <string name="car_permission_label_diag_read" msgid="7248894224877702604">"อ่านข้อมูลการวินิจฉัย"</string>
     <string name="car_permission_desc_diag_read" msgid="1121426363040966178">"อ่านข้อมูลการวินิจฉัยจากรถ"</string>
     <string name="car_permission_label_diag_clear" msgid="4783070510879698157">"ล้างข้อมูลการวินิจฉัย"</string>
@@ -90,6 +93,8 @@
     <string name="car_permission_desc_car_engine_detailed" msgid="1746863362811347700">"เข้าถึงข้อมูลเครื่องยนต์ของรถโดยละเอียด"</string>
     <string name="car_permission_label_car_energy_ports" msgid="8548990315169219454">"เข้าถึงฝาถังน้ำมันและพอร์ตชาร์จ"</string>
     <string name="car_permission_desc_car_energy_ports" msgid="7771185999828794949">"เข้าถึงฝาถังน้ำมันและพอร์ตชาร์จ"</string>
+    <string name="car_permission_label_control_car_energy_ports" msgid="4375137311026313475">"ควบคุมฝาถังน้ำมันและพอร์ตชาร์จของรถ"</string>
+    <string name="car_permission_desc_control_car_energy_ports" msgid="7364633710492525387">"ควบคุมฝาถังน้ำมันและพอร์ตชาร์จของรถ"</string>
     <string name="car_permission_label_car_identification" msgid="5896712510164020478">"อ่านการระบุตัวรถ"</string>
     <string name="car_permission_desc_car_identification" msgid="4132040867171275059">"เข้าถึงการระบุตัวรถ"</string>
     <string name="car_permission_label_control_car_doors" msgid="3032058819250195700">"ควบคุมประตูรถ"</string>
@@ -102,6 +107,8 @@
     <string name="car_permission_desc_control_car_seats" msgid="2407536601226470563">"ควบคุมที่นั่งในรถ"</string>
     <string name="car_permission_label_car_info" msgid="4707513570676492315">"เข้าถึงข้อมูลเบื้องต้นของรถ"</string>
     <string name="car_permission_desc_car_info" msgid="2118081474543537653">"เข้าถึงข้อมูลเบื้องต้นของรถ"</string>
+    <string name="car_permission_label_vendor_permission_info" msgid="4471260460536888654">"เข้าถึงข้อมูลสิทธิ์ผู้จำหน่ายของรถ"</string>
+    <string name="car_permission_desc_vendor_permission_info" msgid="8152113853528488398">"เข้าถึงข้อมูลสิทธิ์ผู้จำหน่ายของรถ"</string>
     <string name="car_permission_label_car_exterior_lights" msgid="541304469604902110">"อ่านสถานะไฟภายนอกรถ"</string>
     <string name="car_permission_desc_car_exterior_lights" msgid="4038037584100849318">"เข้าถึงสถานะไฟภายนอกรถ"</string>
     <string name="car_permission_label_control_car_exterior_lights" msgid="101357531386232141">"อ่านไฟภายนอกรถ"</string>
@@ -128,5 +135,9 @@
     <string name="car_permission_desc_enroll_trust" msgid="4148649994602185130">"อนุญาตการลงทะเบียนอุปกรณ์ที่เชื่อถือได้"</string>
     <string name="car_permission_label_car_test_service" msgid="9159328930558208708">"ควบคุมโหมดการทดสอบของรถยนต์"</string>
     <string name="car_permission_desc_car_test_service" msgid="7426844534110145843">"ควบคุมโหมดการทดสอบของรถยนต์"</string>
+    <string name="car_permission_label_control_car_features" msgid="3905791560378888286">"เปิดหรือปิดใช้ฟีเจอร์ของรถยนต์"</string>
+    <string name="car_permission_desc_control_car_features" msgid="7646711104530599901">"เปิดหรือปิดใช้ฟีเจอร์ของรถยนต์"</string>
+    <string name="car_permission_label_use_car_watchdog" msgid="6973938293170413475">"ใช้ Watchdog ในรถ"</string>
+    <string name="car_permission_desc_use_car_watchdog" msgid="8244592601805516086">"ใช้ Watchdog ในรถ"</string>
     <string name="trust_device_default_name" msgid="4213625926070261253">"อุปกรณ์ของฉัน"</string>
 </resources>
diff --git a/service/res/values-tl/strings.xml b/service/res/values-tl/strings.xml
index 2d31f5e..8b9ca7e 100644
--- a/service/res/values-tl/strings.xml
+++ b/service/res/values-tl/strings.xml
@@ -22,6 +22,8 @@
     <string name="car_permission_desc_camera" msgid="917024932164501426">"I-access ang (mga) camera ng iyong sasakyan."</string>
     <string name="car_permission_label_energy" msgid="7409144323527821558">"i-access ang impormasyon ng enerhiya ng sasakyan"</string>
     <string name="car_permission_desc_energy" msgid="3392963810053235407">"I-access ang impormasyon sa enerhiya ng iyong sasakyan."</string>
+    <string name="car_permission_label_adjust_range_remaining" msgid="839033553999920138">"isaayos ang natitirang range ng sasakyan"</string>
+    <string name="car_permission_desc_adjust_range_remaining" msgid="2369321650437370673">"Isaayos ang value ng natitirang range ng sasakyan."</string>
     <string name="car_permission_label_hvac" msgid="1499454192558727843">"i-access ang hvac ng sasakyan"</string>
     <string name="car_permission_desc_hvac" msgid="3754229695589774195">"I-access ang hvac ng iyong sasakyan."</string>
     <string name="car_permission_label_mileage" msgid="4661317074631150551">"i-access ang impormasyon ng mileage ng sasakyan"</string>
@@ -62,16 +64,17 @@
     <string name="car_permission_desc_car_ux_restrictions_configuration" msgid="5711926927484813777">"I-configure ang Mga Paghihigpit sa UX"</string>
     <string name="car_permission_label_car_handle_usb_aoap_device" msgid="72783989504378036">"Makipag-ugnayan sa pamamagitan ng USB device sa AOAP mode"</string>
     <string name="car_permission_desc_car_handle_usb_aoap_device" msgid="273505990971317034">"Binibigyang-daan ang isang app na makipag-ugnayan sa isang device sa AOAP mode"</string>
+    <string name="car_permission_label_read_car_occupant_awareness_state" msgid="125517953575032758">"Access sa Pagbasa sa Occupant Awareness System"</string>
+    <string name="car_permission_desc_read_car_occupant_awareness_state" msgid="188865882598414986">"Pinapayagan ang pagbasa sa status at data ng pag-detect para sa Occupant Awareness System"</string>
+    <string name="car_permission_label_control_car_occupant_awareness_system" msgid="7163330266691094542">"Kontrolin ang Graph ng Occupant Awareness System"</string>
+    <string name="car_permission_desc_control_car_occupant_awareness_system" msgid="7123482622084531911">"Pinapayagan ang pagkontrol sa pagsimula at paghinto sa detection graph ng Occupant Awareness System"</string>
     <string name="car_permission_label_bind_input_service" msgid="6698489034024273750">"Serbisyo sa Input ng Sasakyan"</string>
     <string name="car_permission_desc_bind_input_service" msgid="1670323419931890170">"Pangasiwaan ang mga event ng input"</string>
     <string name="car_can_bus_failure" msgid="2334035748788283914">"Hindi gumana ang CAN bus"</string>
     <string name="car_can_bus_failure_desc" msgid="4125516222786484733">"Hindi tumugon ang CAN bus. Hugutin at muling isaksak ang headunit box at i-restart ang sasakyan"</string>
-    <!-- no translation found for activity_blocked_text (8088902789540147995) -->
-    <skip />
+    <string name="activity_blocked_text" msgid="5342114426610711378">"Para sa seguridad, hindi available ang aktibidad habang nagmamaneho.\nPara magpatuloy, maghintay hanggang sa makaparada."</string>
     <string name="exit_button_message" msgid="8554690915924055685">"Para magsimula sa mga ligtas na feature ng app, piliin ang <xliff:g id="EXIT_BUTTON">%s</xliff:g>."</string>
     <string name="exit_button" msgid="5829638404777671253">"Bumalik"</string>
-    <string name="exit_button_close_application" msgid="8824289547809332460">"Isara ang app"</string>
-    <string name="exit_button_go_back" msgid="3469083862100560326">"Bumalik"</string>
     <string name="car_permission_label_diag_read" msgid="7248894224877702604">"basahin ang data ng mga diagnostic"</string>
     <string name="car_permission_desc_diag_read" msgid="1121426363040966178">"Basahin ang data ng mga diagnostic mula sa sasakyan."</string>
     <string name="car_permission_label_diag_clear" msgid="4783070510879698157">"i-clear ang data ng mga diagnostic"</string>
@@ -90,6 +93,8 @@
     <string name="car_permission_desc_car_engine_detailed" msgid="1746863362811347700">"I-access ang detalyadong impormasyon sa makina ng iyong sasakyan."</string>
     <string name="car_permission_label_car_energy_ports" msgid="8548990315169219454">"i-access ang takip ng gasolina at charge port ng sasakyan"</string>
     <string name="car_permission_desc_car_energy_ports" msgid="7771185999828794949">"I-access ang takip ng gasolina at charge port ng sasakyan."</string>
+    <string name="car_permission_label_control_car_energy_ports" msgid="4375137311026313475">"kontrolin ang takip ng gasolina at charge port ng sasakyan"</string>
+    <string name="car_permission_desc_control_car_energy_ports" msgid="7364633710492525387">"Kontrolin ang takip ng gasolina at charge port ng sasakyan."</string>
     <string name="car_permission_label_car_identification" msgid="5896712510164020478">"basahin ang pagkakakilanlan ng sasakyan"</string>
     <string name="car_permission_desc_car_identification" msgid="4132040867171275059">"I-access ang pagkakakilanlan ng sasakyan."</string>
     <string name="car_permission_label_control_car_doors" msgid="3032058819250195700">"kontrolin ang mga pintuan ng sasakyan"</string>
@@ -102,6 +107,8 @@
     <string name="car_permission_desc_control_car_seats" msgid="2407536601226470563">"Kontrolin ang mga upuan ng sasakyan."</string>
     <string name="car_permission_label_car_info" msgid="4707513570676492315">"i-access ang pangunahing impormasyon ng sasakyan"</string>
     <string name="car_permission_desc_car_info" msgid="2118081474543537653">"I-access ang pangunahing impormasyon ng sasakyan."</string>
+    <string name="car_permission_label_vendor_permission_info" msgid="4471260460536888654">"i-access ang impormasyon ng pahintulot ng vendor ng sasakyan"</string>
+    <string name="car_permission_desc_vendor_permission_info" msgid="8152113853528488398">"I-access ang impormasyon ng pahintulot ng vendor ng sasakyan."</string>
     <string name="car_permission_label_car_exterior_lights" msgid="541304469604902110">"basahin ang status ng mga ilaw sa labas ng sasakyan"</string>
     <string name="car_permission_desc_car_exterior_lights" msgid="4038037584100849318">"I-access ang status ng mga ilaw sa labas ng sasakyan."</string>
     <string name="car_permission_label_control_car_exterior_lights" msgid="101357531386232141">"basahin ang mga ilaw sa labas ng sasakyan"</string>
@@ -128,5 +135,9 @@
     <string name="car_permission_desc_enroll_trust" msgid="4148649994602185130">"Payagan ang Pag-enroll ng Pinagkakatiwalaang Device"</string>
     <string name="car_permission_label_car_test_service" msgid="9159328930558208708">"Kontrolin ang test mode ng kotse"</string>
     <string name="car_permission_desc_car_test_service" msgid="7426844534110145843">"Kontrolin ang test mode ng kotse"</string>
+    <string name="car_permission_label_control_car_features" msgid="3905791560378888286">"I-enable o i-disable ang mga feature ng kotse."</string>
+    <string name="car_permission_desc_control_car_features" msgid="7646711104530599901">"I-enable o i-disable ang mga feature ng kotse."</string>
+    <string name="car_permission_label_use_car_watchdog" msgid="6973938293170413475">"gamitin ang watchdog ng sasakyan"</string>
+    <string name="car_permission_desc_use_car_watchdog" msgid="8244592601805516086">"Gamitin ang watchdog ng sasakyan."</string>
     <string name="trust_device_default_name" msgid="4213625926070261253">"Aking Device"</string>
 </resources>
diff --git a/service/res/values-tr/strings.xml b/service/res/values-tr/strings.xml
index 22ef803..6062d11 100644
--- a/service/res/values-tr/strings.xml
+++ b/service/res/values-tr/strings.xml
@@ -22,6 +22,8 @@
     <string name="car_permission_desc_camera" msgid="917024932164501426">"Aracınızın kameralarına erişim."</string>
     <string name="car_permission_label_energy" msgid="7409144323527821558">"aracın enerji bilgilerine erişim"</string>
     <string name="car_permission_desc_energy" msgid="3392963810053235407">"Aracınızın enerji bilgilerine erişim."</string>
+    <string name="car_permission_label_adjust_range_remaining" msgid="839033553999920138">"aracın kalan menzil değerini düzenleme"</string>
+    <string name="car_permission_desc_adjust_range_remaining" msgid="2369321650437370673">"Aracın kalan menzil değerini düzenler."</string>
     <string name="car_permission_label_hvac" msgid="1499454192558727843">"aracın HVAC\'sine erişim"</string>
     <string name="car_permission_desc_hvac" msgid="3754229695589774195">"Aracınızın HVAC\'sine erişim."</string>
     <string name="car_permission_label_mileage" msgid="4661317074631150551">"aracın kilometre bilgilerine erişim"</string>
@@ -62,16 +64,17 @@
     <string name="car_permission_desc_car_ux_restrictions_configuration" msgid="5711926927484813777">"Kullanıcı Deneyimi Kısıtlamalarını Yapılandırma"</string>
     <string name="car_permission_label_car_handle_usb_aoap_device" msgid="72783989504378036">"AOAP modunda USB cihazıyla iletişim kur"</string>
     <string name="car_permission_desc_car_handle_usb_aoap_device" msgid="273505990971317034">"Uygulamaların AOAP modunda cihazlarla iletişim kurmasına izin verir"</string>
+    <string name="car_permission_label_read_car_occupant_awareness_state" msgid="125517953575032758">"Yolcu Algılama Sistemi Okuma Erişimi"</string>
+    <string name="car_permission_desc_read_car_occupant_awareness_state" msgid="188865882598414986">"Yolcu Algılama Sistemi için durumun ve tespit verilerinin okunmasına izin verir"</string>
+    <string name="car_permission_label_control_car_occupant_awareness_system" msgid="7163330266691094542">"Yolcu Algılama Sistemi Grafiğini kontrol et"</string>
+    <string name="car_permission_desc_control_car_occupant_awareness_system" msgid="7123482622084531911">"Yolcu Algılama Sistemi tespit grafiğinin başlatılmasının ve durdurulmasının kontrol edilmesine izin verir"</string>
     <string name="car_permission_label_bind_input_service" msgid="6698489034024273750">"Araç Giriş Hizmeti"</string>
     <string name="car_permission_desc_bind_input_service" msgid="1670323419931890170">"Giriş olaylarını işleme"</string>
     <string name="car_can_bus_failure" msgid="2334035748788283914">"CAN veri yolu başarısız"</string>
     <string name="car_can_bus_failure_desc" msgid="4125516222786484733">"CAN veri yolu yanıt vermiyor. Ana birim kutusunu söküp tekrar takın ve aracı yeniden çalıştırın"</string>
-    <!-- no translation found for activity_blocked_text (8088902789540147995) -->
-    <skip />
+    <string name="activity_blocked_text" msgid="5342114426610711378">"Güvenliğiniz için bu etkinlik sürüş sırasında gerçekleştirilemez.\nDevam etmek için park edene dek bekleyin."</string>
     <string name="exit_button_message" msgid="8554690915924055685">"Güvenli uygulama özellikleriyle baştan başlamak için <xliff:g id="EXIT_BUTTON">%s</xliff:g> düğmesini seçin."</string>
     <string name="exit_button" msgid="5829638404777671253">"Geri"</string>
-    <string name="exit_button_close_application" msgid="8824289547809332460">"Uygulamayı kapat"</string>
-    <string name="exit_button_go_back" msgid="3469083862100560326">"Geri"</string>
     <string name="car_permission_label_diag_read" msgid="7248894224877702604">"teşhis verilerini okuma"</string>
     <string name="car_permission_desc_diag_read" msgid="1121426363040966178">"Araçtan teşhis verilerini okuma."</string>
     <string name="car_permission_label_diag_clear" msgid="4783070510879698157">"teşhis verilerini temizleme"</string>
@@ -90,6 +93,8 @@
     <string name="car_permission_desc_car_engine_detailed" msgid="1746863362811347700">"Aracınızın ayrıntılı motor bilgilerine erişim."</string>
     <string name="car_permission_label_car_energy_ports" msgid="8548990315169219454">"aracın yakıt kapağına ve şarj bağlantı noktasına erişim"</string>
     <string name="car_permission_desc_car_energy_ports" msgid="7771185999828794949">"Aracın yakıt kapağına ve şarj bağlantı noktasına erişim."</string>
+    <string name="car_permission_label_control_car_energy_ports" msgid="4375137311026313475">"aracın yakıt kapağını ve şarj bağlantı noktasını kontrol et"</string>
+    <string name="car_permission_desc_control_car_energy_ports" msgid="7364633710492525387">"Aracın yakıt kapağını ve şarj bağlantı noktasını kontrol edin."</string>
     <string name="car_permission_label_car_identification" msgid="5896712510164020478">"aracın kimliğini okuma"</string>
     <string name="car_permission_desc_car_identification" msgid="4132040867171275059">"Aracın kimliğine erişim."</string>
     <string name="car_permission_label_control_car_doors" msgid="3032058819250195700">"aracın kapılarını kontrol etme"</string>
@@ -102,6 +107,8 @@
     <string name="car_permission_desc_control_car_seats" msgid="2407536601226470563">"Aracın koltuklarını kontrol etme."</string>
     <string name="car_permission_label_car_info" msgid="4707513570676492315">"aracın temel bilgilerine erişim"</string>
     <string name="car_permission_desc_car_info" msgid="2118081474543537653">"Aracın temel bilgilerine erişim."</string>
+    <string name="car_permission_label_vendor_permission_info" msgid="4471260460536888654">"aracın tedarikçi firma izin bilgilerine erişin"</string>
+    <string name="car_permission_desc_vendor_permission_info" msgid="8152113853528488398">"Aracın tedarikçi firma izin bilgilerine erişin."</string>
     <string name="car_permission_label_car_exterior_lights" msgid="541304469604902110">"aracın dış ışıklarının durumunu okuma"</string>
     <string name="car_permission_desc_car_exterior_lights" msgid="4038037584100849318">"Aracın dış ışıklarının durumuna erişim."</string>
     <string name="car_permission_label_control_car_exterior_lights" msgid="101357531386232141">"aracın dış ışıklarını okuma"</string>
@@ -128,5 +135,9 @@
     <string name="car_permission_desc_enroll_trust" msgid="4148649994602185130">"Güvenilen Cihaz Kaydına İzin Ver"</string>
     <string name="car_permission_label_car_test_service" msgid="9159328930558208708">"Aracın test modunu kontrol etme"</string>
     <string name="car_permission_desc_car_test_service" msgid="7426844534110145843">"Aracın test modunu kontrol etme"</string>
+    <string name="car_permission_label_control_car_features" msgid="3905791560378888286">"Arabanın özelliklerini etkinleştirin veya devre dışı bırakın"</string>
+    <string name="car_permission_desc_control_car_features" msgid="7646711104530599901">"Arabanın özelliklerini etkinleştirin veya devre dışı bırakın."</string>
+    <string name="car_permission_label_use_car_watchdog" msgid="6973938293170413475">"araç güvenlik zamanlayıcısını kullan"</string>
+    <string name="car_permission_desc_use_car_watchdog" msgid="8244592601805516086">"Araç güvenlik zamanlayıcısını kullan."</string>
     <string name="trust_device_default_name" msgid="4213625926070261253">"Cihazım"</string>
 </resources>
diff --git a/service/res/values-uk/strings.xml b/service/res/values-uk/strings.xml
index f457103..235494c 100644
--- a/service/res/values-uk/strings.xml
+++ b/service/res/values-uk/strings.xml
@@ -22,6 +22,8 @@
     <string name="car_permission_desc_camera" msgid="917024932164501426">"Доступ до камер автомобіля."</string>
     <string name="car_permission_label_energy" msgid="7409144323527821558">"доступ до інформації про енергоспоживання автомобіля"</string>
     <string name="car_permission_desc_energy" msgid="3392963810053235407">"Доступ до інформації про енергоспоживання автомобіля."</string>
+    <string name="car_permission_label_adjust_range_remaining" msgid="839033553999920138">"коригувати залишок пробігу автомобіля"</string>
+    <string name="car_permission_desc_adjust_range_remaining" msgid="2369321650437370673">"Коригувати значення залишку пробігу автомобіля."</string>
     <string name="car_permission_label_hvac" msgid="1499454192558727843">"доступ до системи клімат-контролю автомобіля"</string>
     <string name="car_permission_desc_hvac" msgid="3754229695589774195">"Доступ до системи клімат-контролю автомобіля."</string>
     <string name="car_permission_label_mileage" msgid="4661317074631150551">"доступ до інформації про пробіг автомобіля"</string>
@@ -62,16 +64,17 @@
     <string name="car_permission_desc_car_ux_restrictions_configuration" msgid="5711926927484813777">"Налаштувати обмеження щодо використання функцій"</string>
     <string name="car_permission_label_car_handle_usb_aoap_device" msgid="72783989504378036">"Зв’язуватися з USB-пристроєм в режимі AOAP"</string>
     <string name="car_permission_desc_car_handle_usb_aoap_device" msgid="273505990971317034">"Дозволяє додатку зв’язуватися з пристроєм у режимі AOAP"</string>
+    <string name="car_permission_label_read_car_occupant_awareness_state" msgid="125517953575032758">"Перегляд даних системи визначення присутності пасажира"</string>
+    <string name="car_permission_desc_read_car_occupant_awareness_state" msgid="188865882598414986">"Дозволяє переглядати статус і дані системи визначення присутності пасажира"</string>
+    <string name="car_permission_label_control_car_occupant_awareness_system" msgid="7163330266691094542">"Керування графіком визначення присутності пасажира"</string>
+    <string name="car_permission_desc_control_car_occupant_awareness_system" msgid="7123482622084531911">"Дозволяє керувати графіком визначення присутності пасажира"</string>
     <string name="car_permission_label_bind_input_service" msgid="6698489034024273750">"Сервіс даних про вхідні події автомобіля"</string>
     <string name="car_permission_desc_bind_input_service" msgid="1670323419931890170">"Обробка вхідних подій"</string>
     <string name="car_can_bus_failure" msgid="2334035748788283914">"Помилка CAN-шини"</string>
     <string name="car_can_bus_failure_desc" msgid="4125516222786484733">"CAN-шина не відповідає. Від’єднайте та знову під’єднайте головний пристрій аудіосистеми й заведіть автомобіль ще раз"</string>
-    <!-- no translation found for activity_blocked_text (8088902789540147995) -->
-    <skip />
+    <string name="activity_blocked_text" msgid="5342114426610711378">"Задля вашої безпеки ці дії недоступні під час поїздки.\nЩоб продовжити, зупиніть автомобіль."</string>
     <string name="exit_button_message" msgid="8554690915924055685">"Щоб почати знову з безпечними функціями додатка, натисніть кнопку \"<xliff:g id="EXIT_BUTTON">%s</xliff:g>\"."</string>
     <string name="exit_button" msgid="5829638404777671253">"Назад"</string>
-    <string name="exit_button_close_application" msgid="8824289547809332460">"Закрити додаток"</string>
-    <string name="exit_button_go_back" msgid="3469083862100560326">"Назад"</string>
     <string name="car_permission_label_diag_read" msgid="7248894224877702604">"перегляд даних діагностики"</string>
     <string name="car_permission_desc_diag_read" msgid="1121426363040966178">"Перегляд даних діагностики автомобіля."</string>
     <string name="car_permission_label_diag_clear" msgid="4783070510879698157">"очищення даних діагностики"</string>
@@ -90,6 +93,8 @@
     <string name="car_permission_desc_car_engine_detailed" msgid="1746863362811347700">"Доступ до детальної інформації про двигун автомобіля."</string>
     <string name="car_permission_label_car_energy_ports" msgid="8548990315169219454">"доступ до даних про кришку паливного бака чи порт заряджання автомобіля"</string>
     <string name="car_permission_desc_car_energy_ports" msgid="7771185999828794949">"Доступ до даних про кришку паливного бака чи порт заряджання автомобіля."</string>
+    <string name="car_permission_label_control_car_energy_ports" msgid="4375137311026313475">"керування кришкою паливного бака й портом заряджання автомобіля"</string>
+    <string name="car_permission_desc_control_car_energy_ports" msgid="7364633710492525387">"Керування кришкою паливного бака й портом заряджання автомобіля."</string>
     <string name="car_permission_label_car_identification" msgid="5896712510164020478">"перегляд ідентифікаційного номера автомобіля"</string>
     <string name="car_permission_desc_car_identification" msgid="4132040867171275059">"Доступ до ідентифікаційного номера автомобіля."</string>
     <string name="car_permission_label_control_car_doors" msgid="3032058819250195700">"керування дверима автомобіля"</string>
@@ -102,6 +107,8 @@
     <string name="car_permission_desc_control_car_seats" msgid="2407536601226470563">"Керування сидіннями автомобіля."</string>
     <string name="car_permission_label_car_info" msgid="4707513570676492315">"доступ до основної інформації про автомобіль"</string>
     <string name="car_permission_desc_car_info" msgid="2118081474543537653">"Доступ до основної інформації про автомобіль."</string>
+    <string name="car_permission_label_vendor_permission_info" msgid="4471260460536888654">"мають доступ до інформації про дозволи виробника автомобіля"</string>
+    <string name="car_permission_desc_vendor_permission_info" msgid="8152113853528488398">"Мають доступ до інформації про дозволи виробника автомобіля."</string>
     <string name="car_permission_label_car_exterior_lights" msgid="541304469604902110">"перегляд стану зовнішніх світлових приладів автомобіля"</string>
     <string name="car_permission_desc_car_exterior_lights" msgid="4038037584100849318">"Керування зовнішніми світловими приладами автомобіля."</string>
     <string name="car_permission_label_control_car_exterior_lights" msgid="101357531386232141">"перегляд зовнішніх світлових приладів автомобіля"</string>
@@ -128,5 +135,9 @@
     <string name="car_permission_desc_enroll_trust" msgid="4148649994602185130">"Дозволити реєстрацію надійних пристроїв"</string>
     <string name="car_permission_label_car_test_service" msgid="9159328930558208708">"Керувати режимом тестування автомобіля"</string>
     <string name="car_permission_desc_car_test_service" msgid="7426844534110145843">"Керувати режимом тестування автомобіля"</string>
+    <string name="car_permission_label_control_car_features" msgid="3905791560378888286">"Вмикати чи вимикати функції автомобіля"</string>
+    <string name="car_permission_desc_control_car_features" msgid="7646711104530599901">"Вмикати чи вимикати функції автомобіля"</string>
+    <string name="car_permission_label_use_car_watchdog" msgid="6973938293170413475">"використовувати сторожовий таймер автомобіля"</string>
+    <string name="car_permission_desc_use_car_watchdog" msgid="8244592601805516086">"Використовувати сторожовий таймер автомобіля."</string>
     <string name="trust_device_default_name" msgid="4213625926070261253">"Мій пристрій"</string>
 </resources>
diff --git a/service/res/values-ur/strings.xml b/service/res/values-ur/strings.xml
index 6669e93..0025f4a 100644
--- a/service/res/values-ur/strings.xml
+++ b/service/res/values-ur/strings.xml
@@ -22,6 +22,8 @@
     <string name="car_permission_desc_camera" msgid="917024932164501426">"اپنی کار کے کیمرے (کیمروں) تک رسائی حاصل کریں۔"</string>
     <string name="car_permission_label_energy" msgid="7409144323527821558">"کار کی انرجی کی معلومات تک رسائی حاصل کریں"</string>
     <string name="car_permission_desc_energy" msgid="3392963810053235407">"اپنی کار کی انرجی کی معلومات تک رسائی حاصل کریں۔"</string>
+    <string name="car_permission_label_adjust_range_remaining" msgid="839033553999920138">"کار کی باقی حد ایڈجسٹ کریں"</string>
+    <string name="car_permission_desc_adjust_range_remaining" msgid="2369321650437370673">"کار کی باقی حد قدر کو ایڈجسٹ کریں۔"</string>
     <string name="car_permission_label_hvac" msgid="1499454192558727843">"کار کی hvac تک رسائی حاصل کریں"</string>
     <string name="car_permission_desc_hvac" msgid="3754229695589774195">"اپنی کار کی hvac تک رسائی حاصل کریں۔"</string>
     <string name="car_permission_label_mileage" msgid="4661317074631150551">"کار کی مائلیج کی معلومات تک رسائی حاصل کریں"</string>
@@ -62,16 +64,17 @@
     <string name="car_permission_desc_car_ux_restrictions_configuration" msgid="5711926927484813777">"UX کی پابندیاں ترتیب دیں"</string>
     <string name="car_permission_label_car_handle_usb_aoap_device" msgid="72783989504378036">"USB آلہ کے ساتھ AOAP وضع میں مواصلت کریں"</string>
     <string name="car_permission_desc_car_handle_usb_aoap_device" msgid="273505990971317034">"آلہ کے ساتھ AOAP وضع میں ایپ مواصلت کر سکتی ہے"</string>
+    <string name="car_permission_label_read_car_occupant_awareness_state" msgid="125517953575032758">"کار میں بیٹھنے والے فرد کے لیے بنے آگاہی کے سسٹم کی رسائی کو پڑھیں"</string>
+    <string name="car_permission_desc_read_car_occupant_awareness_state" msgid="188865882598414986">"کار میں بیٹھنے والے فرد کے لیے بنے آگاہی کے سسٹم کیلئے پڑھنے کی حیثیت اور ڈیٹا کا پتہ لگانے کی اجازت دیتا ہے"</string>
+    <string name="car_permission_label_control_car_occupant_awareness_system" msgid="7163330266691094542">"کار میں بیٹھنے والے فرد کے لیے بنے آگاہی کے سسٹم کے گراف کو کنٹرول کریں"</string>
+    <string name="car_permission_desc_control_car_occupant_awareness_system" msgid="7123482622084531911">"کار میں بیٹھنے والے فرد کے لیے بنا آگاہی کا سسٹم پتہ لگانے کے گراف کو شروع کرنے اور روکنے کی اجازت دیتا ہے"</string>
     <string name="car_permission_label_bind_input_service" msgid="6698489034024273750">"کار کی ان پٹ سروس"</string>
     <string name="car_permission_desc_bind_input_service" msgid="1670323419931890170">"ایونٹس کے ان پٹ کو ہینڈل کریں"</string>
     <string name="car_can_bus_failure" msgid="2334035748788283914">"کین بس ناکام ہو گئی"</string>
     <string name="car_can_bus_failure_desc" msgid="4125516222786484733">"کین بس جواب نہیں دیتی ہے۔ ہیڈیونٹ باکس کو ان پلگ کر کے دوبارہ پلگ کریں اور کار کو دوبارہ شروع کریں"</string>
-    <!-- no translation found for activity_blocked_text (8088902789540147995) -->
-    <skip />
+    <string name="activity_blocked_text" msgid="5342114426610711378">"آپ کی حفاظت کے لیے یہ سرگرمی آپ کے کار چلانے کے دوران دستیاب نہیں ہے۔\n جاری رکھنے کے لیے کار کے پارک ہونے تک انتظار کریں۔"</string>
     <string name="exit_button_message" msgid="8554690915924055685">"محفوظ اپپ کی خصوصیات کے ساتھ شروع کرنے کے لیے <xliff:g id="EXIT_BUTTON">%s</xliff:g> پر کلک کریں۔"</string>
     <string name="exit_button" msgid="5829638404777671253">"پیچھے"</string>
-    <string name="exit_button_close_application" msgid="8824289547809332460">"ایپ بند کریں"</string>
-    <string name="exit_button_go_back" msgid="3469083862100560326">"پیچھے"</string>
     <string name="car_permission_label_diag_read" msgid="7248894224877702604">"تشخیصی ڈیٹا پڑھیں"</string>
     <string name="car_permission_desc_diag_read" msgid="1121426363040966178">"کار کے تشخیصی ڈیٹا کے بارے میں پڑھیں۔"</string>
     <string name="car_permission_label_diag_clear" msgid="4783070510879698157">"تشخیصی ڈیٹا صاف کریں"</string>
@@ -90,6 +93,8 @@
     <string name="car_permission_desc_car_engine_detailed" msgid="1746863362811347700">"اپنی کار کے انجن کی تفصیلی معلومات تک رسائی حاصل کریں۔"</string>
     <string name="car_permission_label_car_energy_ports" msgid="8548990315169219454">"کار کے ایندھن کے دروازے اور چارج پورٹ تک رسائی حاصل کریں"</string>
     <string name="car_permission_desc_car_energy_ports" msgid="7771185999828794949">"کار کے ایندھن کے دروازے اور چارج پورٹ تک رسائی حاصل کریں۔"</string>
+    <string name="car_permission_label_control_car_energy_ports" msgid="4375137311026313475">"کار کے ایندھن کے دروازے اور چارج پورٹ کنٹرول کریں"</string>
+    <string name="car_permission_desc_control_car_energy_ports" msgid="7364633710492525387">"کار کے ایندھن کے دروازے اور چارج پورٹ کنٹرول کریں۔"</string>
     <string name="car_permission_label_car_identification" msgid="5896712510164020478">"کار کی شناخت کے بارے میں پڑھیں"</string>
     <string name="car_permission_desc_car_identification" msgid="4132040867171275059">"کار کی شناخت تک رسائی حاصل کریں۔"</string>
     <string name="car_permission_label_control_car_doors" msgid="3032058819250195700">"کار کے دروازوں کو کنٹرول کریں"</string>
@@ -102,6 +107,8 @@
     <string name="car_permission_desc_control_car_seats" msgid="2407536601226470563">"کار کی سیٹوں کو کنٹرول کریں۔"</string>
     <string name="car_permission_label_car_info" msgid="4707513570676492315">"کار کی بنیادی معلومات تک رسائی حاصل کریں"</string>
     <string name="car_permission_desc_car_info" msgid="2118081474543537653">"کار کی بنیادی معلومات حاصل کریں۔"</string>
+    <string name="car_permission_label_vendor_permission_info" msgid="4471260460536888654">"کار کے وینڈر کے اجازت کی معلومات تک رسائی حاصل کریں"</string>
+    <string name="car_permission_desc_vendor_permission_info" msgid="8152113853528488398">"کار کے وینڈر کے اجازت کی معلومات تک رسائی حاصل کریں۔"</string>
     <string name="car_permission_label_car_exterior_lights" msgid="541304469604902110">"کار کی بیرونی روشنیوں کی صورتحال کے بارے میں پڑھیں"</string>
     <string name="car_permission_desc_car_exterior_lights" msgid="4038037584100849318">"کار کی بیرونی روشنیوں کی صورتحال تک رسائی حاصل کریں۔"</string>
     <string name="car_permission_label_control_car_exterior_lights" msgid="101357531386232141">"کار کی بیرونی روشنیوں کے بارے میں پڑھیں"</string>
@@ -128,5 +135,9 @@
     <string name="car_permission_desc_enroll_trust" msgid="4148649994602185130">"بھروسہ مند آلات کے اندراج کی اجازت دیں"</string>
     <string name="car_permission_label_car_test_service" msgid="9159328930558208708">"کار کے ٹیسٹ وضع کو کنٹرول کریں"</string>
     <string name="car_permission_desc_car_test_service" msgid="7426844534110145843">"کار کے ٹیسٹ وضع کو کنٹرول کریں"</string>
+    <string name="car_permission_label_control_car_features" msgid="3905791560378888286">"کار کی خصوصیات کو فعال یا غیر فعال کریں"</string>
+    <string name="car_permission_desc_control_car_features" msgid="7646711104530599901">"کار کی خصوصیات کو فعال یا غیر فعال کریں۔"</string>
+    <string name="car_permission_label_use_car_watchdog" msgid="6973938293170413475">"کار کے واچ ڈاگ کا ستعمال کریں"</string>
+    <string name="car_permission_desc_use_car_watchdog" msgid="8244592601805516086">"کار کے واچ ڈاگ کا ستعمال کریں۔"</string>
     <string name="trust_device_default_name" msgid="4213625926070261253">"میرا آلہ"</string>
 </resources>
diff --git a/service/res/values-uz/strings.xml b/service/res/values-uz/strings.xml
index 5adc769..9186a3e 100644
--- a/service/res/values-uz/strings.xml
+++ b/service/res/values-uz/strings.xml
@@ -22,6 +22,8 @@
     <string name="car_permission_desc_camera" msgid="917024932164501426">"Avtomobil kamerasidan foydalanish"</string>
     <string name="car_permission_label_energy" msgid="7409144323527821558">"avtomobilning energiya manbalari haqidagi axborotga kirish"</string>
     <string name="car_permission_desc_energy" msgid="3392963810053235407">"Avtomobilning energiya axborotlariga kirish"</string>
+    <string name="car_permission_label_adjust_range_remaining" msgid="839033553999920138">"avtomobil mavjud yonilgʻi bilan bosib oʻtadigan masofani tuzatish"</string>
+    <string name="car_permission_desc_adjust_range_remaining" msgid="2369321650437370673">"Avtomobil mavjud yonilgʻi bilan bosib oʻtadigan masofani tuzatish."</string>
     <string name="car_permission_label_hvac" msgid="1499454192558727843">"avtomobilning HVAC tizimiga kirish"</string>
     <string name="car_permission_desc_hvac" msgid="3754229695589774195">"Avtomobilning HVAC tizimiga kirish."</string>
     <string name="car_permission_label_mileage" msgid="4661317074631150551">"avtomobil yonilgʻisi qancha masofaga yetishi haqidagi axborotga kirish"</string>
@@ -62,16 +64,17 @@
     <string name="car_permission_desc_car_ux_restrictions_configuration" msgid="5711926927484813777">"Funksiyalardan foydalanishni cheklash"</string>
     <string name="car_permission_label_car_handle_usb_aoap_device" msgid="72783989504378036">"USB qurilma bilan AOAP rejimida axborot almashish"</string>
     <string name="car_permission_desc_car_handle_usb_aoap_device" msgid="273505990971317034">"Ilova AOAP rejimida qurilma bilan axborot almasha oladi"</string>
+    <string name="car_permission_label_read_car_occupant_awareness_state" msgid="125517953575032758">"Yoʻlovchilarni aniqlash tizimini oʻqishga ruxsat"</string>
+    <string name="car_permission_desc_read_car_occupant_awareness_state" msgid="188865882598414986">"Yoʻlovchilarni aniqlash tizimi uchun holat va aniqlash maʼlumotlarini oʻqishga ruxsat beradi"</string>
+    <string name="car_permission_label_control_car_occupant_awareness_system" msgid="7163330266691094542">"Yoʻlovchilarni aniqlash tizimi chizmasini boshqarish"</string>
+    <string name="car_permission_desc_control_car_occupant_awareness_system" msgid="7123482622084531911">"Yoʻlovchilarni aniqlash tizimining aniqlash chizmasini boshlash va toʻxtatishni boshqarishga ruxsat beradi"</string>
     <string name="car_permission_label_bind_input_service" msgid="6698489034024273750">"Avtomobilda matn kiritish xizmati"</string>
     <string name="car_permission_desc_bind_input_service" msgid="1670323419931890170">"Matn kiritish hodisalari bilan ishlash imkoniyati"</string>
     <string name="car_can_bus_failure" msgid="2334035748788283914">"CAN shinalarida xatolik yuz berdi"</string>
     <string name="car_can_bus_failure_desc" msgid="4125516222786484733">"CAN javob bermayapti. Konnektorni chiqaring va qayta ulang, keyin avtomobilni oʻt oldiring"</string>
-    <!-- no translation found for activity_blocked_text (8088902789540147995) -->
-    <skip />
+    <string name="activity_blocked_text" msgid="5342114426610711378">"Avtomobil haydayotganingizda bu harakatni amalga oshira olmaysiz.\nUni bajarish uchun avtomobilni toʻxtating."</string>
     <string name="exit_button_message" msgid="8554690915924055685">"Ilovani xavfsiz rejimda ishga tushirish uchun <xliff:g id="EXIT_BUTTON">%s</xliff:g> tugmasini bosing."</string>
     <string name="exit_button" msgid="5829638404777671253">"Orqaga"</string>
-    <string name="exit_button_close_application" msgid="8824289547809332460">"Ilovani yopish"</string>
-    <string name="exit_button_go_back" msgid="3469083862100560326">"Orqaga"</string>
     <string name="car_permission_label_diag_read" msgid="7248894224877702604">"diagnostika axborotini ochish"</string>
     <string name="car_permission_desc_diag_read" msgid="1121426363040966178">"Avtomobildan diagnostika axborotini olish."</string>
     <string name="car_permission_label_diag_clear" msgid="4783070510879698157">"diagnostika axborotini tozalash"</string>
@@ -90,6 +93,8 @@
     <string name="car_permission_desc_car_engine_detailed" msgid="1746863362811347700">"Avtomobilning motori haqidagi batafsil axborotga kirish."</string>
     <string name="car_permission_label_car_energy_ports" msgid="8548990315169219454">"avtomobilning yonilgʻi darajasi va quvvatlash porti haqidagi axborotga kirish"</string>
     <string name="car_permission_desc_car_energy_ports" msgid="7771185999828794949">"Avtomobilingizning yonilgʻi darajasi va quvvatlash porti haqidagi axborotga kirish."</string>
+    <string name="car_permission_label_control_car_energy_ports" msgid="4375137311026313475">"avtomobilning yonilgʻi darajasi va quvvatlash porti haqidagi axborotni boshqarish."</string>
+    <string name="car_permission_desc_control_car_energy_ports" msgid="7364633710492525387">"Avtomobilning yonilgʻi darajasi va quvvatlash porti haqidagi axborotni boshqarish."</string>
     <string name="car_permission_label_car_identification" msgid="5896712510164020478">"avtomobilning identifikatsiya axborotini ochish"</string>
     <string name="car_permission_desc_car_identification" msgid="4132040867171275059">"Avtomobilning identifikatsiya axborotiga kirish"</string>
     <string name="car_permission_label_control_car_doors" msgid="3032058819250195700">"avtomobilning eshiklarini boshqarish"</string>
@@ -102,6 +107,8 @@
     <string name="car_permission_desc_control_car_seats" msgid="2407536601226470563">"Avtomobil oʻrindiqlarini boshqarish"</string>
     <string name="car_permission_label_car_info" msgid="4707513570676492315">"avtomobil haqidagi batafsil axborotga kirish"</string>
     <string name="car_permission_desc_car_info" msgid="2118081474543537653">"Avtomobil haqidagi batafsil axborotga kirish."</string>
+    <string name="car_permission_label_vendor_permission_info" msgid="4471260460536888654">"avtomobilning taʼminotchi ruxsatlari axborotiga kirish"</string>
+    <string name="car_permission_desc_vendor_permission_info" msgid="8152113853528488398">"Avtomobilning taʼminotchi ruxsatlari axborotiga kirish."</string>
     <string name="car_permission_label_car_exterior_lights" msgid="541304469604902110">"avtomobilning tashqi chiroqlari holati haqidagi axborotni ochish"</string>
     <string name="car_permission_desc_car_exterior_lights" msgid="4038037584100849318">"Avtomobilning tashqi chiroqlari holati haqidagi axborotiga kirish."</string>
     <string name="car_permission_label_control_car_exterior_lights" msgid="101357531386232141">"avtomobilning tashqi chiroqlari holati haqidagi axborotni ochish"</string>
@@ -128,5 +135,9 @@
     <string name="car_permission_desc_enroll_trust" msgid="4148649994602185130">"Ishonchli qurilma registratsiyasiga ruxsat berish"</string>
     <string name="car_permission_label_car_test_service" msgid="9159328930558208708">"Avtomobilning sinov rejimini boshqarish"</string>
     <string name="car_permission_desc_car_test_service" msgid="7426844534110145843">"Avtomobilning sinov rejimini boshqarish"</string>
+    <string name="car_permission_label_control_car_features" msgid="3905791560378888286">"Avtomobil funksiyalarini yoqish yoki faolsizlantirish"</string>
+    <string name="car_permission_desc_control_car_features" msgid="7646711104530599901">"Avtomobil funksiyalarini yoqish yoki faolsizlantirish"</string>
+    <string name="car_permission_label_use_car_watchdog" msgid="6973938293170413475">"avtomobilni kuzatish tizimidan foydalanish."</string>
+    <string name="car_permission_desc_use_car_watchdog" msgid="8244592601805516086">"Avtomobilni kuzatish tizimidan foydalanish."</string>
     <string name="trust_device_default_name" msgid="4213625926070261253">"Qurilmam"</string>
 </resources>
diff --git a/service/res/values-vi/strings.xml b/service/res/values-vi/strings.xml
index 47edb4b..6af0b35 100644
--- a/service/res/values-vi/strings.xml
+++ b/service/res/values-vi/strings.xml
@@ -22,6 +22,8 @@
     <string name="car_permission_desc_camera" msgid="917024932164501426">"Truy cập vào (các) camera trên ô tô."</string>
     <string name="car_permission_label_energy" msgid="7409144323527821558">"truy cập vào thông tin về năng lượng của ô tô"</string>
     <string name="car_permission_desc_energy" msgid="3392963810053235407">"Truy cập vào thông tin về mức năng lượng trên ô tô."</string>
+    <string name="car_permission_label_adjust_range_remaining" msgid="839033553999920138">"điều chỉnh quãng đường còn đi được của ô tô"</string>
+    <string name="car_permission_desc_adjust_range_remaining" msgid="2369321650437370673">"Điều chỉnh giá trị quãng đường còn đi được của ô tô."</string>
     <string name="car_permission_label_hvac" msgid="1499454192558727843">"truy cập vào hệ thống điều hòa không khí (hvac) của ô tô"</string>
     <string name="car_permission_desc_hvac" msgid="3754229695589774195">"Truy cập vào hvac của ô tô."</string>
     <string name="car_permission_label_mileage" msgid="4661317074631150551">"truy cập vào thông tin về quãng đường đi được của ô tô"</string>
@@ -62,16 +64,17 @@
     <string name="car_permission_desc_car_ux_restrictions_configuration" msgid="5711926927484813777">"Định cấu hình các hạn chế trải nghiệm người dùng"</string>
     <string name="car_permission_label_car_handle_usb_aoap_device" msgid="72783989504378036">"Kết nối với thiết bị USB ở chế độ AOAP"</string>
     <string name="car_permission_desc_car_handle_usb_aoap_device" msgid="273505990971317034">"Cho phép ứng dụng kết nối với một thiết bị ở chế độ AOAP"</string>
+    <string name="car_permission_label_read_car_occupant_awareness_state" msgid="125517953575032758">"Quyền truy cập đọc Occupant Awareness System"</string>
+    <string name="car_permission_desc_read_car_occupant_awareness_state" msgid="188865882598414986">"Cho phép đọc trạng thái và dữ liệu phát hiện của Occupant Awareness System"</string>
+    <string name="car_permission_label_control_car_occupant_awareness_system" msgid="7163330266691094542">"Biểu đồ kiểm soát Occupant Awareness System"</string>
+    <string name="car_permission_desc_control_car_occupant_awareness_system" msgid="7123482622084531911">"Cho phép kiểm soát việc bắt đầu và dừng biểu đồ phát hiện Occupant Awareness System"</string>
     <string name="car_permission_label_bind_input_service" msgid="6698489034024273750">"Dịch vụ nhập dành cho ô tô"</string>
     <string name="car_permission_desc_bind_input_service" msgid="1670323419931890170">"Xử lý sự kiện nhập"</string>
     <string name="car_can_bus_failure" msgid="2334035748788283914">"Đường dẫn chính CAN không hoạt động"</string>
     <string name="car_can_bus_failure_desc" msgid="4125516222786484733">"Đường dẫn chính CAN không phản hồi. Rút rồi cắm lại hộp bộ đầu và khởi động lại ô tô"</string>
-    <!-- no translation found for activity_blocked_text (8088902789540147995) -->
-    <skip />
+    <string name="activity_blocked_text" msgid="5342114426610711378">"Vì lý do an toàn, bạn không sử dụng được hoạt động này trong khi lái xe.\nHãy đợi cho tới khi bạn đỗ xe để tiếp tục."</string>
     <string name="exit_button_message" msgid="8554690915924055685">"Để bắt đầu lại với các tính năng an toàn của ứng dụng, hãy chọn <xliff:g id="EXIT_BUTTON">%s</xliff:g>."</string>
     <string name="exit_button" msgid="5829638404777671253">"Quay lại"</string>
-    <string name="exit_button_close_application" msgid="8824289547809332460">"Tắt ứng dụng"</string>
-    <string name="exit_button_go_back" msgid="3469083862100560326">"Quay lại"</string>
     <string name="car_permission_label_diag_read" msgid="7248894224877702604">"đọc dữ liệu chẩn đoán"</string>
     <string name="car_permission_desc_diag_read" msgid="1121426363040966178">"Đọc dữ liệu chẩn đoán từ ô tô."</string>
     <string name="car_permission_label_diag_clear" msgid="4783070510879698157">"xóa dữ liệu chẩn đoán"</string>
@@ -90,6 +93,8 @@
     <string name="car_permission_desc_car_engine_detailed" msgid="1746863362811347700">"Truy cập vào thông tin chi tiết về động cơ trên ô tô của bạn."</string>
     <string name="car_permission_label_car_energy_ports" msgid="8548990315169219454">"truy cập vào cổng sạc và cổng nhiên liệu của ô tô"</string>
     <string name="car_permission_desc_car_energy_ports" msgid="7771185999828794949">"Truy cập vào cổng sạc và cổng nhiên liệu trên ô tô."</string>
+    <string name="car_permission_label_control_car_energy_ports" msgid="4375137311026313475">"kiểm soát cổng sạc và cổng nhiên liệu của ô tô"</string>
+    <string name="car_permission_desc_control_car_energy_ports" msgid="7364633710492525387">"Kiểm soát cổng sạc và cổng nhiên liệu của ô tô."</string>
     <string name="car_permission_label_car_identification" msgid="5896712510164020478">"đọc thông tin nhận dạng ô tô"</string>
     <string name="car_permission_desc_car_identification" msgid="4132040867171275059">"Truy cập vào thông tin nhận dạng ô tô."</string>
     <string name="car_permission_label_control_car_doors" msgid="3032058819250195700">"kiểm soát cửa ô tô"</string>
@@ -102,6 +107,8 @@
     <string name="car_permission_desc_control_car_seats" msgid="2407536601226470563">"Kiểm soát ghế ngồi trên ô tô."</string>
     <string name="car_permission_label_car_info" msgid="4707513570676492315">"truy cập vào thông tin cơ bản của ô tô"</string>
     <string name="car_permission_desc_car_info" msgid="2118081474543537653">"Truy cập vào thông tin cơ bản của ô tô."</string>
+    <string name="car_permission_label_vendor_permission_info" msgid="4471260460536888654">"truy cập vào thông tin về quyền của nhà sản xuất ô tô"</string>
+    <string name="car_permission_desc_vendor_permission_info" msgid="8152113853528488398">"Truy cập vào thông tin về quyền của nhà sản xuất ô tô."</string>
     <string name="car_permission_label_car_exterior_lights" msgid="541304469604902110">"đọc trạng thái đèn bên ngoài ô tô"</string>
     <string name="car_permission_desc_car_exterior_lights" msgid="4038037584100849318">"Truy cập vào trạng thái đèn bên ngoài ô tô."</string>
     <string name="car_permission_label_control_car_exterior_lights" msgid="101357531386232141">"đọc đèn bên ngoài ô tô"</string>
@@ -128,5 +135,9 @@
     <string name="car_permission_desc_enroll_trust" msgid="4148649994602185130">"Cho phép đăng ký thiết bị tin cậy"</string>
     <string name="car_permission_label_car_test_service" msgid="9159328930558208708">"Kiểm soát chế độ kiểm tra của ô tô"</string>
     <string name="car_permission_desc_car_test_service" msgid="7426844534110145843">"Kiểm soát chế độ kiểm tra của ô tô"</string>
+    <string name="car_permission_label_control_car_features" msgid="3905791560378888286">"Bật hoặc tắt các tính năng của ô tô"</string>
+    <string name="car_permission_desc_control_car_features" msgid="7646711104530599901">"Bật hoặc tắt các tính năng của ô tô."</string>
+    <string name="car_permission_label_use_car_watchdog" msgid="6973938293170413475">"dùng dịch vụ theo dõi tình trạng xe"</string>
+    <string name="car_permission_desc_use_car_watchdog" msgid="8244592601805516086">"Dùng dịch vụ theo dõi tình trạng xe."</string>
     <string name="trust_device_default_name" msgid="4213625926070261253">"Thiết bị của tôi"</string>
 </resources>
diff --git a/service/res/values-zh-rCN/strings.xml b/service/res/values-zh-rCN/strings.xml
index 2291b89..f9de733 100644
--- a/service/res/values-zh-rCN/strings.xml
+++ b/service/res/values-zh-rCN/strings.xml
@@ -22,6 +22,8 @@
     <string name="car_permission_desc_camera" msgid="917024932164501426">"访问汽车摄像头。"</string>
     <string name="car_permission_label_energy" msgid="7409144323527821558">"访问汽车的能耗信息"</string>
     <string name="car_permission_desc_energy" msgid="3392963810053235407">"访问汽车的能耗信息。"</string>
+    <string name="car_permission_label_adjust_range_remaining" msgid="839033553999920138">"调整汽车的剩余可行驶距离"</string>
+    <string name="car_permission_desc_adjust_range_remaining" msgid="2369321650437370673">"调整汽车的剩余可行驶距离值。"</string>
     <string name="car_permission_label_hvac" msgid="1499454192558727843">"访问汽车的暖通空调"</string>
     <string name="car_permission_desc_hvac" msgid="3754229695589774195">"访问汽车的 HVAC。"</string>
     <string name="car_permission_label_mileage" msgid="4661317074631150551">"访问汽车的行驶里程信息"</string>
@@ -62,16 +64,17 @@
     <string name="car_permission_desc_car_ux_restrictions_configuration" msgid="5711926927484813777">"配置用户体验限制条件"</string>
     <string name="car_permission_label_car_handle_usb_aoap_device" msgid="72783989504378036">"在 AOAP 模式下与 USB 设备通信"</string>
     <string name="car_permission_desc_car_handle_usb_aoap_device" msgid="273505990971317034">"允许应用在 AOAP 模式下与设备通信"</string>
+    <string name="car_permission_label_read_car_occupant_awareness_state" msgid="125517953575032758">"Occupant Awareness System 读取权限"</string>
+    <string name="car_permission_desc_read_car_occupant_awareness_state" msgid="188865882598414986">"允许读取 Occupant Awareness System 的状态和检测数据"</string>
+    <string name="car_permission_label_control_car_occupant_awareness_system" msgid="7163330266691094542">"控制 Occupant Awareness System 图表"</string>
+    <string name="car_permission_desc_control_car_occupant_awareness_system" msgid="7123482622084531911">"允许控制 Occupant Awareness System 检测图表的启动和停止"</string>
     <string name="car_permission_label_bind_input_service" msgid="6698489034024273750">"汽车输入服务"</string>
     <string name="car_permission_desc_bind_input_service" msgid="1670323419931890170">"处理输入事件"</string>
     <string name="car_can_bus_failure" msgid="2334035748788283914">"CAN 总线故障"</string>
     <string name="car_can_bus_failure_desc" msgid="4125516222786484733">"CAN 总线没有响应。请将主机盒插头拔下并插回,然后重新启动汽车"</string>
-    <!-- no translation found for activity_blocked_text (8088902789540147995) -->
-    <skip />
+    <string name="activity_blocked_text" msgid="5342114426610711378">"为了确保您的安全,您无法在开车时执行这项活动。\n要继续,请先停车。"</string>
     <string name="exit_button_message" msgid="8554690915924055685">"要重新开始使用安全的应用功能,请选择<xliff:g id="EXIT_BUTTON">%s</xliff:g>。"</string>
     <string name="exit_button" msgid="5829638404777671253">"返回"</string>
-    <string name="exit_button_close_application" msgid="8824289547809332460">"关闭应用"</string>
-    <string name="exit_button_go_back" msgid="3469083862100560326">"返回"</string>
     <string name="car_permission_label_diag_read" msgid="7248894224877702604">"读取诊断数据"</string>
     <string name="car_permission_desc_diag_read" msgid="1121426363040966178">"读取汽车的诊断数据。"</string>
     <string name="car_permission_label_diag_clear" msgid="4783070510879698157">"清除诊断数据"</string>
@@ -90,6 +93,8 @@
     <string name="car_permission_desc_car_engine_detailed" msgid="1746863362811347700">"访问汽车的详细引擎信息。"</string>
     <string name="car_permission_label_car_energy_ports" msgid="8548990315169219454">"访问汽车的油箱盖和充电端口信息"</string>
     <string name="car_permission_desc_car_energy_ports" msgid="7771185999828794949">"访问汽车的油箱盖和充电端口信息。"</string>
+    <string name="car_permission_label_control_car_energy_ports" msgid="4375137311026313475">"控制汽车的油箱盖和充电端口"</string>
+    <string name="car_permission_desc_control_car_energy_ports" msgid="7364633710492525387">"控制汽车的油箱盖和充电端口。"</string>
     <string name="car_permission_label_car_identification" msgid="5896712510164020478">"读取汽车的识别信息"</string>
     <string name="car_permission_desc_car_identification" msgid="4132040867171275059">"访问汽车的标识信息。"</string>
     <string name="car_permission_label_control_car_doors" msgid="3032058819250195700">"控制车门"</string>
@@ -102,6 +107,8 @@
     <string name="car_permission_desc_control_car_seats" msgid="2407536601226470563">"控制车座。"</string>
     <string name="car_permission_label_car_info" msgid="4707513570676492315">"访问汽车的基本信息"</string>
     <string name="car_permission_desc_car_info" msgid="2118081474543537653">"访问汽车的基本信息。"</string>
+    <string name="car_permission_label_vendor_permission_info" msgid="4471260460536888654">"访问汽车的供应商权限信息"</string>
+    <string name="car_permission_desc_vendor_permission_info" msgid="8152113853528488398">"访问汽车的供应商权限信息。"</string>
     <string name="car_permission_label_car_exterior_lights" msgid="541304469604902110">"读取汽车的外部灯具状态信息"</string>
     <string name="car_permission_desc_car_exterior_lights" msgid="4038037584100849318">"访问汽车的外部灯具状态信息。"</string>
     <string name="car_permission_label_control_car_exterior_lights" msgid="101357531386232141">"读取汽车的外部灯具信息"</string>
@@ -128,5 +135,9 @@
     <string name="car_permission_desc_enroll_trust" msgid="4148649994602185130">"允许注册可信设备"</string>
     <string name="car_permission_label_car_test_service" msgid="9159328930558208708">"控制汽车的测试模式"</string>
     <string name="car_permission_desc_car_test_service" msgid="7426844534110145843">"控制汽车的测试模式"</string>
+    <string name="car_permission_label_control_car_features" msgid="3905791560378888286">"启用或停用汽车的功能"</string>
+    <string name="car_permission_desc_control_car_features" msgid="7646711104530599901">"启用或停用汽车的功能。"</string>
+    <string name="car_permission_label_use_car_watchdog" msgid="6973938293170413475">"使用汽车监控定时器"</string>
+    <string name="car_permission_desc_use_car_watchdog" msgid="8244592601805516086">"使用汽车监控定时器。"</string>
     <string name="trust_device_default_name" msgid="4213625926070261253">"我的设备"</string>
 </resources>
diff --git a/service/res/values-zh-rHK/strings.xml b/service/res/values-zh-rHK/strings.xml
index f8aa550..45ddc60 100644
--- a/service/res/values-zh-rHK/strings.xml
+++ b/service/res/values-zh-rHK/strings.xml
@@ -22,6 +22,8 @@
     <string name="car_permission_desc_camera" msgid="917024932164501426">"存取汽車攝錄機。"</string>
     <string name="car_permission_label_energy" msgid="7409144323527821558">"存取汽車的能源資訊"</string>
     <string name="car_permission_desc_energy" msgid="3392963810053235407">"存取汽車的電量資訊。"</string>
+    <string name="car_permission_label_adjust_range_remaining" msgid="839033553999920138">"調整汽車油量餘額"</string>
+    <string name="car_permission_desc_adjust_range_remaining" msgid="2369321650437370673">"調整汽車油量餘額。"</string>
     <string name="car_permission_label_hvac" msgid="1499454192558727843">"存取汽車的暖通空調"</string>
     <string name="car_permission_desc_hvac" msgid="3754229695589774195">"存取汽車的暖通空調。"</string>
     <string name="car_permission_label_mileage" msgid="4661317074631150551">"存取汽車里數資訊"</string>
@@ -62,16 +64,17 @@
     <string name="car_permission_desc_car_ux_restrictions_configuration" msgid="5711926927484813777">"設定使用者體驗限制"</string>
     <string name="car_permission_label_car_handle_usb_aoap_device" msgid="72783989504378036">"與啟用 AOAP 模式的 USB 裝置通訊"</string>
     <string name="car_permission_desc_car_handle_usb_aoap_device" msgid="273505990971317034">"允許應用程式與啟用 AOAP 模式的裝置通訊"</string>
+    <string name="car_permission_label_read_car_occupant_awareness_state" msgid="125517953575032758">"Occupant Awareness System 的讀取權限"</string>
+    <string name="car_permission_desc_read_car_occupant_awareness_state" msgid="188865882598414986">"允許讀取 Occupant Awareness System 的狀態和偵測數據"</string>
+    <string name="car_permission_label_control_car_occupant_awareness_system" msgid="7163330266691094542">"控制 Occupant Awareness System 圖表"</string>
+    <string name="car_permission_desc_control_car_occupant_awareness_system" msgid="7123482622084531911">"允許控制何時開始和停止 Occupant Awareness System 的偵測圖表"</string>
     <string name="car_permission_label_bind_input_service" msgid="6698489034024273750">"汽車輸入服務"</string>
     <string name="car_permission_desc_bind_input_service" msgid="1670323419931890170">"處理輸入活動"</string>
     <string name="car_can_bus_failure" msgid="2334035748788283914">"控制器區域網路操作失敗"</string>
     <string name="car_can_bus_failure_desc" msgid="4125516222786484733">"控制器區域網路未有回覆。請拔除並重新插上汽車音響主機,然後重新啟動汽車"</string>
-    <!-- no translation found for activity_blocked_text (8088902789540147995) -->
-    <skip />
+    <string name="activity_blocked_text" msgid="5342114426610711378">"基於安全理由,駕駛時無法執行此操作。\n如要繼續,請留待泊車後操作。"</string>
     <string name="exit_button_message" msgid="8554690915924055685">"如要以安全應用程式功能重新啟動,請選擇 <xliff:g id="EXIT_BUTTON">%s</xliff:g>。"</string>
     <string name="exit_button" msgid="5829638404777671253">"返回"</string>
-    <string name="exit_button_close_application" msgid="8824289547809332460">"關閉應用程式"</string>
-    <string name="exit_button_go_back" msgid="3469083862100560326">"返回"</string>
     <string name="car_permission_label_diag_read" msgid="7248894224877702604">"讀取診斷資料"</string>
     <string name="car_permission_desc_diag_read" msgid="1121426363040966178">"讀取來自汽車的診斷資料。"</string>
     <string name="car_permission_label_diag_clear" msgid="4783070510879698157">"清除診斷資料"</string>
@@ -90,6 +93,8 @@
     <string name="car_permission_desc_car_engine_detailed" msgid="1746863362811347700">"存取汽車引擎詳情。"</string>
     <string name="car_permission_label_car_energy_ports" msgid="8548990315169219454">"存取汽車油箱蓋及充電埠"</string>
     <string name="car_permission_desc_car_energy_ports" msgid="7771185999828794949">"存取汽車油箱蓋及充電埠。"</string>
+    <string name="car_permission_label_control_car_energy_ports" msgid="4375137311026313475">"控制汽車油箱蓋及充電埠"</string>
+    <string name="car_permission_desc_control_car_energy_ports" msgid="7364633710492525387">"控制汽車油箱蓋及充電埠。"</string>
     <string name="car_permission_label_car_identification" msgid="5896712510164020478">"讀取汽車識別資訊"</string>
     <string name="car_permission_desc_car_identification" msgid="4132040867171275059">"存取汽車識別資訊。"</string>
     <string name="car_permission_label_control_car_doors" msgid="3032058819250195700">"控制車門"</string>
@@ -102,6 +107,8 @@
     <string name="car_permission_desc_control_car_seats" msgid="2407536601226470563">"控制汽車座位。"</string>
     <string name="car_permission_label_car_info" msgid="4707513570676492315">"存取汽車基本資訊"</string>
     <string name="car_permission_desc_car_info" msgid="2118081474543537653">"存取汽車基本資訊。"</string>
+    <string name="car_permission_label_vendor_permission_info" msgid="4471260460536888654">"存取汽車供應商權限資訊"</string>
+    <string name="car_permission_desc_vendor_permission_info" msgid="8152113853528488398">"存取汽車供應商權限資訊。"</string>
     <string name="car_permission_label_car_exterior_lights" msgid="541304469604902110">"讀取汽車外部燈光狀態"</string>
     <string name="car_permission_desc_car_exterior_lights" msgid="4038037584100849318">"存取汽車外部燈光狀態。"</string>
     <string name="car_permission_label_control_car_exterior_lights" msgid="101357531386232141">"讀取汽車外部燈光"</string>
@@ -128,5 +135,9 @@
     <string name="car_permission_desc_enroll_trust" msgid="4148649994602185130">"允許註冊信任的裝置"</string>
     <string name="car_permission_label_car_test_service" msgid="9159328930558208708">"控制汽車的測試模式"</string>
     <string name="car_permission_desc_car_test_service" msgid="7426844534110145843">"控制汽車的測試模式"</string>
+    <string name="car_permission_label_control_car_features" msgid="3905791560378888286">"啟用或停用汽車的功能"</string>
+    <string name="car_permission_desc_control_car_features" msgid="7646711104530599901">"啟用或停用汽車的功能。"</string>
+    <string name="car_permission_label_use_car_watchdog" msgid="6973938293170413475">"使用汽車監控服務"</string>
+    <string name="car_permission_desc_use_car_watchdog" msgid="8244592601805516086">"使用汽車監控服務。"</string>
     <string name="trust_device_default_name" msgid="4213625926070261253">"我的裝置"</string>
 </resources>
diff --git a/service/res/values-zh-rTW/strings.xml b/service/res/values-zh-rTW/strings.xml
index e3aedee..fc57e09 100644
--- a/service/res/values-zh-rTW/strings.xml
+++ b/service/res/values-zh-rTW/strings.xml
@@ -22,6 +22,8 @@
     <string name="car_permission_desc_camera" msgid="917024932164501426">"存取車輛攝影機。"</string>
     <string name="car_permission_label_energy" msgid="7409144323527821558">"存取車輛的能源資訊"</string>
     <string name="car_permission_desc_energy" msgid="3392963810053235407">"存取車輛的能源資訊。"</string>
+    <string name="car_permission_label_adjust_range_remaining" msgid="839033553999920138">"調整汽車的剩餘可行駛距離"</string>
+    <string name="car_permission_desc_adjust_range_remaining" msgid="2369321650437370673">"調整汽車的剩餘可行駛距離值。"</string>
     <string name="car_permission_label_hvac" msgid="1499454192558727843">"存取車輛空調"</string>
     <string name="car_permission_desc_hvac" msgid="3754229695589774195">"存取車輛空調。"</string>
     <string name="car_permission_label_mileage" msgid="4661317074631150551">"存取車輛的行駛里程資訊"</string>
@@ -62,16 +64,17 @@
     <string name="car_permission_desc_car_ux_restrictions_configuration" msgid="5711926927484813777">"設定使用者體驗限制"</string>
     <string name="car_permission_label_car_handle_usb_aoap_device" msgid="72783989504378036">"使用 AOAP 模式與 USB 裝置通訊"</string>
     <string name="car_permission_desc_car_handle_usb_aoap_device" msgid="273505990971317034">"允許應用程式使用 AOAP 模式與裝置通訊"</string>
+    <string name="car_permission_label_read_car_occupant_awareness_state" msgid="125517953575032758">"Occupant Awareness System 讀取權限"</string>
+    <string name="car_permission_desc_read_car_occupant_awareness_state" msgid="188865882598414986">"允許讀取 Occupant Awareness System 的狀態和偵測資料"</string>
+    <string name="car_permission_label_control_car_occupant_awareness_system" msgid="7163330266691094542">"控管 Occupant Awareness System 圖表"</string>
+    <string name="car_permission_desc_control_car_occupant_awareness_system" msgid="7123482622084531911">"允許控管 Occupant Awareness System 偵測圖表的啟用和停用狀況"</string>
     <string name="car_permission_label_bind_input_service" msgid="6698489034024273750">"車輛輸入服務"</string>
     <string name="car_permission_desc_bind_input_service" msgid="1670323419931890170">"處理輸入事件"</string>
     <string name="car_can_bus_failure" msgid="2334035748788283914">"控制器區域網路發生問題"</string>
     <string name="car_can_bus_failure_desc" msgid="4125516222786484733">"控制器區域網路無回應。請將主機盒插頭拔下並插回,然後重新啟動車輛"</string>
-    <!-- no translation found for activity_blocked_text (8088902789540147995) -->
-    <skip />
+    <string name="activity_blocked_text" msgid="5342114426610711378">"為了你的安全,開車期間不得進行這個活動。\n如要繼續,請先停車。"</string>
     <string name="exit_button_message" msgid="8554690915924055685">"如要使用安全應用程式功能重新啟動,請選取「離開」按鈕 <xliff:g id="EXIT_BUTTON">%s</xliff:g>。"</string>
     <string name="exit_button" msgid="5829638404777671253">"返回"</string>
-    <string name="exit_button_close_application" msgid="8824289547809332460">"關閉應用程式"</string>
-    <string name="exit_button_go_back" msgid="3469083862100560326">"返回"</string>
     <string name="car_permission_label_diag_read" msgid="7248894224877702604">"讀取診斷資料"</string>
     <string name="car_permission_desc_diag_read" msgid="1121426363040966178">"讀取車輛的診斷資料。"</string>
     <string name="car_permission_label_diag_clear" msgid="4783070510879698157">"清除診斷資料"</string>
@@ -90,6 +93,8 @@
     <string name="car_permission_desc_car_engine_detailed" msgid="1746863362811347700">"存取車輛的詳細引擎資訊。"</string>
     <string name="car_permission_label_car_energy_ports" msgid="8548990315169219454">"存取車輛的油孔蓋和充電口"</string>
     <string name="car_permission_desc_car_energy_ports" msgid="7771185999828794949">"存取車輛的油孔蓋和充電口。"</string>
+    <string name="car_permission_label_control_car_energy_ports" msgid="4375137311026313475">"控管車輛的油孔蓋和充電口"</string>
+    <string name="car_permission_desc_control_car_energy_ports" msgid="7364633710492525387">"控管車輛的油孔蓋和充電口。"</string>
     <string name="car_permission_label_car_identification" msgid="5896712510164020478">"讀取車輛識別號碼"</string>
     <string name="car_permission_desc_car_identification" msgid="4132040867171275059">"存取車輛識別號碼。"</string>
     <string name="car_permission_label_control_car_doors" msgid="3032058819250195700">"控制車門"</string>
@@ -102,6 +107,8 @@
     <string name="car_permission_desc_control_car_seats" msgid="2407536601226470563">"控制車輛座椅。"</string>
     <string name="car_permission_label_car_info" msgid="4707513570676492315">"存取車輛的基本資訊"</string>
     <string name="car_permission_desc_car_info" msgid="2118081474543537653">"存取車輛的基本資訊。"</string>
+    <string name="car_permission_label_vendor_permission_info" msgid="4471260460536888654">"存取車商權限資訊"</string>
+    <string name="car_permission_desc_vendor_permission_info" msgid="8152113853528488398">"存取車商權限資訊。"</string>
     <string name="car_permission_label_car_exterior_lights" msgid="541304469604902110">"讀取車輛外部燈光狀態"</string>
     <string name="car_permission_desc_car_exterior_lights" msgid="4038037584100849318">"存取車輛外部燈光狀態。"</string>
     <string name="car_permission_label_control_car_exterior_lights" msgid="101357531386232141">"讀取車輛外部燈光"</string>
@@ -128,5 +135,9 @@
     <string name="car_permission_desc_enroll_trust" msgid="4148649994602185130">"允許註冊信任的裝置"</string>
     <string name="car_permission_label_car_test_service" msgid="9159328930558208708">"控制車輛的測試模式"</string>
     <string name="car_permission_desc_car_test_service" msgid="7426844534110145843">"控制車輛的測試模式"</string>
+    <string name="car_permission_label_control_car_features" msgid="3905791560378888286">"啟用或停用車輛的功能"</string>
+    <string name="car_permission_desc_control_car_features" msgid="7646711104530599901">"啟用或停用車輛的功能。"</string>
+    <string name="car_permission_label_use_car_watchdog" msgid="6973938293170413475">"使用車輛監控計時器"</string>
+    <string name="car_permission_desc_use_car_watchdog" msgid="8244592601805516086">"使用車輛監控計時器。"</string>
     <string name="trust_device_default_name" msgid="4213625926070261253">"我的裝置"</string>
 </resources>
diff --git a/service/res/values-zu/strings.xml b/service/res/values-zu/strings.xml
index bd2c3c8..cd06d2f 100644
--- a/service/res/values-zu/strings.xml
+++ b/service/res/values-zu/strings.xml
@@ -22,6 +22,8 @@
     <string name="car_permission_desc_camera" msgid="917024932164501426">"Finyelela ikhamera yemoto yakho."</string>
     <string name="car_permission_label_energy" msgid="7409144323527821558">"finyelela ulwazi lamandla lemoto"</string>
     <string name="car_permission_desc_energy" msgid="3392963810053235407">"Finyelela ulwazi lwamandla lwemoto yakho."</string>
+    <string name="car_permission_label_adjust_range_remaining" msgid="839033553999920138">"lungisa ivelu elisele lebanga lemoto"</string>
+    <string name="car_permission_desc_adjust_range_remaining" msgid="2369321650437370673">"Lungisa ivelu elisele lebanga lemoto."</string>
     <string name="car_permission_label_hvac" msgid="1499454192558727843">"i-hvac yemoto"</string>
     <string name="car_permission_desc_hvac" msgid="3754229695589774195">"Finyelela i-hvac yemoto yakho."</string>
     <string name="car_permission_label_mileage" msgid="4661317074631150551">"finyelela ulwazi lwe-mileage lemoto"</string>
@@ -62,15 +64,17 @@
     <string name="car_permission_desc_car_ux_restrictions_configuration" msgid="5711926927484813777">"Lungisa imikhawulo ye-UX"</string>
     <string name="car_permission_label_car_handle_usb_aoap_device" msgid="72783989504378036">"Xhumana nedivayisi ye-USB kumodi ye-AOAP"</string>
     <string name="car_permission_desc_car_handle_usb_aoap_device" msgid="273505990971317034">"Ivumela uhlelo lokusebenza ukuthi luxhumane nedivayisi kumodi ye-AOAP"</string>
+    <string name="car_permission_label_read_car_occupant_awareness_state" msgid="125517953575032758">"Ukufinyelela Ekufundeni Kwesistimu Yokwazisa Yomgibeli"</string>
+    <string name="car_permission_desc_read_car_occupant_awareness_state" msgid="188865882598414986">"Ivumela isimo sokufundwa nokutholakala kwedatha Yesistimu Yokwazisa Yomgibeli"</string>
+    <string name="car_permission_label_control_car_occupant_awareness_system" msgid="7163330266691094542">"Lawula Igrafu Yesistimu Yokwazisa Yomgibeli"</string>
+    <string name="car_permission_desc_control_car_occupant_awareness_system" msgid="7123482622084531911">"Ivumela ukulawula ukuqalwa nokumiswa kwegrafu yokutholakala Kwesistimu Yokwazisa Yomgibeli"</string>
     <string name="car_permission_label_bind_input_service" msgid="6698489034024273750">"Isevisi yokufaka yemoto"</string>
     <string name="car_permission_desc_bind_input_service" msgid="1670323419931890170">"Phatha imicimbi yokungena"</string>
     <string name="car_can_bus_failure" msgid="2334035748788283914">"Ibhasi ye-CAN yehlulekile"</string>
     <string name="car_can_bus_failure_desc" msgid="4125516222786484733">"Ibhasi ye-CAN ayiphenduli. Nqamula futhi uxhume ibhokisi le-headunit ukuze uqalise kabusha imoto"</string>
-    <string name="activity_blocked_text" msgid="8088902789540147995">"Awukwazi ukusebenzisa lesi sici ngenkathi ushayela"</string>
+    <string name="activity_blocked_text" msgid="5342114426610711378">"Ngokuphepha kwakho, lo msebenzi awutholakali uma ushayela.\nUkuze uqhubeke, linda uze umiswe."</string>
     <string name="exit_button_message" msgid="8554690915924055685">"Ukuze uqalise futhi ngezici zohlelo lokusebenza, khetha <xliff:g id="EXIT_BUTTON">%s</xliff:g>."</string>
     <string name="exit_button" msgid="5829638404777671253">"Emuva"</string>
-    <string name="exit_button_close_application" msgid="8824289547809332460">"Vala uhlelo lokusebenza"</string>
-    <string name="exit_button_go_back" msgid="3469083862100560326">"Emuva"</string>
     <string name="car_permission_label_diag_read" msgid="7248894224877702604">"funda idatha yokuxilonga"</string>
     <string name="car_permission_desc_diag_read" msgid="1121426363040966178">"Ukufunda idatha yokuxilonga kusuka emotweni."</string>
     <string name="car_permission_label_diag_clear" msgid="4783070510879698157">"sula idatha yokuxilonga"</string>
@@ -89,6 +93,8 @@
     <string name="car_permission_desc_car_engine_detailed" msgid="1746863362811347700">"Finyelela ulwazi lwenjini olunemininingwane lwemoto yakho."</string>
     <string name="car_permission_label_car_energy_ports" msgid="8548990315169219454">"iinyelela umnyango kaphethiloli nembobo yokushaja"</string>
     <string name="car_permission_desc_car_energy_ports" msgid="7771185999828794949">"Finyelela umnyango kaphethiloli nembobo yokushaja."</string>
+    <string name="car_permission_label_control_car_energy_ports" msgid="4375137311026313475">"lawula umnyango wesibaseli semoto kanye nembobo yokushaja"</string>
+    <string name="car_permission_desc_control_car_energy_ports" msgid="7364633710492525387">"Lawula umnyango wesibaseli semoto kanye nembobo yokushaja."</string>
     <string name="car_permission_label_car_identification" msgid="5896712510164020478">"funda ubunikazi bemoto"</string>
     <string name="car_permission_desc_car_identification" msgid="4132040867171275059">"Finyelela isihlonzi semoto."</string>
     <string name="car_permission_label_control_car_doors" msgid="3032058819250195700">"lawula iminyango yemoto"</string>
@@ -101,6 +107,8 @@
     <string name="car_permission_desc_control_car_seats" msgid="2407536601226470563">"Lawula izihlalo zemoto."</string>
     <string name="car_permission_label_car_info" msgid="4707513570676492315">"finyelela ulwazi oluyisisekelo lwemoto"</string>
     <string name="car_permission_desc_car_info" msgid="2118081474543537653">"Finyelela ulwazi oluyisisekelo."</string>
+    <string name="car_permission_label_vendor_permission_info" msgid="4471260460536888654">"finyelela ulwazi lwemvume lomthengisi wemoto"</string>
+    <string name="car_permission_desc_vendor_permission_info" msgid="8152113853528488398">"Finyelela ulwazi lwemvume lomthengisi wemoto."</string>
     <string name="car_permission_label_car_exterior_lights" msgid="541304469604902110">"funda isimo somkhanyo wemoto sangaphakathi"</string>
     <string name="car_permission_desc_car_exterior_lights" msgid="4038037584100849318">"Finyelela isimo sezibani sangaphandle semoto."</string>
     <string name="car_permission_label_control_car_exterior_lights" msgid="101357531386232141">"Funda isimo sezibani zangaphandle zemoto"</string>
@@ -127,5 +135,9 @@
     <string name="car_permission_desc_enroll_trust" msgid="4148649994602185130">"Vumela ukubhaliswa kwamadivayisi athenjwayo"</string>
     <string name="car_permission_label_car_test_service" msgid="9159328930558208708">"Lawula imodi yokuhlola yemoto"</string>
     <string name="car_permission_desc_car_test_service" msgid="7426844534110145843">"Lawula imodi yokuhlola yemoto"</string>
+    <string name="car_permission_label_control_car_features" msgid="3905791560378888286">"Nika amandla noma khubaza izici zemoto."</string>
+    <string name="car_permission_desc_control_car_features" msgid="7646711104530599901">"Nika amandla noma khubaza izici zemoto."</string>
+    <string name="car_permission_label_use_car_watchdog" msgid="6973938293170413475">"sebenzisa unogada wemoto"</string>
+    <string name="car_permission_desc_use_car_watchdog" msgid="8244592601805516086">"Sebenzisa unogada wemoto."</string>
     <string name="trust_device_default_name" msgid="4213625926070261253">"Idivayisi yami"</string>
 </resources>
diff --git a/service/res/values/config.xml b/service/res/values/config.xml
index afd8555..2816198 100644
--- a/service/res/values/config.xml
+++ b/service/res/values/config.xml
@@ -225,6 +225,49 @@
          There is no default bugreporting app.-->
     <string name="config_car_bugreport_application" translatable="false"></string>
 
+    <!--
+        Lists all occupant (= driver + passenger) zones available in the car.
+        Some examples are:
+        <item>occupantZoneId=0,occupantType=DRIVER,seatRow=1,seatSide=driver</item>
+        <item>occupantZoneId=1,occupantType=FRONT_PASSENGER,seatRow=1,seatSide=oppositeDriver</item>
+        <item>occupantZoneId=2,occupantType=REAR_PASSENGER,seatRow=2,seatSide=left</item>
+        <item>occupantZoneId=3,occupantType=REAR_PASSENGER,seatRow=2,seatSide=right</item>
+
+        occupantZoneId: Unique unsigned integer id to represent each passenger zone. Each zone
+                        should have different id.
+        occupantType: Occupant type for the display. Use * part from
+                       CarOccupantZoneManager.OCCUPANT_TYPE_* like DRIVER, FRONT_PASSENGER,
+                       REAR_PASSENGER and etc.
+        seatRow: Integer telling which row the seat is located. Row 1 is for front seats.
+        seatSide: left/center/right for known side. Or can use driver/center/oppositeDriver to
+                  handle both right-hand driving and left-hand driving in one place.
+                  If car's RHD / LHD is not specified, LHD will be assumed and driver side becomes
+                  left.
+    -->
+    <string-array translatable="false" name="config_occupant_zones">
+        <item>occupantZoneId=0,occupantType=DRIVER,seatRow=1,seatSide=driver</item>
+    </string-array>
+    <!--
+        Specifies configuration of displays in system telling its usage / type and assigned
+        occupant.
+
+        Some examples are:
+        <item>displayPort=0,displayType=MAIN,occupantZoneId=0</item>
+        <item>displayPort=1,displayType=INSTRUMENT_CLUSTER,occupantZoneId=0</item>
+        <item>displayPort=2,displayType=MAIN,occupantZoneId=1</item>
+        <item>displayPort=3,displayType=MAIN,occupantZoneId=2</item>
+        <item>displayPort=4,displayType=MAIN,occupantZoneId=3</item>
+
+        displayPort: Unique port id for the display.
+        displayType: Display type for the display. Use * part from
+                       CarOccupantZoneManager.DISPLAY_TYPE_* like MAIN, INSTRUMENT_CLUSTER and
+                       etc.
+        occupantZoneId: occupantZoneId specified from config_occupant_zones.
+
+    -->
+    <string-array translatable="false" name="config_occupant_display_mapping">
+    </string-array>
+
     <!-- Specifies notice UI that will be launched when user starts a car or do user
          switching. It is recommended to use dialog with at least TYPE_APPLICATION_OVERLAY window
          type to show the UI regardless of activity launches. Target package will be auto-granted
@@ -247,6 +290,33 @@
          This default says to prevent changing the user during Resume. -->
     <bool name="config_disableUserSwitchDuringResume" translatable="false">true</bool>
 
+    <!--
+        Specifies optional features that can be enabled by this image. Note that vhal can disable
+        them depending on product variation.
+        Feature name can be either service name defined in Car.*_SERVICE for Car*Manager or any
+        optional feature defined under @OptionalFeature annotation.
+        Note that '/' is used to have subfeature under main feature like "MAIN_FEATURE/SUB_FEATURE".
+
+        Some examples are:
+        <item>storage_monitoring</item>
+        <item>com.android.car.user.CarUserNoticeService</item>
+        <item>com.example.Feature/SubFeature</item>
+
+        The default list defined below will enable all optional features defined.
+    -->
+    <string-array translatable="false" name="config_allowed_optional_car_features">
+        <item>com.android.car.user.CarUserNoticeService</item>
+        <item>diagnostic</item>
+        <item>storage_monitoring</item>
+        <item>vehicle_map_service</item>
+        <item>vehicle_map_subscriber_service</item>
+    </string-array>
+
+    <!-- Configuration to enable passenger support.
+         If this is set to true and there is a passenger display, a user can login to the passenger
+         display and use it as a normal Android user. -->
+    <bool name="enablePassengerSupport">false</bool>
+
     <!-- Class name of the custom country detector to be used. Override the default value in the
          device specific config file.  -->
     <string name="config_customCountryDetector" translatable="false">com.android.server.location.ComprehensiveCountryDetector</string>
diff --git a/service/res/values/strings.xml b/service/res/values/strings.xml
index 343e851..0215f32 100644
--- a/service/res/values/strings.xml
+++ b/service/res/values/strings.xml
@@ -28,6 +28,10 @@
     <string name="car_permission_label_energy">access car\u2019s energy information</string>
     <!-- Permission text: can access your car's energy information [CHAR LIMIT=NONE] -->
     <string name="car_permission_desc_energy">Access your car\u2019s energy information.</string>
+    <!-- Permission text: can adjust value of your car's range remaining [CHAR LIMIT=NONE] -->
+    <string name="car_permission_label_adjust_range_remaining">adjust car\u2019s range remaining</string>
+    <!-- Permission text: can adjust value of your car's range remaining [CHAR LIMIT=NONE] -->
+    <string name="car_permission_desc_adjust_range_remaining">Adjust car\u2019s range remaining value.</string>
     <!-- Permission text: apps can control car hvac [CHAR LIMIT=NONE] -->
     <string name="car_permission_label_hvac">access car\u2019s hvac</string>
     <!-- Permission text: apps can control car hvac [CHAR LIMIT=NONE] -->
@@ -102,6 +106,12 @@
     <string name="car_permission_label_car_handle_usb_aoap_device">Communicate with USB device in AOAP mode</string>
     <string name="car_permission_desc_car_handle_usb_aoap_device">Allows an app to communicate with a device in AOAP mode</string>
 
+    <string name="car_permission_label_read_car_occupant_awareness_state">Occupant Awareness System Read Access</string>
+    <string name="car_permission_desc_read_car_occupant_awareness_state">Allows reading status and detection data for Occupant Awareness System</string>
+
+    <string name="car_permission_label_control_car_occupant_awareness_system">Control Occupant Awareness System Graph</string>
+    <string name="car_permission_desc_control_car_occupant_awareness_system">Allows controlling the start and stopping of the Occupant Awareness System detection graph</string>
+
     <!-- Permission text: apps can handle input events [CHAR LIMIT=NONE] -->
     <string name="car_permission_label_bind_input_service">Car Input Service</string>
     <!-- Permission text: apps can handle input events [CHAR LIMIT=NONE] -->
@@ -172,6 +182,11 @@
     <!-- Permission text: apps can access car's fuel door and ev charge port [CHAR LIMIT=NONE] -->
     <string name="car_permission_desc_car_energy_ports">Access car\u2019s fuel door and charge port.</string>
 
+    <!-- Permission text: apps can control car's fuel door and ev charge port [CHAR LIMIT=NONE] -->
+    <string name="car_permission_label_control_car_energy_ports">control car\u2019s fuel door and charge port</string>
+    <!-- Permission text: apps can control car's fuel door and ev charge port [CHAR LIMIT=NONE] -->
+    <string name="car_permission_desc_control_car_energy_ports">Control car\u2019s fuel door and charge port.</string>
+
     <!-- Permission text: apps can access car's VIN information [CHAR LIMIT=NONE] -->
     <string name="car_permission_label_car_identification">read car\u2019s identification</string>
     <!-- Permission text: apps can access car's VIN information [CHAR LIMIT=NONE] -->
@@ -202,6 +217,11 @@
     <!-- Permission text: apps read car's basic information [CHAR LIMIT=NONE] -->
     <string name="car_permission_desc_car_info">Access car\u2019s basic information.</string>
 
+    <!-- Permission text: apps read car's vendor permissions information [CHAR LIMIT=NONE] -->
+    <string name="car_permission_label_vendor_permission_info">access car\u2019s vendor permission information</string>
+    <!-- Permission text: apps read car's vendor permissions information [CHAR LIMIT=NONE] -->
+    <string name="car_permission_desc_vendor_permission_info">Access car\u2019s vendor permission information.</string>
+
     <!-- Permission text: apps access car's exterior lights state [CHAR LIMIT=NONE] -->
     <string name="car_permission_label_car_exterior_lights">read car\u2019s exterior lights state</string>
     <!-- Permission text: apps access car's exterior lights state [CHAR LIMIT=NONE] -->
@@ -265,6 +285,196 @@
     <!-- Permission text: Control car's test mode [CHAR LIMIT=NONE] -->
     <string name="car_permission_desc_car_test_service">Control car\u2019s test mode</string>
 
+    <!-- Permission text: apps control vendor properties related with window [CHAR LIMIT=NONE] -->
+    <string name="car_permission_label_set_car_vendor_category_window" translatable="false">control vendor specific window properties</string>
+    <!-- Permission text: apps control vendor properties related with window [CHAR LIMIT=NONE] -->
+    <string name="car_permission_desc_set_car_vendor_category_window" translatable="false">Control vendor specific window properties.</string>
+
+    <!-- Permission text: apps access vendor properties related with window [CHAR LIMIT=NONE] -->
+    <string name="car_permission_label_get_car_vendor_category_window" translatable="false">access vendor specific window properties</string>
+    <!-- Permission text: apps access vendor properties related with window [CHAR LIMIT=NONE] -->
+    <string name="car_permission_desc_get_car_vendor_category_window" translatable="false">Access vendor specific window properties.</string>
+
+    <!-- Permission text: apps control vendor properties related with door [CHAR LIMIT=NONE] -->
+    <string name="car_permission_label_set_car_vendor_category_door" translatable="false">control vendor specific door properties</string>
+    <!-- Permission text: apps control vendor properties related with door [CHAR LIMIT=NONE] -->
+    <string name="car_permission_desc_set_car_vendor_category_door" translatable="false">Control vendor specific door properties.</string>
+
+    <!-- Permission text: apps access vendor properties related with door [CHAR LIMIT=NONE] -->
+    <string name="car_permission_label_get_car_vendor_category_door" translatable="false">access vendor specific door properties</string>
+    <!-- Permission text: apps access vendor properties related with door [CHAR LIMIT=NONE] -->
+    <string name="car_permission_desc_get_car_vendor_category_door" translatable="false">Access vendor specific door properties.</string>
+
+    <!-- Permission text: apps control vendor properties related with seat [CHAR LIMIT=NONE] -->
+    <string name="car_permission_label_set_car_vendor_category_seat" translatable="false">control vendor specific seat properties</string>
+    <!-- Permission text: apps control vendor properties related with seat [CHAR LIMIT=NONE] -->
+    <string name="car_permission_desc_set_car_vendor_category_seat" translatable="false">Control vendor specific seat properties.</string>
+
+    <!-- Permission text: apps access vendor properties related with seat [CHAR LIMIT=NONE] -->
+    <string name="car_permission_label_get_car_vendor_category_seat" translatable="false">access vendor specific seat properties</string>
+    <!-- Permission text: apps access vendor properties related with seat [CHAR LIMIT=NONE] -->
+    <string name="car_permission_desc_get_car_vendor_category_seat" translatable="false">Access vendor specific seat properties.</string>
+
+    <!-- Permission text: apps control vendor properties related with mirror [CHAR LIMIT=NONE] -->
+    <string name="car_permission_label_set_car_vendor_category_mirror" translatable="false">control vendor specific mirror properties</string>
+    <!-- Permission text: apps control vendor properties related with mirror [CHAR LIMIT=NONE] -->
+    <string name="car_permission_desc_set_car_vendor_category_mirror" translatable="false">Control vendor specific mirror properties.</string>
+
+    <!-- Permission text: apps access vendor properties related with mirror [CHAR LIMIT=NONE] -->
+    <string name="car_permission_label_get_car_vendor_category_mirror" translatable="false">access vendor specific mirror properties</string>
+    <!-- Permission text: apps access vendor properties related with mirror [CHAR LIMIT=NONE] -->
+    <string name="car_permission_desc_get_car_vendor_category_mirror" translatable="false">Access vendor specific mirror properties.</string>
+
+    <!-- Permission text: apps control vendor properties related with info [CHAR LIMIT=NONE] -->
+    <string name="car_permission_label_set_car_vendor_category_info" translatable="false">control vendor specific information properties</string>
+    <!-- Permission text: apps control vendor properties related with info [CHAR LIMIT=NONE] -->
+    <string name="car_permission_desc_set_car_vendor_category_info" translatable="false">Control vendor specific information properties.</string>
+
+    <!-- Permission text: apps access vendor properties related with info [CHAR LIMIT=NONE] -->
+    <string name="car_permission_label_get_car_vendor_category_info" translatable="false">access vendor specific information properties</string>
+    <!-- Permission text: apps access vendor properties related with info [CHAR LIMIT=NONE] -->
+    <string name="car_permission_desc_get_car_vendor_category_info" translatable="false">Access vendor specific information properties.</string>
+
+    <!-- Permission text: apps control vendor properties related with engine [CHAR LIMIT=NONE] -->
+    <string name="car_permission_label_set_car_vendor_category_engine" translatable="false">control vendor specific engine properties</string>
+    <!-- Permission text: apps control vendor properties related with engine [CHAR LIMIT=NONE] -->
+    <string name="car_permission_desc_set_car_vendor_category_engine" translatable="false">Control vendor specific engine properties.</string>
+
+    <!-- Permission text: apps access vendor properties related with engine [CHAR LIMIT=NONE] -->
+    <string name="car_permission_label_get_car_vendor_category_engine" translatable="false">access vendor specific engine properties</string>
+    <!-- Permission text: apps access vendor properties related with engine [CHAR LIMIT=NONE] -->
+    <string name="car_permission_desc_get_car_vendor_category_engine" translatable="false">Access vendor specific engine properties.</string>
+
+    <!-- Permission text: apps control vendor properties related with hvac [CHAR LIMIT=NONE] -->
+    <string name="car_permission_label_set_car_vendor_category_hvac" translatable="false">control vendor specific hvac properties</string>
+    <!-- Permission text: apps control vendor properties related with hvac [CHAR LIMIT=NONE] -->
+    <string name="car_permission_desc_set_car_vendor_category_hvac" translatable="false">Control vendor specific hvac properties.</string>
+
+    <!-- Permission text: apps access vendor properties related with hvac [CHAR LIMIT=NONE] -->
+    <string name="car_permission_label_get_car_vendor_category_hvac" translatable="false">access vendor specific hvac properties</string>
+    <!-- Permission text: apps access vendor properties related with hvac [CHAR LIMIT=NONE] -->
+    <string name="car_permission_desc_get_car_vendor_category_hvac" translatable="false">Access vendor specific hvac properties.</string>
+
+    <!-- Permission text: apps control vendor properties related with light [CHAR LIMIT=NONE] -->
+    <string name="car_permission_label_set_car_vendor_category_light" translatable="false">control vendor specific light properties</string>
+    <!-- Permission text: apps control vendor properties related with light [CHAR LIMIT=NONE] -->
+    <string name="car_permission_desc_set_car_vendor_category_light" translatable="false">Control vendor specific light properties.</string>
+
+    <!-- Permission text: apps access vendor properties related with light [CHAR LIMIT=NONE] -->
+    <string name="car_permission_label_get_car_vendor_category_light" translatable="false">access vendor specific light properties</string>
+    <!-- Permission text: apps access vendor properties related with light [CHAR LIMIT=NONE] -->
+    <string name="car_permission_desc_get_car_vendor_category_light" translatable="false">Access vendor specific light properties.</string>
+
+    <!-- Permission text: apps access properties in category 1 [CHAR LIMIT=NONE] -->
+    <string name="car_permission_label_get_car_vendor_category_1" translatable="false">access vendor specific properties in category 1</string>
+    <!-- Permission text: apps access vendor properties in category 1 [CHAR LIMIT=NONE] -->
+    <string name="car_permission_desc_get_car_vendor_category_1" translatable="false">Access vendor specific properties in category 1.</string>
+
+    <!-- Permission text: apps access vendor properties in category 2 [CHAR LIMIT=NONE] -->
+    <string name="car_permission_label_get_car_vendor_category_2" translatable="false">access vendor specific properties in category 2</string>
+    <!-- Permission text: apps access vendor properties in category 2 [CHAR LIMIT=NONE] -->
+    <string name="car_permission_desc_get_car_vendor_category_2" translatable="false">Access vendor specific properties in category 2.</string>
+
+    <!-- Permission text: apps access properties in category 3 [CHAR LIMIT=NONE] -->
+    <string name="car_permission_label_get_car_vendor_category_3" translatable="false">access vendor specific properties in category 3</string>
+    <!-- Permission text: apps access  properties in category 3 [CHAR LIMIT=NONE] -->
+    <string name="car_permission_desc_get_car_vendor_category_3" translatable="false">Access vendor specific properties in category 3.</string>
+
+    <!-- Permission text: apps access vendor properties in category 4 [CHAR LIMIT=NONE] -->
+    <string name="car_permission_label_get_car_vendor_category_4" translatable="false">access vendor specific properties in category 4</string>
+    <!-- Permission text: apps access vendor properties in category 4 [CHAR LIMIT=NONE] -->
+    <string name="car_permission_desc_get_car_vendor_category_4" translatable="false">Access vendor specific properties in category 4.</string>
+
+    <!-- Permission text: apps access vendor properties in category 5 [CHAR LIMIT=NONE] -->
+    <string name="car_permission_label_get_car_vendor_category_5" translatable="false">access vendor specific properties in category 5</string>
+    <!-- Permission text: apps access vendor properties in category 5 [CHAR LIMIT=NONE] -->
+    <string name="car_permission_desc_get_car_vendor_category_5" translatable="false">Access vendor specific properties in category 5.</string>
+
+    <!-- Permission text: apps access vendor properties in category 6 [CHAR LIMIT=NONE] -->
+    <string name="car_permission_label_get_car_vendor_category_6" translatable="false">access vendor specific properties in category 6</string>
+    <!-- Permission text: apps access and control vendor properties in category 6 [CHAR LIMIT=NONE] -->
+    <string name="car_permission_desc_get_car_vendor_category_6" translatable="false">Access vendor specific properties in category 6.</string>
+
+    <!-- Permission text: apps access vendor properties in category 7 [CHAR LIMIT=NONE] -->
+    <string name="car_permission_label_get_car_vendor_category_7" translatable="false">access vendor specific properties in category 7</string>
+    <!-- Permission text: apps access vendor properties in category 7 [CHAR LIMIT=NONE] -->
+    <string name="car_permission_desc_get_car_vendor_category_7" translatable="false">Access vendor specific properties in category 7.</string>
+
+    <!-- Permission text: apps access vendor properties in category 8 [CHAR LIMIT=NONE] -->
+    <string name="car_permission_label_get_car_vendor_category_8" translatable="false">access vendor specific properties in category 8</string>
+    <!-- Permission text: apps access vendor properties in category 8 [CHAR LIMIT=NONE] -->
+    <string name="car_permission_desc_get_car_vendor_category_8" translatable="false">Access vendor specific properties in category 8.</string>
+
+    <!-- Permission text: apps access vendor properties in category 9 [CHAR LIMIT=NONE] -->
+    <string name="car_permission_label_get_car_vendor_category_9" translatable="false">access vendor specific properties in category 9</string>
+    <!-- Permission text: apps access vendor properties in category 9 [CHAR LIMIT=NONE] -->
+    <string name="car_permission_desc_get_car_vendor_category_9" translatable="false">Access vendor specific properties in category 9.</string>
+
+    <!-- Permission text: apps access vendor properties in category 10 [CHAR LIMIT=NONE] -->
+    <string name="car_permission_label_get_car_vendor_category_10" translatable="false">access vendor specific properties in category 10</string>
+    <!-- Permission text: apps access vendor properties in category 10 [CHAR LIMIT=NONE] -->
+    <string name="car_permission_desc_get_car_vendor_category_10" translatable="false">Access vendor specific properties in category 10.</string>
+
+    <!-- Permission text: apps control vendor properties in category 1 [CHAR LIMIT=NONE] -->
+    <string name="car_permission_label_set_car_vendor_category_1" translatable="false">control vendor specific properties in category 1</string>
+    <!-- Permission text: apps control vendor properties in category 1 [CHAR LIMIT=NONE] -->
+    <string name="car_permission_desc_set_car_vendor_category_1" translatable="false">Control vendor specific properties in category 1.</string>
+
+    <!-- Permission text: apps control vendor properties in category 2 [CHAR LIMIT=NONE] -->
+    <string name="car_permission_label_set_car_vendor_category_2" translatable="false">control vendor specific properties in category 2</string>
+    <!-- Permission text: apps control vendor properties in category 2 [CHAR LIMIT=NONE] -->
+    <string name="car_permission_desc_set_car_vendor_category_2" translatable="false">Control vendor specific properties in category 2.</string>
+
+    <!-- Permission text: apps control vendor properties in category 3 [CHAR LIMIT=NONE] -->
+    <string name="car_permission_label_set_car_vendor_category_3" translatable="false">control vendor specific properties in category 3</string>
+    <!-- Permission text: apps control vendor properties in category 3 [CHAR LIMIT=NONE] -->
+    <string name="car_permission_desc_set_car_vendor_category_3" translatable="false">Control vendor specific properties in category 3.</string>
+
+    <!-- Permission text: apps control vendor properties in category 4 [CHAR LIMIT=NONE] -->
+    <string name="car_permission_label_set_car_vendor_category_4" translatable="false">control vendor specific properties in category 4</string>
+    <!-- Permission text: apps control vendor properties in category 4 [CHAR LIMIT=NONE] -->
+    <string name="car_permission_desc_set_car_vendor_category_4" translatable="false">Control vendor specific properties in category 4.</string>
+
+    <!-- Permission text: apps control vendor properties in category 5 [CHAR LIMIT=NONE] -->
+    <string name="car_permission_label_set_car_vendor_category_5" translatable="false">control vendor specific properties in category 5</string>
+    <!-- Permission text: apps control vendor properties in category 5 [CHAR LIMIT=NONE] -->
+    <string name="car_permission_desc_set_car_vendor_category_5" translatable="false">Control vendor specific properties in category 5.</string>
+
+    <!-- Permission text: apps control vendor properties in category 6 [CHAR LIMIT=NONE] -->
+    <string name="car_permission_label_set_car_vendor_category_6" translatable="false">control vendor specific properties in category 6</string>
+    <!-- Permission text: apps control vendor properties in category 6 [CHAR LIMIT=NONE] -->
+    <string name="car_permission_desc_set_car_vendor_category_6" translatable="false">Control vendor specific properties in category 6.</string>
+
+    <!-- Permission text: apps control vendor properties in category 7 [CHAR LIMIT=NONE] -->
+    <string name="car_permission_label_set_car_vendor_category_7" translatable="false">control vendor specific properties in category 7</string>
+    <!-- Permission text: apps control vendor properties in category 7 [CHAR LIMIT=NONE] -->
+    <string name="car_permission_desc_set_car_vendor_category_7" translatable="false">Control vendor specific properties in category 7.</string>
+
+    <!-- Permission text: apps control vendor properties in category 8 [CHAR LIMIT=NONE] -->
+    <string name="car_permission_label_set_car_vendor_category_8" translatable="false">control vendor specific properties in category 8</string>
+    <!-- Permission text: apps control vendor properties in category 8 [CHAR LIMIT=NONE] -->
+    <string name="car_permission_desc_set_car_vendor_category_8" translatable="false">Control vendor specific properties in category 8.</string>
+
+    <!-- Permission text: apps control vendor properties in category 9 [CHAR LIMIT=NONE] -->
+    <string name="car_permission_label_set_car_vendor_category_9" translatable="false">control vendor specific properties in category 9</string>
+    <!-- Permission text: apps control vendor properties in category 9 [CHAR LIMIT=NONE] -->
+    <string name="car_permission_desc_set_car_vendor_category_9" translatable="false">Control vendor specific properties in category 9.</string>
+
+    <!-- Permission text: apps control vendor properties in category 10 [CHAR LIMIT=NONE] -->
+    <string name="car_permission_label_set_car_vendor_category_10" translatable="false">control vendor specific properties in category 10</string>
+    <!-- Permission text: apps control vendor properties in category 10 [CHAR LIMIT=NONE] -->
+    <string name="car_permission_desc_set_car_vendor_category_10" translatable="false">Control vendor specific properties in category 10.</string>
+
+    <!-- Permission text: enable or disable car's features [CHAR LIMIT=NONE] -->
+    <string name="car_permission_label_control_car_features">Enable or disable car\u2019s features</string>
+    <!-- Permission text: enable or disable car's features [CHAR LIMIT=NONE] -->
+    <string name="car_permission_desc_control_car_features">Enable or disable car\u2019s features.</string>
+
+    <!-- Permission text: apps use car watchdog [CHAR LIMIT=NONE] -->
+    <string name="car_permission_label_use_car_watchdog">use car watchdog</string>
+    <!-- Permission text: apps use car watchdog [CHAR LIMIT=NONE] -->
+    <string name="car_permission_desc_use_car_watchdog">Use car watchdog.</string>
+
     <!-- The default name of device enrolled as trust device [CHAR LIMIT=NONE] -->
     <string name="trust_device_default_name">My Device</string>
 
diff --git a/service/res/xml/car_ux_restrictions_map.xml b/service/res/xml/car_ux_restrictions_map.xml
index 813796b..92adb40 100644
--- a/service/res/xml/car_ux_restrictions_map.xml
+++ b/service/res/xml/car_ux_restrictions_map.xml
@@ -48,13 +48,14 @@
         4. If the above conditions are not met, mapping behavior is undefined. -->
         <!-- This is restrictions for moving and speed [0,5m/s) -->
         <DrivingState car:state="moving" car:minSpeed="0" car:maxSpeed="5.0">
-            <Restrictions car:requiresDistractionOptimization="true" car:uxr="fully_restricted"/>
+            <Restrictions car:requiresDistractionOptimization="true" car:uxr="no_dialpad|no_filtering|limit_string_length|no_keyboard|no_video|limit_content|no_setup|no_text_message"/>
         </DrivingState>
 
         <!-- Restrictions for speed >=5 -->
         <DrivingState car:state="moving" car:minSpeed="5.0">
-            <Restrictions car:requiresDistractionOptimization="true" car:uxr="fully_restricted"/>
+            <Restrictions car:requiresDistractionOptimization="true" car:uxr="no_dialpad|no_filtering|limit_string_length|no_keyboard|no_video|limit_content|no_setup|no_text_message"/>
         </DrivingState>
+
     </RestrictionMapping>
 
     <!-- Configure restriction parameters here-->
@@ -65,4 +66,4 @@
         <ContentRestrictions car:maxCumulativeItems="21" car:maxDepth="3"/>
     </RestrictionParameters>
 
-</UxRestrictions>
+</UxRestrictions>
\ No newline at end of file
diff --git a/service/res/xml/car_volume_groups.xml b/service/res/xml/car_volume_groups.xml
index c900329..2fdd61f 100644
--- a/service/res/xml/car_volume_groups.xml
+++ b/service/res/xml/car_volume_groups.xml
@@ -17,17 +17,14 @@
 <!--
   This configuration is replaced by car_audio_configuration.xml
 
-  Notes on backward compatibility
-  - A new audioUseUnifiedConfiguration flag is added, and the default value
-  is false
-  - If OEM does not explicitly set audioUseUnifiedConfiguration to be true,
-  car_volume_groups.xml will be used, CarAudioService also queries
-  IAudioControl HAL (getBusForContext)
-  - Otherwise, CarAudioService loads the new car_audio_configuration.xml
+  Notes on backward compatibility:
+  If audioUseDynamicRouting is true and no car_audio_configuration.xml file
+  is found in either /vendor/etc or /system/etc, then car_volume_groups.xml will
+  be used and IAudioControl.getBusForContext will be queried
 
   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
-  Defines the all available volume groups for volume control in a car.
+  Defines all of the available volume groups for volume control in a car.
   One can overlay this configuration to customize the groups.
 
   This configuration will be populated by CarAudioService and
@@ -40,7 +37,7 @@
     - All gain controllers (set on each bus) in one group have same step value
 
   It is fine that there are buses that do not appear in any group, those buses
-  may be reserved for other usages.
+  may be reserved for other purposes.
 
   Important note: when overlaying this configuration,
   make sure the resources are in the same package as CarAudioService.
diff --git a/service/src/com/android/car/AppFocusService.java b/service/src/com/android/car/AppFocusService.java
index 9d46dad..f3a668b 100644
--- a/service/src/com/android/car/AppFocusService.java
+++ b/service/src/com/android/car/AppFocusService.java
@@ -26,14 +26,18 @@
 import android.os.Looper;
 import android.os.Message;
 import android.os.RemoteException;
+import android.util.ArraySet;
 import android.util.Log;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
 
 import java.io.PrintWriter;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Map;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
 import java.util.Set;
-import java.util.concurrent.CopyOnWriteArrayList;
 
 /**
  * App focus service ensures only one instance of application type is active at a time.
@@ -44,17 +48,32 @@
     private static final boolean DBG_EVENT = false;
 
     private final SystemActivityMonitoringService mSystemActivityMonitoringService;
+
+    private final Object mLock = new Object();
+
+    @GuardedBy("mLock")
     private final ClientHolder mAllChangeClients;
+
+    @GuardedBy("mLock")
     private final OwnershipClientHolder mAllOwnershipClients;
+
     /** K: appType, V: client owning it */
-    private final HashMap<Integer, OwnershipClientInfo> mFocusOwners = new HashMap<>();
-    private final Set<Integer> mActiveAppTypes = new HashSet<>();
-    private final CopyOnWriteArrayList<FocusOwnershipCallback> mFocusOwnershipCallbacks =
-            new CopyOnWriteArrayList<>();
+    @GuardedBy("mLock")
+    private final SparseArray<OwnershipClientInfo> mFocusOwners = new SparseArray<>();
+
+    @GuardedBy("mLock")
+    private final Set<Integer> mActiveAppTypes = new ArraySet<>();
+
+    @GuardedBy("mLock")
+    private final List<FocusOwnershipCallback> mFocusOwnershipCallbacks = new ArrayList<>();
+
     private final BinderInterfaceContainer.BinderEventHandler<IAppFocusListener>
             mAllBinderEventHandler = bInterface -> { /* nothing to do.*/ };
 
+    @GuardedBy("mLock")
     private DispatchHandler mDispatchHandler;
+
+    @GuardedBy("mLock")
     private HandlerThread mHandlerThread;
 
     public AppFocusService(Context context,
@@ -66,7 +85,7 @@
 
     @Override
     public void registerFocusListener(IAppFocusListener listener, int appType) {
-        synchronized (this) {
+        synchronized (mLock) {
             ClientInfo info = (ClientInfo) mAllChangeClients.getBinderInterface(listener);
             if (info == null) {
                 info = new ClientInfo(mAllChangeClients, listener, Binder.getCallingUid(),
@@ -80,7 +99,7 @@
 
     @Override
     public void unregisterFocusListener(IAppFocusListener listener, int appType) {
-        synchronized (this) {
+        synchronized (mLock) {
             ClientInfo info = (ClientInfo) mAllChangeClients.getBinderInterface(listener);
             if (info == null) {
                 return;
@@ -94,26 +113,26 @@
 
     @Override
     public int[] getActiveAppTypes() {
-        synchronized (this) {
-            return toIntArray(mActiveAppTypes);
+        synchronized (mLock) {
+            return mActiveAppTypes.stream().mapToInt(Integer::intValue).toArray();
         }
     }
 
     @Override
     public boolean isOwningFocus(IAppFocusOwnershipCallback callback, int appType) {
-        synchronized (this) {
-            OwnershipClientInfo info =
-                    (OwnershipClientInfo) mAllOwnershipClients.getBinderInterface(callback);
-            if (info == null) {
-                return false;
-            }
-            return info.getOwnedAppTypes().contains(appType);
+        OwnershipClientInfo info;
+        synchronized (mLock) {
+            info = (OwnershipClientInfo) mAllOwnershipClients.getBinderInterface(callback);
         }
+        if (info == null) {
+            return false;
+        }
+        return info.getOwnedAppTypes().contains(appType);
     }
 
     @Override
     public int requestAppFocus(IAppFocusOwnershipCallback callback, int appType) {
-        synchronized (this) {
+        synchronized (mLock) {
             OwnershipClientInfo info =
                     (OwnershipClientInfo) mAllOwnershipClients.getBinderInterface(callback);
             if (info == null) {
@@ -170,7 +189,7 @@
 
     @Override
     public void abandonAppFocus(IAppFocusOwnershipCallback callback, int appType) {
-        synchronized (this) {
+        synchronized (mLock) {
             OwnershipClientInfo info =
                     (OwnershipClientInfo) mAllOwnershipClients.getBinderInterface(callback);
             if (info == null) {
@@ -186,7 +205,8 @@
                 // ignore as listener doesn't own focus.
                 return;
             }
-            if (mFocusOwners.remove(appType) != null) {
+            if (mFocusOwners.contains(appType)) {
+                mFocusOwners.remove(appType);
                 mActiveAppTypes.remove(appType);
                 info.removeOwnedAppType(appType);
                 if (DBG) {
@@ -210,20 +230,31 @@
 
     @Override
     public void init() {
-        synchronized (this) {
+        synchronized (mLock) {
             mHandlerThread = new HandlerThread(AppFocusService.class.getSimpleName());
             mHandlerThread.start();
             mDispatchHandler = new DispatchHandler(mHandlerThread.getLooper());
         }
     }
 
+    @VisibleForTesting
+    public Looper getLooper() {
+        synchronized (mLock) {
+            return mHandlerThread.getLooper();
+        }
+    }
+
     @Override
     public void release() {
-        synchronized (this) {
+        synchronized (mLock) {
+            if (mDispatchHandler == null) {
+                return;
+            }
             mHandlerThread.quitSafely();
             try {
                 mHandlerThread.join(1000);
             } catch (InterruptedException e) {
+                Thread.currentThread().interrupt();
                 Log.e(CarLog.TAG_APP_FOCUS, "Timeout while waiting for handler thread to join.");
             }
             mDispatchHandler = null;
@@ -238,15 +269,17 @@
     public void onBinderDeath(
             BinderInterfaceContainer.BinderInterface<IAppFocusOwnershipCallback> bInterface) {
         OwnershipClientInfo info = (OwnershipClientInfo) bInterface;
-        for (Integer appType : info.getOwnedAppTypes()) {
-            abandonAppFocus(bInterface.binderInterface, appType);
+        synchronized (mLock) {
+            for (Integer appType : info.getOwnedAppTypes()) {
+                abandonAppFocus(bInterface.binderInterface, appType);
+            }
         }
     }
 
     @Override
     public void dump(PrintWriter writer) {
         writer.println("**AppFocusService**");
-        synchronized (this) {
+        synchronized (mLock) {
             writer.println("mActiveAppTypes:" + mActiveAppTypes);
             for (BinderInterfaceContainer.BinderInterface<IAppFocusOwnershipCallback> client :
                     mAllOwnershipClients.getInterfaces()) {
@@ -260,8 +293,8 @@
      * Returns true if process with given uid and pid owns provided focus.
      */
     public boolean isFocusOwner(int uid, int pid, int appType) {
-        synchronized (this) {
-            if (mFocusOwners.containsKey(appType)) {
+        synchronized (mLock) {
+            if (mFocusOwners.contains(appType)) {
                 OwnershipClientInfo clientInfo = mFocusOwners.get(appType);
                 return clientInfo.getUid() == uid && clientInfo.getPid() == pid;
             }
@@ -284,16 +317,15 @@
      * {@link FocusOwnershipCallback#onFocusAcquired} call immediately in the same thread.
      */
     public void registerContextOwnerChangedCallback(FocusOwnershipCallback callback) {
-        mFocusOwnershipCallbacks.add(callback);
-
-        HashSet<Map.Entry<Integer, OwnershipClientInfo>> owners;
-        synchronized (this) {
-            owners = new HashSet<>(mFocusOwners.entrySet());
+        SparseArray<OwnershipClientInfo> owners;
+        synchronized (mLock) {
+            mFocusOwnershipCallbacks.add(callback);
+            owners = mFocusOwners.clone();
         }
-
-        for (Map.Entry<Integer, OwnershipClientInfo> entry : owners) {
-            OwnershipClientInfo clientInfo = entry.getValue();
-            callback.onFocusAcquired(entry.getKey(), clientInfo.getUid(), clientInfo.getPid());
+        for (int idx = 0; idx < owners.size(); idx++) {
+            int key = owners.keyAt(idx);
+            OwnershipClientInfo clientInfo = owners.valueAt(idx);
+            callback.onFocusAcquired(key, clientInfo.getUid(), clientInfo.getPid());
         }
     }
 
@@ -301,16 +333,19 @@
      * Unregisters provided callback.
      */
     public void unregisterContextOwnerChangedCallback(FocusOwnershipCallback callback) {
-        mFocusOwnershipCallbacks.remove(callback);
+        synchronized (mLock) {
+            mFocusOwnershipCallbacks.remove(callback);
+        }
     }
 
     private void updateFocusOwner(int appType, OwnershipClientInfo owner) {
         CarServiceUtils.runOnMain(() -> {
-            synchronized (this) {
+            List<FocusOwnershipCallback> focusOwnershipCallbacks;
+            synchronized (mLock) {
                 mFocusOwners.put(appType, owner);
+                focusOwnershipCallbacks = new ArrayList<>(mFocusOwnershipCallbacks);
             }
-
-            for (FocusOwnershipCallback callback : mFocusOwnershipCallbacks) {
+            for (FocusOwnershipCallback callback : focusOwnershipCallbacks) {
                 callback.onFocusAcquired(appType, owner.getUid(), owner.getPid());
             }
         });
@@ -350,11 +385,13 @@
         }
     }
 
-    private static class ClientInfo extends
+    private class ClientInfo extends
             BinderInterfaceContainer.BinderInterface<IAppFocusListener> {
         private final int mUid;
         private final int mPid;
-        private final Set<Integer> mAppTypes = new HashSet<>();
+
+        @GuardedBy("AppFocusService.mLock")
+        private final Set<Integer> mAppTypes = new ArraySet<>();
 
         private ClientInfo(ClientHolder holder, IAppFocusListener binder, int uid, int pid,
                 int appType) {
@@ -364,32 +401,40 @@
             this.mAppTypes.add(appType);
         }
 
-        private synchronized Set<Integer> getAppTypes() {
-            return mAppTypes;
+        private Set<Integer> getAppTypes() {
+            synchronized (mLock) {
+                return Collections.unmodifiableSet(mAppTypes);
+            }
         }
 
-        private synchronized boolean addAppType(Integer appType) {
-            return mAppTypes.add(appType);
+        private boolean addAppType(Integer appType) {
+            synchronized (mLock) {
+                return mAppTypes.add(appType);
+            }
         }
 
-        private synchronized boolean removeAppType(Integer appType) {
-            return mAppTypes.remove(appType);
+        private boolean removeAppType(Integer appType) {
+            synchronized (mLock) {
+                return mAppTypes.remove(appType);
+            }
         }
 
         @Override
         public String toString() {
-            synchronized (this) {
+            synchronized (mLock) {
                 return "ClientInfo{mUid=" + mUid + ",mPid=" + mPid
                         + ",appTypes=" + mAppTypes + "}";
             }
         }
     }
 
-    private static class OwnershipClientInfo extends
+    private class OwnershipClientInfo extends
             BinderInterfaceContainer.BinderInterface<IAppFocusOwnershipCallback> {
         private final int mUid;
         private final int mPid;
-        private final Set<Integer> mOwnedAppTypes = new HashSet<>();
+
+        @GuardedBy("AppFocusService.mLock")
+        private final Set<Integer> mOwnedAppTypes = new ArraySet<>();
 
         private OwnershipClientInfo(OwnershipClientHolder holder, IAppFocusOwnershipCallback binder,
                 int uid, int pid) {
@@ -398,25 +443,31 @@
             this.mPid = pid;
         }
 
-        private synchronized Set<Integer> getOwnedAppTypes() {
+        private Set<Integer> getOwnedAppTypes() {
             if (DBG_EVENT) {
                 Log.i(CarLog.TAG_APP_FOCUS, "getOwnedAppTypes " + mOwnedAppTypes);
             }
-            return mOwnedAppTypes;
+            synchronized (mLock) {
+                return Collections.unmodifiableSet(mOwnedAppTypes);
+            }
         }
 
-        private synchronized boolean addOwnedAppType(Integer appType) {
+        private boolean addOwnedAppType(Integer appType) {
             if (DBG_EVENT) {
                 Log.i(CarLog.TAG_APP_FOCUS, "addOwnedAppType " + appType);
             }
-            return mOwnedAppTypes.add(appType);
+            synchronized (mLock) {
+                return mOwnedAppTypes.add(appType);
+            }
         }
 
-        private synchronized boolean removeOwnedAppType(Integer appType) {
+        private boolean removeOwnedAppType(Integer appType) {
             if (DBG_EVENT) {
                 Log.i(CarLog.TAG_APP_FOCUS, "removeOwnedAppType " + appType);
             }
-            return mOwnedAppTypes.remove(appType);
+            synchronized (mLock) {
+                return mOwnedAppTypes.remove(appType);
+            }
         }
 
         int getUid() {
@@ -429,7 +480,7 @@
 
         @Override
         public String toString() {
-            synchronized (this) {
+            synchronized (mLock) {
                 return "ClientInfo{mUid=" + mUid + ",mPid=" + mPid
                         + ",owned=" + mOwnedAppTypes + "}";
             }
@@ -481,13 +532,4 @@
             }
         }
     }
-
-    private static int[] toIntArray(Set<Integer> intSet) {
-        int[] intArr = new int[intSet.size()];
-        int index = 0;
-        for (Integer value : intSet) {
-            intArr[index++] = value;
-        }
-        return intArr;
-    }
 }
diff --git a/service/src/com/android/car/BinderInterfaceContainer.java b/service/src/com/android/car/BinderInterfaceContainer.java
index 5d57b16..b97db35 100644
--- a/service/src/com/android/car/BinderInterfaceContainer.java
+++ b/service/src/com/android/car/BinderInterfaceContainer.java
@@ -21,16 +21,27 @@
 import android.os.IInterface;
 import android.os.RemoteException;
 
+import com.android.internal.annotations.GuardedBy;
+
 import java.util.Collection;
+import java.util.Collections;
 import java.util.HashMap;
+import java.util.Map;
 
 /**
  * Helper class to hold client's binder interface.
+ *
+ * @param <T> type of the value that is wrapped by this class
  */
 public class BinderInterfaceContainer<T extends IInterface> {
 
-    public static class BinderInterface<T extends IInterface>
-            implements IBinder.DeathRecipient {
+    /**
+     * Wrapper class for objects that want to be notified whenever they are unliked from
+     * the container ({@link BinderInterfaceContainer}).
+     *
+     * @param <T> type of the value that is wrapped by this class
+     */
+    public static class BinderInterface<T extends IInterface> implements IBinder.DeathRecipient {
         public final T binderInterface;
         private final BinderInterfaceContainer<T> mContainer;
 
@@ -46,13 +57,25 @@
         }
     }
 
+    /**
+     * Interface to be implemented by object that want to be notified whenever a binder is unliked
+     * (dies).
+     */
     public interface BinderEventHandler<T extends IInterface> {
         void onBinderDeath(BinderInterface<T> bInterface);
     }
 
-    private final BinderEventHandler<T> mEventHandler;
-    private final HashMap<IBinder, BinderInterface<T>> mBinders = new HashMap<>();
+    private final Object mLock = new Object();
 
+    private final BinderEventHandler<T> mEventHandler;
+
+    @GuardedBy("mLock")
+    private final Map<IBinder, BinderInterface<T>> mBinders = new HashMap<>();
+
+    /**
+     * Constructs a new <code>BinderInterfaceContainer</code> passing an event handler to be used to
+     * notify listeners when a registered binder dies (unlinked).
+     */
     public BinderInterfaceContainer(@Nullable BinderEventHandler<T> eventHandler) {
         mEventHandler = eventHandler;
     }
@@ -61,9 +84,14 @@
         mEventHandler = null;
     }
 
+    /**
+     * Add the instance of {@link IInterface} representing the binder interface to this container.
+     *
+     * Internally, this object will be wrapped in an {@link BinderInterface} when added.
+     */
     public void addBinder(T binderInterface) {
         IBinder binder = binderInterface.asBinder();
-        synchronized (this) {
+        synchronized (mLock) {
             BinderInterface<T> bInterface = mBinders.get(binder);
             if (bInterface != null) {
                 return;
@@ -78,9 +106,13 @@
         }
     }
 
+    /**
+     * Removes the {@link BinderInterface} object associated with the passed parameter (if there is
+     * any).
+     */
     public void removeBinder(T binderInterface) {
         IBinder binder = binderInterface.asBinder();
-        synchronized(this) {
+        synchronized (mLock) {
             BinderInterface<T> bInterface = mBinders.get(binder);
             if (bInterface == null) {
                 return;
@@ -90,16 +122,22 @@
         }
     }
 
+    /**
+     * Returns the {@link BinderInterface} object associated with the passed parameter.
+     */
     public BinderInterface<T> getBinderInterface(T binderInterface) {
         IBinder binder = binderInterface.asBinder();
-        synchronized (this) {
+        synchronized (mLock) {
             return mBinders.get(binder);
         }
     }
 
+    /**
+     * Adds a new {@link BinderInterface} in this container.
+     */
     public void addBinderInterface(BinderInterface<T> bInterface) {
         IBinder binder = bInterface.binderInterface.asBinder();
-        synchronized (this) {
+        synchronized (mLock) {
             try {
                 binder.linkToDeath(bInterface, 0);
             } catch (RemoteException e) {
@@ -109,21 +147,35 @@
         }
     }
 
+    /**
+     * Returns an unmodified collection containing all registered {@link BinderInterface} objects
+     * with this container.
+     */
     public Collection<BinderInterface<T>> getInterfaces() {
-        synchronized (this) {
-            return mBinders.values();
+        synchronized (mLock) {
+            return Collections.unmodifiableCollection(mBinders.values());
         }
     }
 
-    public synchronized int size() {
-        return mBinders.size();
+    /**
+     * Returns the number of registered {@link BinderInterface} objects in this container.
+     */
+    public int size() {
+        synchronized (mLock) {
+            return mBinders.size();
+        }
     }
 
-    public synchronized void clear() {
-        Collection<BinderInterface<T>> interfaces = getInterfaces();
-        for (BinderInterface<T> bInterface : interfaces) {
-            IBinder binder = bInterface.binderInterface.asBinder();
-            binder.unlinkToDeath(bInterface, 0);
+    /**
+     * Clears all registered {@link BinderInterface} objects.
+     */
+    public void clear() {
+        synchronized (mLock) {
+            Collection<BinderInterface<T>> interfaces = getInterfaces();
+            for (BinderInterface<T> bInterface : interfaces) {
+                IBinder binder = bInterface.binderInterface.asBinder();
+                binder.unlinkToDeath(bInterface, 0);
+            }
         }
         mBinders.clear();
     }
diff --git a/service/src/com/android/car/BluetoothProfileDeviceManager.java b/service/src/com/android/car/BluetoothProfileDeviceManager.java
index e73d9ea..cfd45e5 100644
--- a/service/src/com/android/car/BluetoothProfileDeviceManager.java
+++ b/service/src/com/android/car/BluetoothProfileDeviceManager.java
@@ -91,12 +91,12 @@
         sProfileActions.put(BluetoothProfile.A2DP_SINK,
                 new BluetoothProfileInfo(BluetoothA2dpSink.ACTION_CONNECTION_STATE_CHANGED,
                         KEY_BLUETOOTH_A2DP_SINK_DEVICES, new ParcelUuid[] {
-                            BluetoothUuid.AudioSource
+                            BluetoothUuid.A2DP_SOURCE
                         }, new int[] {}));
         sProfileActions.put(BluetoothProfile.HEADSET_CLIENT,
                 new BluetoothProfileInfo(BluetoothHeadsetClient.ACTION_CONNECTION_STATE_CHANGED,
                         KEY_BLUETOOTH_HFP_CLIENT_DEVICES, new ParcelUuid[] {
-                            BluetoothUuid.Handsfree_AG,
+                            BluetoothUuid.HFP_AG,
                             BluetoothUuid.HSP_AG
                         }, new int[] {BluetoothProfile.MAP_CLIENT, BluetoothProfile.PBAP_CLIENT}));
         sProfileActions.put(BluetoothProfile.MAP_CLIENT,
diff --git a/service/src/com/android/car/CarBluetoothService.java b/service/src/com/android/car/CarBluetoothService.java
index 54d15a1..0a454af 100644
--- a/service/src/com/android/car/CarBluetoothService.java
+++ b/service/src/com/android/car/CarBluetoothService.java
@@ -20,11 +20,12 @@
 import android.bluetooth.BluetoothProfile;
 import android.car.ICarBluetooth;
 import android.car.ICarBluetoothUserService;
-import android.car.ICarUserService;
+import android.car.IPerUserCarService;
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.os.IBinder;
 import android.os.RemoteException;
+import android.os.UserHandle;
 import android.util.Log;
 import android.util.SparseArray;
 
@@ -88,37 +89,41 @@
     @GuardedBy("mPerUserLock")
     private int mUserId;
     @GuardedBy("mPerUserLock")
-    private ICarUserService mCarUserService;
+    private IPerUserCarService mPerUserCarService;
     @GuardedBy("mPerUserLock")
     private ICarBluetoothUserService mCarBluetoothUserService;
     private final PerUserCarServiceHelper mUserServiceHelper;
     private final PerUserCarServiceHelper.ServiceCallback mUserServiceCallback =
             new PerUserCarServiceHelper.ServiceCallback() {
         @Override
-        public void onServiceConnected(ICarUserService carUserService) {
+        public void onServiceConnected(IPerUserCarService perUserCarService) {
             logd("Connected to PerUserCarService");
             synchronized (mPerUserLock) {
                 // Explicitly clear out existing per-user objects since we can't rely on the
                 // onServiceDisconnected and onPreUnbind calls to always be called before this
-                destroyUser();
+                destroyUserLocked();
 
-                mCarUserService = carUserService;
+                mPerUserCarService = perUserCarService;
 
                 // Create new objects with our new set of profile proxies
-                initializeUser();
+                initializeUserLocked();
             }
         }
 
         @Override
         public void onPreUnbind() {
-            logd("Before Unbinding from PerCarUserService");
-            destroyUser();
+            logd("Before Unbinding from PerUserCarService");
+            synchronized (mPerUserLock) {
+                destroyUserLocked();
+            }
         }
 
         @Override
         public void onServiceDisconnected() {
             logd("Disconnected from PerUserCarService");
-            destroyUser();
+            synchronized (mPerUserLock) {
+                destroyUserLocked();
+            }
         }
     };
 
@@ -130,7 +135,7 @@
      *                            to in order to receive user switch events
      */
     public CarBluetoothService(Context context, PerUserCarServiceHelper userSwitchService) {
-        mUserId = -1;
+        mUserId = UserHandle.USER_NULL;
         mContext = context;
         mUserServiceHelper = userSwitchService;
         mUseDefaultPolicy = mContext.getResources().getBoolean(
@@ -157,7 +162,9 @@
     public void release() {
         logd("release()");
         mUserServiceHelper.unregisterServiceCallback(mUserServiceCallback);
-        destroyUser();
+        synchronized (mPerUserLock) {
+            destroyUserLocked();
+        }
     }
 
     /**
@@ -174,37 +181,33 @@
      *
      * Only call this following a known user switch once we've connected to the user service helper.
      */
-    private void initializeUser() {
+    private void initializeUserLocked() {
         logd("Initializing new user");
-        synchronized (mPerUserLock) {
-            mUserId = ActivityManager.getCurrentUser();
-            createBluetoothUserService();
-            createBluetoothProfileDeviceManagers();
-            createBluetoothProfileInhibitManager();
+        mUserId = ActivityManager.getCurrentUser();
+        createBluetoothUserServiceLocked();
+        createBluetoothProfileDeviceManagersLocked();
+        createBluetoothProfileInhibitManagerLocked();
 
-            // Determine if we need to begin the default policy
-            mBluetoothDeviceConnectionPolicy = null;
-            if (mUseDefaultPolicy) {
-                createBluetoothDeviceConnectionPolicy();
-            }
-            logd("Switched to user " + mUserId);
+        // Determine if we need to begin the default policy
+        mBluetoothDeviceConnectionPolicy = null;
+        if (mUseDefaultPolicy) {
+            createBluetoothDeviceConnectionPolicyLocked();
         }
+        logd("Switched to user " + mUserId);
     }
 
     /**
      * Destroy the current user context, defined by the set of profile proxies, profile device
      * managers, inhibit manager and the policy.
      */
-    private void destroyUser() {
+    private void destroyUserLocked() {
         logd("Destroying user " + mUserId);
-        synchronized (mPerUserLock) {
-            destroyBluetoothDeviceConnectionPolicy();
-            destroyBluetoothProfileInhibitManager();
-            destroyBluetoothProfileDeviceManagers();
-            destroyBluetoothUserService();
-            mCarUserService = null;
-            mUserId = -1;
-        }
+        destroyBluetoothDeviceConnectionPolicyLocked();
+        destroyBluetoothProfileInhibitManagerLocked();
+        destroyBluetoothProfileDeviceManagersLocked();
+        destroyBluetoothUserServiceLocked();
+        mPerUserCarService = null;
+        mUserId = UserHandle.USER_NULL;
     }
 
     /**
@@ -213,17 +216,17 @@
      * Also sets up the connection proxy objects required to communicate with the Bluetooth
      * Profile Services.
      */
-    private void createBluetoothUserService() {
-        synchronized (mPerUserLock) {
-            if (mCarUserService != null) {
-                try {
-                    mCarBluetoothUserService = mCarUserService.getBluetoothUserService();
-                    mCarBluetoothUserService.setupBluetoothConnectionProxies();
-                } catch (RemoteException e) {
-                    Log.e(TAG, "Remote Service Exception on ServiceConnection Callback: "
-                            + e.getMessage());
-                }
+    private void createBluetoothUserServiceLocked() {
+        if (mPerUserCarService != null) {
+            try {
+                mCarBluetoothUserService = mPerUserCarService.getBluetoothUserService();
+                mCarBluetoothUserService.setupBluetoothConnectionProxies();
+            } catch (RemoteException e) {
+                Log.e(TAG, "Remote Service Exception on ServiceConnection Callback: "
+                        + e.getMessage());
             }
+        } else {
+            logd("PerUserCarService not connected. Cannot get bluetooth user proxy objects");
         }
     }
 
@@ -231,130 +234,116 @@
      * Close out the Per User Car Bluetooth profile proxy connections and destroys the Car Bluetooth
      * User Service object.
      */
-    private void destroyBluetoothUserService() {
-        synchronized (mPerUserLock) {
-            if (mCarBluetoothUserService == null) return;
-            try {
-                mCarBluetoothUserService.closeBluetoothConnectionProxies();
-            } catch (RemoteException e) {
-                Log.e(TAG, "Remote Service Exception on ServiceConnection Callback: "
-                        + e.getMessage());
-            }
-            mCarBluetoothUserService = null;
+    private void destroyBluetoothUserServiceLocked() {
+        if (mCarBluetoothUserService == null) return;
+        try {
+            mCarBluetoothUserService.closeBluetoothConnectionProxies();
+        } catch (RemoteException e) {
+            Log.e(TAG, "Remote Service Exception on ServiceConnection Callback: "
+                    + e.getMessage());
         }
+        mCarBluetoothUserService = null;
     }
 
     /**
      * Clears out Profile Device Managers and re-creates them for the current user.
      */
-    private void createBluetoothProfileDeviceManagers() {
-        synchronized (mPerUserLock) {
-            if (mUserId == -1) {
-                logd("No foreground user, cannot create profile device managers");
-                return;
-            }
-            for (int profileId : sManagedProfiles) {
-                BluetoothProfileDeviceManager deviceManager = mProfileDeviceManagers.get(profileId);
-                if (deviceManager != null) {
-                    deviceManager.stop();
-                    mProfileDeviceManagers.remove(profileId);
-                    logd("Existing device manager removed for profile "
-                            + Utils.getProfileName(profileId));
-                }
-
-                deviceManager = BluetoothProfileDeviceManager.create(mContext, mUserId,
-                        mCarBluetoothUserService, profileId);
-                if (deviceManager == null) {
-                    logd("Failed to create profile device manager for "
-                            + Utils.getProfileName(profileId));
-                    continue;
-                }
-                mProfileDeviceManagers.put(profileId, deviceManager);
-                logd("Created profile device manager for " + Utils.getProfileName(profileId));
+    private void createBluetoothProfileDeviceManagersLocked() {
+        if (mUserId == UserHandle.USER_NULL) {
+            logd("No foreground user, cannot create profile device managers");
+            return;
+        }
+        for (int profileId : sManagedProfiles) {
+            BluetoothProfileDeviceManager deviceManager = mProfileDeviceManagers.get(profileId);
+            if (deviceManager != null) {
+                deviceManager.stop();
+                mProfileDeviceManagers.remove(profileId);
+                logd("Existing device manager removed for profile "
+                        + Utils.getProfileName(profileId));
             }
 
-            for (int i = 0; i < mProfileDeviceManagers.size(); i++) {
-                int key = mProfileDeviceManagers.keyAt(i);
-                BluetoothProfileDeviceManager deviceManager =
-                        (BluetoothProfileDeviceManager) mProfileDeviceManagers.get(key);
-                deviceManager.start();
+            deviceManager = BluetoothProfileDeviceManager.create(mContext, mUserId,
+                    mCarBluetoothUserService, profileId);
+            if (deviceManager == null) {
+                logd("Failed to create profile device manager for "
+                        + Utils.getProfileName(profileId));
+                continue;
             }
+            mProfileDeviceManagers.put(profileId, deviceManager);
+            logd("Created profile device manager for " + Utils.getProfileName(profileId));
+        }
+
+        for (int i = 0; i < mProfileDeviceManagers.size(); i++) {
+            int key = mProfileDeviceManagers.keyAt(i);
+            BluetoothProfileDeviceManager deviceManager =
+                    (BluetoothProfileDeviceManager) mProfileDeviceManagers.get(key);
+            deviceManager.start();
         }
     }
 
     /**
      * Stops and clears the entire set of Profile Device Managers.
      */
-    private void destroyBluetoothProfileDeviceManagers() {
-        synchronized (mPerUserLock) {
-            for (int i = 0; i < mProfileDeviceManagers.size(); i++) {
-                int key = mProfileDeviceManagers.keyAt(i);
-                BluetoothProfileDeviceManager deviceManager =
-                        (BluetoothProfileDeviceManager) mProfileDeviceManagers.get(key);
-                deviceManager.stop();
-            }
-            mProfileDeviceManagers.clear();
+    private void destroyBluetoothProfileDeviceManagersLocked() {
+        for (int i = 0; i < mProfileDeviceManagers.size(); i++) {
+            int key = mProfileDeviceManagers.keyAt(i);
+            BluetoothProfileDeviceManager deviceManager =
+                    (BluetoothProfileDeviceManager) mProfileDeviceManagers.get(key);
+            deviceManager.stop();
         }
+        mProfileDeviceManagers.clear();
     }
 
     /**
      * Creates an instance of a BluetoothProfileInhibitManager under the current user
      */
-    private void createBluetoothProfileInhibitManager() {
+    private void createBluetoothProfileInhibitManagerLocked() {
         logd("Creating inhibit manager");
-        synchronized (mPerUserLock) {
-            if (mUserId == -1) {
-                logd("No foreground user, cannot create profile inhibit manager");
-                return;
-            }
-            mInhibitManager = new BluetoothProfileInhibitManager(mContext, mUserId,
-                    mCarBluetoothUserService);
-            mInhibitManager.start();
+        if (mUserId == UserHandle.USER_NULL) {
+            logd("No foreground user, cannot create profile inhibit manager");
+            return;
         }
+        mInhibitManager = new BluetoothProfileInhibitManager(mContext, mUserId,
+                mCarBluetoothUserService);
+        mInhibitManager.start();
     }
 
     /**
      * Destroys the current instance of a BluetoothProfileInhibitManager, if one exists
      */
-    private void destroyBluetoothProfileInhibitManager() {
+    private void destroyBluetoothProfileInhibitManagerLocked() {
         logd("Destroying inhibit manager");
-        synchronized (mPerUserLock) {
-            if (mInhibitManager == null) return;
-            mInhibitManager.stop();
-            mInhibitManager = null;
-        }
+        if (mInhibitManager == null) return;
+        mInhibitManager.stop();
+        mInhibitManager = null;
     }
 
     /**
      * Creates an instance of a BluetoothDeviceConnectionPolicy under the current user
      */
-    private void createBluetoothDeviceConnectionPolicy() {
+    private void createBluetoothDeviceConnectionPolicyLocked() {
         logd("Creating device connection policy");
-        synchronized (mPerUserLock) {
-            if (mUserId == -1) {
-                logd("No foreground user, cannot create device connection policy");
-                return;
-            }
-            mBluetoothDeviceConnectionPolicy = BluetoothDeviceConnectionPolicy.create(mContext,
-                    mUserId, this);
-            if (mBluetoothDeviceConnectionPolicy == null) {
-                logd("Failed to create default Bluetooth device connection policy.");
-                return;
-            }
-            mBluetoothDeviceConnectionPolicy.init();
+        if (mUserId == UserHandle.USER_NULL) {
+            logd("No foreground user, cannot create device connection policy");
+            return;
         }
+        mBluetoothDeviceConnectionPolicy = BluetoothDeviceConnectionPolicy.create(mContext,
+                mUserId, this);
+        if (mBluetoothDeviceConnectionPolicy == null) {
+            logd("Failed to create default Bluetooth device connection policy.");
+            return;
+        }
+        mBluetoothDeviceConnectionPolicy.init();
     }
 
     /**
      * Destroys the current instance of a BluetoothDeviceConnectionPolicy, if one exists
      */
-    private void destroyBluetoothDeviceConnectionPolicy() {
+    private void destroyBluetoothDeviceConnectionPolicyLocked() {
         logd("Destroying device connection policy");
-        synchronized (mPerUserLock) {
-            if (mBluetoothDeviceConnectionPolicy != null) {
-                mBluetoothDeviceConnectionPolicy.release();
-                mBluetoothDeviceConnectionPolicy = null;
-            }
+        if (mBluetoothDeviceConnectionPolicy != null) {
+            mBluetoothDeviceConnectionPolicy.release();
+            mBluetoothDeviceConnectionPolicy = null;
         }
     }
 
diff --git a/service/src/com/android/car/CarBluetoothUserService.java b/service/src/com/android/car/CarBluetoothUserService.java
index 028e66f..566a872 100644
--- a/service/src/com/android/car/CarBluetoothUserService.java
+++ b/service/src/com/android/car/CarBluetoothUserService.java
@@ -27,10 +27,9 @@
 import android.util.Log;
 import android.util.SparseBooleanArray;
 
-import com.android.internal.util.Preconditions;
-
 import java.util.Arrays;
 import java.util.List;
+import java.util.Objects;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.locks.Condition;
 import java.util.concurrent.locks.ReentrantLock;
@@ -69,7 +68,7 @@
     /**
      * Create a CarBluetoothUserService instance.
      *
-     * @param serice - A reference to a PerUserCarService, so we can use its context to receive
+     * @param service - A reference to a PerUserCarService, so we can use its context to receive
      *                 updates as a particular user.
      */
     public CarBluetoothUserService(PerUserCarService service) {
@@ -82,7 +81,7 @@
         mBluetoothProxyLock = new ReentrantLock();
         mConditionAllProxiesConnected = mBluetoothProxyLock.newCondition();
         mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
-        Preconditions.checkNotNull(mBluetoothAdapter, "Bluetooth adapter cannot be null");
+        Objects.requireNonNull(mBluetoothAdapter, "Bluetooth adapter cannot be null");
     }
 
     /**
diff --git a/service/src/com/android/car/CarExperimentalFeatureServiceController.java b/service/src/com/android/car/CarExperimentalFeatureServiceController.java
new file mode 100644
index 0000000..5f9de9c
--- /dev/null
+++ b/service/src/com/android/car/CarExperimentalFeatureServiceController.java
@@ -0,0 +1,237 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car;
+
+import android.car.IExperimentalCar;
+import android.car.IExperimentalCarHelper;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.IBinder;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.util.ArrayMap;
+import android.util.Log;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Controls binding to ExperimentalCarService and interfaces for experimental features.
+ */
+public final class CarExperimentalFeatureServiceController implements CarServiceBase {
+
+    private static final String TAG = "CAR.EXPERIMENTAL";
+
+    private final Context mContext;
+
+    private final ServiceConnection mServiceConnection = new ServiceConnection() {
+        @Override
+        public void onServiceConnected(ComponentName name, IBinder service) {
+            IExperimentalCar experimentalCar;
+            synchronized (mLock) {
+                experimentalCar = IExperimentalCar.Stub.asInterface(service);
+                mExperimentalCar = experimentalCar;
+            }
+            if (experimentalCar == null) {
+                Log.e(TAG, "Experimental car returned null binder");
+                return;
+            }
+            CarFeatureController featureController = CarLocalServices.getService(
+                    CarFeatureController.class);
+            List<String> enabledExperimentalFeatures =
+                    featureController.getEnabledExperimentalFeatures();
+            try {
+                experimentalCar.init(mHelper, enabledExperimentalFeatures);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Experimental car service crashed", e);
+            }
+        }
+
+        @Override
+        public void onServiceDisconnected(ComponentName name) {
+            resetFeatures();
+        }
+    };
+
+    private final IExperimentalCarHelper mHelper = new IExperimentalCarHelper.Stub() {
+        @Override
+        public void onInitComplete(List<String> allAvailableFeatures, List<String> startedFeatures,
+                List<String> classNames, List<IBinder> binders) {
+            if (allAvailableFeatures == null) {
+                Log.e(TAG, "Experimental car passed null allAvailableFeatures");
+                return;
+            }
+            if (startedFeatures == null || classNames == null || binders == null) {
+                Log.i(TAG, "Nothing enabled in Experimental car");
+                return;
+            }
+            int sizeOfStartedFeatures = startedFeatures.size();
+            if (sizeOfStartedFeatures != classNames.size()
+                    || sizeOfStartedFeatures != binders.size()) {
+                Log.e(TAG,
+                        "Experimental car passed wrong lists of enabled features, startedFeatures:"
+                        + startedFeatures + " classNames:" + classNames + " binders:" + binders);
+            }
+            // Do conversion to make indexed accesses
+            ArrayList<String> classNamesInArray = new ArrayList<>(classNames);
+            ArrayList<IBinder> bindersInArray = new ArrayList<>(binders);
+            synchronized (mLock) {
+                for (int i = 0; i < startedFeatures.size(); i++) {
+                    mEnabledFeatures.put(startedFeatures.get(i),
+                            new FeatureInfo(classNamesInArray.get(i),
+                                    bindersInArray.get(i)));
+                }
+            }
+            CarFeatureController featureController = CarLocalServices.getService(
+                    CarFeatureController.class);
+            featureController.setAvailableExperimentalFeatureList(allAvailableFeatures);
+            Log.i(TAG, "Available experimental features:" + allAvailableFeatures);
+            Log.i(TAG, "Started experimental features:" + startedFeatures);
+        }
+    };
+
+    private final Object mLock = new Object();
+
+    @GuardedBy("mLock")
+    private IExperimentalCar mExperimentalCar;
+
+    @GuardedBy("mLock")
+    private final ArrayMap<String, FeatureInfo> mEnabledFeatures = new ArrayMap<>();
+
+    @GuardedBy("mLock")
+    private boolean mBound;
+
+    private static class FeatureInfo {
+        public final String className;
+        public final IBinder binder;
+
+        FeatureInfo(String className, IBinder binder) {
+            this.className = className;
+            this.binder = binder;
+        }
+    }
+
+    public CarExperimentalFeatureServiceController(Context context) {
+        mContext = context;
+    }
+
+    @Override
+    public void init() {
+        // Do binding only for real car servie
+        Intent intent = new Intent();
+        intent.setComponent(new ComponentName("com.android.experimentalcar",
+                "com.android.experimentalcar.ExperimentalCarService"));
+        boolean bound = bindService(intent);
+        if (!bound) {
+            Log.e(TAG, "Cannot bind to experimental car service, intent:" + intent);
+        }
+        synchronized (mLock) {
+            mBound = bound;
+        }
+    }
+
+    /**
+     * Bind service. Separated for testing.
+     * Test will override this. Default behavior will not bind if it is not real run (=system uid).
+     */
+    @VisibleForTesting
+    public boolean bindService(Intent intent) {
+        int myUid = Process.myUid();
+        if (myUid != Process.SYSTEM_UID) {
+            Log.w(TAG, "Binding experimental service skipped as this may be test env, uid:"
+                    + myUid);
+            return false;
+        }
+        try {
+            return mContext.bindServiceAsUser(intent, mServiceConnection,
+                    Context.BIND_AUTO_CREATE, UserHandle.SYSTEM);
+        } catch (Exception e) {
+            // Do not crash car service for case like package not found and etc.
+            Log.e(TAG, "Cannot bind to experimental car service", e);
+            return false;
+        }
+    }
+
+    @Override
+    public void release() {
+        synchronized (mLock) {
+            if (mBound) {
+                mContext.unbindService(mServiceConnection);
+            }
+            mBound = false;
+            resetFeatures();
+        }
+    }
+
+    @Override
+    public void dump(PrintWriter writer) {
+        writer.println("*CarExperimentalFeatureServiceController*");
+
+        synchronized (mLock) {
+            writer.println(" mEnabledFeatures, number of features:" + mEnabledFeatures.size()
+                    + ", format: (feature, class)");
+            for (int i = 0; i < mEnabledFeatures.size(); i++) {
+                String feature = mEnabledFeatures.keyAt(i);
+                FeatureInfo info = mEnabledFeatures.valueAt(i);
+                writer.println(feature + "," + info.className);
+            }
+            writer.println("mBound:" + mBound);
+        }
+    }
+
+    /**
+     * Returns class name for experimental feature.
+     */
+    public String getCarManagerClassForFeature(String featureName) {
+        FeatureInfo info;
+        synchronized (mLock) {
+            info = mEnabledFeatures.get(featureName);
+        }
+        if (info == null) {
+            return null;
+        }
+        return info.className;
+    }
+
+    /**
+     * Returns service binder for experimental feature.
+     */
+    public IBinder getCarService(String serviceName) {
+        FeatureInfo info;
+        synchronized (mLock) {
+            info = mEnabledFeatures.get(serviceName);
+        }
+        if (info == null) {
+            return null;
+        }
+        return info.binder;
+    }
+
+    private void resetFeatures() {
+        synchronized (mLock) {
+            mExperimentalCar = null;
+            mEnabledFeatures.clear();
+        }
+    }
+}
diff --git a/service/src/com/android/car/CarFeatureController.java b/service/src/com/android/car/CarFeatureController.java
new file mode 100644
index 0000000..5bbeb01
--- /dev/null
+++ b/service/src/com/android/car/CarFeatureController.java
@@ -0,0 +1,437 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car;
+
+import android.annotation.NonNull;
+import android.car.Car;
+import android.car.Car.FeaturerRequestEnum;
+import android.car.CarFeatures;
+import android.content.Context;
+import android.os.Build;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.util.AtomicFile;
+import android.util.Log;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+
+/**
+ * Component controlling the feature of car.
+ */
+public final class CarFeatureController implements CarServiceBase {
+
+    private static final String TAG = "CAR.FEATURE";
+
+    // Use HaseSet for better search performance. Memory consumption is fixed and it not an issue.
+    // Should keep alphabetical order under each bucket.
+    // Update CarFeatureTest as well when this is updated.
+    private static final HashSet<String> MANDATORY_FEATURES = new HashSet<>(Arrays.asList(
+            Car.APP_FOCUS_SERVICE,
+            Car.AUDIO_SERVICE,
+            Car.BLUETOOTH_SERVICE,
+            Car.CAR_BUGREPORT_SERVICE,
+            Car.CAR_CONFIGURATION_SERVICE,
+            Car.CAR_DRIVING_STATE_SERVICE,
+            Car.CAR_MEDIA_SERVICE,
+            Car.CAR_NAVIGATION_SERVICE,
+            Car.CAR_OCCUPANT_ZONE_SERVICE,
+            Car.CAR_TRUST_AGENT_ENROLLMENT_SERVICE,
+            Car.CAR_USER_SERVICE,
+            Car.CAR_UX_RESTRICTION_SERVICE,
+            Car.INFO_SERVICE,
+            Car.PACKAGE_SERVICE,
+            Car.POWER_SERVICE,
+            Car.PROJECTION_SERVICE,
+            Car.PROPERTY_SERVICE,
+            Car.TEST_SERVICE,
+            // All items below here are deprecated, but still should be supported
+            Car.CAR_INSTRUMENT_CLUSTER_SERVICE,
+            Car.CABIN_SERVICE,
+            Car.HVAC_SERVICE,
+            Car.SENSOR_SERVICE,
+            Car.VENDOR_EXTENSION_SERVICE
+    ));
+
+    private static final HashSet<String> OPTIONAL_FEATURES = new HashSet<>(Arrays.asList(
+            CarFeatures.FEATURE_CAR_USER_NOTICE_SERVICE,
+            Car.DIAGNOSTIC_SERVICE,
+            Car.OCCUPANT_AWARENESS_SERVICE,
+            Car.STORAGE_MONITORING_SERVICE,
+            Car.VEHICLE_MAP_SERVICE,
+            Car.VMS_SUBSCRIBER_SERVICE
+    ));
+
+    private static final String FEATURE_CONFIG_FILE_NAME = "car_feature_config.txt";
+
+    // Last line starts with this with number of features for extra sanity check.
+    private static final String CONFIG_FILE_LAST_LINE_MARKER = ",,";
+
+    // Set once in constructor and not updated. Access it without lock so that it can be accessed
+    // quickly.
+    private final HashSet<String> mEnabledFeatures;
+
+    private final Context mContext;
+
+    private final List<String> mDefaultEnabledFeaturesFromConfig;
+    private final List<String> mDisabledFeaturesFromVhal;
+
+    private final HandlerThread mHandlerThread;
+    private final Handler mHandler;
+
+    private final Object mLock = new Object();
+
+    @GuardedBy("mLock")
+    private final AtomicFile mFeatureConfigFile;
+
+    @GuardedBy("mLock")
+    private final List<String> mPendingEnabledFeatures = new ArrayList<>();
+
+    @GuardedBy("mLock")
+    private final List<String> mPendingDisabledFeatures = new ArrayList<>();
+
+    @GuardedBy("mLock")
+    private HashSet<String> mAvailableExperimentalFeatures = new HashSet<>();
+
+    private final Runnable mDefaultConfigWriter = () -> {
+        persistToFeatureConfigFile();
+    };
+
+    public CarFeatureController(@NonNull Context context,
+            @NonNull String[] defaultEnabledFeaturesFromConfig,
+            @NonNull String[] disabledFeaturesFromVhal, @NonNull File dataDir) {
+        mContext = context;
+        mDefaultEnabledFeaturesFromConfig = Arrays.asList(defaultEnabledFeaturesFromConfig);
+        mDisabledFeaturesFromVhal = Arrays.asList(disabledFeaturesFromVhal);
+        mEnabledFeatures = new HashSet<>(MANDATORY_FEATURES);
+        mFeatureConfigFile = new AtomicFile(new File(dataDir, FEATURE_CONFIG_FILE_NAME), TAG);
+        boolean shouldLoadDefaultConfig = !mFeatureConfigFile.exists();
+        if (!shouldLoadDefaultConfig) {
+            if (!loadFromConfigFileLocked()) {
+                shouldLoadDefaultConfig = true;
+            }
+        }
+        mHandlerThread = new HandlerThread(TAG);
+        mHandlerThread.start();
+        mHandler = new Handler(mHandlerThread.getLooper());
+        // Separate if to use this as backup for failure in loadFromConfigFileLocked()
+        if (shouldLoadDefaultConfig) {
+            parseDefaultConfig();
+            dispatchDefaultConfigUpdate();
+        }
+    }
+
+    @Override
+    public void init() {
+        // nothing should be done here. This should work with only constructor.
+    }
+
+    @Override
+    public void release() {
+        // nothing should be done here.
+    }
+
+    @Override
+    public void dump(PrintWriter writer) {
+        writer.println("*CarFeatureController*");
+        writer.println(" mEnabledFeatures:" + mEnabledFeatures);
+        writer.println(" mDefaultEnabledFeaturesFromConfig:" + mDefaultEnabledFeaturesFromConfig);
+        writer.println(" mDisabledFeaturesFromVhal:" + mDisabledFeaturesFromVhal);
+        synchronized (mLock) {
+            writer.println(" mAvailableExperimentalFeatures:" + mAvailableExperimentalFeatures);
+            writer.println(" mPendingEnabledFeatures:" + mPendingEnabledFeatures);
+            writer.println(" mPendingDisabledFeatures:" + mPendingDisabledFeatures);
+        }
+    }
+
+    /** Check {@link Car#isFeatureEnabled(String)} */
+    public boolean isFeatureEnabled(String featureName) {
+        return mEnabledFeatures.contains(featureName);
+    }
+
+    @FeaturerRequestEnum
+    private int checkFeatureExisting(String featureName) {
+        if (MANDATORY_FEATURES.contains(featureName)) {
+            return Car.FEATURE_REQUEST_MANDATORY;
+        }
+        if (!OPTIONAL_FEATURES.contains(featureName)) {
+            synchronized (mLock) {
+                if (!mAvailableExperimentalFeatures.contains(featureName)) {
+                    Log.e(TAG, "enableFeature requested for non-existing feature:"
+                            + featureName);
+                    return Car.FEATURE_REQUEST_NOT_EXISTING;
+                }
+            }
+        }
+        return Car.FEATURE_REQUEST_SUCCESS;
+    }
+
+    /** Check {@link Car#enableFeature(String)} */
+    public int enableFeature(String featureName) {
+        assertPermission();
+        int checkResult = checkFeatureExisting(featureName);
+        if (checkResult != Car.FEATURE_REQUEST_SUCCESS) {
+            return checkResult;
+        }
+
+        boolean alreadyEnabled = mEnabledFeatures.contains(featureName);
+        boolean shouldUpdateConfigFile = false;
+        synchronized (mLock) {
+            if (mPendingDisabledFeatures.remove(featureName)) {
+                shouldUpdateConfigFile = true;
+            }
+            if (!mPendingEnabledFeatures.contains(featureName) && !alreadyEnabled) {
+                shouldUpdateConfigFile = true;
+                mPendingEnabledFeatures.add(featureName);
+            }
+        }
+        if (shouldUpdateConfigFile) {
+            Log.w(TAG, "Enabling feature in config file:" + featureName);
+            dispatchDefaultConfigUpdate();
+        }
+        if (alreadyEnabled) {
+            return Car.FEATURE_REQUEST_ALREADY_IN_THE_STATE;
+        } else {
+            return Car.FEATURE_REQUEST_SUCCESS;
+        }
+    }
+
+    /** Check {@link Car#disableFeature(String)} */
+    public int disableFeature(String featureName) {
+        assertPermission();
+        int checkResult = checkFeatureExisting(featureName);
+        if (checkResult != Car.FEATURE_REQUEST_SUCCESS) {
+            return checkResult;
+        }
+
+        boolean alreadyDisabled = !mEnabledFeatures.contains(featureName);
+        boolean shouldUpdateConfigFile = false;
+        synchronized (mLock) {
+            if (mPendingEnabledFeatures.remove(featureName)) {
+                shouldUpdateConfigFile = true;
+            }
+            if (!mPendingDisabledFeatures.contains(featureName) && !alreadyDisabled) {
+                shouldUpdateConfigFile = true;
+                mPendingDisabledFeatures.add(featureName);
+            }
+        }
+        if (shouldUpdateConfigFile) {
+            Log.w(TAG, "Disabling feature in config file:" + featureName);
+            dispatchDefaultConfigUpdate();
+        }
+        if (alreadyDisabled) {
+            return Car.FEATURE_REQUEST_ALREADY_IN_THE_STATE;
+        } else {
+            return Car.FEATURE_REQUEST_SUCCESS;
+        }
+    }
+
+    /**
+     * Set available experimental features. Only features set through this call will be allowed to
+     * be enabled for experimental features. Setting this is not allowed for USER build.
+     *
+     * @return True if set is allowed and set. False if experimental feature is not allowed.
+     */
+    public boolean setAvailableExperimentalFeatureList(List<String> experimentalFeatures) {
+        assertPermission();
+        if (Build.IS_USER) {
+            Log.e(TAG, "Experimental feature list set for USER build",
+                    new RuntimeException());
+            return false;
+        }
+        synchronized (mLock) {
+            mAvailableExperimentalFeatures.clear();
+            mAvailableExperimentalFeatures.addAll(experimentalFeatures);
+        }
+        return true;
+    }
+
+    /** Check {@link Car#getAllEnabledFeatures()} */
+    public List<String> getAllEnabledFeatures() {
+        assertPermission();
+        return new ArrayList<>(mEnabledFeatures);
+    }
+
+    /** Check {@link Car#getAllPendingDisabledFeatures()} */
+    public List<String> getAllPendingDisabledFeatures() {
+        assertPermission();
+        synchronized (mLock) {
+            return new ArrayList<>(mPendingDisabledFeatures);
+        }
+    }
+
+    /** Check {@link Car#getAllPendingEnabledFeatures()} */
+    public List<String> getAllPendingEnabledFeatures() {
+        assertPermission();
+        synchronized (mLock) {
+            return new ArrayList<>(mPendingEnabledFeatures);
+        }
+    }
+
+    /** Returns currently enabled experimental features */
+    public @NonNull List<String> getEnabledExperimentalFeatures() {
+        if (Build.IS_USER) {
+            Log.e(TAG, "getEnabledExperimentalFeatures called in USER build",
+                    new RuntimeException());
+            return Collections.emptyList();
+        }
+        ArrayList<String> experimentalFeature = new ArrayList<>();
+        for (String feature: mEnabledFeatures) {
+            if (MANDATORY_FEATURES.contains(feature)) {
+                continue;
+            }
+            if (OPTIONAL_FEATURES.contains(feature)) {
+                continue;
+            }
+            experimentalFeature.add(feature);
+        }
+        return experimentalFeature;
+    }
+
+    void handleCorruptConfigFileLocked(String msg, String line) {
+        Log.e(TAG, msg + ", considered as corrupt, line:" + line);
+        mEnabledFeatures.clear();
+    }
+
+    private boolean loadFromConfigFileLocked() {
+        // done without lock, should be only called from constructor.
+        FileInputStream fis;
+        try {
+            fis = mFeatureConfigFile.openRead();
+        } catch (FileNotFoundException e) {
+            Log.i(TAG, "Feature config file not found, this could be 1st boot");
+            return false;
+        }
+        try (BufferedReader reader = new BufferedReader(
+                new InputStreamReader(fis, StandardCharsets.UTF_8))) {
+            boolean lastLinePassed = false;
+            while (true) {
+                String line = reader.readLine();
+                if (line == null) {
+                    if (!lastLinePassed) {
+                        handleCorruptConfigFileLocked("No last line checksum", "");
+                        return false;
+                    }
+                    break;
+                }
+                if (lastLinePassed && !line.isEmpty()) {
+                    handleCorruptConfigFileLocked(
+                            "Config file has additional line after last line marker", line);
+                    return false;
+                } else {
+                    if (line.startsWith(CONFIG_FILE_LAST_LINE_MARKER)) {
+                        int numberOfFeatures;
+                        try {
+                            numberOfFeatures = Integer.valueOf(line.substring(
+                                    CONFIG_FILE_LAST_LINE_MARKER.length()));
+                        } catch (NumberFormatException e) {
+                            handleCorruptConfigFileLocked(
+                                    "Config file has corrupt last line, not a number",
+                                    line);
+                            return false;
+                        }
+                        int actualNumberOfFeatures = mEnabledFeatures.size();
+                        if (numberOfFeatures != actualNumberOfFeatures) {
+                            handleCorruptConfigFileLocked(
+                                    "Config file has wrong number of features, expected:"
+                                            + numberOfFeatures
+                                            + " actual:" + actualNumberOfFeatures, line);
+                            return false;
+                        }
+                        lastLinePassed = true;
+                    } else {
+                        mEnabledFeatures.add(line);
+                    }
+                }
+            }
+        } catch (IOException e) {
+            Log.w(TAG, "Cannot load config file", e);
+            return false;
+        }
+        Log.i(TAG, "Loaded features:" + mEnabledFeatures);
+        return true;
+    }
+
+    private void persistToFeatureConfigFile() {
+        HashSet<String> features = new HashSet<>(mEnabledFeatures);
+        synchronized (mLock) {
+            features.removeAll(mPendingDisabledFeatures);
+            features.addAll(mPendingEnabledFeatures);
+            FileOutputStream fos;
+            try {
+                fos = mFeatureConfigFile.startWrite();
+            } catch (IOException e) {
+                Log.e(TAG, "Cannot create config file", e);
+                return;
+            }
+            try (BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(fos,
+                    StandardCharsets.UTF_8))) {
+                Log.i(TAG, "Updating features:" + features);
+                for (String feature : features) {
+                    writer.write(feature);
+                    writer.newLine();
+                }
+                writer.write(CONFIG_FILE_LAST_LINE_MARKER + features.size());
+                writer.flush();
+                mFeatureConfigFile.finishWrite(fos);
+            } catch (IOException e) {
+                mFeatureConfigFile.failWrite(fos);
+                Log.e(TAG, "Cannot create config file", e);
+            }
+        }
+    }
+
+    private void assertPermission() {
+        ICarImpl.assertPermission(mContext, Car.PERMISSION_CONTROL_CAR_FEATURES);
+    }
+
+    private void dispatchDefaultConfigUpdate() {
+        mHandler.removeCallbacks(mDefaultConfigWriter);
+        mHandler.post(mDefaultConfigWriter);
+    }
+
+    private void parseDefaultConfig() {
+        for (String feature : mDefaultEnabledFeaturesFromConfig) {
+            if (!OPTIONAL_FEATURES.contains(feature)) {
+                throw new IllegalArgumentException(
+                        "config_default_enabled_optional_car_features include non-optional "
+                                + "features:" + feature);
+            }
+            if (mDisabledFeaturesFromVhal.contains(feature)) {
+                continue;
+            }
+            mEnabledFeatures.add(feature);
+        }
+        Log.i(TAG, "Loaded default features:" + mEnabledFeatures);
+    }
+}
diff --git a/service/src/com/android/car/CarInputService.java b/service/src/com/android/car/CarInputService.java
index c5ae16d..5e80838 100644
--- a/service/src/com/android/car/CarInputService.java
+++ b/service/src/com/android/car/CarInputService.java
@@ -64,6 +64,9 @@
 import java.util.function.IntSupplier;
 import java.util.function.Supplier;
 
+/**
+ * CarInputService monitors and handles input event through vehicle HAL.
+ */
 public class CarInputService implements CarServiceBase, InputHalService.InputListener {
 
     /** An interface to receive {@link KeyEvent}s as they occur. */
@@ -72,15 +75,16 @@
         void onKeyEvent(KeyEvent event);
     }
 
-    private static final class KeyPressTimer {
-        private final Handler mHandler;
+    private final class KeyPressTimer {
         private final Runnable mLongPressRunnable;
         private final Runnable mCallback = this::onTimerExpired;
         private final IntSupplier mLongPressDelaySupplier;
 
-        @GuardedBy("this")
-        private boolean mDown = false;
-        @GuardedBy("this")
+        @GuardedBy("CarInputService.this.mLock")
+        private final Handler mHandler;
+        @GuardedBy("CarInputService.this.mLock")
+        private boolean mDown;
+        @GuardedBy("CarInputService.this.mLock")
         private boolean mLongPress = false;
 
         KeyPressTimer(
@@ -91,11 +95,13 @@
         }
 
         /** Marks that a key was pressed, and starts the long-press timer. */
-        synchronized void keyDown() {
-            mDown = true;
-            mLongPress = false;
-            mHandler.removeCallbacks(mCallback);
-            mHandler.postDelayed(mCallback, mLongPressDelaySupplier.getAsInt());
+        void keyDown() {
+            synchronized (mLock) {
+                mDown = true;
+                mLongPress = false;
+                mHandler.removeCallbacks(mCallback);
+                mHandler.postDelayed(mCallback, mLongPressDelaySupplier.getAsInt());
+            }
         }
 
         /**
@@ -103,21 +109,22 @@
          *
          * Returns true if the press was a long-press.
          */
-        synchronized boolean keyUp() {
-            mHandler.removeCallbacks(mCallback);
-            mDown = false;
-            return mLongPress;
+        boolean keyUp() {
+            synchronized (mLock) {
+                mHandler.removeCallbacks(mCallback);
+                mDown = false;
+                return mLongPress;
+            }
         }
 
         private void onTimerExpired() {
-            synchronized (this) {
+            synchronized (mLock) {
                 // If the timer expires after key-up, don't retroactively make the press long.
                 if (!mDown) {
                     return;
                 }
                 mLongPress = true;
             }
-
             mLongPressRunnable.run();
         }
     }
@@ -161,26 +168,29 @@
     // long-press delay defined in ViewConfiguration. May be overridden for testing.
     private final IntSupplier mLongPressDelaySupplier;
 
-    @GuardedBy("this")
+    private final Object mLock = new Object();
+
+    @GuardedBy("mLock")
     private CarProjectionManager.ProjectionKeyEventHandler mProjectionKeyEventHandler;
-    @GuardedBy("this")
+
+    @GuardedBy("mLock")
     private final BitSet mProjectionKeyEventsSubscribed = new BitSet();
 
     private final KeyPressTimer mVoiceKeyTimer;
     private final KeyPressTimer mCallKeyTimer;
 
-    @GuardedBy("this")
+    @GuardedBy("mLock")
     private KeyEventListener mInstrumentClusterKeyListener;
 
-    @GuardedBy("this")
+    @GuardedBy("mLock")
     @VisibleForTesting
     ICarInputListener mCarInputListener;
 
-    @GuardedBy("this")
+    @GuardedBy("mLock")
     private boolean mCarInputListenerBound = false;
 
     // Maps display -> keycodes handled.
-    @GuardedBy("this")
+    @GuardedBy("mLock")
     private final SetMultimap<Integer, Integer> mHandledKeys = new SetMultimap<>();
 
     private final Binder mCallback = new Binder() {
@@ -206,7 +216,7 @@
                 Log.d(CarLog.TAG_INPUT, "onServiceConnected, name: "
                         + name + ", binder: " + binder);
             }
-            synchronized (CarInputService.this) {
+            synchronized (mLock) {
                 mCarInputListener = ICarInputListener.Stub.asInterface(binder);
             }
         }
@@ -214,7 +224,7 @@
         @Override
         public void onServiceDisconnected(ComponentName name) {
             Log.d(CarLog.TAG_INPUT, "onServiceDisconnected, name: " + name);
-            synchronized (CarInputService.this) {
+            synchronized (mLock) {
                 mCarInputListener = null;
             }
         }
@@ -224,7 +234,7 @@
 
     // BluetoothHeadsetClient set through mBluetoothProfileServiceListener, and used by
     // launchBluetoothVoiceRecognition().
-    @GuardedBy("mBluetoothProfileServiceListener")
+    @GuardedBy("mLock")
     private BluetoothHeadsetClient mBluetoothHeadsetClient;
 
     private final BluetoothProfile.ServiceListener mBluetoothProfileServiceListener =
@@ -233,7 +243,7 @@
         public void onServiceConnected(int profile, BluetoothProfile proxy) {
             if (profile == BluetoothProfile.HEADSET_CLIENT) {
                 Log.d(CarLog.TAG_INPUT, "Bluetooth proxy connected for HEADSET_CLIENT profile");
-                synchronized (this) {
+                synchronized (mLock) {
                     mBluetoothHeadsetClient = (BluetoothHeadsetClient) proxy;
                 }
             }
@@ -243,7 +253,7 @@
         public void onServiceDisconnected(int profile) {
             if (profile == BluetoothProfile.HEADSET_CLIENT) {
                 Log.d(CarLog.TAG_INPUT, "Bluetooth proxy disconnected for HEADSET_CLIENT profile");
-                synchronized (this) {
+                synchronized (mLock) {
                     mBluetoothHeadsetClient = null;
                 }
             }
@@ -253,11 +263,8 @@
     @Nullable
     private static ComponentName getDefaultInputComponent(Context context) {
         String carInputService = context.getString(R.string.inputService);
-        if (TextUtils.isEmpty(carInputService)) {
-            return null;
-        }
-
-        return ComponentName.unflattenFromString(carInputService);
+        return TextUtils.isEmpty(carInputService)
+                ? null : ComponentName.unflattenFromString(carInputService);
     }
 
     private static int getViewLongPressDelay(ContentResolver cr) {
@@ -302,10 +309,12 @@
     }
 
     @VisibleForTesting
-    synchronized void setHandledKeys(InputFilter[] handledKeys) {
-        mHandledKeys.clear();
-        for (InputFilter handledKey : handledKeys) {
-            mHandledKeys.put(handledKey.mTargetDisplay, handledKey.mKeyCode);
+    void setHandledKeys(InputFilter[] handledKeys) {
+        synchronized (mLock) {
+            mHandledKeys.clear();
+            for (InputFilter handledKey : handledKeys) {
+                mHandledKeys.put(handledKey.mTargetDisplay, handledKey.mKeyCode);
+            }
         }
     }
 
@@ -315,7 +324,7 @@
     public void setProjectionKeyEventHandler(
             @Nullable CarProjectionManager.ProjectionKeyEventHandler listener,
             @Nullable BitSet events) {
-        synchronized (this) {
+        synchronized (mLock) {
             mProjectionKeyEventHandler = listener;
             mProjectionKeyEventsSubscribed.clear();
             if (events != null) {
@@ -325,7 +334,7 @@
     }
 
     public void setInstrumentClusterKeyListener(KeyEventListener listener) {
-        synchronized (this) {
+        synchronized (mLock) {
             mInstrumentClusterKeyListener = listener;
         }
     }
@@ -339,9 +348,8 @@
             Log.d(CarLog.TAG_INPUT, "Hal supports key input.");
         }
 
-
         mInputHalService.setInputListener(this);
-        synchronized (this) {
+        synchronized (mLock) {
             mCarInputListenerBound = bindCarInputService();
         }
         if (mBluetoothAdapter != null) {
@@ -352,7 +360,7 @@
 
     @Override
     public void release() {
-        synchronized (this) {
+        synchronized (mLock) {
             mProjectionKeyEventHandler = null;
             mProjectionKeyEventsSubscribed.clear();
             mInstrumentClusterKeyListener = null;
@@ -360,8 +368,6 @@
                 mContext.unbindService(mInputServiceConnection);
                 mCarInputListenerBound = false;
             }
-        }
-        synchronized (mBluetoothProfileServiceListener) {
             if (mBluetoothHeadsetClient != null) {
                 mBluetoothAdapter.closeProfileProxy(
                         BluetoothProfile.HEADSET_CLIENT, mBluetoothHeadsetClient);
@@ -374,7 +380,7 @@
     public void onKeyEvent(KeyEvent event, int targetDisplay) {
         // Give a car specific input listener the opportunity to intercept any input from the car
         ICarInputListener carInputListener;
-        synchronized (this) {
+        synchronized (mLock) {
             carInputListener = mCarInputListener;
         }
         if (carInputListener != null && isCustomEventHandler(event, targetDisplay)) {
@@ -407,8 +413,10 @@
         }
     }
 
-    private synchronized boolean isCustomEventHandler(KeyEvent event, int targetDisplay) {
-        return mHandledKeys.containsEntry(targetDisplay, event.getKeyCode());
+    private boolean isCustomEventHandler(KeyEvent event, int targetDisplay) {
+        synchronized (mLock) {
+            return mHandledKeys.containsEntry(targetDisplay, event.getKeyCode());
+        }
     }
 
     private void handleVoiceAssistKey(KeyEvent event) {
@@ -490,7 +498,7 @@
 
     private boolean dispatchProjectionKeyEvent(@CarProjectionManager.KeyEventNum int event) {
         CarProjectionManager.ProjectionKeyEventHandler projectionKeyEventHandler;
-        synchronized (this) {
+        synchronized (mLock) {
             projectionKeyEventHandler = mProjectionKeyEventHandler;
             if (projectionKeyEventHandler == null || !mProjectionKeyEventsSubscribed.get(event)) {
                 // No event handler, or event handler doesn't want this event - we're done.
@@ -526,7 +534,6 @@
             mTelecomManager.acceptRingingCall();
             return true;
         }
-
         return false;
     }
 
@@ -536,7 +543,7 @@
     }
 
     private boolean launchBluetoothVoiceRecognition() {
-        synchronized (mBluetoothProfileServiceListener) {
+        synchronized (mLock) {
             if (mBluetoothHeadsetClient == null || !isBluetoothVoiceRecognitionEnabled()) {
                 return false;
             }
@@ -580,7 +587,7 @@
 
     private void handleInstrumentClusterKey(KeyEvent event) {
         KeyEventListener listener = null;
-        synchronized (this) {
+        synchronized (mLock) {
             listener = mInstrumentClusterKeyListener;
         }
         if (listener == null) {
@@ -590,11 +597,13 @@
     }
 
     @Override
-    public synchronized void dump(PrintWriter writer) {
+    public void dump(PrintWriter writer) {
         writer.println("*Input Service*");
         writer.println("mCustomInputServiceComponent: " + mCustomInputServiceComponent);
-        writer.println("mCarInputListenerBound: " + mCarInputListenerBound);
-        writer.println("mCarInputListener: " + mCarInputListener);
+        synchronized (mLock) {
+            writer.println("mCarInputListenerBound: " + mCarInputListenerBound);
+            writer.println("mCarInputListener: " + mCarInputListener);
+        }
         writer.println("Long-press delay: " + mLongPressDelaySupplier.getAsInt() + "ms");
     }
 
diff --git a/service/src/com/android/car/CarLocationService.java b/service/src/com/android/car/CarLocationService.java
index d4daac2..e612ac6 100644
--- a/service/src/com/android/car/CarLocationService.java
+++ b/service/src/com/android/car/CarLocationService.java
@@ -17,14 +17,13 @@
 package com.android.car;
 
 import android.app.ActivityManager;
-import android.car.ICarUserService;
 import android.car.ILocationManagerProxy;
+import android.car.IPerUserCarService;
 import android.car.drivingstate.CarDrivingStateEvent;
 import android.car.drivingstate.ICarDrivingStateChangeListener;
 import android.car.hardware.power.CarPowerManager;
 import android.car.hardware.power.CarPowerManager.CarPowerStateListener;
 import android.car.hardware.power.CarPowerManager.CarPowerStateListenerWithCompletion;
-import android.car.userlib.CarUserManagerHelper;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -36,6 +35,7 @@
 import android.os.RemoteException;
 import android.os.SystemClock;
 import android.os.UserHandle;
+import android.os.UserManager;
 import android.util.AtomicFile;
 import android.util.JsonReader;
 import android.util.JsonWriter;
@@ -62,7 +62,6 @@
         CarPowerStateListenerWithCompletion {
     private static final String TAG = "CarLocationService";
     private static final String FILENAME = "location_cache.json";
-    private static final boolean DBG = true;
     // The accuracy for the stored timestamp
     private static final long GRANULARITY_ONE_DAY_MS = 24 * 60 * 60 * 1000L;
     // The time-to-live for the cached location
@@ -70,6 +69,20 @@
     // The maximum number of times to try injecting a location
     private static final int MAX_LOCATION_INJECTION_ATTEMPTS = 10;
 
+    // Constants for location serialization.
+    private static final String PROVIDER = "provider";
+    private static final String LATITUDE = "latitude";
+    private static final String LONGITUDE = "longitude";
+    private static final String ALTITUDE = "altitude";
+    private static final String SPEED = "speed";
+    private static final String BEARING = "bearing";
+    private static final String ACCURACY = "accuracy";
+    private static final String VERTICAL_ACCURACY = "verticalAccuracy";
+    private static final String SPEED_ACCURACY = "speedAccuracy";
+    private static final String BEARING_ACCURACY = "bearingAccuracy";
+    private static final String IS_FROM_MOCK_PROVIDER = "isFromMockProvider";
+    private static final String CAPTURE_TIME = "captureTime";
+
     // Used internally for mHandlerThread synchronization
     private final Object mLock = new Object();
 
@@ -77,7 +90,6 @@
     private final Object mLocationManagerProxyLock = new Object();
 
     private final Context mContext;
-    private final CarUserManagerHelper mCarUserManagerHelper;
     private int mTaskCount = 0;
     private HandlerThread mHandlerThread;
     private Handler mHandler;
@@ -92,23 +104,23 @@
     private final PerUserCarServiceHelper.ServiceCallback mUserServiceCallback =
             new PerUserCarServiceHelper.ServiceCallback() {
                 @Override
-                public void onServiceConnected(ICarUserService carUserService) {
+                public void onServiceConnected(IPerUserCarService perUserCarService) {
                     logd("Connected to PerUserCarService");
-                    if (carUserService == null) {
-                        logd("ICarUserService is null. Cannot get location manager proxy");
+                    if (perUserCarService == null) {
+                        logd("IPerUserCarService is null. Cannot get location manager proxy");
                         return;
                     }
                     synchronized (mLocationManagerProxyLock) {
                         try {
-                            mILocationManagerProxy = carUserService.getLocationManagerProxy();
+                            mILocationManagerProxy = perUserCarService.getLocationManagerProxy();
                         } catch (RemoteException e) {
-                            Log.e(TAG, "RemoteException from ICarUserService", e);
+                            Log.e(TAG, "RemoteException from IPerUserCarService", e);
                             return;
                         }
                     }
                     int currentUser = ActivityManager.getCurrentUser();
-                    logd("Current user: " + currentUser);
-                    if (mCarUserManagerHelper.isHeadlessSystemUser()
+                    logd("Current user: %s", currentUser);
+                    if (UserManager.isHeadlessSystemUserMode()
                             && currentUser > UserHandle.USER_SYSTEM) {
                         asyncOperation(() -> loadLocation());
                     }
@@ -116,7 +128,7 @@
 
                 @Override
                 public void onPreUnbind() {
-                    logd("Before Unbinding from PerCarUserService");
+                    logd("Before Unbinding from PerUserCarService");
                     synchronized (mLocationManagerProxyLock) {
                         mILocationManagerProxy = null;
                     }
@@ -135,7 +147,7 @@
             new ICarDrivingStateChangeListener.Stub() {
                 @Override
                 public void onDrivingStateChanged(CarDrivingStateEvent event) {
-                    logd("onDrivingStateChanged " + event);
+                    logd("onDrivingStateChanged: %s", event);
                     if (event != null
                             && event.eventValue == CarDrivingStateEvent.DRIVING_STATE_MOVING) {
                         deleteCacheFile();
@@ -147,10 +159,9 @@
                 }
             };
 
-    public CarLocationService(Context context, CarUserManagerHelper carUserManagerHelper) {
+    public CarLocationService(Context context) {
         logd("constructed");
         mContext = context;
-        mCarUserManagerHelper = carUserManagerHelper;
     }
 
     @Override
@@ -204,7 +215,7 @@
 
     @Override
     public void onStateChanged(int state, CompletableFuture<Void> future) {
-        logd("onStateChanged: " + state);
+        logd("onStateChanged: %s", state);
         switch (state) {
             case CarPowerStateListener.SHUTDOWN_PREPARE:
                 asyncOperation(() -> {
@@ -242,7 +253,7 @@
 
     @Override
     public void onReceive(Context context, Intent intent) {
-        logd("onReceive " + intent);
+        logd("onReceive %s", intent);
         // If the system user is headless but the current user is still the system user, then we
         // should not delete the location cache file due to missing location permissions.
         if (isCurrentUserHeadlessSystemUser()) {
@@ -258,7 +269,7 @@
             try {
                 if (action == LocationManager.MODE_CHANGED_ACTION) {
                     boolean locationEnabled = mILocationManagerProxy.isLocationEnabled();
-                    logd("isLocationEnabled(): " + locationEnabled);
+                    logd("isLocationEnabled(): %s", locationEnabled);
                     if (!locationEnabled) {
                         deleteCacheFile();
                     }
@@ -274,7 +285,8 @@
     /** Tells whether the current foreground user is the headless system user. */
     private boolean isCurrentUserHeadlessSystemUser() {
         int currentUserId = ActivityManager.getCurrentUser();
-        return mCarUserManagerHelper.isHeadlessSystemUser() && currentUserId == 0;
+        return UserManager.isHeadlessSystemUserMode()
+                && currentUserId == UserHandle.USER_SYSTEM;
     }
 
     /**
@@ -304,39 +316,39 @@
                 fos = atomicFile.startWrite();
                 try (JsonWriter jsonWriter = new JsonWriter(new OutputStreamWriter(fos, "UTF-8"))) {
                     jsonWriter.beginObject();
-                    jsonWriter.name("provider").value(location.getProvider());
-                    jsonWriter.name("latitude").value(location.getLatitude());
-                    jsonWriter.name("longitude").value(location.getLongitude());
+                    jsonWriter.name(PROVIDER).value(location.getProvider());
+                    jsonWriter.name(LATITUDE).value(location.getLatitude());
+                    jsonWriter.name(LONGITUDE).value(location.getLongitude());
                     if (location.hasAltitude()) {
-                        jsonWriter.name("altitude").value(location.getAltitude());
+                        jsonWriter.name(ALTITUDE).value(location.getAltitude());
                     }
                     if (location.hasSpeed()) {
-                        jsonWriter.name("speed").value(location.getSpeed());
+                        jsonWriter.name(SPEED).value(location.getSpeed());
                     }
                     if (location.hasBearing()) {
-                        jsonWriter.name("bearing").value(location.getBearing());
+                        jsonWriter.name(BEARING).value(location.getBearing());
                     }
                     if (location.hasAccuracy()) {
-                        jsonWriter.name("accuracy").value(location.getAccuracy());
+                        jsonWriter.name(ACCURACY).value(location.getAccuracy());
                     }
                     if (location.hasVerticalAccuracy()) {
-                        jsonWriter.name("verticalAccuracy").value(
+                        jsonWriter.name(VERTICAL_ACCURACY).value(
                                 location.getVerticalAccuracyMeters());
                     }
                     if (location.hasSpeedAccuracy()) {
-                        jsonWriter.name("speedAccuracy").value(
+                        jsonWriter.name(SPEED_ACCURACY).value(
                                 location.getSpeedAccuracyMetersPerSecond());
                     }
                     if (location.hasBearingAccuracy()) {
-                        jsonWriter.name("bearingAccuracy").value(
+                        jsonWriter.name(BEARING_ACCURACY).value(
                                 location.getBearingAccuracyDegrees());
                     }
                     if (location.isFromMockProvider()) {
-                        jsonWriter.name("isFromMockProvider").value(true);
+                        jsonWriter.name(IS_FROM_MOCK_PROVIDER).value(true);
                     }
                     long currentTime = location.getTime();
                     // Round the time down to only be accurate within one day.
-                    jsonWriter.name("captureTime").value(
+                    jsonWriter.name(CAPTURE_TIME).value(
                             currentTime - currentTime % GRANULARITY_ONE_DAY_MS);
                     jsonWriter.endObject();
                 }
@@ -353,7 +365,7 @@
      */
     private void loadLocation() {
         Location location = readLocationFromCacheFile();
-        logd("Read location from timestamp " + location.getTime());
+        logd("Read location from timestamp %s", location.getTime());
         long currentTime = System.currentTimeMillis();
         if (location.getTime() + TTL_THIRTY_DAYS_MS < currentTime) {
             logd("Location expired.");
@@ -370,43 +382,58 @@
 
     private Location readLocationFromCacheFile() {
         Location location = new Location((String) null);
-        AtomicFile atomicFile = new AtomicFile(getLocationCacheFile());
+        File file = getLocationCacheFile();
+        AtomicFile atomicFile = new AtomicFile(file);
         try (FileInputStream fis = atomicFile.openRead()) {
             JsonReader reader = new JsonReader(new InputStreamReader(fis, "UTF-8"));
             reader.beginObject();
             while (reader.hasNext()) {
                 String name = reader.nextName();
-                if (name.equals("provider")) {
-                    location.setProvider(reader.nextString());
-                } else if (name.equals("latitude")) {
-                    location.setLatitude(reader.nextDouble());
-                } else if (name.equals("longitude")) {
-                    location.setLongitude(reader.nextDouble());
-                } else if (name.equals("altitude")) {
-                    location.setAltitude(reader.nextDouble());
-                } else if (name.equals("speed")) {
-                    location.setSpeed((float) reader.nextDouble());
-                } else if (name.equals("bearing")) {
-                    location.setBearing((float) reader.nextDouble());
-                } else if (name.equals("accuracy")) {
-                    location.setAccuracy((float) reader.nextDouble());
-                } else if (name.equals("verticalAccuracy")) {
-                    location.setVerticalAccuracyMeters((float) reader.nextDouble());
-                } else if (name.equals("speedAccuracy")) {
-                    location.setSpeedAccuracyMetersPerSecond((float) reader.nextDouble());
-                } else if (name.equals("bearingAccuracy")) {
-                    location.setBearingAccuracyDegrees((float) reader.nextDouble());
-                } else if (name.equals("isFromMockProvider")) {
-                    location.setIsFromMockProvider(reader.nextBoolean());
-                } else if (name.equals("captureTime")) {
-                    location.setTime(reader.nextLong());
-                } else {
-                    reader.skipValue();
+                switch (name) {
+                    case PROVIDER:
+                        location.setProvider(reader.nextString());
+                        break;
+                    case LATITUDE:
+                        location.setLatitude(reader.nextDouble());
+                        break;
+                    case LONGITUDE:
+                        location.setLongitude(reader.nextDouble());
+                        break;
+                    case ALTITUDE:
+                        location.setAltitude(reader.nextDouble());
+                        break;
+                    case SPEED:
+                        location.setSpeed((float) reader.nextDouble());
+                        break;
+                    case BEARING:
+                        location.setBearing((float) reader.nextDouble());
+                        break;
+                    case ACCURACY:
+                        location.setAccuracy((float) reader.nextDouble());
+                        break;
+                    case VERTICAL_ACCURACY:
+                        location.setVerticalAccuracyMeters((float) reader.nextDouble());
+                        break;
+                    case SPEED_ACCURACY:
+                        location.setSpeedAccuracyMetersPerSecond((float) reader.nextDouble());
+                        break;
+                    case BEARING_ACCURACY:
+                        location.setBearingAccuracyDegrees((float) reader.nextDouble());
+                        break;
+                    case IS_FROM_MOCK_PROVIDER:
+                        location.setIsFromMockProvider(reader.nextBoolean());
+                        break;
+                    case CAPTURE_TIME:
+                        location.setTime(reader.nextLong());
+                        break;
+                    default:
+                        Log.w(TAG, String.format("Unrecognized key: %s", name));
+                        reader.skipValue();
                 }
             }
             reader.endObject();
         } catch (FileNotFoundException e) {
-            Log.d(TAG, "Location cache file not found.");
+            logd("Location cache file not found: %s", file);
         } catch (IOException e) {
             Log.e(TAG, "Unable to read from disk", e);
         } catch (NumberFormatException | IllegalStateException e) {
@@ -416,8 +443,13 @@
     }
 
     private void deleteCacheFile() {
-        boolean deleted = getLocationCacheFile().delete();
-        logd("Deleted cache file: " + deleted);
+        File file = getLocationCacheFile();
+        boolean deleted = file.delete();
+        if (deleted) {
+            logd("Successfully deleted cache file at %s", file);
+        } else {
+            logd("Failed to delete cache file at %s", file);
+        }
     }
 
     /**
@@ -437,11 +469,11 @@
                 }
             }
         }
-        logd("Injected location with result " + success + " on attempt "
-                + attemptCount);
         if (success) {
+            logd("Successfully injected stored location on attempt %s.", attemptCount);
             return;
         } else if (attemptCount <= MAX_LOCATION_INJECTION_ATTEMPTS) {
+            logd("Failed to inject stored location on attempt %s.", attemptCount);
             asyncOperation(() -> {
                 injectLocation(location, attemptCount + 1);
             }, 200 * attemptCount);
@@ -452,9 +484,7 @@
 
     private File getLocationCacheFile() {
         SystemInterface systemInterface = CarLocalServices.getService(SystemInterface.class);
-        File file = new File(systemInterface.getSystemCarDir(), FILENAME);
-        logd("File: " + file);
-        return file;
+        return new File(systemInterface.getSystemCarDir(), FILENAME);
     }
 
     @VisibleForTesting
@@ -487,9 +517,8 @@
         }, delayMillis);
     }
 
-    private static void logd(String msg) {
-        if (DBG) {
-            Log.d(TAG, msg);
-        }
+    private static void logd(String msg, Object... vals) {
+        // Disable logs here if they become too spammy.
+        Log.d(TAG, String.format(msg, vals));
     }
 }
diff --git a/service/src/com/android/car/CarLog.java b/service/src/com/android/car/CarLog.java
index a77a549..fa99c58 100644
--- a/service/src/com/android/car/CarLog.java
+++ b/service/src/com/android/car/CarLog.java
@@ -34,6 +34,8 @@
     public static final String TAG_MEDIA = "CAR.MEDIA";
     public static final String TAG_MONITORING = "CAR.MONITORING";
     public static final String TAG_NAV = "CAR.NAV";
+    public static final String TAG_OCCUPANT = "CAR.OCCUPANT";
+    public static final String TAG_OAS = "CAR.OAS";
     public static final String TAG_PACKAGE = "CAR.PACKAGE";
     public static final String TAG_POWER = "CAR.POWER";
     public static final String TAG_PROJECTION = "CAR.PROJECTION";
diff --git a/service/src/com/android/car/CarMediaService.java b/service/src/com/android/car/CarMediaService.java
index 9f57179..c5d8fdd 100644
--- a/service/src/com/android/car/CarMediaService.java
+++ b/service/src/com/android/car/CarMediaService.java
@@ -51,6 +51,8 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
+import com.android.car.user.CarUserService;
+
 import java.io.PrintWriter;
 import java.util.ArrayDeque;
 import java.util.ArrayList;
@@ -88,6 +90,7 @@
     private static final int AUTOPLAY_CONFIG_RETAIN_PREVIOUS = 3;
 
     private final Context mContext;
+    private final CarUserService mUserService;
     private final UserManager mUserManager;
     private final MediaSessionManager mMediaSessionManager;
     private final MediaSessionUpdater mMediaSessionUpdater = new MediaSessionUpdater();
@@ -103,7 +106,6 @@
     private int mCurrentPlaybackState;
 
     private boolean mPendingInit;
-    private int mCurrentUser;
 
     private final RemoteCallbackList<ICarMediaSourceListener> mMediaSourceListeners =
             new RemoteCallbackList();
@@ -151,18 +153,24 @@
         }
     };
 
-    private final BroadcastReceiver mUserSwitchReceiver = new BroadcastReceiver() {
+    private final CarUserService.UserCallback mUserCallback = new CarUserService.UserCallback() {
+
         @Override
-        public void onReceive(Context context, Intent intent) {
-            mCurrentUser = ActivityManager.getCurrentUser();
+        public void onSwitchUser(int userId) {
             if (Log.isLoggable(CarLog.TAG_MEDIA, Log.DEBUG)) {
-                Log.d(CarLog.TAG_MEDIA, "Switched to user " + mCurrentUser);
+                Log.d(CarLog.TAG_MEDIA, "Switched to user " + userId);
             }
-            maybeInitUser();
+            maybeInitUser(userId);
         }
+
+        @Override
+        public void onUserLockChanged(int userId, boolean unlocked) {
+            // Do Nothing
+        }
+
     };
 
-    public CarMediaService(Context context) {
+    public CarMediaService(Context context, CarUserService userService) {
         mContext = context;
         mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
         mMediaSessionManager = mContext.getSystemService(MediaSessionManager.class);
@@ -176,35 +184,33 @@
         mPackageUpdateFilter.addAction(Intent.ACTION_PACKAGE_REPLACED);
         mPackageUpdateFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
         mPackageUpdateFilter.addDataScheme("package");
-
-        IntentFilter userSwitchFilter = new IntentFilter();
-        userSwitchFilter.addAction(Intent.ACTION_USER_SWITCHED);
-        mContext.registerReceiver(mUserSwitchReceiver, userSwitchFilter);
+        mUserService = userService;
+        mUserService.addUserCallback(mUserCallback);
 
         mPlayOnMediaSourceChangedConfig =
                 mContext.getResources().getInteger(R.integer.config_mediaSourceChangedAutoplay);
         mPlayOnBootConfig = mContext.getResources().getInteger(R.integer.config_mediaBootAutoplay);
-        mCurrentUser = ActivityManager.getCurrentUser();
     }
 
     @Override
     // This method is called from ICarImpl after CarMediaService is created.
     public void init() {
-        maybeInitUser();
+        int currentUser = ActivityManager.getCurrentUser();
+        maybeInitUser(currentUser);
     }
 
-    private void maybeInitUser() {
-        if (mCurrentUser == 0) {
+    private void maybeInitUser(int userId) {
+        if (userId == UserHandle.USER_SYSTEM) {
             return;
         }
-        if (mUserManager.isUserUnlocked(mCurrentUser)) {
-            initUser();
+        if (mUserManager.isUserUnlocked(userId)) {
+            initUser(userId);
         } else {
             mPendingInit = true;
         }
     }
 
-    private void initUser() {
+    private void initUser(int userId) {
         // SharedPreferences are shared among different users thus only need initialized once. And
         // they should be initialized after user 0 is unlocked because SharedPreferences in
         // credential encrypted storage are not available until after user 0 is unlocked.
@@ -217,13 +223,13 @@
         if (mIsPackageUpdateReceiverRegistered) {
             mContext.unregisterReceiver(mPackageUpdateReceiver);
         }
-        UserHandle currentUser = new UserHandle(mCurrentUser);
+        UserHandle currentUser = new UserHandle(userId);
         mContext.registerReceiverAsUser(mPackageUpdateReceiver, currentUser,
                 mPackageUpdateFilter, null, null);
         mIsPackageUpdateReceiverRegistered = true;
 
-        mPrimaryMediaComponent =
-                isCurrentUserEphemeral() ? getDefaultMediaSource() : getLastMediaSource();
+        mPrimaryMediaComponent = isCurrentUserEphemeral() ? getDefaultMediaSource()
+                : getLastMediaSource();
         mActiveUserMediaController = null;
 
         updateMediaSessionCallbackForCurrentUser();
@@ -264,12 +270,13 @@
     }
 
     private boolean isCurrentUserEphemeral() {
-        return mUserManager.getUserInfo(mCurrentUser).isEphemeral();
+        return mUserManager.getUserInfo(ActivityManager.getCurrentUser()).isEphemeral();
     }
 
     @Override
     public void release() {
         mMediaSessionUpdater.unregisterCallbacks();
+        mUserService.removeUserCallback(mUserCallback);
     }
 
     @Override
@@ -285,9 +292,8 @@
             writer.println(
                     "\tCurrent browse service extra: " + getClassName(mActiveUserMediaController));
         }
-        writer.println("\tNumber of active media sessions: "
-                + mMediaSessionManager.getActiveSessionsForUser(null,
-                ActivityManager.getCurrentUser()).size());
+        writer.println("\tNumber of active media sessions: " + mMediaSessionManager
+                .getActiveSessionsForUser(null, ActivityManager.getCurrentUser()).size());
     }
 
     /**
@@ -348,13 +354,13 @@
                 if (!unlocked) {
                     return;
                 }
-                // No need to handle user0, non current foreground user.
+                // No need to handle system user, non current foreground user.
                 if (userHandle == UserHandle.USER_SYSTEM
                         || userHandle != ActivityManager.getCurrentUser()) {
                     return;
                 }
                 if (mPendingInit) {
-                    initUser();
+                    initUser(userHandle);
                     mPendingInit = false;
                 }
             }
@@ -542,7 +548,7 @@
         notifyListeners();
 
         startMediaConnectorService(shouldStartPlayback(mPlayOnMediaSourceChangedConfig),
-                new UserHandle(mCurrentUser));
+                new UserHandle(ActivityManager.getCurrentUser()));
         // Reset current playback state for the new source, in the case that the app is in an error
         // state (e.g. not signed in). This state will be updated from the app callback registered
         // below, to make sure mCurrentPlaybackState reflects the current source only.
@@ -673,7 +679,7 @@
             return;
         }
         String componentName = component.flattenToString();
-        String key = SOURCE_KEY + mCurrentUser;
+        String key = SOURCE_KEY + ActivityManager.getCurrentUser();
         String serialized = mSharedPrefs.getString(key, null);
         if (serialized == null) {
             mSharedPrefs.edit().putString(key, componentName).apply();
@@ -681,14 +687,13 @@
             Deque<String> componentNames = getComponentNameList(serialized);
             componentNames.remove(componentName);
             componentNames.addFirst(componentName);
-            mSharedPrefs.edit().putString(key, serializeComponentNameList(componentNames))
-                    .apply();
+            mSharedPrefs.edit().putString(key, serializeComponentNameList(componentNames)).apply();
         }
     }
 
     private ComponentName getLastMediaSource() {
         if (sharedPrefsInitialized()) {
-            String key = SOURCE_KEY + mCurrentUser;
+            String key = SOURCE_KEY + ActivityManager.getCurrentUser();
             String serialized = mSharedPrefs.getString(key, null);
             if (!TextUtils.isEmpty(serialized)) {
                 for (String name : getComponentNameList(serialized)) {
@@ -734,7 +739,7 @@
      * Builds a string key for saving the playback state for a specific media source (and user)
      */
     private String getPlaybackStateKey() {
-        return PLAYBACK_STATE_KEY + mCurrentUser
+        return PLAYBACK_STATE_KEY + ActivityManager.getCurrentUser()
                 + (mPrimaryMediaComponent == null ? "" : mPrimaryMediaComponent.flattenToString());
     }
 
diff --git a/service/src/com/android/car/CarNightService.java b/service/src/com/android/car/CarNightService.java
index dcf75b5..754c675 100644
--- a/service/src/com/android/car/CarNightService.java
+++ b/service/src/com/android/car/CarNightService.java
@@ -26,11 +26,16 @@
 import android.os.RemoteException;
 import android.util.Log;
 
+import com.android.internal.annotations.GuardedBy;
+
 import java.io.PrintWriter;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.List;
 
+/**
+ * Class used to handle events used to set vehicle in night mode.
+ */
 public class CarNightService implements CarServiceBase {
 
     public static final boolean DBG = false;
@@ -43,80 +48,109 @@
     public static final int FORCED_DAY_MODE = 1;
     public static final int FORCED_NIGHT_MODE = 2;
 
+    private final Object mLock = new Object();
+    @GuardedBy("mLock")
     private int mNightSetting = UiModeManager.MODE_NIGHT_YES;
+    @GuardedBy("mLock")
     private int mForcedMode = FORCED_SENSOR_MODE;
+    @GuardedBy("mLock")
+    private long mLastSensorEventTime = -1;
     private final Context mContext;
+    @GuardedBy("mLock")
     private final UiModeManager mUiModeManager;
-    private CarPropertyService mCarPropertyService;
+    private final CarPropertyService mCarPropertyService;
 
     private final ICarPropertyEventListener mICarPropertyEventListener =
             new ICarPropertyEventListener.Stub() {
-        @Override
-        public void onEvent(List<CarPropertyEvent> events) throws RemoteException {
-            for (CarPropertyEvent event : events) {
-                handlePropertyEvent(event);
-            }
-        }
-    };
+                @Override
+                public void onEvent(List<CarPropertyEvent> events) throws RemoteException {
+                    synchronized (mLock) {
+                        for (CarPropertyEvent event : events) {
+                            onNightModeCarPropertyEventLocked(event);
+                        }
+                    }
+                }
+            };
 
     /**
-     * Handle CarPropertyEvents
-     * @param event
+     * Acts on {@link CarPropertyEvent} events marked with
+     * {@link CarPropertyEvent.PROPERTY_EVENT_PROPERTY_CHANGE} and marked with {@link
+     * VehicleProperty.NIGHT_MODE} by
+     * setting the vehicle in night mode.
+     * <p>
+     * This method does nothing if the event parameter is {@code null}.
+     *
+     * @param event the car property event to be handled
      */
-    public synchronized void handlePropertyEvent(CarPropertyEvent event) {
+    @GuardedBy("mLock")
+    private void onNightModeCarPropertyEventLocked(CarPropertyEvent event) {
         if (event == null) {
             return;
         }
         if (event.getEventType() == CarPropertyEvent.PROPERTY_EVENT_PROPERTY_CHANGE) {
             // Only handle onChange events
             CarPropertyValue value = event.getCarPropertyValue();
-            if (value.getPropertyId() == VehicleProperty.NIGHT_MODE) {
+            if (value.getPropertyId() == VehicleProperty.NIGHT_MODE
+                    && value.getTimestamp() > mLastSensorEventTime) {
+                mLastSensorEventTime = value.getTimestamp();
                 boolean nightMode = (Boolean) value.getValue();
-                setNightMode(nightMode);
+                Log.i(CarLog.TAG_SENSOR, "Set dayNight Mode as "
+                        + nightMode + " at timestamp: " + mLastSensorEventTime);
+                setNightModeLocked(nightMode);
             }
         }
     }
 
-    private synchronized void setNightMode(boolean nightMode) {
+    @GuardedBy("mLock")
+    private void setNightModeLocked(boolean nightMode) {
         if (nightMode) {
             mNightSetting = UiModeManager.MODE_NIGHT_YES;
-            if (DBG)  Log.d(CarLog.TAG_SENSOR, "CAR dayNight handleSensorEvent NIGHT");
+            if (DBG) Log.d(CarLog.TAG_SENSOR, "CAR dayNight handleSensorEvent NIGHT");
         } else {
             mNightSetting = UiModeManager.MODE_NIGHT_NO;
-            if (DBG)  Log.d(CarLog.TAG_SENSOR, "CAR dayNight handleSensorEvent DAY");
+            if (DBG) Log.d(CarLog.TAG_SENSOR, "CAR dayNight handleSensorEvent DAY");
         }
         if (mUiModeManager != null && (mForcedMode == FORCED_SENSOR_MODE)) {
             mUiModeManager.setNightMode(mNightSetting);
-            if (DBG)  Log.d(CarLog.TAG_SENSOR, "CAR dayNight handleSensorEvent APPLIED");
+            if (DBG) Log.d(CarLog.TAG_SENSOR, "CAR dayNight handleSensorEvent APPLIED");
         } else {
-            if (DBG)  Log.d(CarLog.TAG_SENSOR, "CAR dayNight handleSensorEvent IGNORED");
+            if (DBG) Log.d(CarLog.TAG_SENSOR, "CAR dayNight handleSensorEvent IGNORED");
         }
     }
 
-    public synchronized int forceDayNightMode(@DayNightSensorMode int mode) {
-        if (mUiModeManager == null) {
-            return -1;
-        }
-        int resultMode;
-        switch (mode) {
-            case FORCED_SENSOR_MODE:
-                resultMode = mNightSetting;
-                mForcedMode = FORCED_SENSOR_MODE;
-                break;
-            case FORCED_DAY_MODE:
-                resultMode = UiModeManager.MODE_NIGHT_NO;
-                mForcedMode = FORCED_DAY_MODE;
-                break;
-            case FORCED_NIGHT_MODE:
-                resultMode = UiModeManager.MODE_NIGHT_YES;
-                mForcedMode = FORCED_NIGHT_MODE;
-                break;
-            default:
-                Log.e(CarLog.TAG_SENSOR, "Unknown forced day/night mode " + mode);
+    /**
+     * Sets {@link UiModeManager} to night mode according to the {@link DayNightSensorMode} passed
+     * as parameter.
+     *
+     * @param mode the sensor mode used to set vehicle in night mode
+     * @return the current night mode, or {@code -1} on error
+     */
+    public int forceDayNightMode(@DayNightSensorMode int mode) {
+        synchronized (mLock) {
+            if (mUiModeManager == null) {
                 return -1;
+            }
+            int resultMode;
+            switch (mode) {
+                case FORCED_SENSOR_MODE:
+                    resultMode = mNightSetting;
+                    mForcedMode = FORCED_SENSOR_MODE;
+                    break;
+                case FORCED_DAY_MODE:
+                    resultMode = UiModeManager.MODE_NIGHT_NO;
+                    mForcedMode = FORCED_DAY_MODE;
+                    break;
+                case FORCED_NIGHT_MODE:
+                    resultMode = UiModeManager.MODE_NIGHT_YES;
+                    mForcedMode = FORCED_NIGHT_MODE;
+                    break;
+                default:
+                    Log.e(CarLog.TAG_SENSOR, "Unknown forced day/night mode " + mode);
+                    return -1;
+            }
+            mUiModeManager.setNightMode(resultMode);
+            return mUiModeManager.getNightMode();
         }
-        mUiModeManager.setNightMode(resultMode);
-        return mUiModeManager.getNightMode();
     }
 
     CarNightService(Context context, CarPropertyService propertyService) {
@@ -129,33 +163,39 @@
     }
 
     @Override
-    public synchronized void init() {
+    public void init() {
         if (DBG) {
-            Log.d(CarLog.TAG_SENSOR,"CAR dayNight init.");
+            Log.d(CarLog.TAG_SENSOR, "CAR dayNight init.");
         }
-        mCarPropertyService.registerListener(VehicleProperty.NIGHT_MODE, 0,
-                mICarPropertyEventListener);
-        CarPropertyValue propertyValue = mCarPropertyService.getProperty(
-                VehicleProperty.NIGHT_MODE, 0);
-        if (propertyValue != null && propertyValue.getTimestamp() != 0) {
-            setNightMode((Boolean) propertyValue.getValue());
-        } else {
-            Log.w(CarLog.TAG_SENSOR, "Failed to get value of NIGHT_MODE");
-            // Initial in Night Mode
-            setNightMode(true);
+        synchronized (mLock) {
+            mCarPropertyService.registerListener(VehicleProperty.NIGHT_MODE, 0,
+                    mICarPropertyEventListener);
+            CarPropertyValue propertyValue = mCarPropertyService.getProperty(
+                    VehicleProperty.NIGHT_MODE, 0);
+            if (propertyValue != null && propertyValue.getTimestamp() != 0) {
+                mLastSensorEventTime = propertyValue.getTimestamp();
+                setNightModeLocked((Boolean) propertyValue.getValue());
+            } else {
+                Log.w(CarLog.TAG_SENSOR, "Failed to get value of NIGHT_MODE");
+                setNightModeLocked(true);
+            }
         }
     }
 
     @Override
-    public synchronized void release() {
+    public void release() {
     }
 
     @Override
-    public synchronized void dump(PrintWriter writer) {
-        writer.println("*DAY NIGHT POLICY*");
-        writer.println("Mode:" +
-                ((mNightSetting == UiModeManager.MODE_NIGHT_YES) ? "night" : "day"));
-        writer.println("Forced Mode? " + (mForcedMode == FORCED_SENSOR_MODE ? "false"
-                : (mForcedMode == FORCED_DAY_MODE ? "day" : "night")));
+    public void dump(PrintWriter writer) {
+        synchronized (mLock) {
+            writer.println("*DAY NIGHT POLICY*");
+            writer.println(
+                    "Mode:" + ((mNightSetting == UiModeManager.MODE_NIGHT_YES) ? "night" : "day"));
+            writer.println("Forced Mode? " + (mForcedMode == FORCED_SENSOR_MODE
+                    ? "false, timestamp of dayNight sensor is: " + mLastSensorEventTime
+                    : (mForcedMode == FORCED_DAY_MODE ? "day" : "night")));
+        }
     }
 }
+
diff --git a/service/src/com/android/car/CarOccupantZoneService.java b/service/src/com/android/car/CarOccupantZoneService.java
new file mode 100644
index 0000000..9fd84fb
--- /dev/null
+++ b/service/src/com/android/car/CarOccupantZoneService.java
@@ -0,0 +1,874 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.app.ActivityManager;
+import android.car.Car;
+import android.car.CarInfoManager;
+import android.car.CarOccupantZoneManager;
+import android.car.CarOccupantZoneManager.OccupantTypeEnum;
+import android.car.CarOccupantZoneManager.OccupantZoneInfo;
+import android.car.ICarOccupantZone;
+import android.car.ICarOccupantZoneCallback;
+import android.car.VehicleAreaSeat;
+import android.car.media.CarAudioManager;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.hardware.display.DisplayManager;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.RemoteCallbackList;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.util.Log;
+import android.view.Display;
+import android.view.DisplayAddress;
+
+import com.android.car.user.CarUserService;
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.Preconditions;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * Service to implement CarOccupantZoneManager API.
+ */
+public final class CarOccupantZoneService extends ICarOccupantZone.Stub
+        implements CarServiceBase {
+
+    private final Object mLock = new Object();
+    private final Context mContext;
+    private final DisplayManager mDisplayManager;
+
+    /** key: zone id */
+    @GuardedBy("mLock")
+    private final HashMap<Integer, OccupantZoneInfo> mOccupantsConfig = new HashMap<>();
+
+    @VisibleForTesting
+    static class DisplayConfig {
+        public final int displayType;
+        public final int occupantZoneId;
+
+        DisplayConfig(int displayType, int occupantZoneId) {
+            this.displayType = displayType;
+            this.occupantZoneId = occupantZoneId;
+        }
+
+        @Override
+        public String toString() {
+            // do not include type as this is only used for dump
+            StringBuilder b = new StringBuilder(64);
+            b.append("{displayType=");
+            b.append(Integer.toHexString(displayType));
+            b.append(" occupantZoneId=");
+            b.append(occupantZoneId);
+            b.append("}");
+            return b.toString();
+        }
+    }
+
+    /** key: display port address */
+    @GuardedBy("mLock")
+    private final HashMap<Integer, DisplayConfig> mDisplayConfigs = new HashMap<>();
+
+    /** key: audio zone id */
+    @GuardedBy("mLock")
+    private final HashMap<Integer, Integer> mAudioZoneConfig = new HashMap<>();
+
+    @VisibleForTesting
+    static class DisplayInfo {
+        public final Display display;
+        public final int displayType;
+
+        DisplayInfo(Display display, int displayType) {
+            this.display = display;
+            this.displayType = displayType;
+        }
+
+        @Override
+        public String toString() {
+            // do not include type as this is only used for dump
+            StringBuilder b = new StringBuilder(64);
+            b.append("{displayId=");
+            b.append(display.getDisplayId());
+            b.append(" displayType=");
+            b.append(displayType);
+            b.append("}");
+            return b.toString();
+        }
+    }
+
+    @VisibleForTesting
+    static class OccupantConfig {
+        public int userId = UserHandle.USER_NULL;
+        public final LinkedList<DisplayInfo> displayInfos = new LinkedList<>();
+        public int audioZoneId = CarAudioManager.INVALID_AUDIO_ZONE;
+
+        @Override
+        public String toString() {
+            // do not include type as this is only used for dump
+            StringBuilder b = new StringBuilder(128);
+            b.append("{userId=");
+            b.append(userId);
+            b.append(" displays=");
+            for (DisplayInfo info : displayInfos) {
+                b.append(info.toString());
+            }
+            b.append(" audioZoneId=");
+            if (audioZoneId != CarAudioManager.INVALID_AUDIO_ZONE) {
+                b.append(audioZoneId);
+            } else {
+                b.append("none");
+            }
+            b.append("}");
+            return b.toString();
+        }
+    }
+
+    /** key : zoneId */
+    @GuardedBy("mLock")
+    private final HashMap<Integer, OccupantConfig> mActiveOccupantConfigs = new HashMap<>();
+
+    @VisibleForTesting
+    final CarUserService.UserCallback mUserCallback = new CarUserService.UserCallback() {
+        @Override
+        public void onUserLockChanged(@UserIdInt int userId, boolean unlocked) {
+            // nothing to do
+        }
+
+        @Override
+        public void onSwitchUser(@UserIdInt int userId) {
+            handleUserChange();
+        }
+    };
+
+    final CarUserService.PassengerCallback mPassengerCallback =
+            new CarUserService.PassengerCallback() {
+                @Override
+                public void onPassengerStarted(@UserIdInt int passengerId, int zoneId) {
+                    handlePassengerStarted(passengerId, zoneId);
+                }
+
+                @Override
+                public void onPassengerStopped(@UserIdInt int passengerId) {
+                    handlePassengerStopped(passengerId);
+                }
+            };
+
+    @VisibleForTesting
+    final DisplayManager.DisplayListener mDisplayListener =
+            new DisplayManager.DisplayListener() {
+        @Override
+        public void onDisplayAdded(int displayId) {
+            handleDisplayChange();
+        }
+
+        @Override
+        public void onDisplayRemoved(int displayId) {
+            handleDisplayChange();
+        }
+
+        @Override
+        public void onDisplayChanged(int displayId) {
+            // nothing to do
+        }
+    };
+
+    private final RemoteCallbackList<ICarOccupantZoneCallback> mClientCallbacks =
+            new RemoteCallbackList<>();
+
+    @GuardedBy("mLock")
+    private int mDriverSeat = VehicleAreaSeat.SEAT_UNKNOWN;
+
+    public CarOccupantZoneService(Context context) {
+        mContext = context;
+        mDisplayManager = context.getSystemService(DisplayManager.class);
+    }
+
+    @VisibleForTesting
+    public CarOccupantZoneService(Context context, DisplayManager displayManager) {
+        mContext = context;
+        mDisplayManager = displayManager;
+    }
+
+    @Override
+    public void init() {
+        // This does not require connection as binder will be passed directly.
+        Car car = new Car(mContext, /* service= */null, /* handler= */ null);
+        CarInfoManager infoManager = new CarInfoManager(car, CarLocalServices.getService(
+                CarPropertyService.class));
+        int driverSeat = infoManager.getDriverSeat();
+        synchronized (mLock) {
+            mDriverSeat = driverSeat;
+            parseOccupantZoneConfigsLocked();
+            parseDisplayConfigsLocked();
+            handleActiveDisplaysLocked();
+            handleAudioZoneChangesLocked();
+            handleUserChangesLocked();
+        }
+        CarUserService userService = CarLocalServices.getService(CarUserService.class);
+        userService.addUserCallback(mUserCallback);
+        userService.addPassengerCallback(mPassengerCallback);
+        mDisplayManager.registerDisplayListener(mDisplayListener,
+                new Handler(Looper.getMainLooper()));
+        CarUserService.ZoneUserBindingHelper helper = new CarUserService.ZoneUserBindingHelper() {
+            @Override
+            @NonNull
+            public List<OccupantZoneInfo> getOccupantZones(@OccupantTypeEnum int occupantType) {
+                List<OccupantZoneInfo> zones = new ArrayList<OccupantZoneInfo>();
+                for (OccupantZoneInfo ozi : getAllOccupantZones()) {
+                    if (ozi.occupantType == occupantType) {
+                        zones.add(ozi);
+                    }
+                }
+                return zones;
+            }
+
+            @Override
+            public boolean assignUserToOccupantZone(@UserIdInt int userId, int zoneId) {
+                // Check if the user is already assigned to the other zone.
+                synchronized (mLock) {
+                    for (Map.Entry<Integer, OccupantConfig> entry :
+                            mActiveOccupantConfigs.entrySet()) {
+                        OccupantConfig config = entry.getValue();
+                        if (config.userId == userId && zoneId != entry.getKey()) {
+                            Log.w(CarLog.TAG_OCCUPANT,
+                                    "cannot assign user to two different zone simultaneously");
+                            return false;
+                        }
+                    }
+                    OccupantConfig zoneConfig = mActiveOccupantConfigs.get(zoneId);
+                    if (zoneConfig == null) {
+                        Log.w(CarLog.TAG_OCCUPANT, "cannot find the zone(" + zoneId + ")");
+                        return false;
+                    }
+                    if (zoneConfig.userId != UserHandle.USER_NULL && zoneConfig.userId != userId) {
+                        Log.w(CarLog.TAG_OCCUPANT,
+                                "other user already occupies the zone(" + zoneId + ")");
+                        return false;
+                    }
+                    zoneConfig.userId = userId;
+                    return true;
+                }
+            }
+
+            @Override
+            public boolean unassignUserFromOccupantZone(@UserIdInt int userId) {
+                synchronized (mLock) {
+                    for (OccupantConfig config : mActiveOccupantConfigs.values()) {
+                        if (config.userId == userId) {
+                            config.userId = UserHandle.USER_NULL;
+                            break;
+                        }
+                    }
+                    return true;
+                }
+            }
+
+            @Override
+            public boolean isPassengerDisplayAvailable() {
+                for (OccupantZoneInfo ozi : getAllOccupantZones()) {
+                    if (getDisplayForOccupant(ozi.zoneId,
+                            CarOccupantZoneManager.DISPLAY_TYPE_MAIN) != Display.INVALID_DISPLAY
+                            && ozi.occupantType != CarOccupantZoneManager.OCCUPANT_TYPE_DRIVER) {
+                            return true;
+                    }
+                }
+                return false;
+            }
+        };
+        userService.setZoneUserBindingHelper(helper);
+    }
+
+    @Override
+    public void release() {
+        mDisplayManager.unregisterDisplayListener(mDisplayListener);
+        CarUserService userService = CarLocalServices.getService(CarUserService.class);
+        userService.removeUserCallback(mUserCallback);
+        userService.removePassengerCallback(mPassengerCallback);
+        synchronized (mLock) {
+            mOccupantsConfig.clear();
+            mDisplayConfigs.clear();
+            mAudioZoneConfig.clear();
+            mActiveOccupantConfigs.clear();
+        }
+    }
+
+    /** Return cloned mOccupantsConfig for testing */
+    @VisibleForTesting
+    @NonNull
+    public HashMap<Integer, OccupantZoneInfo> getOccupantsConfig() {
+        synchronized (mLock) {
+            return (HashMap<Integer, OccupantZoneInfo>) mOccupantsConfig.clone();
+        }
+    }
+
+    /** Return cloned mDisplayConfigs for testing */
+    @VisibleForTesting
+    @NonNull
+    public HashMap<Integer, DisplayConfig> getDisplayConfigs() {
+        synchronized (mLock) {
+            return (HashMap<Integer, DisplayConfig>) mDisplayConfigs.clone();
+        }
+    }
+
+    /** Return cloned mAudioConfigs for testing */
+    @VisibleForTesting
+    @NonNull
+    HashMap<Integer, Integer> getAudioConfigs() {
+        synchronized (mLock) {
+            return (HashMap<Integer, Integer>) mAudioZoneConfig.clone();
+        }
+    }
+
+    /** Return cloned mActiveOccupantConfigs for testing */
+    @VisibleForTesting
+    @NonNull
+    public HashMap<Integer, OccupantConfig> getActiveOccupantConfigs() {
+        synchronized (mLock) {
+            return (HashMap<Integer, OccupantConfig>) mActiveOccupantConfigs.clone();
+        }
+    }
+
+    @Override
+    public void dump(PrintWriter writer) {
+        writer.println("*OccupantZoneService*");
+        synchronized (mLock) {
+            writer.println("**mOccupantsConfig**");
+            for (Map.Entry<Integer, OccupantZoneInfo> entry : mOccupantsConfig.entrySet()) {
+                writer.println(" zoneId=" + entry.getKey()
+                        + " info=" + entry.getValue().toString());
+            }
+            writer.println("**mDisplayConfigs**");
+            for (Map.Entry<Integer, DisplayConfig> entry : mDisplayConfigs.entrySet()) {
+                writer.println(" port=" + Integer.toHexString(entry.getKey())
+                        + " config=" + entry.getValue().toString());
+            }
+            writer.println("**mAudioZoneConfigs**");
+            for (Map.Entry<Integer, Integer> entry : mAudioZoneConfig.entrySet()) {
+                writer.println(" audioZoneId=" + Integer.toHexString(entry.getKey())
+                        + " zoneId=" + entry.getValue());
+            }
+            writer.println("**mActiveOccupantConfigs**");
+            for (Map.Entry<Integer, OccupantConfig> entry : mActiveOccupantConfigs.entrySet()) {
+                writer.println(" zoneId=" + entry.getKey()
+                        + " config=" + entry.getValue().toString());
+            }
+        }
+    }
+
+    @Override
+    public List<OccupantZoneInfo> getAllOccupantZones() {
+        synchronized (mLock) {
+            List<OccupantZoneInfo> infos = new ArrayList<>();
+            for (Integer zoneId : mActiveOccupantConfigs.keySet()) {
+                // no need for deep copy as OccupantZoneInfo itself is static.
+                infos.add(mOccupantsConfig.get(zoneId));
+            }
+            return infos;
+        }
+    }
+
+    @Override
+    public int[] getAllDisplaysForOccupantZone(int occupantZoneId) {
+        synchronized (mLock) {
+            OccupantConfig config = mActiveOccupantConfigs.get(occupantZoneId);
+            if (config == null) {
+                return new int[0];
+            }
+            int[] displayIds = new int[config.displayInfos.size()];
+            int i = 0;
+            for (DisplayInfo displayInfo : config.displayInfos) {
+                displayIds[i] = displayInfo.display.getDisplayId();
+                i++;
+            }
+            return displayIds;
+        }
+    }
+
+    @Override
+    public int getDisplayForOccupant(int occupantZoneId, int displayType) {
+        synchronized (mLock) {
+            OccupantConfig config = mActiveOccupantConfigs.get(occupantZoneId);
+            if (config == null) {
+                return Display.INVALID_DISPLAY;
+            }
+            for (DisplayInfo displayInfo : config.displayInfos) {
+                if (displayType == displayInfo.displayType) {
+                    return displayInfo.display.getDisplayId();
+                }
+            }
+        }
+        return Display.INVALID_DISPLAY;
+    }
+
+    @Override
+    public int getAudioZoneIdForOccupant(int occupantZoneId) {
+        enforcePermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS);
+        synchronized (mLock) {
+            OccupantConfig config = mActiveOccupantConfigs.get(occupantZoneId);
+            if (config != null) {
+                return config.audioZoneId;
+            }
+            // check if the occupant id exist at all
+            if (!mOccupantsConfig.containsKey(occupantZoneId)) {
+                return CarAudioManager.INVALID_AUDIO_ZONE;
+            }
+            // Exist but not active
+            return getAudioZoneIdForOccupantLocked(occupantZoneId);
+        }
+    }
+
+    private int getAudioZoneIdForOccupantLocked(int occupantZoneId) {
+        for (Map.Entry<Integer, Integer> entry : mAudioZoneConfig.entrySet()) {
+            if (occupantZoneId == entry.getValue()) {
+                return entry.getKey();
+            }
+        }
+        return CarAudioManager.INVALID_AUDIO_ZONE;
+    }
+
+    @Override
+    public CarOccupantZoneManager.OccupantZoneInfo getOccupantForAudioZoneId(int audioZoneId) {
+        enforcePermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS);
+        synchronized (mLock) {
+            Integer occupantZoneId = mAudioZoneConfig.get(audioZoneId);
+            if (occupantZoneId == null) {
+                return null;
+            }
+            // To support headless zones return the occupant configuration.
+            return mOccupantsConfig.get(occupantZoneId);
+        }
+    }
+
+    @Nullable
+    private DisplayConfig findDisplayConfigForDisplayLocked(int displayId) {
+        for (Map.Entry<Integer, DisplayConfig> entry : mDisplayConfigs.entrySet()) {
+            Display display = mDisplayManager.getDisplay(displayId);
+            if (display == null) {
+                continue;
+            }
+            Byte portAddress = getPortAddress(display);
+            if (portAddress == null) {
+                continue;
+            }
+            DisplayConfig config =
+                mDisplayConfigs.get(Byte.toUnsignedInt(portAddress));
+            return config;
+        }
+        return null;
+    }
+
+    @Override
+    public int getDisplayType(int displayId) {
+        synchronized (mLock) {
+            DisplayConfig config = findDisplayConfigForDisplayLocked(displayId);
+            if (config != null) {
+                return config.displayType;
+            }
+        }
+        return CarOccupantZoneManager.DISPLAY_TYPE_UNKNOWN;
+    }
+
+    @Override
+    public int getUserForOccupant(int occupantZoneId) {
+        synchronized (mLock) {
+            OccupantConfig config = mActiveOccupantConfigs.get(occupantZoneId);
+            if (config == null) {
+                return UserHandle.USER_NULL;
+            }
+            return config.userId;
+        }
+    }
+
+    @Override
+    public int getOccupantZoneIdForUserId(int userId) {
+        synchronized (mLock) {
+            for (int occupantZoneId : mActiveOccupantConfigs.keySet()) {
+                OccupantConfig config = mActiveOccupantConfigs.get(occupantZoneId);
+                if (config.userId == userId) {
+                    return occupantZoneId;
+                }
+            }
+            Log.w(CarLog.TAG_OCCUPANT, "Could not find occupantZoneId for userId" + userId
+                    + " returning invalid occupant zone id " + OccupantZoneInfo.INVALID_ZONE_ID);
+            return OccupantZoneInfo.INVALID_ZONE_ID;
+        }
+    }
+
+    @Override
+    public void setAudioZoneIdsForOccupantZoneIds(@NonNull int[] audioZoneIds,
+            @NonNull int[] occupantZoneIds) {
+        Objects.requireNonNull(audioZoneIds, "audioZoneIds can not be null");
+        Objects.requireNonNull(audioZoneIds, "occupantZoneIds can not be null");
+        Preconditions.checkArgument(audioZoneIds.length == occupantZoneIds.length,
+                "audioZoneIds and occupantZoneIds must have the same size.");
+        boolean activeConfigChange = false;
+        synchronized (mLock) {
+            validateOccupantZoneIdsLocked(occupantZoneIds);
+            mAudioZoneConfig.clear();
+            for (int i = 0; i < audioZoneIds.length; i++) {
+                mAudioZoneConfig.put(audioZoneIds[i], occupantZoneIds[i]);
+            }
+            //If there are any active displays for the zone send change event
+            handleAudioZoneChangesLocked();
+        }
+        sendConfigChangeEvent(CarOccupantZoneManager.ZONE_CONFIG_CHANGE_FLAG_AUDIO);
+    }
+
+    private void validateOccupantZoneIdsLocked(int[] occupantZoneIds) {
+        for (int i = 0; i < occupantZoneIds.length; i++) {
+            if (!mOccupantsConfig.containsKey(occupantZoneIds[i])) {
+                throw new IllegalArgumentException("occupantZoneId " + occupantZoneIds[i]
+                        + " does not exist.");
+            }
+        }
+    }
+
+    @Override
+    public void registerCallback(ICarOccupantZoneCallback callback) {
+        mClientCallbacks.register(callback);
+    }
+
+    @Override
+    public void unregisterCallback(ICarOccupantZoneCallback callback) {
+        mClientCallbacks.unregister(callback);
+    }
+
+    private void throwFormatErrorInOccupantZones(String msg) {
+        throw new RuntimeException("Format error in config_occupant_zones resource:" + msg);
+    }
+
+    // For overriding in test
+    @VisibleForTesting
+    int getDriverSeat() {
+        synchronized (mLock) {
+            return mDriverSeat;
+        }
+    }
+
+    private void parseOccupantZoneConfigsLocked() {
+        final Resources res = mContext.getResources();
+        // examples:
+        // <item>occupantZoneId=0,occupantType=DRIVER,seatRow=1,seatSide=driver</item>
+        // <item>occupantZoneId=1,occupantType=FRONT_PASSENGER,seatRow=1,
+        // searSide=oppositeDriver</item>
+        boolean hasDriver = false;
+        int driverSeat = getDriverSeat();
+        int driverSeatSide = VehicleAreaSeat.SIDE_LEFT; // default LHD : Left Hand Drive
+        if (driverSeat == VehicleAreaSeat.SEAT_ROW_1_RIGHT) {
+            driverSeatSide = VehicleAreaSeat.SIDE_RIGHT;
+        }
+        for (String config : res.getStringArray(R.array.config_occupant_zones)) {
+            int zoneId = OccupantZoneInfo.INVALID_ZONE_ID;
+            int type = CarOccupantZoneManager.OCCUPANT_TYPE_INVALID;
+            int seatRow = 0; // invalid row
+            int seatSide = VehicleAreaSeat.SIDE_LEFT;
+            String[] entries = config.split(",");
+            for (String entry : entries) {
+                String[] keyValuePair = entry.split("=");
+                if (keyValuePair.length != 2) {
+                    throwFormatErrorInOccupantZones("No key/value pair:" + entry);
+                }
+                switch (keyValuePair[0]) {
+                    case "occupantZoneId":
+                        zoneId = Integer.parseInt(keyValuePair[1]);
+                        break;
+                    case "occupantType":
+                        switch (keyValuePair[1]) {
+                            case "DRIVER":
+                                type = CarOccupantZoneManager.OCCUPANT_TYPE_DRIVER;
+                                break;
+                            case "FRONT_PASSENGER":
+                                type = CarOccupantZoneManager.OCCUPANT_TYPE_FRONT_PASSENGER;
+                                break;
+                            case "REAR_PASSENGER":
+                                type = CarOccupantZoneManager.OCCUPANT_TYPE_REAR_PASSENGER;
+                                break;
+                            default:
+                                throwFormatErrorInOccupantZones("Unrecognized type:" + entry);
+                                break;
+                        }
+                        break;
+                    case "seatRow":
+                        seatRow = Integer.parseInt(keyValuePair[1]);
+                        break;
+                    case "seatSide":
+                        switch (keyValuePair[1]) {
+                            case "driver":
+                                seatSide = driverSeatSide;
+                                break;
+                            case "oppositeDriver":
+                                seatSide = -driverSeatSide;
+                                break;
+                            case "left":
+                                seatSide = VehicleAreaSeat.SIDE_LEFT;
+                                break;
+                            case "center":
+                                seatSide = VehicleAreaSeat.SIDE_CENTER;
+                                break;
+                            case "right":
+                                seatSide = VehicleAreaSeat.SIDE_RIGHT;
+                                break;
+                            default:
+                                throwFormatErrorInOccupantZones("Unregognized seatSide:" + entry);
+                                break;
+
+                        }
+                        break;
+                    default:
+                        throwFormatErrorInOccupantZones("Unrecognized key:" + entry);
+                        break;
+                }
+            }
+            if (zoneId == OccupantZoneInfo.INVALID_ZONE_ID) {
+                throwFormatErrorInOccupantZones("Missing zone id:" + config);
+            }
+            if (type == CarOccupantZoneManager.OCCUPANT_TYPE_INVALID) {
+                throwFormatErrorInOccupantZones("Missing type:" + config);
+            }
+            if (type == CarOccupantZoneManager.OCCUPANT_TYPE_DRIVER) {
+                if (hasDriver) {
+                    throwFormatErrorInOccupantZones("Multiple driver:" + config);
+                } else {
+                    hasDriver = true;
+                }
+
+            }
+            int seat = VehicleAreaSeat.fromRowAndSide(seatRow, seatSide);
+            if (seat == VehicleAreaSeat.SEAT_UNKNOWN) {
+                throwFormatErrorInOccupantZones("Invalid seat:" + config);
+            }
+            OccupantZoneInfo info = new OccupantZoneInfo(zoneId, type, seat);
+            if (mOccupantsConfig.containsKey(zoneId)) {
+                throwFormatErrorInOccupantZones("Duplicate zone id:" + config);
+            }
+            mOccupantsConfig.put(zoneId, info);
+        }
+    }
+
+    private void throwFormatErrorInDisplayMapping(String msg) {
+        throw new RuntimeException(
+                "Format error in config_occupant_display_mapping resource:" + msg);
+    }
+
+    private void parseDisplayConfigsLocked() {
+        final Resources res = mContext.getResources();
+        // examples:
+        // <item>displayPort=0,displayType=MAIN,occupantZoneId=0</item>
+        // <item>displayPort=1,displayType=INSTRUMENT_CLUSTER,occupantZoneId=0</item>
+        boolean hasDriver = false;
+        final int invalidPort = -1;
+        for (String config : res.getStringArray(R.array.config_occupant_display_mapping)) {
+            int port = invalidPort;
+            int type = CarOccupantZoneManager.DISPLAY_TYPE_UNKNOWN;
+            int zoneId = OccupantZoneInfo.INVALID_ZONE_ID;
+            String[] entries = config.split(",");
+            for (String entry : entries) {
+                String[] keyValuePair = entry.split("=");
+                if (keyValuePair.length != 2) {
+                    throwFormatErrorInDisplayMapping("No key/value pair:" + entry);
+                }
+                switch (keyValuePair[0]) {
+                    case "displayPort":
+                        port = Integer.parseInt(keyValuePair[1]);
+                        break;
+                    case "displayType":
+                        switch (keyValuePair[1]) {
+                            case "MAIN":
+                                type = CarOccupantZoneManager.DISPLAY_TYPE_MAIN;
+                                break;
+                            case "INSTRUMENT_CLUSTER":
+                                type = CarOccupantZoneManager.DISPLAY_TYPE_INSTRUMENT_CLUSTER;
+                                break;
+                            case "HUD":
+                                type = CarOccupantZoneManager.DISPLAY_TYPE_HUD;
+                                break;
+                            case "INPUT":
+                                type = CarOccupantZoneManager.DISPLAY_TYPE_INPUT;
+                                break;
+                            case "AUXILIARY":
+                                type = CarOccupantZoneManager.DISPLAY_TYPE_AUXILIARY;
+                                break;
+                            default:
+                                throwFormatErrorInDisplayMapping(
+                                        "Unrecognized display type:" + entry);
+                                break;
+                        }
+                        break;
+                    case "occupantZoneId":
+                        zoneId = Integer.parseInt(keyValuePair[1]);
+                        break;
+                    default:
+                        throwFormatErrorInDisplayMapping("Unrecognized key:" + entry);
+                        break;
+
+                }
+            }
+            // Now check validity
+            if (port == invalidPort) {
+                throwFormatErrorInDisplayMapping("Missing or invalid displayPort:" + config);
+            }
+
+            if (type == CarOccupantZoneManager.DISPLAY_TYPE_UNKNOWN) {
+                throwFormatErrorInDisplayMapping("Missing or invalid displayType:" + config);
+            }
+            if (zoneId == OccupantZoneInfo.INVALID_ZONE_ID) {
+                throwFormatErrorInDisplayMapping("Missing or invalid occupantZoneId:" + config);
+            }
+            if (!mOccupantsConfig.containsKey(zoneId)) {
+                throwFormatErrorInDisplayMapping(
+                        "Missing or invalid occupantZoneId:" + config);
+            }
+            if (mDisplayConfigs.containsKey(port)) {
+                throwFormatErrorInDisplayMapping("Duplicate displayPort:" + config);
+            }
+            mDisplayConfigs.put(port, new DisplayConfig(type, zoneId));
+        }
+    }
+
+    private Byte getPortAddress(Display display) {
+        DisplayAddress address = display.getAddress();
+        if (address instanceof DisplayAddress.Physical) {
+            DisplayAddress.Physical physicalAddress = (DisplayAddress.Physical) address;
+            if (physicalAddress != null) {
+                return physicalAddress.getPort();
+            }
+        }
+        return null;
+    }
+
+    private void handleActiveDisplaysLocked() {
+        mActiveOccupantConfigs.clear();
+        for (Display display : mDisplayManager.getDisplays()) {
+            Byte rawPortAddress = getPortAddress(display);
+            if (rawPortAddress == null) {
+                continue;
+            }
+
+            int portAddress = Byte.toUnsignedInt(rawPortAddress);
+            DisplayConfig displayConfig = mDisplayConfigs.get(portAddress);
+            if (displayConfig == null) {
+                Log.w(CarLog.TAG_OCCUPANT,
+                        "Display id:" + display.getDisplayId() + " port:" + portAddress
+                                + " does not have configurations");
+                continue;
+            }
+            OccupantConfig occupantConfig = mActiveOccupantConfigs.get(
+                    displayConfig.occupantZoneId);
+            if (occupantConfig == null) {
+                occupantConfig = new OccupantConfig();
+                mActiveOccupantConfigs.put(displayConfig.occupantZoneId, occupantConfig);
+            }
+            occupantConfig.displayInfos.add(new DisplayInfo(display, displayConfig.displayType));
+        }
+    }
+
+    @VisibleForTesting
+    int getCurrentUser() {
+        return ActivityManager.getCurrentUser();
+    }
+
+    private void handleUserChangesLocked() {
+        int driverUserId = getCurrentUser();
+        OccupantConfig driverConfig = getDriverOccupantConfigLocked();
+        if (driverConfig != null) {
+            driverConfig.userId = driverUserId;
+        }
+    }
+
+    @Nullable
+    private OccupantConfig getDriverOccupantConfigLocked() {
+        for (Map.Entry<Integer, OccupantZoneInfo> entry: mOccupantsConfig.entrySet()) {
+            if (entry.getValue().occupantType == CarOccupantZoneManager.OCCUPANT_TYPE_DRIVER) {
+                return mActiveOccupantConfigs.get(entry.getKey());
+            }
+        }
+        return null;
+    }
+
+    private void handleAudioZoneChangesLocked() {
+        for (Map.Entry<Integer, Integer> entry: mAudioZoneConfig.entrySet()) {
+            int occupantZoneId = entry.getValue();
+            OccupantConfig occupantConfig =
+                    mActiveOccupantConfigs.get(occupantZoneId);
+            if (occupantConfig == null) {
+                //no active display for zone just continue
+                continue;
+            }
+            // Found an active configuration, add audio to it.
+            occupantConfig.audioZoneId = entry.getKey();
+        }
+    }
+
+    private void sendConfigChangeEvent(int changeFlags) {
+        final int n = mClientCallbacks.beginBroadcast();
+        for (int i = 0; i < n; i++) {
+            ICarOccupantZoneCallback callback = mClientCallbacks.getBroadcastItem(i);
+            try {
+                callback.onOccupantZoneConfigChanged(changeFlags);
+            } catch (RemoteException ignores) {
+                // ignore
+            }
+        }
+        mClientCallbacks.finishBroadcast();
+    }
+
+    private void handleUserChange() {
+        synchronized (mLock) {
+            handleUserChangesLocked();
+        }
+        sendConfigChangeEvent(CarOccupantZoneManager.ZONE_CONFIG_CHANGE_FLAG_USER);
+    }
+
+    private void handlePassengerStarted(@UserIdInt int passengerId, int zoneId) {
+        sendConfigChangeEvent(CarOccupantZoneManager.ZONE_CONFIG_CHANGE_FLAG_USER);
+    }
+
+    private void handlePassengerStopped(@UserIdInt int passengerId) {
+        sendConfigChangeEvent(CarOccupantZoneManager.ZONE_CONFIG_CHANGE_FLAG_USER);
+    }
+
+    private void handleDisplayChange() {
+        synchronized (mLock) {
+            handleActiveDisplaysLocked();
+            //audio zones should be re-checked for changed display
+            handleAudioZoneChangesLocked();
+            // user should be re-checked for changed displays
+            handleUserChangesLocked();
+        }
+        sendConfigChangeEvent(CarOccupantZoneManager.ZONE_CONFIG_CHANGE_FLAG_DISPLAY);
+    }
+
+    private void enforcePermission(String permissionName) {
+        if (mContext.checkCallingOrSelfPermission(permissionName)
+                != PackageManager.PERMISSION_GRANTED) {
+            throw new SecurityException("requires permission " + permissionName);
+        }
+    }
+}
diff --git a/service/src/com/android/car/CarPowerManagementService.java b/service/src/com/android/car/CarPowerManagementService.java
index 2cb679b..eb5dcd8 100644
--- a/service/src/com/android/car/CarPowerManagementService.java
+++ b/service/src/com/android/car/CarPowerManagementService.java
@@ -113,6 +113,10 @@
     @GuardedBy("mLock")
     private boolean mIsResuming;
     @GuardedBy("mLock")
+    private int mShutdownPrepareTimeMs = MIN_MAX_GARAGE_MODE_DURATION_MS;
+    @GuardedBy("mLock")
+    private int mShutdownPollingIntervalMs = SHUTDOWN_POLLING_INTERVAL_MS;
+    @GuardedBy("mLock")
     private boolean mRebootAfterGarageMode;
     private final boolean mDisableUserSwitchDuringResume;
     private final CarUserManagerHelper mCarUserManagerHelper;
@@ -125,8 +129,6 @@
     // maxGarageModeRunningDurationInSecs should be equal or greater than this. 15 min for now.
     private static final int MIN_MAX_GARAGE_MODE_DURATION_MS = 15 * 60 * 1000;
 
-    private static int sShutdownPrepareTimeMs = MIN_MAX_GARAGE_MODE_DURATION_MS;
-
     // in secs
     private static final String PROP_MAX_GARAGE_MODE_DURATION_OVERRIDE =
             "android.car.garagemodeduration";
@@ -160,14 +162,14 @@
         mUserManager = userManager;
         mDisableUserSwitchDuringResume = resources
                 .getBoolean(R.bool.config_disableUserSwitchDuringResume);
-        sShutdownPrepareTimeMs = resources.getInteger(
+        mShutdownPrepareTimeMs = resources.getInteger(
                 R.integer.maxGarageModeRunningDurationInSecs) * 1000;
-        if (sShutdownPrepareTimeMs < MIN_MAX_GARAGE_MODE_DURATION_MS) {
+        if (mShutdownPrepareTimeMs < MIN_MAX_GARAGE_MODE_DURATION_MS) {
             Log.w(CarLog.TAG_POWER,
                     "maxGarageModeRunningDurationInSecs smaller than minimum required, resource:"
-                    + sShutdownPrepareTimeMs + "(ms) while should exceed:"
+                    + mShutdownPrepareTimeMs + "(ms) while should exceed:"
                     +  MIN_MAX_GARAGE_MODE_DURATION_MS + "(ms), Ignore resource.");
-            sShutdownPrepareTimeMs = MIN_MAX_GARAGE_MODE_DURATION_MS;
+            mShutdownPrepareTimeMs = MIN_MAX_GARAGE_MODE_DURATION_MS;
         }
     }
 
@@ -189,12 +191,14 @@
     }
 
     @VisibleForTesting
-    protected static void setShutdownPrepareTimeout(int timeoutMs) {
-        // Override the timeout to keep testing time short
-        if (timeoutMs < SHUTDOWN_EXTEND_MAX_MS) {
-            sShutdownPrepareTimeMs = SHUTDOWN_EXTEND_MAX_MS;
-        } else {
-            sShutdownPrepareTimeMs = timeoutMs;
+    protected void setShutdownTimersForTest(int pollingIntervalMs, int shutdownTimeoutMs) {
+        // Override timers to keep testing time short
+        // Passing in '0' resets the value to the default
+        synchronized (mLock) {
+            mShutdownPollingIntervalMs =
+                    (pollingIntervalMs == 0) ? SHUTDOWN_POLLING_INTERVAL_MS : pollingIntervalMs;
+            mShutdownPrepareTimeMs =
+                    (shutdownTimeoutMs == 0) ? SHUTDOWN_EXTEND_MAX_MS : shutdownTimeoutMs;
         }
     }
 
@@ -247,16 +251,19 @@
 
     @Override
     public void dump(PrintWriter writer) {
-        writer.println("*PowerManagementService*");
-        writer.print("mCurrentState:" + mCurrentState);
-        writer.print(",mProcessingStartTime:" + mProcessingStartTime);
-        writer.print(",mLastSleepEntryTime:" + mLastSleepEntryTime);
-        writer.print(",mNextWakeupSec:" + mNextWakeupSec);
-        writer.print(",mShutdownOnNextSuspend:" + mShutdownOnNextSuspend);
-        writer.print(",mShutdownOnFinish:" + mShutdownOnFinish);
-        writer.print(",sShutdownPrepareTimeMs:" + sShutdownPrepareTimeMs);
-        writer.print(",mDisableUserSwitchDuringResume:" + mDisableUserSwitchDuringResume);
-        writer.println(",mRebootAfterGarageMode:" + mRebootAfterGarageMode);
+        synchronized (mLock) {
+            writer.println("*PowerManagementService*");
+            writer.print("mCurrentState:" + mCurrentState);
+            writer.print(",mProcessingStartTime:" + mProcessingStartTime);
+            writer.print(",mLastSleepEntryTime:" + mLastSleepEntryTime);
+            writer.print(",mNextWakeupSec:" + mNextWakeupSec);
+            writer.print(",mShutdownOnNextSuspend:" + mShutdownOnNextSuspend);
+            writer.print(",mShutdownOnFinish:" + mShutdownOnFinish);
+            writer.print(",mShutdownPollingIntervalMs:" + mShutdownPollingIntervalMs);
+            writer.print(",mShutdownPrepareTimeMs:" + mShutdownPrepareTimeMs);
+            writer.print(",mDisableUserSwitchDuringResume:" + mDisableUserSwitchDuringResume);
+            writer.println(",mRebootAfterGarageMode:" + mRebootAfterGarageMode);
+        }
     }
 
     @Override
@@ -313,7 +320,7 @@
         }
         handler.cancelProcessingComplete();
         Log.i(CarLog.TAG_POWER, "setCurrentState " + state.toString());
-        CarStatsLog.logPowerState(state.mState);
+        CarStatsLogHelper.logPowerState(state.mState);
         mCurrentState = state;
         switch (state.mState) {
             case CpmsState.WAIT_FOR_VHAL:
@@ -608,13 +615,18 @@
     }
 
     private void doHandlePreprocessing() {
-        int pollingCount = (sShutdownPrepareTimeMs / SHUTDOWN_POLLING_INTERVAL_MS) + 1;
+        int intervalMs;
+        int pollingCount;
+        synchronized (mLock) {
+            intervalMs = mShutdownPollingIntervalMs;
+            pollingCount = (mShutdownPrepareTimeMs / mShutdownPollingIntervalMs) + 1;
+        }
         if (Build.IS_USERDEBUG || Build.IS_ENG) {
             int shutdownPrepareTimeOverrideInSecs =
                     SystemProperties.getInt(PROP_MAX_GARAGE_MODE_DURATION_OVERRIDE, -1);
             if (shutdownPrepareTimeOverrideInSecs >= 0) {
                 pollingCount =
-                        (shutdownPrepareTimeOverrideInSecs * 1000 / SHUTDOWN_POLLING_INTERVAL_MS)
+                        (shutdownPrepareTimeOverrideInSecs * 1000 / intervalMs)
                                 + 1;
                 Log.i(CarLog.TAG_POWER,
                         "Garage mode duration overridden secs:"
@@ -622,7 +634,7 @@
             }
         }
         Log.i(CarLog.TAG_POWER, "processing before shutdown expected for: "
-                + sShutdownPrepareTimeMs + " ms, adding polling:" + pollingCount);
+                + mShutdownPrepareTimeMs + " ms, adding polling:" + pollingCount);
         synchronized (mLock) {
             mProcessingStartTime = SystemClock.elapsedRealtime();
             releaseTimerLocked();
@@ -631,7 +643,7 @@
             mTimer.scheduleAtFixedRate(
                     new ShutdownProcessingTimerTask(pollingCount),
                     0 /*delay*/,
-                    SHUTDOWN_POLLING_INTERVAL_MS);
+                    intervalMs);
         }
     }
 
@@ -742,7 +754,8 @@
                 return (newState.mState == CpmsState.SHUTDOWN_PREPARE)
                     || (newState.mState == CpmsState.SIMULATE_SLEEP);
             case CpmsState.SHUTDOWN_PREPARE:
-                // If VHAL sends SHUTDOWN_IMMEDIATELY while in SHUTDOWN_PREPARE state, do it.
+                // If VHAL sends SHUTDOWN_IMMEDIATELY or SLEEP_IMMEDIATELY while in
+                // SHUTDOWN_PREPARE state, do it.
                 return ((newState.mState == CpmsState.SHUTDOWN_PREPARE) && !newState.mCanPostpone)
                     || (newState.mState == CpmsState.WAIT_FOR_FINISH)
                     || (newState.mState == CpmsState.WAIT_FOR_VHAL);
@@ -769,6 +782,7 @@
             listenerState = mShutdownOnFinish
                     ? CarPowerStateListener.SHUTDOWN_ENTER : CarPowerStateListener.SUSPEND_ENTER;
         }
+
         onApPowerStateChange(CpmsState.WAIT_FOR_FINISH, listenerState);
     }
 
@@ -1149,7 +1163,7 @@
      */
     public void forceSimulatedResume() {
         PowerHandler handler;
-        synchronized (this) {
+        synchronized (mLock) {
             // Cancel Garage Mode in case it's running
             mPendingPowerStates.addFirst(new CpmsState(CpmsState.WAIT_FOR_VHAL,
                                                        CarPowerStateListener.SHUTDOWN_CANCELLED));
diff --git a/service/src/com/android/car/CarProjectionService.java b/service/src/com/android/car/CarProjectionService.java
index 5d15d8f..7cac89a 100644
--- a/service/src/com/android/car/CarProjectionService.java
+++ b/service/src/com/android/car/CarProjectionService.java
@@ -26,14 +26,12 @@
 import static android.net.wifi.WifiManager.WIFI_AP_STATE_DISABLED;
 import static android.net.wifi.WifiManager.WIFI_AP_STATE_ENABLED;
 import static android.net.wifi.WifiManager.WIFI_AP_STATE_ENABLING;
-import static android.net.wifi.WifiManager.WIFI_FREQUENCY_BAND_5GHZ;
 
 import android.annotation.Nullable;
 import android.app.ActivityOptions;
 import android.bluetooth.BluetoothDevice;
 import android.car.CarProjectionManager;
 import android.car.CarProjectionManager.ProjectionAccessPointCallback;
-import android.car.CarProjectionManager.ProjectionKeyEventHandler;
 import android.car.ICarProjection;
 import android.car.ICarProjectionKeyEventHandler;
 import android.car.ICarProjectionStatusListener;
@@ -49,15 +47,17 @@
 import android.content.pm.PackageManager;
 import android.content.res.Resources;
 import android.graphics.Rect;
-import android.net.wifi.WifiConfiguration;
+import android.net.MacAddress;
+import android.net.wifi.SoftApConfiguration;
+import android.net.wifi.WifiClient;
 import android.net.wifi.WifiManager;
 import android.net.wifi.WifiManager.LocalOnlyHotspotCallback;
 import android.net.wifi.WifiManager.LocalOnlyHotspotReservation;
-import android.net.wifi.WifiManager.SoftApCallback;
 import android.net.wifi.WifiScanner;
 import android.os.Binder;
 import android.os.Bundle;
 import android.os.Handler;
+import android.os.HandlerExecutor;
 import android.os.IBinder;
 import android.os.Message;
 import android.os.Messenger;
@@ -609,14 +609,14 @@
 
         if (mSoftApCallback == null) {
             mSoftApCallback = new ProjectionSoftApCallback();
-            mWifiManager.registerSoftApCallback(mSoftApCallback, mHandler);
+            mWifiManager.registerSoftApCallback(new HandlerExecutor(mHandler), mSoftApCallback);
             ensureApConfiguration();
         }
 
-        if (!mWifiManager.startSoftAp(null /* use existing config*/)) {
+        if (!mWifiManager.startTetheredHotspot(null /* use existing config*/)) {
             // The indicates that AP might be already started.
             if (mWifiManager.getWifiApState() == WIFI_AP_STATE_ENABLED) {
-                sendApStarted(mWifiManager.getWifiApConfiguration());
+                sendApStarted(mWifiManager.getSoftApConfiguration());
             } else {
                 Log.e(TAG, "Failed to start soft AP");
                 sendApFailed(ERROR_GENERIC);
@@ -639,7 +639,7 @@
     private void startLocalOnlyApLocked() {
         if (mLocalOnlyHotspotReservation != null) {
             Log.i(TAG, "Local-only hotspot is already registered.");
-            sendApStarted(mLocalOnlyHotspotReservation.getWifiConfiguration());
+            sendApStarted(mLocalOnlyHotspotReservation.getSoftApConfiguration());
             return;
         }
 
@@ -651,7 +651,7 @@
                 synchronized (mLock) {
                     mLocalOnlyHotspotReservation = reservation;
                 }
-                sendApStarted(reservation.getWifiConfiguration());
+                sendApStarted(reservation.getSoftApConfiguration());
             }
 
             @Override
@@ -707,18 +707,19 @@
         mLocalOnlyHotspotReservation = null;
     }
 
-    private void sendApStarted(WifiConfiguration wifiConfiguration) {
-        WifiConfiguration localWifiConfig = new WifiConfiguration(wifiConfiguration);
-        localWifiConfig.BSSID = mApBssid;
-
+    private void sendApStarted(SoftApConfiguration softApConfiguration) {
+        SoftApConfiguration localSoftApConfig =
+                new SoftApConfiguration.Builder(softApConfiguration)
+                .setBssid(MacAddress.fromString(mApBssid))
+                .build();
         Message message = Message.obtain();
         message.what = CarProjectionManager.PROJECTION_AP_STARTED;
-        message.obj = localWifiConfig;
+        message.obj = localSoftApConfig;
         Log.i(TAG, "Sending PROJECTION_AP_STARTED, ssid: "
-                + localWifiConfig.getPrintableSsid()
-                + ", apBand: " + localWifiConfig.apBand
-                + ", apChannel: " + localWifiConfig.apChannel
-                + ", bssid: " + localWifiConfig.BSSID);
+                + localSoftApConfig.getSsid()
+                + ", apBand: " + localSoftApConfig.getBand()
+                + ", apChannel: " + localSoftApConfig.getChannel()
+                + ", bssid: " + localSoftApConfig.getBssid());
         sendApStatusMessage(message);
     }
 
@@ -937,15 +938,15 @@
 
     private void ensureApConfiguration() {
         // Always prefer 5GHz configuration whenever it is available.
-        WifiConfiguration apConfig = mWifiManager.getWifiApConfiguration();
-        if (apConfig != null && apConfig.apBand != WIFI_FREQUENCY_BAND_5GHZ
+        SoftApConfiguration apConfig = mWifiManager.getSoftApConfiguration();
+        if (apConfig != null && apConfig.getBand() != SoftApConfiguration.BAND_5GHZ
                 && mWifiManager.is5GHzBandSupported()) {
-            apConfig.apBand = WIFI_FREQUENCY_BAND_5GHZ;
-            mWifiManager.setWifiApConfiguration(apConfig);
+            mWifiManager.setSoftApConfiguration(new SoftApConfiguration.Builder(apConfig)
+                    .setBand(SoftApConfiguration.BAND_5GHZ).build());
         }
     }
 
-    private class ProjectionSoftApCallback implements SoftApCallback {
+    private class ProjectionSoftApCallback implements WifiManager.SoftApCallback {
         private boolean mCurrentStateCall = true;
 
         @Override
@@ -963,7 +964,7 @@
 
             switch (state) {
                 case WifiManager.WIFI_AP_STATE_ENABLED: {
-                    sendApStarted(mWifiManager.getWifiApConfiguration());
+                    sendApStarted(mWifiManager.getSoftApConfiguration());
                     break;
                 }
                 case WifiManager.WIFI_AP_STATE_DISABLED: {
@@ -987,8 +988,11 @@
         }
 
         @Override
-        public void onNumClientsChanged(int numClients) {
-            Log.i(TAG, "ProjectionSoftApCallback, onNumClientsChanged: " + numClients);
+        public void onConnectedClientsChanged(List<WifiClient> clients) {
+            if (DBG) {
+                Log.d(TAG, "ProjectionSoftApCallback, onConnectedClientsChanged with "
+                        + clients.size() + " clients");
+            }
         }
     }
 
diff --git a/service/src/com/android/car/CarPropertyService.java b/service/src/com/android/car/CarPropertyService.java
index c9a14c9..c25b3b9 100644
--- a/service/src/com/android/car/CarPropertyService.java
+++ b/service/src/com/android/car/CarPropertyService.java
@@ -32,6 +32,7 @@
 import android.util.SparseArray;
 
 import com.android.car.hal.PropertyHalService;
+import com.android.internal.annotations.GuardedBy;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -44,9 +45,7 @@
 
 /**
  * This class implements the binder interface for ICarProperty.aidl to make it easier to create
- * multiple managers that deal with Vehicle Properties. To create a new service, simply extend
- * this class and call the super() constructor with the appropriate arguments for the new service.
- * {@link CarHvacService} shows the basic usage.
+ * multiple managers that deal with Vehicle Properties.
  */
 public class CarPropertyService extends ICarProperty.Stub
         implements CarServiceBase, PropertyHalService.PropertyHalListener {
@@ -59,7 +58,8 @@
     private boolean mListenerIsSet = false;
     private final Map<Integer, List<Client>> mPropIdClientMap = new ConcurrentHashMap<>();
     private final Object mLock = new Object();
-
+    @GuardedBy("mLock")
+    private final SparseArray<SparseArray<Client>> mSetOperationClientMap = new SparseArray<>();
     public CarPropertyService(Context context, PropertyHalService hal) {
         if (DBG) {
             Log.d(TAG, "CarPropertyService started!");
@@ -154,6 +154,9 @@
         mPropIdClientMap.clear();
         mHal.setListener(null);
         mListenerIsSet = false;
+        synchronized (mLock) {
+            mSetOperationClientMap.clear();
+        }
     }
 
     @Override
@@ -167,7 +170,7 @@
         }
         if (mConfigs.get(propId) == null) {
             // Do not attempt to register an invalid propId
-            Log.e(TAG, "registerListener:  propId is not in config list:  " + propId);
+            Log.e(TAG, "registerListener:  propId is not in config list: 0x" + toHexString(propId));
             return;
         }
         ICarImpl.assertPermission(mContext, mHal.getReadPermission(propId));
@@ -262,6 +265,7 @@
         } else {
             if (propertyClients.remove(client)) {
                 client.removeProperty(propId);
+                clearSetOperationRecorderLocked(propId, client);
             } else {
                 Log.e(TAG, "unregisterListenerBinderLocked: Listener was not registered for "
                            + "propId=0x" + toHexString(propId));
@@ -271,6 +275,7 @@
                 // Last listener for this property unsubscribed.  Clean up
                 mHal.unsubscribeProperty(propId);
                 mPropIdClientMap.remove(propId);
+                mSetOperationClientMap.remove(propId);
                 if (mPropIdClientMap.isEmpty()) {
                     // No more properties are subscribed.  Turn off the listener.
                     mHal.setListener(null);
@@ -341,7 +346,7 @@
     }
 
     @Override
-    public void setProperty(CarPropertyValue prop) {
+    public void setProperty(CarPropertyValue prop, ICarPropertyEventListener listener) {
         int propId = prop.getPropertyId();
         if (mConfigs.get(propId) == null) {
             // Do not attempt to register an invalid propId
@@ -354,6 +359,46 @@
             ICarImpl.assertPermission(mContext, Car.PERMISSION_VENDOR_EXTENSION);
         }
         mHal.setProperty(prop);
+        IBinder listenerBinder = listener.asBinder();
+        synchronized (mLock) {
+            Client client = mClientMap.get(listenerBinder);
+            if (client == null) {
+                client = new Client(listener);
+            }
+            updateSetOperationRecorder(propId, prop.getAreaId(), client);
+        }
+    }
+
+    // Updates recorder for set operation.
+    private void updateSetOperationRecorder(int propId, int areaId, Client client) {
+        if (mSetOperationClientMap.get(propId) != null) {
+            mSetOperationClientMap.get(propId).put(areaId, client);
+        } else {
+            SparseArray<Client> areaIdToClient = new SparseArray<>();
+            areaIdToClient.put(areaId, client);
+            mSetOperationClientMap.put(propId, areaIdToClient);
+        }
+    }
+
+    // Clears map when client unregister for property.
+    private void clearSetOperationRecorderLocked(int propId, Client client) {
+        SparseArray<Client> areaIdToClient = mSetOperationClientMap.get(propId);
+        if (areaIdToClient != null) {
+            List<Integer> indexNeedToRemove = new ArrayList<>();
+            for (int index = 0; index < areaIdToClient.size(); index++) {
+                if (client.equals(areaIdToClient.valueAt(index))) {
+                    indexNeedToRemove.add(index);
+                }
+            }
+
+            for (int index : indexNeedToRemove) {
+                if (DBG) {
+                    Log.d("ErrorEvent", " Clear propId:0x" + toHexString(propId)
+                            + " areaId: 0x" + toHexString(areaIdToClient.keyAt(index)));
+                }
+                areaIdToClient.removeAt(index);
+            }
+        }
     }
 
     // Implement PropertyHalListener interface
@@ -396,23 +441,33 @@
     }
 
     @Override
-    public void onPropertySetError(int property, int area) {
-        List<Client> clients = mPropIdClientMap.get(property);
-        if (clients != null) {
-            List<CarPropertyEvent> eventList = new LinkedList<>();
-            eventList.add(CarPropertyEvent.createErrorEvent(property, area));
-            for (Client c : clients) {
-                try {
-                    c.getListener().onEvent(eventList);
-                } catch (RemoteException ex) {
-                    // If we cannot send a record, its likely the connection snapped. Let the binder
-                    // death handle the situation.
-                    Log.e(TAG, "onEvent calling failed: " + ex);
-                }
+    public void onPropertySetError(int property, int areaId, int errorCode) {
+        Client lastOperatedClient = null;
+        synchronized (mLock) {
+            if (mSetOperationClientMap.get(property) != null
+                    && mSetOperationClientMap.get(property).get(areaId) != null) {
+                lastOperatedClient = mSetOperationClientMap.get(property).get(areaId);
+            } else {
+                Log.e(TAG, "Can not find the client changed propertyId: 0x"
+                        + toHexString(property) + " in areaId: 0x" + toHexString(areaId));
             }
-        } else {
-            Log.e(TAG, "onPropertySetError called with no listener registered for propId=0x"
-                    + toHexString(property));
+
+        }
+        if (lastOperatedClient != null) {
+            dispatchToLastClient(property, areaId, errorCode, lastOperatedClient);
+        }
+    }
+
+    private void dispatchToLastClient(int property, int areaId, int errorCode,
+            Client lastOperatedClient) {
+        try {
+            List<CarPropertyEvent> eventList = new LinkedList<>();
+            eventList.add(
+                    CarPropertyEvent.createErrorEventWithErrorCode(property, areaId,
+                            errorCode));
+            lastOperatedClient.getListener().onEvent(eventList);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "onEvent calling failed: " + ex);
         }
     }
 }
diff --git a/service/src/com/android/car/CarService.java b/service/src/com/android/car/CarService.java
index 044c791..3d3dfa2 100644
--- a/service/src/com/android/car/CarService.java
+++ b/service/src/com/android/car/CarService.java
@@ -99,7 +99,6 @@
         linkToDeath(mVehicle, mVehicleDeathRecipient);
 
         ServiceManager.addService("car_service", mICarImpl);
-        ServiceManager.addService("car_stats", mICarImpl.getStatsService());
         SystemProperties.set("boot.car_service_created", "1");
         super.onCreate();
     }
@@ -167,12 +166,14 @@
 
     @Nullable
     private static IVehicle getVehicle() {
+        final String instanceName = SystemProperties.get("ro.vehicle.hal", "default");
+
         try {
-            return android.hardware.automotive.vehicle.V2_0.IVehicle.getService();
+            return android.hardware.automotive.vehicle.V2_0.IVehicle.getService(instanceName);
         } catch (RemoteException e) {
-            Log.e(CarLog.TAG_SERVICE, "Failed to get IVehicle service", e);
+            Log.e(CarLog.TAG_SERVICE, "Failed to get IVehicle/" + instanceName + " service", e);
         } catch (NoSuchElementException e) {
-            Log.e(CarLog.TAG_SERVICE, "IVehicle service not registered yet");
+            Log.e(CarLog.TAG_SERVICE, "IVehicle/" + instanceName + " service not registered yet");
         }
         return null;
     }
diff --git a/service/src/com/android/car/CarStatsLog.java b/service/src/com/android/car/CarStatsLog.java
deleted file mode 100644
index a0c9f2b..0000000
--- a/service/src/com/android/car/CarStatsLog.java
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.car;
-
-import android.util.StatsLog;
-
-/**
- * CarStatsLog provides API to send Car events to statd.
- * @hide
- */
-public class CarStatsLog {
-    /**
-     * Logs a power state change event.
-     * @param state an integer defined in CarPowerManagementService.CpmsState
-     */
-    public static void logPowerState(int state) {
-        StatsLog.write(StatsLog.CAR_POWER_STATE_CHANGED, state);
-    }
-
-    /** Logs a GarageMode start event. */
-    public static void logGarageModeStart() {
-        StatsLog.write(StatsLog.GARAGE_MODE_INFO, true);
-    }
-
-    /** Logs a GarageMode stop event. */
-    public static void logGarageModeStop() {
-        StatsLog.write(StatsLog.GARAGE_MODE_INFO, false);
-    }
-}
diff --git a/service/src/com/android/car/CarStatsLogHelper.java b/service/src/com/android/car/CarStatsLogHelper.java
new file mode 100644
index 0000000..d69c8c3
--- /dev/null
+++ b/service/src/com/android/car/CarStatsLogHelper.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car;
+
+/**
+ * CarStatsLogHelper provides API to send Car events to statd.
+ * @hide
+ */
+public class CarStatsLogHelper {
+    /**
+     * Logs a power state change event.
+     * @param state an integer defined in CarPowerManagementService.CpmsState
+     */
+    public static void logPowerState(int state) {
+        CarStatsLog.write(CarStatsLog.CAR_POWER_STATE_CHANGED, state);
+    }
+
+    /** Logs a GarageMode start event. */
+    public static void logGarageModeStart() {
+        CarStatsLog.write(CarStatsLog.GARAGE_MODE_INFO, true);
+    }
+
+    /** Logs a GarageMode stop event. */
+    public static void logGarageModeStop() {
+        CarStatsLog.write(CarStatsLog.GARAGE_MODE_INFO, false);
+    }
+}
diff --git a/service/src/com/android/car/CarStorageMonitoringService.java b/service/src/com/android/car/CarStorageMonitoringService.java
index fb487b4..2d15a3f 100644
--- a/service/src/com/android/car/CarStorageMonitoringService.java
+++ b/service/src/com/android/car/CarStorageMonitoringService.java
@@ -46,6 +46,7 @@
 import com.android.car.storagemonitoring.WearInformation;
 import com.android.car.storagemonitoring.WearInformationProvider;
 import com.android.car.systeminterface.SystemInterface;
+import com.android.internal.annotations.GuardedBy;
 
 import org.json.JSONArray;
 import org.json.JSONException;
@@ -69,6 +70,10 @@
 import java.util.stream.Collectors;
 import java.util.stream.IntStream;
 
+/**
+ * A service to provide storage monitoring data like I/O statistics. In order to receive such data,
+ * users need to implement {@link IIoStatsListener} and register themselves against this service.
+ */
 public class CarStorageMonitoringService extends ICarStorageMonitoring.Stub
         implements CarServiceBase {
     public static final String INTENT_EXCESSIVE_IO =
@@ -93,21 +98,38 @@
     private final OnShutdownReboot mOnShutdownReboot;
     private final SystemInterface mSystemInterface;
     private final UidIoStatsProvider mUidIoStatsProvider;
-    private final SlidingWindow<IoStats> mIoStatsSamples;
-    private final RemoteCallbackList<IIoStatsListener> mListeners;
-    private final Object mIoStatsSamplesLock = new Object();
-    private final Configuration mConfiguration;
 
+    private final Object mLock = new Object();
+
+    @GuardedBy("mLock")
+    private final SlidingWindow<IoStats> mIoStatsSamples;
+
+    private final RemoteCallbackList<IIoStatsListener> mListeners;
+    private final Configuration mConfiguration;
     private final CarPermission mStorageMonitoringPermission;
 
+    @GuardedBy("mLock")
     private UptimeTracker mUptimeTracker = null;
+
+    @GuardedBy("mLock")
     private Optional<WearInformation> mWearInformation = Optional.empty();
-    private List<WearEstimateChange> mWearEstimateChanges = Collections.emptyList();
+
+    @GuardedBy("mLock")
+    private List<WearEstimateChange> mWearEstimateChanges;
+
+    @GuardedBy("mLock")
     private List<IoStatsEntry> mBootIoStats = Collections.emptyList();
+
+    @GuardedBy("mLock")
     private IoStatsTracker mIoStatsTracker = null;
+
+    @GuardedBy("mLock")
     private boolean mInitialized = false;
 
+    @GuardedBy("mLock")
     private long mShutdownCostInfo = SHUTDOWN_COST_INFO_MISSING;
+
+    @GuardedBy("mLock")
     private String mShutdownCostMissingReason;
 
     public CarStorageMonitoringService(Context context, SystemInterface systemInterface) {
@@ -131,8 +153,10 @@
         mWearEstimateChanges = Collections.emptyList();
         mIoStatsSamples = new SlidingWindow<>(mConfiguration.ioStatsNumSamplesToStore);
         mListeners = new RemoteCallbackList<>();
-        systemInterface.scheduleActionForBootCompleted(this::doInitServiceIfNeeded,
-            Duration.ofSeconds(10));
+        systemInterface.scheduleActionForBootCompleted(() -> {
+            synchronized (mLock) {
+                doInitServiceIfNeededLocked();
+            }}, Duration.ofSeconds(10));
     }
 
     private Optional<WearInformation> loadWearInformation() {
@@ -164,7 +188,8 @@
     }
 
     // returns true iff a new event was added (and hence the history needs to be saved)
-    private boolean addEventIfNeeded(WearHistory wearHistory) {
+    @GuardedBy("mLock")
+    private boolean addEventIfNeededLocked(WearHistory wearHistory) {
         if (!mWearInformation.isPresent()) return false;
 
         WearInformation wearInformation = mWearInformation.get();
@@ -180,9 +205,9 @@
         if (currentWearEstimate.equals(lastWearEstimate)) return false;
 
         WearEstimateRecord newRecord = new WearEstimateRecord(lastWearEstimate,
-            currentWearEstimate,
-            mUptimeTracker.getTotalUptime(),
-            Instant.now());
+                currentWearEstimate,
+                mUptimeTracker.getTotalUptime(),
+                Instant.now());
         Log.d(TAG, "new wear record generated " + newRecord);
         wearHistory.add(newRecord);
         return true;
@@ -199,10 +224,11 @@
     @Override
     public void init() {
         Log.d(TAG, "CarStorageMonitoringService init()");
-
-        mUptimeTracker = new UptimeTracker(mUptimeTrackerFile,
-            mConfiguration.uptimeIntervalBetweenUptimeDataWriteMs,
-            mSystemInterface);
+        synchronized (mLock) {
+            mUptimeTracker = new UptimeTracker(mUptimeTrackerFile,
+                    mConfiguration.uptimeIntervalBetweenUptimeDataWriteMs,
+                    mSystemInterface);
+        }
     }
 
     private void launchWearChangeActivity() {
@@ -210,22 +236,21 @@
         if (activityPath.isEmpty()) return;
         try {
             final ComponentName activityComponent =
-                Objects.requireNonNull(ComponentName.unflattenFromString(activityPath));
+                    Objects.requireNonNull(ComponentName.unflattenFromString(activityPath));
             Intent intent = new Intent();
             intent.setComponent(activityComponent);
             intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
             mContext.startActivity(intent);
         } catch (ActivityNotFoundException | NullPointerException e) {
-            Log.e(TAG,
-                "value of activityHandlerForFlashWearChanges invalid non-empty string " +
-                    activityPath, e);
+            Log.e(TAG, "value of activityHandlerForFlashWearChanges invalid non-empty string "
+                    + activityPath, e);
         }
     }
 
     private static void logOnAdverseWearLevel(WearInformation wearInformation) {
         if (wearInformation.preEolInfo > WearInformation.PRE_EOL_INFO_NORMAL ||
-            Math.max(wearInformation.lifetimeEstimateA,
-                wearInformation.lifetimeEstimateB) >= MIN_WEAR_ESTIMATE_OF_CONCERN) {
+                Math.max(wearInformation.lifetimeEstimateA,
+                        wearInformation.lifetimeEstimateB) >= MIN_WEAR_ESTIMATE_OF_CONCERN) {
             Log.w(TAG, "flash storage reached wear a level that requires attention: "
                     + wearInformation);
         }
@@ -237,29 +262,31 @@
     }
 
     private void collectNewIoMetrics() {
+        SparseArray<IoStatsEntry> currentSample;
+        boolean needsExcessiveIoBroadcast;
         IoStats ioStats;
-
-        mIoStatsTracker.update(loadNewIoStats());
-        synchronized (mIoStatsSamplesLock) {
+        synchronized (mLock) {
+            mIoStatsTracker.update(loadNewIoStats());
+            currentSample = mIoStatsTracker.getCurrentSample();
             ioStats = new IoStats(
-                SparseArrayStream.valueStream(mIoStatsTracker.getCurrentSample())
-                    .collect(Collectors.toList()),
-                mSystemInterface.getUptime());
+                    SparseArrayStream.valueStream(currentSample).collect(Collectors.toList()),
+                    mSystemInterface.getUptime());
             mIoStatsSamples.add(ioStats);
+            needsExcessiveIoBroadcast = needsExcessiveIoBroadcastLocked();
         }
 
+        dispatchNewIoEvent(ioStats);
+
         if (DBG) {
-            SparseArray<IoStatsEntry> currentSample = mIoStatsTracker.getCurrentSample();
             if (currentSample.size() == 0) {
                 Log.d(TAG, "no new I/O stat data");
             } else {
                 SparseArrayStream.valueStream(currentSample).forEach(
-                    uidIoStats -> Log.d(TAG, "updated I/O stat data: " + uidIoStats));
+                        uidIoStats -> Log.d(TAG, "updated I/O stat data: " + uidIoStats));
             }
         }
 
-        dispatchNewIoEvent(ioStats);
-        if (needsExcessiveIoBroadcast()) {
+        if (needsExcessiveIoBroadcast) {
             Log.d(TAG, "about to send " + INTENT_EXCESSIVE_IO);
             sendExcessiveIoBroadcast();
         }
@@ -287,33 +314,33 @@
         mContext.sendBroadcast(intent, mStorageMonitoringPermission.toString());
     }
 
-    private boolean needsExcessiveIoBroadcast() {
-        synchronized (mIoStatsSamplesLock) {
-            return mIoStatsSamples.count((IoStats delta) -> {
-                Metrics total = delta.getTotals();
-                final boolean tooManyBytesWritten =
+    @GuardedBy("mLock")
+    private boolean needsExcessiveIoBroadcastLocked() {
+        return mIoStatsSamples.count((IoStats delta) -> {
+            Metrics total = delta.getTotals();
+            final boolean tooManyBytesWritten =
                     (total.bytesWrittenToStorage > mConfiguration.acceptableBytesWrittenPerSample);
-                final boolean tooManyFsyncCalls =
+            final boolean tooManyFsyncCalls =
                     (total.fsyncCalls > mConfiguration.acceptableFsyncCallsPerSample);
-                return tooManyBytesWritten || tooManyFsyncCalls;
-            }) > mConfiguration.maxExcessiveIoSamplesInWindow;
-        }
+            return tooManyBytesWritten || tooManyFsyncCalls;
+        }) > mConfiguration.maxExcessiveIoSamplesInWindow;
     }
 
     private void dispatchNewIoEvent(IoStats delta) {
         final int listenersCount = mListeners.beginBroadcast();
         IntStream.range(0, listenersCount).forEach(
-            i -> {
-                try {
-                    mListeners.getBroadcastItem(i).onSnapshot(delta);
-                } catch (RemoteException e) {
-                    Log.w(TAG, "failed to dispatch snapshot", e);
-                }
-            });
+                i -> {
+                    try {
+                        mListeners.getBroadcastItem(i).onSnapshot(delta);
+                    } catch (RemoteException e) {
+                        Log.w(TAG, "failed to dispatch snapshot", e);
+                    }
+                });
         mListeners.finishBroadcast();
     }
 
-    private synchronized void doInitServiceIfNeeded() {
+    @GuardedBy("mLock")
+    private void doInitServiceIfNeededLocked() {
         if (mInitialized) return;
 
         Log.d(TAG, "initializing CarStorageMonitoringService");
@@ -322,7 +349,7 @@
 
         // TODO(egranata): can this be done lazily?
         final WearHistory wearHistory = loadWearHistory();
-        final boolean didWearChangeHappen = addEventIfNeeded(wearHistory);
+        final boolean didWearChangeHappen = addEventIfNeededLocked(wearHistory);
         if (didWearChangeHappen) {
             storeWearHistory(wearHistory);
         }
@@ -341,15 +368,15 @@
 
         long bootUptime = mSystemInterface.getUptime();
         mBootIoStats = SparseArrayStream.valueStream(loadNewIoStats())
-            .map(record -> {
-                // at boot, assume all UIDs have been running for as long as the system has
-                // been up, since we don't really know any better
-                IoStatsEntry stats = new IoStatsEntry(record, bootUptime);
-                if (DBG) {
-                    Log.d(TAG, "loaded boot I/O stat data: " + stats);
-                }
-                return stats;
-            }).collect(Collectors.toList());
+                .map(record -> {
+                    // at boot, assume all UIDs have been running for as long as the system has
+                    // been up, since we don't really know any better
+                    IoStatsEntry stats = new IoStatsEntry(record, bootUptime);
+                    if (DBG) {
+                        Log.d(TAG, "loaded boot I/O stat data: " + stats);
+                    }
+                    return stats;
+                }).collect(Collectors.toList());
 
         mIoStatsTracker = new IoStatsTracker(mBootIoStats,
                 mConfiguration.ioStatsRefreshRateMs,
@@ -357,12 +384,12 @@
 
         if (mConfiguration.ioStatsNumSamplesToStore > 0) {
             mSystemInterface.scheduleAction(this::collectNewIoMetrics,
-                mConfiguration.ioStatsRefreshRateMs);
+                    mConfiguration.ioStatsRefreshRateMs);
         } else {
             Log.i(TAG, "service configuration disabled I/O sample window. not collecting samples");
         }
 
-        mShutdownCostInfo = computeShutdownCost();
+        mShutdownCostInfo = computeShutdownCostLocked();
         Log.d(TAG, "calculated data written in last shutdown was " +
                 mShutdownCostInfo + " bytes");
         mLifetimeWriteFile.delete();
@@ -372,7 +399,8 @@
         mInitialized = true;
     }
 
-    private long computeShutdownCost() {
+    @GuardedBy("mLock")
+    private long computeShutdownCostLocked() {
         List<LifetimeWriteInfo> shutdownWrites = loadLifetimeWrites();
         if (shutdownWrites.isEmpty()) {
             Log.d(TAG, "lifetime write data from last shutdown missing");
@@ -391,10 +419,10 @@
 
         Map<String, Long> shutdownLifetimeWrites = new HashMap<>();
         shutdownWrites.forEach(li ->
-            shutdownLifetimeWrites.put(li.partition, li.writtenBytes));
+                shutdownLifetimeWrites.put(li.partition, li.writtenBytes));
 
         // for every partition currently available, look for it in the shutdown data
-        for(int i = 0; i < currentWrites.size(); ++i) {
+        for (int i = 0; i < currentWrites.size(); ++i) {
             LifetimeWriteInfo li = currentWrites.get(i);
             // if this partition was not available when we last shutdown the system, then
             // just pretend we had written the same amount of data then as we have now
@@ -402,19 +430,19 @@
                     shutdownLifetimeWrites.getOrDefault(li.partition, li.writtenBytes);
             final long costDelta = li.writtenBytes - writtenAtShutdown;
             if (costDelta >= 0) {
-                Log.d(TAG, "partition " + li.partition + " had " + costDelta +
-                    " bytes written to it during shutdown");
+                Log.d(TAG, "partition " + li.partition + " had " + costDelta
+                        + " bytes written to it during shutdown");
                 shutdownCost += costDelta;
             } else {
                 // the counter of written bytes should be monotonic; a decrease might mean
                 // corrupt data, improper shutdown or that the kernel in use does not
                 // have proper monotonic guarantees on the lifetime write data. If any of these
                 // occur, it's probably safer to just bail out and say we don't know
-                mShutdownCostMissingReason = li.partition + " has a negative write amount (" +
-                        costDelta + " bytes)";
-                Log.e(TAG, "partition " + li.partition + " reported " + costDelta +
-                    " bytes written to it during shutdown. assuming we can't" +
-                    " determine proper shutdown information.");
+                mShutdownCostMissingReason = li.partition + " has a negative write amount ("
+                        + costDelta + " bytes)";
+                Log.e(TAG, "partition " + li.partition + " reported " + costDelta
+                        + " bytes written to it during shutdown. assuming we can't"
+                        + " determine proper shutdown information.");
                 return SHUTDOWN_COST_INFO_MISSING;
             }
         }
@@ -429,7 +457,7 @@
         }
         try {
             JSONObject jsonObject = new JSONObject(
-                new String(Files.readAllBytes(mLifetimeWriteFile.toPath())));
+                    new String(Files.readAllBytes(mLifetimeWriteFile.toPath())));
 
             JSONArray jsonArray = jsonObject.getJSONArray("lifetimeWriteInfo");
 
@@ -447,7 +475,7 @@
     private void logLifetimeWrites() {
         try {
             LifetimeWriteInfo[] lifetimeWriteInfos =
-                mSystemInterface.getLifetimeWriteInfoProvider().load();
+                    mSystemInterface.getLifetimeWriteInfoProvider().load();
             JsonWriter jsonWriter = new JsonWriter(new FileWriter(mLifetimeWriteFile));
             jsonWriter.beginObject();
             jsonWriter.name("lifetimeWriteInfo").beginArray();
@@ -465,8 +493,10 @@
     @Override
     public void release() {
         Log.i(TAG, "tearing down CarStorageMonitoringService");
-        if (mUptimeTracker != null) {
-            mUptimeTracker.onDestroy();
+        synchronized (mLock) {
+            if (mUptimeTracker != null) {
+                mUptimeTracker.onDestroy();
+            }
         }
         mOnShutdownReboot.clearActions();
         mListeners.kill();
@@ -474,39 +504,38 @@
 
     @Override
     public void dump(PrintWriter writer) {
-        doInitServiceIfNeeded();
-
         writer.println("*CarStorageMonitoringService*");
-        writer.println("last wear information retrieved: " +
-            mWearInformation.map(WearInformation::toString).orElse("missing"));
-        writer.println("wear change history: " +
-            mWearEstimateChanges.stream()
-                .map(WearEstimateChange::toString)
-                .collect(Collectors.joining("\n")));
-        writer.println("boot I/O stats: " +
-            mBootIoStats.stream()
-                .map(IoStatsEntry::toString)
-                .collect(Collectors.joining("\n")));
-        writer.println("aggregate I/O stats: " +
-            SparseArrayStream.valueStream(mIoStatsTracker.getTotal())
-                .map(IoStatsEntry::toString)
-                .collect(Collectors.joining("\n")));
-        writer.println("I/O stats snapshots: ");
-        synchronized (mIoStatsSamplesLock) {
+        synchronized (mLock) {
+            doInitServiceIfNeededLocked();
+            writer.println("last wear information retrieved: "
+                    + mWearInformation.map(WearInformation::toString).orElse("missing"));
+            writer.println("wear change history: "
+                    + mWearEstimateChanges.stream()
+                    .map(WearEstimateChange::toString)
+                    .collect(Collectors.joining("\n")));
+            writer.println("boot I/O stats: "
+                    + mBootIoStats.stream()
+                    .map(IoStatsEntry::toString)
+                    .collect(Collectors.joining("\n")));
+            writer.println("aggregate I/O stats: "
+                    + SparseArrayStream.valueStream(mIoStatsTracker.getTotal())
+                    .map(IoStatsEntry::toString)
+                    .collect(Collectors.joining("\n")));
+            writer.println("I/O stats snapshots: ");
             writer.println(
-                mIoStatsSamples.stream().map(
-                    sample -> sample.getStats().stream()
-                        .map(IoStatsEntry::toString)
-                        .collect(Collectors.joining("\n")))
-                    .collect(Collectors.joining("\n------\n")));
-        }
-        if (mShutdownCostInfo < 0) {
-            writer.print("last shutdown cost: missing. ");
-            if (mShutdownCostMissingReason != null && !mShutdownCostMissingReason.isEmpty()) {
-                writer.println("reason: " + mShutdownCostMissingReason);
+                    mIoStatsSamples.stream().map(
+                            sample -> sample.getStats().stream()
+                                    .map(IoStatsEntry::toString)
+                                    .collect(Collectors.joining("\n")))
+                            .collect(Collectors.joining("\n------\n")));
+            if (mShutdownCostInfo < 0) {
+                writer.print("last shutdown cost: missing. ");
+                if (mShutdownCostMissingReason != null && !mShutdownCostMissingReason.isEmpty()) {
+                    writer.println("reason: " + mShutdownCostMissingReason);
+                }
+            } else {
+                writer.println("last shutdown cost: " + mShutdownCostInfo + " bytes, estimated");
             }
-        } else {
-            writer.println("last shutdown cost: " + mShutdownCostInfo + " bytes, estimated");
         }
     }
 
@@ -515,70 +544,82 @@
     @Override
     public int getPreEolIndicatorStatus() {
         mStorageMonitoringPermission.assertGranted();
-        doInitServiceIfNeeded();
+        synchronized (mLock) {
+            doInitServiceIfNeededLocked();
 
-        return mWearInformation.map(wi -> wi.preEolInfo)
-                .orElse(WearInformation.UNKNOWN_PRE_EOL_INFO);
+            return mWearInformation.map(wi -> wi.preEolInfo)
+                    .orElse(WearInformation.UNKNOWN_PRE_EOL_INFO);
+        }
     }
 
     @Override
     public WearEstimate getWearEstimate() {
         mStorageMonitoringPermission.assertGranted();
-        doInitServiceIfNeeded();
+        synchronized (mLock) {
+            doInitServiceIfNeededLocked();
 
-        return mWearInformation.map(wi ->
-                new WearEstimate(wi.lifetimeEstimateA,wi.lifetimeEstimateB)).orElse(
+            return mWearInformation.map(wi ->
+                    new WearEstimate(wi.lifetimeEstimateA, wi.lifetimeEstimateB)).orElse(
                     WearEstimate.UNKNOWN_ESTIMATE);
+        }
     }
 
     @Override
     public List<WearEstimateChange> getWearEstimateHistory() {
         mStorageMonitoringPermission.assertGranted();
-        doInitServiceIfNeeded();
+        synchronized (mLock) {
+            doInitServiceIfNeededLocked();
 
-        return mWearEstimateChanges;
+            return Collections.unmodifiableList(mWearEstimateChanges);
+        }
     }
 
     @Override
     public List<IoStatsEntry> getBootIoStats() {
         mStorageMonitoringPermission.assertGranted();
-        doInitServiceIfNeeded();
+        doInitServiceIfNeededLocked();
 
-        return mBootIoStats;
+        return Collections.unmodifiableList(mBootIoStats);
     }
 
     @Override
     public List<IoStatsEntry> getAggregateIoStats() {
         mStorageMonitoringPermission.assertGranted();
-        doInitServiceIfNeeded();
+        synchronized (mLock) {
+            doInitServiceIfNeededLocked();
 
-        return SparseArrayStream.valueStream(mIoStatsTracker.getTotal())
-                .collect(Collectors.toList());
+            return Collections.unmodifiableList(SparseArrayStream.valueStream(
+                    mIoStatsTracker.getTotal()).collect(Collectors.toList()));
+        }
     }
 
     @Override
     public long getShutdownDiskWriteAmount() {
         mStorageMonitoringPermission.assertGranted();
-        doInitServiceIfNeeded();
+        synchronized (mLock) {
+            doInitServiceIfNeededLocked();
 
-        return mShutdownCostInfo;
+            return mShutdownCostInfo;
+        }
     }
 
     @Override
     public List<IoStats> getIoStatsDeltas() {
         mStorageMonitoringPermission.assertGranted();
-        doInitServiceIfNeeded();
+        synchronized (mLock) {
+            doInitServiceIfNeededLocked();
 
-        synchronized (mIoStatsSamplesLock) {
-            return mIoStatsSamples.stream().collect(Collectors.toList());
+            return Collections.unmodifiableList(
+                    mIoStatsSamples.stream().collect(Collectors.toList()));
         }
     }
 
     @Override
     public void registerListener(IIoStatsListener listener) {
         mStorageMonitoringPermission.assertGranted();
-        doInitServiceIfNeeded();
-
+        synchronized (mLock) {
+            doInitServiceIfNeededLocked();
+        }
         mListeners.register(listener);
     }
 
@@ -609,13 +650,11 @@
                     resources.getInteger(R.integer.acceptableFsyncCallsPerSample);
             maxExcessiveIoSamplesInWindow =
                     resources.getInteger(R.integer.maxExcessiveIoSamplesInWindow);
-            uptimeIntervalBetweenUptimeDataWriteMs =
-                        60 * 60 * 1000 *
-                        resources.getInteger(R.integer.uptimeHoursIntervalBetweenUptimeDataWrite);
+            uptimeIntervalBetweenUptimeDataWriteMs = 60 * 60 * 1000
+                    * resources.getInteger(R.integer.uptimeHoursIntervalBetweenUptimeDataWrite);
             acceptableHoursPerOnePercentFlashWear =
                     resources.getInteger(R.integer.acceptableHoursPerOnePercentFlashWear);
-            ioStatsRefreshRateMs =
-                    1000 * resources.getInteger(R.integer.ioStatsRefreshRateSeconds);
+            ioStatsRefreshRateMs = 1000 * resources.getInteger(R.integer.ioStatsRefreshRateSeconds);
             activityHandlerForFlashWearChanges =
                     resources.getString(R.string.activityHandlerForFlashWearChanges);
             intentReceiverForUnacceptableIoMetrics =
@@ -624,25 +663,24 @@
 
         @Override
         public String toString() {
-            return String.format(
-                "acceptableBytesWrittenPerSample = %d, " +
-                "acceptableFsyncCallsPerSample = %d, " +
-                "acceptableHoursPerOnePercentFlashWear = %d, " +
-                "activityHandlerForFlashWearChanges = %s, " +
-                "intentReceiverForUnacceptableIoMetrics = %s, " +
-                "ioStatsNumSamplesToStore = %d, " +
-                "ioStatsRefreshRateMs = %d, " +
-                "maxExcessiveIoSamplesInWindow = %d, " +
-                "uptimeIntervalBetweenUptimeDataWriteMs = %d",
-                acceptableBytesWrittenPerSample,
-                acceptableFsyncCallsPerSample,
-                acceptableHoursPerOnePercentFlashWear,
-                activityHandlerForFlashWearChanges,
-                intentReceiverForUnacceptableIoMetrics,
-                ioStatsNumSamplesToStore,
-                ioStatsRefreshRateMs,
-                maxExcessiveIoSamplesInWindow,
-                uptimeIntervalBetweenUptimeDataWriteMs);
+            return String.format("acceptableBytesWrittenPerSample = %d, "
+                            + "acceptableFsyncCallsPerSample = %d, "
+                            + "acceptableHoursPerOnePercentFlashWear = %d, "
+                            + "activityHandlerForFlashWearChanges = %s, "
+                            + "intentReceiverForUnacceptableIoMetrics = %s, "
+                            + "ioStatsNumSamplesToStore = %d, "
+                            + "ioStatsRefreshRateMs = %d, "
+                            + "maxExcessiveIoSamplesInWindow = %d, "
+                            + "uptimeIntervalBetweenUptimeDataWriteMs = %d",
+                    acceptableBytesWrittenPerSample,
+                    acceptableFsyncCallsPerSample,
+                    acceptableHoursPerOnePercentFlashWear,
+                    activityHandlerForFlashWearChanges,
+                    intentReceiverForUnacceptableIoMetrics,
+                    ioStatsNumSamplesToStore,
+                    ioStatsRefreshRateMs,
+                    maxExcessiveIoSamplesInWindow,
+                    uptimeIntervalBetweenUptimeDataWriteMs);
         }
     }
 }
diff --git a/service/src/com/android/car/CarUxRestrictionsManagerService.java b/service/src/com/android/car/CarUxRestrictionsManagerService.java
index 0ea757f..32de1d2 100644
--- a/service/src/com/android/car/CarUxRestrictionsManagerService.java
+++ b/service/src/com/android/car/CarUxRestrictionsManagerService.java
@@ -46,7 +46,9 @@
 import android.os.Binder;
 import android.os.Build;
 import android.os.IBinder;
+import android.os.IRemoteCallback;
 import android.os.Process;
+import android.os.RemoteCallbackList;
 import android.os.RemoteException;
 import android.os.SystemClock;
 import android.util.ArraySet;
@@ -56,13 +58,13 @@
 import android.util.JsonWriter;
 import android.util.Log;
 import android.util.Slog;
+import android.util.SparseArray;
 import android.view.Display;
 import android.view.DisplayAddress;
 
 import com.android.car.systeminterface.SystemInterface;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.Preconditions;
 
 import org.xmlpull.v1.XmlPullParserException;
 
@@ -107,7 +109,6 @@
     private static final int MAX_TRANSITION_LOG_SIZE = 20;
     private static final int PROPERTY_UPDATE_RATE = 5; // Update rate in Hz
     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;
@@ -121,6 +122,8 @@
     private static final String JSON_NAME_RESTRICTIONS = "restrictions";
 
     @VisibleForTesting
+    static final byte DEFAULT_PORT = 0;
+    @VisibleForTesting
     static final String CONFIG_FILENAME_PRODUCTION = "ux_restrictions_prod_config.json";
     @VisibleForTesting
     static final String CONFIG_FILENAME_STAGED = "ux_restrictions_staged_config.json";
@@ -137,8 +140,11 @@
     // Byte represents a physical port for display.
     private byte mDefaultDisplayPhysicalPort;
     private final List<Byte> mPhysicalPorts = new ArrayList<>();
-    // This lookup caches the mapping from an int display id
-    // to a byte that represents a physical port.
+    /**
+     * This lookup caches the mapping from an int display id to a byte that represents a physical
+     * port. It includes mappings for virtual displays.
+     */
+    @GuardedBy("mMapLock")
     private final Map<Integer, Byte> mPortLookup = new HashMap<>();
     private Map<Byte, CarUxRestrictionsConfiguration> mCarUxRestrictionsConfigurations;
     private Map<Byte, CarUxRestrictions> mCurrentUxRestrictions;
@@ -317,6 +323,9 @@
         mUxRClients.clear();
         mDrivingStateService.unregisterDrivingStateChangeListener(
                 mICarDrivingStateChangeEventListener);
+        synchronized (mMapLock) {
+            mActivityViewDisplayInfoMap.clear();
+        }
     }
 
     // Binder methods
@@ -399,7 +408,10 @@
      */
     @Override
     public synchronized CarUxRestrictions getCurrentUxRestrictions(int displayId) {
-        CarUxRestrictions restrictions = mCurrentUxRestrictions.get(getPhysicalPort(displayId));
+        CarUxRestrictions restrictions;
+        synchronized (mMapLock) {
+            restrictions = mCurrentUxRestrictions.get(getPhysicalPortLocked(displayId));
+        }
         if (restrictions == null) {
             Log.e(TAG, String.format(
                     "Restrictions are null for displayId:%d. Returning full restrictions.",
@@ -497,15 +509,7 @@
         }
         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();
+            writeJson(jsonWriter, configs);
         } catch (IOException e) {
             Log.e(TAG, "Could not persist config", e);
             stagedFile.failWrite(fos);
@@ -515,6 +519,20 @@
         return true;
     }
 
+    @VisibleForTesting
+    void writeJson(JsonWriter jsonWriter, List<CarUxRestrictionsConfiguration> configs)
+            throws IOException {
+        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();
+    }
+
     @Nullable
     private List<CarUxRestrictionsConfiguration> readPersistedConfig(File file) {
         if (!file.exists()) {
@@ -717,6 +735,12 @@
         for (Utils.TransitionLog tlog : mTransitionLogs) {
             writer.println(tlog);
         }
+        writer.println("UX Restriction display info:");
+        for (int i = mActivityViewDisplayInfoMap.size() - 1; i >= 0; --i) {
+            DisplayInfo info = mActivityViewDisplayInfoMap.valueAt(i);
+            writer.printf("Display%d: physicalDisplayId=%d, owner=%s\n",
+                    mActivityViewDisplayInfoMap.keyAt(i), info.mPhysicalDisplayId, info.mOwner);
+        }
     }
 
     /**
@@ -737,7 +761,8 @@
      * Map the driving state to the corresponding UX Restrictions and dispatch the
      * UX Restriction change to the registered clients.
      */
-    private synchronized void handleDrivingStateEvent(CarDrivingStateEvent event) {
+    @VisibleForTesting
+    synchronized void handleDrivingStateEvent(CarDrivingStateEvent event) {
         if (event == null) {
             return;
         }
@@ -803,9 +828,9 @@
      */
     private synchronized void handleDispatchUxRestrictions(@CarDrivingState int currentDrivingState,
             float speed) {
-        Preconditions.checkNotNull(mCarUxRestrictionsConfigurations,
+        Objects.requireNonNull(mCarUxRestrictionsConfigurations,
                 "mCarUxRestrictionsConfigurations must be initialized");
-        Preconditions.checkNotNull(mCurrentUxRestrictions,
+        Objects.requireNonNull(mCurrentUxRestrictions,
                 "mCurrentUxRestrictions must be initialized");
 
         if (isDebugBuild() && !mUxRChangeBroadcastEnabled) {
@@ -857,7 +882,10 @@
 
         logd("dispatching to clients");
         for (UxRestrictionsClient client : mUxRClients) {
-            Byte clientDisplayPort = getPhysicalPort(client.mDisplayId);
+            Byte clientDisplayPort;
+            synchronized (mMapLock) {
+                clientDisplayPort = getPhysicalPortLocked(client.mDisplayId);
+            }
             if (clientDisplayPort == null) {
                 clientDisplayPort = mDefaultDisplayPhysicalPort;
             }
@@ -897,7 +925,8 @@
                 byte port = ((DisplayAddress.Physical) display.getAddress()).getPort();
                 if (Log.isLoggable(TAG, Log.INFO)) {
                     Log.i(TAG, String.format(
-                            "Display %d uses port %d", display.getDisplayId(), port));
+                            "Display %d uses port %d", display.getDisplayId(),
+                            Byte.toUnsignedInt(port)));
                 }
                 mPhysicalPorts.add(port);
             } else {
@@ -972,7 +1001,8 @@
      * DisplayManager#getDisplay(int)} is not aware of the provided id.
      */
     @Nullable
-    private Byte getPhysicalPort(int displayId) {
+    @GuardedBy("mMapLock")
+    private Byte getPhysicalPortLocked(int displayId) {
         if (!mPortLookup.containsKey(displayId)) {
             Display display = mDisplayManager.getDisplay(displayId);
             if (display == null) {
@@ -987,7 +1017,8 @@
 
     private byte getPhysicalPort(@NonNull Display display) {
         if (display.getType() == Display.TYPE_VIRTUAL) {
-            // We require all virtual displays to be launched on default display.
+            Log.e(TAG, "Display " + display
+                    + " is a virtual display and does not have a known port.");
             return mDefaultDisplayPhysicalPort;
         }
 
@@ -1061,4 +1092,97 @@
             Slog.d(TAG, msg);
         }
     }
+
+    private final Object mMapLock = new Object();
+
+    private static final class DisplayInfo {
+        final IRemoteCallback mOwner;
+        final int mPhysicalDisplayId;
+
+        DisplayInfo(IRemoteCallback owner, int physicalDisplayId) {
+            mOwner = owner;
+            mPhysicalDisplayId = physicalDisplayId;
+        }
+    }
+
+    @GuardedBy("mMapLock")
+    private final SparseArray<DisplayInfo> mActivityViewDisplayInfoMap = new SparseArray<>();
+
+    @GuardedBy("mMapLock")
+    private final RemoteCallbackList<IRemoteCallback> mRemoteCallbackList =
+            new RemoteCallbackList<>() {
+                @Override
+                public void onCallbackDied(IRemoteCallback callback) {
+                    synchronized (mMapLock) {
+                        // Descending order to delete items safely from SpareArray.gc().
+                        for (int i = mActivityViewDisplayInfoMap.size() - 1; i >= 0; --i) {
+                            DisplayInfo info = mActivityViewDisplayInfoMap.valueAt(i);
+                            if (info.mOwner == callback) {
+                                logd("onCallbackDied: clean up callback=" + callback);
+                                mActivityViewDisplayInfoMap.removeAt(i);
+                                mPortLookup.remove(mActivityViewDisplayInfoMap.keyAt(i));
+                            }
+                        }
+                    }
+                }
+            };
+
+    @Override
+    public void reportVirtualDisplayToPhysicalDisplay(IRemoteCallback callback,
+            int virtualDisplayId, int physicalDisplayId) {
+        logd("reportVirtualDisplayToPhysicalDisplay: callback=" + callback
+                + ", virtualDisplayId=" + virtualDisplayId
+                + ", physicalDisplayId=" + physicalDisplayId);
+        boolean release = physicalDisplayId == Display.INVALID_DISPLAY;
+        checkCallerOwnsDisplay(virtualDisplayId, release);
+        synchronized (mMapLock) {
+            if (release) {
+                mRemoteCallbackList.unregister(callback);
+                mActivityViewDisplayInfoMap.delete(virtualDisplayId);
+                mPortLookup.remove(virtualDisplayId);
+                return;
+            }
+            mRemoteCallbackList.register(callback);
+            mActivityViewDisplayInfoMap.put(virtualDisplayId,
+                    new DisplayInfo(callback, physicalDisplayId));
+            mPortLookup.put(virtualDisplayId, (byte) physicalDisplayId);
+        }
+    }
+
+    @Override
+    public int getMappedPhysicalDisplayOfVirtualDisplay(int displayId) {
+        logd("getMappedPhysicalDisplayOfVirtualDisplay: displayId=" + displayId);
+        synchronized (mMapLock) {
+            DisplayInfo foundInfo = mActivityViewDisplayInfoMap.get(displayId);
+            if (foundInfo == null) {
+                return Display.INVALID_DISPLAY;
+            }
+            // ActivityView can be placed in another ActivityView, so we should repeat the process
+            // until no parent is found (reached to the physical display).
+            while (foundInfo != null) {
+                displayId = foundInfo.mPhysicalDisplayId;
+                foundInfo = mActivityViewDisplayInfoMap.get(displayId);
+            }
+        }
+        return displayId;
+    }
+
+    private void checkCallerOwnsDisplay(int displayId, boolean release) {
+        Display display = mDisplayManager.getDisplay(displayId);
+        if (display == null) {
+            // Bypasses the permission check for non-existing display when releasing it, since
+            // reportVirtualDisplayToPhysicalDisplay() and releasing display happens simultaneously
+            // and it's no harm to release the information on the non-existing display.
+            if (release) return;
+            throw new IllegalArgumentException(
+                    "Cannot find display for non-existent displayId: " + displayId);
+        }
+
+        int callingUid = Binder.getCallingUid();
+        int displayOwnerUid = display.getOwnerUid();
+        if (callingUid != displayOwnerUid) {
+            throw new SecurityException("The caller doesn't own the display: callingUid="
+                    + callingUid + ", displayOwnerUid=" + displayOwnerUid);
+        }
+    }
 }
diff --git a/service/src/com/android/car/ICarImpl.java b/service/src/com/android/car/ICarImpl.java
index 483dd7f..af66305 100644
--- a/service/src/com/android/car/ICarImpl.java
+++ b/service/src/com/android/car/ICarImpl.java
@@ -17,12 +17,15 @@
 package com.android.car;
 
 import android.annotation.MainThread;
+import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.app.ActivityOptions;
 import android.app.UiModeManager;
 import android.car.Car;
+import android.car.CarFeatures;
 import android.car.ICar;
 import android.car.cluster.renderer.IInstrumentClusterNavigation;
+import android.car.user.CarUserManager;
 import android.car.userlib.CarUserManagerHelper;
 import android.content.ComponentName;
 import android.content.Context;
@@ -30,13 +33,20 @@
 import android.content.pm.PackageManager;
 import android.content.res.Resources;
 import android.hardware.automotive.vehicle.V2_0.IVehicle;
+import android.hardware.automotive.vehicle.V2_0.InitialUserInfoResponseAction;
+import android.hardware.automotive.vehicle.V2_0.UsersInfo;
 import android.hardware.automotive.vehicle.V2_0.VehicleArea;
 import android.os.Binder;
 import android.os.Build;
 import android.os.IBinder;
 import android.os.Process;
+import android.os.RemoteException;
+import android.os.ResultReceiver;
+import android.os.ShellCallback;
+import android.os.ShellCommand;
 import android.os.SystemClock;
 import android.os.Trace;
+import android.os.UserManager;
 import android.util.Log;
 import android.util.Slog;
 import android.util.TimingsTraceLog;
@@ -47,35 +57,42 @@
 import com.android.car.cluster.InstrumentClusterService;
 import com.android.car.garagemode.GarageModeService;
 import com.android.car.hal.InputHalService;
+import com.android.car.hal.UserHalHelper;
+import com.android.car.hal.UserHalService;
+import com.android.car.hal.UserHalService.HalCallback;
 import com.android.car.hal.VehicleHal;
-import com.android.car.internal.FeatureConfiguration;
 import com.android.car.pm.CarPackageManagerService;
 import com.android.car.stats.CarStatsService;
 import com.android.car.systeminterface.SystemInterface;
 import com.android.car.trust.CarTrustedDeviceService;
 import com.android.car.user.CarUserNoticeService;
 import com.android.car.user.CarUserService;
-import com.android.car.vms.VmsBrokerService;
-import com.android.car.vms.VmsClientManager;
+import com.android.car.user.UserMetrics;
+import com.android.car.vms.VmsNewBrokerService;
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.car.ICarServiceHelper;
+import com.android.internal.util.ArrayUtils;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
 
 public class ICarImpl extends ICar.Stub {
 
     public static final String INTERNAL_INPUT_SERVICE = "internal_input";
     public static final String INTERNAL_SYSTEM_ACTIVITY_MONITORING_SERVICE =
             "system_activity_monitoring";
-    public static final String INTERNAL_VMS_MANAGER = "vms_manager";
 
     private final Context mContext;
     private final VehicleHal mHal;
 
+    private final CarFeatureController mFeatureController;
+
     private final SystemInterface mSystemInterface;
 
     private final SystemActivityMonitoringService mSystemActivityMonitoringService;
@@ -84,6 +101,7 @@
     private final CarInputService mCarInputService;
     private final CarDrivingStateService mCarDrivingStateService;
     private final CarUxRestrictionsManagerService mCarUXRestrictionsService;
+    private final OccupantAwarenessService mOccupantAwarenessService;
     private final CarAudioService mCarAudioService;
     private final CarProjectionService mCarProjectionService;
     private final CarPropertyService mCarPropertyService;
@@ -103,13 +121,12 @@
     private final CarMediaService mCarMediaService;
     private final CarUserManagerHelper mUserManagerHelper;
     private final CarUserService mCarUserService;
+    private final CarOccupantZoneService mCarOccupantZoneService;
     private final CarUserNoticeService mCarUserNoticeService;
-    private final VmsClientManager mVmsClientManager;
-    private final VmsBrokerService mVmsBrokerService;
-    private final VmsSubscriberService mVmsSubscriberService;
-    private final VmsPublisherService mVmsPublisherService;
+    private final VmsNewBrokerService mVmsBrokerService;
     private final CarBugreportManagerService mCarBugreportManagerService;
     private final CarStatsService mCarStatsService;
+    private final CarExperimentalFeatureServiceController mCarExperimentalFeatureServiceController;
 
     private final CarServiceBase[] mAllServices;
 
@@ -127,31 +144,65 @@
 
     private final String mVehicleInterfaceName;
 
+    private final UserMetrics mUserMetrics = new UserMetrics();
+
     public ICarImpl(Context serviceContext, IVehicle vehicle, SystemInterface systemInterface,
             CanBusErrorNotifier errorNotifier, String vehicleInterfaceName) {
+        this(serviceContext, vehicle, systemInterface, errorNotifier, vehicleInterfaceName,
+                /* carUserService= */ null);
+    }
+
+    @VisibleForTesting
+    ICarImpl(Context serviceContext, IVehicle vehicle, SystemInterface systemInterface,
+            CanBusErrorNotifier errorNotifier, String vehicleInterfaceName,
+            @Nullable CarUserService carUserService) {
         mContext = serviceContext;
         mSystemInterface = systemInterface;
         mHal = new VehicleHal(serviceContext, vehicle);
+        Resources res = mContext.getResources();
+        String[] defaultEnabledFeatures = res.getStringArray(
+                R.array.config_allowed_optional_car_features);
+        // Do this before any other service components to allow feature check. It should work
+        // even without init.
+        // TODO (b/144504820) Add vhal plumbing
+        mFeatureController = new CarFeatureController(serviceContext, defaultEnabledFeatures,
+                /* disabledFeaturesFromVhal= */ new String[0], mSystemInterface.getSystemCarDir());
+        CarLocalServices.addService(CarFeatureController.class, mFeatureController);
         mVehicleInterfaceName = vehicleInterfaceName;
         mUserManagerHelper = new CarUserManagerHelper(serviceContext);
-        final Resources res = mContext.getResources();
-        final int maxRunningUsers = res.getInteger(
-                com.android.internal.R.integer.config_multiuserMaxRunningUsers);
-        mCarUserService = new CarUserService(serviceContext, mUserManagerHelper,
-                ActivityManager.getService(), maxRunningUsers);
+        if (carUserService != null) {
+            mCarUserService = carUserService;
+        } else {
+            UserManager userManager =
+                    (UserManager) serviceContext.getSystemService(Context.USER_SERVICE);
+            int maxRunningUsers = res.getInteger(
+                    com.android.internal.R.integer.config_multiuserMaxRunningUsers);
+            mCarUserService = new CarUserService(serviceContext, mHal.getUserHal(),
+                    mUserManagerHelper, userManager, ActivityManager.getService(), maxRunningUsers);
+        }
+        mCarOccupantZoneService = new CarOccupantZoneService(serviceContext);
         mSystemActivityMonitoringService = new SystemActivityMonitoringService(serviceContext);
         mCarPowerManagementService = new CarPowerManagementService(mContext, mHal.getPowerHal(),
                 systemInterface, mUserManagerHelper);
-        mCarUserNoticeService = new CarUserNoticeService(serviceContext);
+        if (mFeatureController.isFeatureEnabled(CarFeatures.FEATURE_CAR_USER_NOTICE_SERVICE)) {
+            mCarUserNoticeService = new CarUserNoticeService(serviceContext);
+        } else {
+            mCarUserNoticeService = null;
+        }
         mCarPropertyService = new CarPropertyService(serviceContext, mHal.getPropertyHal());
         mCarDrivingStateService = new CarDrivingStateService(serviceContext, mCarPropertyService);
         mCarUXRestrictionsService = new CarUxRestrictionsManagerService(serviceContext,
                 mCarDrivingStateService, mCarPropertyService);
+        if (mFeatureController.isFeatureEnabled(Car.OCCUPANT_AWARENESS_SERVICE)) {
+            mOccupantAwarenessService = new OccupantAwarenessService(serviceContext);
+        } else {
+            mOccupantAwarenessService = null;
+        }
         mCarPackageManagerService = new CarPackageManagerService(serviceContext,
                 mCarUXRestrictionsService,
                 mSystemActivityMonitoringService,
-                mUserManagerHelper);
-        mPerUserCarServiceHelper = new PerUserCarServiceHelper(serviceContext);
+                mCarUserService);
+        mPerUserCarServiceHelper = new PerUserCarServiceHelper(serviceContext, mCarUserService);
         mCarBluetoothService = new CarBluetoothService(serviceContext, mPerUserCarServiceHelper);
         mCarInputService = new CarInputService(serviceContext, mHal.getInputHal());
         mCarProjectionService = new CarProjectionService(
@@ -166,26 +217,40 @@
         mSystemStateControllerService = new SystemStateControllerService(
                 serviceContext, mCarAudioService, this);
         mCarStatsService = new CarStatsService(serviceContext);
-        mVmsBrokerService = new VmsBrokerService();
-        mVmsClientManager = new VmsClientManager(
-                // CarStatsService needs to be passed to the constructor due to HAL init order
-                serviceContext, mCarStatsService, mCarUserService, mVmsBrokerService,
-                mHal.getVmsHal());
-        mVmsSubscriberService = new VmsSubscriberService(
-                serviceContext, mVmsBrokerService, mVmsClientManager, mHal.getVmsHal());
-        mVmsPublisherService = new VmsPublisherService(
-                serviceContext, mCarStatsService, mVmsBrokerService, mVmsClientManager);
-        mCarDiagnosticService = new CarDiagnosticService(serviceContext, mHal.getDiagnosticHal());
-        mCarStorageMonitoringService = new CarStorageMonitoringService(serviceContext,
-                systemInterface);
+        mCarStatsService.init();
+        if (mFeatureController.isFeatureEnabled(Car.VEHICLE_MAP_SERVICE)
+                || mFeatureController.isFeatureEnabled(Car.VMS_SUBSCRIBER_SERVICE)) {
+            mVmsBrokerService = new VmsNewBrokerService(mContext, mCarStatsService);
+        } else {
+            mVmsBrokerService = null;
+        }
+        if (mFeatureController.isFeatureEnabled(Car.DIAGNOSTIC_SERVICE)) {
+            mCarDiagnosticService = new CarDiagnosticService(serviceContext,
+                    mHal.getDiagnosticHal());
+        } else {
+            mCarDiagnosticService = null;
+        }
+        if (mFeatureController.isFeatureEnabled(Car.STORAGE_MONITORING_SERVICE)) {
+            mCarStorageMonitoringService = new CarStorageMonitoringService(serviceContext,
+                    systemInterface);
+        } else {
+            mCarStorageMonitoringService = null;
+        }
         mCarConfigurationService =
                 new CarConfigurationService(serviceContext, new JsonReaderImpl());
-        mCarLocationService = new CarLocationService(mContext, mUserManagerHelper);
+        mCarLocationService = new CarLocationService(serviceContext);
         mCarTrustedDeviceService = new CarTrustedDeviceService(serviceContext);
-        mCarMediaService = new CarMediaService(serviceContext);
+        mCarMediaService = new CarMediaService(serviceContext, mCarUserService);
         mCarBugreportManagerService = new CarBugreportManagerService(serviceContext);
+        if (!Build.IS_USER) {
+            mCarExperimentalFeatureServiceController = new CarExperimentalFeatureServiceController(
+                    serviceContext);
+        } else {
+            mCarExperimentalFeatureServiceController = null;
+        }
 
         CarLocalServices.addService(CarPowerManagementService.class, mCarPowerManagementService);
+        CarLocalServices.addService(CarPropertyService.class, mCarPropertyService);
         CarLocalServices.addService(CarUserService.class, mCarUserService);
         CarLocalServices.addService(CarTrustedDeviceService.class, mCarTrustedDeviceService);
         CarLocalServices.addService(CarUserNoticeService.class, mCarUserNoticeService);
@@ -193,19 +258,23 @@
         CarLocalServices.addService(CarDrivingStateService.class, mCarDrivingStateService);
         CarLocalServices.addService(PerUserCarServiceHelper.class, mPerUserCarServiceHelper);
         CarLocalServices.addService(FixedActivityService.class, mFixedActivityService);
+        CarLocalServices.addService(VmsNewBrokerService.class, mVmsBrokerService);
 
         // Be careful with order. Service depending on other service should be inited later.
         List<CarServiceBase> allServices = new ArrayList<>();
+        allServices.add(mFeatureController);
         allServices.add(mCarUserService);
         allServices.add(mSystemActivityMonitoringService);
         allServices.add(mCarPowerManagementService);
         allServices.add(mCarPropertyService);
         allServices.add(mCarDrivingStateService);
+        allServices.add(mCarOccupantZoneService);
         allServices.add(mCarUXRestrictionsService);
+        addServiceIfNonNull(allServices, mOccupantAwarenessService);
         allServices.add(mCarPackageManagerService);
         allServices.add(mCarInputService);
         allServices.add(mGarageModeService);
-        allServices.add(mCarUserNoticeService);
+        addServiceIfNonNull(allServices, mCarUserNoticeService);
         allServices.add(mAppFocusService);
         allServices.add(mCarAudioService);
         allServices.add(mCarNightService);
@@ -215,19 +284,25 @@
         allServices.add(mPerUserCarServiceHelper);
         allServices.add(mCarBluetoothService);
         allServices.add(mCarProjectionService);
-        allServices.add(mCarDiagnosticService);
-        allServices.add(mCarStorageMonitoringService);
+        addServiceIfNonNull(allServices, mCarDiagnosticService);
+        addServiceIfNonNull(allServices, mCarStorageMonitoringService);
         allServices.add(mCarConfigurationService);
-        allServices.add(mVmsClientManager);
-        allServices.add(mVmsSubscriberService);
-        allServices.add(mVmsPublisherService);
+        addServiceIfNonNull(allServices, mVmsBrokerService);
         allServices.add(mCarTrustedDeviceService);
         allServices.add(mCarMediaService);
         allServices.add(mCarLocationService);
         allServices.add(mCarBugreportManagerService);
+        // Always put mCarExperimentalFeatureServiceController in last.
+        addServiceIfNonNull(allServices, mCarExperimentalFeatureServiceController);
         mAllServices = allServices.toArray(new CarServiceBase[allServices.size()]);
     }
 
+    private void addServiceIfNonNull(List<CarServiceBase> services, CarServiceBase service) {
+        if (service != null) {
+            services.add(service);
+        }
+    }
+
     @MainThread
     void init() {
         mBootTiming = new TimingsTraceLog(VHAL_TIMING_TAG, Trace.TRACE_TAG_HAL);
@@ -239,7 +314,6 @@
             service.init();
         }
         traceEnd();
-        mSystemInterface.reconfigureSecondaryDisplays();
     }
 
     void release() {
@@ -267,18 +341,77 @@
     }
 
     @Override
-    public void setUserLockStatus(int userHandle, int unlocked) {
+    public void setUserLockStatus(int userId, int unlocked) {
         assertCallingFromSystemProcess();
-        mCarUserService.setUserLockStatus(userHandle, unlocked == 1);
-        mCarMediaService.setUserLockStatus(userHandle, unlocked == 1);
+        mCarUserService.setUserLockStatus(userId, unlocked == 1);
+        mCarMediaService.setUserLockStatus(userId, unlocked == 1);
     }
 
     @Override
-    public void onSwitchUser(int userHandle) {
+    public void onSwitchUser(int userId) {
         assertCallingFromSystemProcess();
 
-        Log.i(TAG, "Foreground user switched to " + userHandle);
-        mCarUserService.onSwitchUser(userHandle);
+        Log.i(TAG, "Foreground user switched to " + userId);
+        mCarUserService.onSwitchUser(userId);
+    }
+
+    // TODO(b/146207078): this method is currently used just for metrics logging purposes, but we
+    // should fold the other too (onSwitchUser() and setUserLockStatus()) onto it.
+    @Override
+    public void onUserLifecycleEvent(int eventType, long timestampMs, int fromUserId,
+            int toUserId) {
+        assertCallingFromSystemProcess();
+        Log.i(TAG, "onUserLifecycleEvent(" + CarUserManager.lifecycleEventTypeToString(eventType)
+                + ", " + toUserId);
+        mUserMetrics.onEvent(eventType, timestampMs, fromUserId, toUserId);
+    }
+
+    @Override
+    public void onFirstUserUnlocked(int userId, long timestampMs, long duration) {
+        mUserMetrics.logFirstUnlockedUser(userId, timestampMs, duration);
+    }
+
+    @Override
+    public boolean isFeatureEnabled(String featureName) {
+        return mFeatureController.isFeatureEnabled(featureName);
+    }
+
+    @Override
+    public int enableFeature(String featureName) {
+        // permission check inside the controller
+        return mFeatureController.enableFeature(featureName);
+    }
+
+    @Override
+    public int disableFeature(String featureName) {
+        // permission check inside the controller
+        return mFeatureController.disableFeature(featureName);
+    }
+
+    @Override
+    public List<String> getAllEnabledFeatures() {
+        // permission check inside the controller
+        return mFeatureController.getAllEnabledFeatures();
+    }
+
+    @Override
+    public List<String> getAllPendingDisabledFeatures() {
+        // permission check inside the controller
+        return mFeatureController.getAllPendingDisabledFeatures();
+    }
+
+    @Override
+    public List<String> getAllPendingEnabledFeatures() {
+        // permission check inside the controller
+        return mFeatureController.getAllPendingEnabledFeatures();
+    }
+
+    @Override
+    public String getCarManagerClassForFeature(String featureName) {
+        if (mCarExperimentalFeatureServiceController == null) {
+            return null;
+        }
+        return mCarExperimentalFeatureServiceController.getCarManagerClassForFeature(featureName);
     }
 
     static void assertCallingFromSystemProcess() {
@@ -303,6 +436,10 @@
 
     @Override
     public IBinder getCarService(String serviceName) {
+        if (!mFeatureController.isFeatureEnabled(serviceName)) {
+            Log.w(CarLog.TAG_SERVICE, "getCarService for disabled service:" + serviceName);
+            return null;
+        }
         switch (serviceName) {
             case Car.AUDIO_SERVICE:
                 return mCarAudioService;
@@ -333,9 +470,12 @@
                 return mInstrumentClusterService.getManagerService();
             case Car.PROJECTION_SERVICE:
                 return mCarProjectionService;
+            case Car.VEHICLE_MAP_SERVICE:
+                assertAnyVmsPermission(mContext);
+                return mVmsBrokerService;
             case Car.VMS_SUBSCRIBER_SERVICE:
                 assertVmsSubscriberPermission(mContext);
-                return mVmsSubscriberService;
+                return mVmsBrokerService;
             case Car.TEST_SERVICE: {
                 assertPermission(mContext, Car.PERMISSION_CAR_TEST_SERVICE);
                 synchronized (this) {
@@ -355,6 +495,8 @@
                 return mCarDrivingStateService;
             case Car.CAR_UX_RESTRICTION_SERVICE:
                 return mCarUXRestrictionsService;
+            case Car.OCCUPANT_AWARENESS_SERVICE:
+                return mOccupantAwarenessService;
             case Car.CAR_CONFIGURATION_SERVICE:
                 return mCarConfigurationService;
             case Car.CAR_TRUST_AGENT_ENROLLMENT_SERVICE:
@@ -362,11 +504,22 @@
                 return mCarTrustedDeviceService.getCarTrustAgentEnrollmentService();
             case Car.CAR_MEDIA_SERVICE:
                 return mCarMediaService;
+            case Car.CAR_OCCUPANT_ZONE_SERVICE:
+                return mCarOccupantZoneService;
             case Car.CAR_BUGREPORT_SERVICE:
                 return mCarBugreportManagerService;
+            case Car.CAR_USER_SERVICE:
+                return mCarUserService;
             default:
-                Log.w(CarLog.TAG_SERVICE, "getCarService for unknown service:" + serviceName);
-                return null;
+                IBinder service = null;
+                if (mCarExperimentalFeatureServiceController != null) {
+                    service = mCarExperimentalFeatureServiceController.getCarService(serviceName);
+                }
+                if (service == null) {
+                    Log.w(CarLog.TAG_SERVICE, "getCarService for unknown service:"
+                            + serviceName);
+                }
+                return service;
         }
     }
 
@@ -381,8 +534,6 @@
                 return mCarInputService;
             case INTERNAL_SYSTEM_ACTIVITY_MONITORING_SERVICE:
                 return mSystemActivityMonitoringService;
-            case INTERNAL_VMS_MANAGER:
-                return mVmsClientManager;
             default:
                 Log.w(CarLog.TAG_SERVICE, "getCarInternalService for unknown service:" +
                         serviceName);
@@ -390,10 +541,6 @@
         }
     }
 
-    CarStatsService getStatsService() {
-        return mCarStatsService;
-    }
-
     public static void assertVehicleHalMockPermission(Context context) {
         assertPermission(context, Car.PERMISSION_MOCK_VEHICLE_HAL);
     }
@@ -429,6 +576,16 @@
         assertPermission(context, Car.PERMISSION_CAR_DRIVING_STATE);
     }
 
+    /**
+     * Verify the calling context has either {@link Car#PERMISSION_VMS_SUBSCRIBER} or
+     * {@link Car#PERMISSION_VMS_PUBLISHER}
+     */
+    public static void assertAnyVmsPermission(Context context) {
+        assertAnyPermission(context,
+                Car.PERMISSION_VMS_SUBSCRIBER,
+                Car.PERMISSION_VMS_PUBLISHER);
+    }
+
     public static void assertVmsPublisherPermission(Context context) {
         assertPermission(context, Car.PERMISSION_VMS_PUBLISHER);
     }
@@ -482,26 +639,47 @@
 
         if (args == null || args.length == 0 || (args.length > 0 && "-a".equals(args[0]))) {
             writer.println("*Dump car service*");
-            writer.println("*FutureConfig, DEFAULT:" + FeatureConfiguration.DEFAULT);
-            writer.println("*Dump all services*");
-
             dumpAllServices(writer);
-
-            writer.println("*Dump Vehicle HAL*");
-            writer.println("Vehicle HAL Interface: " + mVehicleInterfaceName);
-            try {
-                // TODO dump all feature flags by creating a dumpable interface
-                mHal.dump(writer);
-            } catch (Exception e) {
-                writer.println("Failed dumping: " + mHal.getClass().getName());
-                e.printStackTrace(writer);
+            dumpAllHals(writer);
+            mUserMetrics.dump(writer);
+        } else if ("--list".equals(args[0])) {
+            dumpListOfServices(writer);
+            return;
+        } else if ("--services".equals(args[0])) {
+            if (args.length < 2) {
+                writer.print("Must pass services to dump when using --services");
+                return;
             }
+            int length = args.length - 1;
+            String[] services = new String[length];
+            System.arraycopy(args, 1, services, 0, length);
+            dumpIndividualServices(writer, services);
+            return;
         } else if ("--metrics".equals(args[0])) {
             // Strip the --metrics flag when passing dumpsys arguments to CarStatsService
             // allowing for nested flag selection
             mCarStatsService.dump(fd, writer, Arrays.copyOfRange(args, 1, args.length));
         } else if ("--vms-hal".equals(args[0])) {
             mHal.getVmsHal().dumpMetrics(fd);
+        } else if ("--hal".equals(args[0])) {
+            if (args.length == 1) {
+                dumpAllHals(writer);
+                return;
+            }
+            int length = args.length - 1;
+            String[] halNames = new String[length];
+            System.arraycopy(args, 1, halNames, 0, length);
+            mHal.dumpSpecificHals(writer, halNames);
+
+        } else if ("--list-hals".equals(args[0])) {
+            mHal.dumpListHals(writer);
+            return;
+        } else if ("--user-metrics".equals(args[0])) {
+            mUserMetrics.dump(writer);
+        } else if ("--first-user-metrics".equals(args[0])) {
+            mUserMetrics.dumpFirstUserUnlockDuration(writer);
+        } else if ("--help".equals(args[0])) {
+            showDumpHelp(writer);
         } else if (Build.IS_USERDEBUG || Build.IS_ENG) {
             execShellCmd(args, writer);
         } else {
@@ -509,7 +687,62 @@
         }
     }
 
+    private void dumpAllHals(PrintWriter writer) {
+        writer.println("*Dump Vehicle HAL*");
+        writer.println("Vehicle HAL Interface: " + mVehicleInterfaceName);
+        try {
+            // TODO dump all feature flags by creating a dumpable interface
+            mHal.dump(writer);
+        } catch (Exception e) {
+            writer.println("Failed dumping: " + mHal.getClass().getName());
+            e.printStackTrace(writer);
+        }
+    }
+
+    private void showDumpHelp(PrintWriter writer) {
+        writer.println("Car service dump usage:");
+        writer.println("[NO ARG]");
+        writer.println("\t  dumps everything (all services and HALs)");
+        writer.println("--help");
+        writer.println("\t  shows this help");
+        writer.println("--list");
+        writer.println("\t  lists the name of all services");
+        writer.println("--list");
+        writer.println("\t  lists the name of all HAls");
+        writer.println("--services <SVC1> [SVC2] [SVCN]");
+        writer.println("\t  dumps just the specific services, where SVC is just the service class");
+        writer.println("\t  name (like CarUserService)");
+        writer.println("--vms-hal");
+        writer.println("\t  dumps the VMS HAL metrics");
+        writer.println("--hal [HAL1] [HAL2] [HALN]");
+        writer.println("\t  dumps just the specified HALs (or all of them if none specified),");
+        writer.println("\t  where HAL is just the class name (like UserHalService)");
+        writer.println("--user-metrics");
+        writer.println("\t  dumps user switching and stopping metrics ");
+        writer.println("--first-user-metrics");
+        writer.println("\t  dumps how long it took to unlock first user since Android started\n");
+        writer.println("\t  (or -1 if not unlocked)");
+        writer.println("-h");
+        writer.println("\t  shows commands usage (NOTE: commands are not available on USER builds");
+        writer.println("[ANYTHING ELSE]");
+        writer.println("\t  runs the given command (use --h to see the available commands)");
+    }
+
+    @Override
+    public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
+            String[] args, ShellCallback callback, ResultReceiver resultReceiver)
+                    throws RemoteException {
+        new CarShellCommand().exec(this, in, out, err, args, callback, resultReceiver);
+    }
+
+    private void dumpListOfServices(PrintWriter writer) {
+        for (CarServiceBase service : mAllServices) {
+            writer.println(service.getClass().getName());
+        }
+    }
+
     private void dumpAllServices(PrintWriter writer) {
+        writer.println("*Dump all services*");
         for (CarServiceBase service : mAllServices) {
             dumpService(service, writer);
         }
@@ -518,6 +751,26 @@
         }
     }
 
+    private void dumpIndividualServices(PrintWriter writer, String... serviceNames) {
+        for (String serviceName : serviceNames) {
+            writer.println("** Dumping " + serviceName + "\n");
+            CarServiceBase service = getCarServiceBySubstring(serviceName);
+            if (service == null) {
+                writer.println("No such service!");
+            } else {
+                dumpService(service, writer);
+            }
+            writer.println();
+        }
+    }
+
+    @Nullable
+    private CarServiceBase getCarServiceBySubstring(String className) {
+        return Arrays.asList(mAllServices).stream()
+                .filter(s -> s.getClass().getSimpleName().equals(className))
+                .findFirst().orElse(null);
+    }
+
     private void dumpService(CarServiceBase service, PrintWriter writer) {
         try {
             service.dump(writer);
@@ -542,7 +795,7 @@
         mBootTiming.traceEnd();
     }
 
-    private class CarShellCommand {
+    private final class CarShellCommand extends ShellCommand {
         private static final String COMMAND_HELP = "-h";
         private static final String COMMAND_DAY_NIGHT_MODE = "day-night-mode";
         private static final String COMMAND_INJECT_VHAL_EVENT = "inject-vhal-event";
@@ -558,9 +811,13 @@
         private static final String COMMAND_SUSPEND = "suspend";
         private static final String COMMAND_ENABLE_TRUSTED_DEVICE = "enable-trusted-device";
         private static final String COMMAND_REMOVE_TRUSTED_DEVICES = "remove-trusted-devices";
+        private static final String COMMAND_SET_UID_TO_ZONE = "set-zoneid-for-uid";
         private static final String COMMAND_START_FIXED_ACTIVITY_MODE = "start-fixed-activity-mode";
         private static final String COMMAND_STOP_FIXED_ACTIVITY_MODE = "stop-fixed-activity-mode";
+        private static final String COMMAND_ENABLE_FEATURE = "enable-feature";
+        private static final String COMMAND_DISABLE_FEATURE = "disable-feature";
         private static final String COMMAND_INJECT_KEY = "inject-key";
+        private static final String COMMAND_GET_INITIAL_USER_INFO = "get-initial-user-info";
 
         private static final String PARAM_DAY_MODE = "day";
         private static final String PARAM_NIGHT_MODE = "night";
@@ -571,6 +828,34 @@
         private static final String PARAM_QUERY_MODE = "query";
         private static final String PARAM_REBOOT = "reboot";
 
+        private static final int RESULT_OK = 0;
+        private static final int RESULT_ERROR = -1; // Arbitrary value, any non-0 is fine
+
+
+        @Override
+        public int onCommand(String cmd) {
+            if (cmd == null) {
+                onHelp();
+                return RESULT_ERROR;
+            }
+            ArrayList<String> argsList = new ArrayList<>();
+            argsList.add(cmd);
+            String arg = null;
+            do {
+                arg = getNextArg();
+                if (arg != null) {
+                    argsList.add(arg);
+                }
+            } while (arg != null);
+            String[] args = new String[argsList.size()];
+            argsList.toArray(args);
+            return exec(args, getOutPrintWriter());
+        }
+
+        @Override
+        public void onHelp() {
+            dumpHelp(getOutPrintWriter());
+        }
 
         private void dumpHelp(PrintWriter pw) {
             pw.println("Car service commands:");
@@ -607,20 +892,50 @@
                     + " wireless projection");
             pw.println("\t--metrics");
             pw.println("\t  When used with dumpsys, only metrics will be in the dumpsys output.");
+            pw.println("\tset-zoneid-for-uid [zoneid] [uid]");
+            pw.println("\t  Maps the audio zoneid to uid.");
             pw.println("\tstart-fixed-activity displayId packageName activityName");
             pw.println("\t  Start an Activity the specified display as fixed mode");
             pw.println("\tstop-fixed-mode displayId");
             pw.println("\t  Stop fixed Activity mode for the given display. "
                     + "The Activity will not be restarted upon crash.");
+            pw.println("\tenable-feature featureName");
+            pw.println("\t  Enable the requested feature. Change will happen after reboot.");
+            pw.println("\t  This requires root/su.");
+            pw.println("\tdisable-feature featureName");
+            pw.println("\t  Disable the requested feature. Change will happen after reboot");
+            pw.println("\t  This requires root/su.");
             pw.println("\tinject-key [-d display] [-t down_delay_ms] key_code");
             pw.println("\t  inject key down / up event to car service");
             pw.println("\t  display: 0 for main, 1 for cluster. If not specified, it will be 0.");
             pw.println("\t  down_delay_ms: delay from down to up key event. If not specified,");
             pw.println("\t                 it will be 0");
             pw.println("\t  key_code: int key code defined in android KeyEvent");
+            pw.printf("\t%s <REQ_TYPE> [--timeout TIMEOUT_MS]\n", COMMAND_GET_INITIAL_USER_INFO);
+            pw.println("\t  Calls the Vehicle HAL to get the initial boot info, passing the given");
+            pw.println("\t  REQ_TYPE (which could be either FIRST_BOOT, FIRST_BOOT_AFTER_OTA, ");
+            pw.println("\t  COLD_BOOT, RESUME, or any numeric value that would be passed 'as-is')");
+            pw.println("\t  and an optional TIMEOUT_MS to wait for the HAL response (if not set,");
+            pw.println("\t  it will use a  default value).");
         }
 
-        public void exec(String[] args, PrintWriter writer) {
+        private int dumpInvalidArguments(PrintWriter pw) {
+            pw.println("Incorrect number of arguments.");
+            dumpHelp(pw);
+            return RESULT_ERROR;
+        }
+
+        private String runSetZoneIdForUid(String zoneString, String uidString) {
+            int uid = Integer.parseInt(uidString);
+            int zoneId = Integer.parseInt(zoneString);
+            if (!ArrayUtils.contains(mCarAudioService.getAudioZoneIds(), zoneId)) {
+                return  "zoneid " + zoneId + " not found";
+            }
+            mCarAudioService.setZoneIdForUid(zoneId, uid);
+            return null;
+        }
+
+        public int exec(String[] args, PrintWriter writer) {
             String arg = args[0];
             switch (arg) {
                 case COMMAND_HELP:
@@ -640,9 +955,7 @@
                     String zone = PARAM_VEHICLE_PROPERTY_AREA_GLOBAL;
                     String data;
                     if (args.length != 3 && args.length != 4) {
-                        writer.println("Incorrect number of arguments.");
-                        dumpHelp(writer);
-                        break;
+                        return dumpInvalidArguments(writer);
                     } else if (args.length == 4) {
                         // Zoned
                         zone = args[2];
@@ -655,9 +968,7 @@
                     break;
                 case COMMAND_INJECT_ERROR_EVENT:
                     if (args.length != 4) {
-                        writer.println("Incorrect number of arguments");
-                        dumpHelp(writer);
-                        break;
+                        return dumpInvalidArguments(writer);
                     }
                     String errorAreaId = args[2];
                     String errorCode = args[3];
@@ -665,9 +976,7 @@
                     break;
                 case COMMAND_ENABLE_UXR:
                     if (args.length != 2) {
-                        writer.println("Incorrect number of arguments");
-                        dumpHelp(writer);
-                        break;
+                        return dumpInvalidArguments(writer);
                     }
                     boolean enableBlocking = Boolean.valueOf(args[1]);
                     if (mCarPackageManagerService != null) {
@@ -676,9 +985,7 @@
                     break;
                 case COMMAND_GET_DO_ACTIVITIES:
                     if (args.length != 2) {
-                        writer.println("Incorrect number of arguments");
-                        dumpHelp(writer);
-                        break;
+                        return dumpInvalidArguments(writer);
                     }
                     String pkgName = args[1].toLowerCase();
                     if (mCarPackageManagerService != null) {
@@ -706,17 +1013,13 @@
                     break;
                 case COMMAND_PROJECTION_UI_MODE:
                     if (args.length != 2) {
-                        writer.println("Incorrect number of arguments");
-                        dumpHelp(writer);
-                        break;
+                        return dumpInvalidArguments(writer);
                     }
                     mCarProjectionService.setUiMode(Integer.valueOf(args[1]));
                     break;
                 case COMMAND_PROJECTION_AP_TETHERING:
                     if (args.length != 2) {
-                        writer.println("Incorrect number of arguments");
-                        dumpHelp(writer);
-                        break;
+                        return dumpInvalidArguments(writer);
                     }
                     mCarProjectionService.setAccessPointTethering(Boolean.valueOf(args[1]));
                     break;
@@ -730,9 +1033,7 @@
                     break;
                 case COMMAND_ENABLE_TRUSTED_DEVICE:
                     if (args.length != 2) {
-                        writer.println("Incorrect number of arguments");
-                        dumpHelp(writer);
-                        break;
+                        return dumpInvalidArguments(writer);
                     }
                     mCarTrustedDeviceService.getCarTrustAgentEnrollmentService()
                             .setTrustedDeviceEnrollmentEnabled(Boolean.valueOf(args[1]));
@@ -741,8 +1042,17 @@
                     break;
                 case COMMAND_REMOVE_TRUSTED_DEVICES:
                     mCarTrustedDeviceService.getCarTrustAgentEnrollmentService()
-                            .removeAllTrustedDevices(
-                                    mUserManagerHelper.getCurrentForegroundUserId());
+                            .removeAllTrustedDevices(ActivityManager.getCurrentUser());
+                    break;
+                case COMMAND_SET_UID_TO_ZONE:
+                    if (args.length != 3) {
+                        return dumpInvalidArguments(writer);
+                    }
+                    String results = runSetZoneIdForUid(args[1], args[2]);
+                    if (results != null) {
+                        writer.println(results);
+                        dumpHelp(writer);
+                    }
                     break;
                 case COMMAND_START_FIXED_ACTIVITY_MODE:
                     handleStartFixedActivity(args, writer);
@@ -750,19 +1060,33 @@
                 case COMMAND_STOP_FIXED_ACTIVITY_MODE:
                     handleStopFixedMode(args, writer);
                     break;
-
+                case COMMAND_ENABLE_FEATURE:
+                    if (args.length != 2) {
+                        return dumpInvalidArguments(writer);
+                    }
+                    handleEnableDisableFeature(args, writer, /* enable= */ true);
+                    break;
+                case COMMAND_DISABLE_FEATURE:
+                    if (args.length != 2) {
+                        return dumpInvalidArguments(writer);
+                    }
+                    handleEnableDisableFeature(args, writer, /* enable= */ false);
+                    break;
                 case COMMAND_INJECT_KEY:
                     if (args.length < 2) {
-                        writer.println("Incorrect number of arguments");
-                        dumpHelp(writer);
-                        break;
+                        return dumpInvalidArguments(writer);
                     }
                     handleInjectKey(args, writer);
                     break;
+                case COMMAND_GET_INITIAL_USER_INFO:
+                    handleGetInitialUserInfo(args, writer);
+                    break;
                 default:
                     writer.println("Unknown command: \"" + arg + "\"");
                     dumpHelp(writer);
+                    return RESULT_ERROR;
             }
+            return RESULT_OK;
         }
 
         private void handleStartFixedActivity(String[] args, PrintWriter writer) {
@@ -809,6 +1133,48 @@
             mFixedActivityService.stopFixedActivityMode(displayId);
         }
 
+        private void handleEnableDisableFeature(String[] args, PrintWriter writer, boolean enable) {
+            if (Binder.getCallingUid() != Process.ROOT_UID) {
+                writer.println("Only allowed to root/su");
+                return;
+            }
+            String featureName = args[1];
+            long id = Binder.clearCallingIdentity();
+            // no permission check here
+            int r;
+            if (enable) {
+                r = mFeatureController.enableFeature(featureName);
+            } else {
+                r = mFeatureController.disableFeature(featureName);
+            }
+            switch (r) {
+                case Car.FEATURE_REQUEST_SUCCESS:
+                    if (enable) {
+                        writer.println("Enabled feature:" + featureName);
+                    } else {
+                        writer.println("Disabled feature:" + featureName);
+                    }
+                    break;
+                case Car.FEATURE_REQUEST_ALREADY_IN_THE_STATE:
+                    if (enable) {
+                        writer.println("Already enabled:" + featureName);
+                    } else {
+                        writer.println("Already disabled:" + featureName);
+                    }
+                    break;
+                case Car.FEATURE_REQUEST_MANDATORY:
+                    writer.println("Cannot change mandatory feature:" + featureName);
+                    break;
+                case Car.FEATURE_REQUEST_NOT_EXISTING:
+                    writer.println("Non-existing feature:" + featureName);
+                    break;
+                default:
+                    writer.println("Unknown error:" + r);
+                    break;
+            }
+            Binder.restoreCallingIdentity(id);
+        }
+
         private void handleInjectKey(String[] args, PrintWriter writer) {
             int i = 1; // 0 is command itself
             int display = InputHalService.DISPLAY_MAIN;
@@ -863,6 +1229,78 @@
             writer.println("Succeeded");
         }
 
+        private void handleGetInitialUserInfo(String[] args, PrintWriter writer) {
+            if (args.length < 2) {
+                writer.println("Insufficient number of args");
+                return;
+            }
+
+            // Gets the request type
+            String typeArg = args[1];
+            int requestType = UserHalHelper.parseInitialUserInfoRequestType(typeArg);
+
+            int timeout = 1_000;
+            for (int i = 2; i < args.length; i++) {
+                String arg = args[i];
+                switch (arg) {
+                    case "--timeout":
+                        timeout = Integer.parseInt(args[++i]);
+                        break;
+                    default:
+                        writer.println("Invalid option at index " + i + ": " + arg);
+                        return;
+
+                }
+            }
+
+            Log.d(TAG, "handleGetInitialUserInfo(): type=" + requestType + " (" + typeArg
+                    + "), timeout=" + timeout);
+
+            UserHalService userHal = mHal.getUserHal();
+            // TODO(b/146207078): use UserHalHelper to populate it with current users
+            UsersInfo usersInfo = new UsersInfo();
+            CountDownLatch latch = new CountDownLatch(1);
+
+            userHal.getInitialUserInfo(requestType, timeout, usersInfo, (status, resp) -> {
+                try {
+                    Log.d(TAG, "GetUserInfoResponse: status=" + status + ", resp=" + resp);
+                    writer.printf("Status: %s\n", UserHalHelper.halCallbackStatusToString(status));
+                    if (status != HalCallback.STATUS_OK) {
+                        return;
+                    }
+                    writer.printf("Request id: %d\n", resp.requestId);
+                    writer.printf("Action: ");
+                    switch (resp.action) {
+                        case InitialUserInfoResponseAction.DEFAULT:
+                            writer.println("default");
+                            break;
+                        case InitialUserInfoResponseAction.SWITCH:
+                            writer.printf("switch to user %d\n", resp.userToSwitchOrCreate.userId);
+                            break;
+                        case InitialUserInfoResponseAction.CREATE:
+                            writer.printf("create user: name=%s, flags=%d\n", resp.userNameToCreate,
+                                    resp.userToSwitchOrCreate.flags);
+                            break;
+                        default:
+                            writer.printf("unknown (%d)\n", resp.action);
+                            break;
+                    }
+                } finally {
+                    latch.countDown();
+                }
+            });
+
+            try {
+                if (!latch.await(timeout, TimeUnit.MILLISECONDS)) {
+                    writer.printf("HAL didn't respond in %dms\n", timeout);
+                }
+            } catch (InterruptedException e) {
+                Thread.currentThread().interrupt();
+                writer.println("Interrupted waiting for HAL");
+            }
+            return;
+        }
+
         private void forceDayNightMode(String arg, PrintWriter writer) {
             int mode;
             switch (arg) {
@@ -899,10 +1337,12 @@
         private void forceGarageMode(String arg, PrintWriter writer) {
             switch (arg) {
                 case PARAM_ON_MODE:
+                    mSystemInterface.setDisplayState(false);
                     mGarageModeService.forceStartGarageMode();
                     writer.println("Garage mode: " + mGarageModeService.isGarageModeActive());
                     break;
                 case PARAM_OFF_MODE:
+                    mSystemInterface.setDisplayState(true);
                     mGarageModeService.stopAndResetGarageMode();
                     writer.println("Garage mode: " + mGarageModeService.isGarageModeActive());
                     break;
diff --git a/service/src/com/android/car/OccupantAwarenessService.java b/service/src/com/android/car/OccupantAwarenessService.java
new file mode 100644
index 0000000..a2353e0
--- /dev/null
+++ b/service/src/com/android/car/OccupantAwarenessService.java
@@ -0,0 +1,406 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car;
+
+import android.annotation.NonNull;
+import android.car.Car;
+import android.car.occupantawareness.IOccupantAwarenessEventCallback;
+import android.car.occupantawareness.OccupantAwarenessDetection;
+import android.car.occupantawareness.OccupantAwarenessDetection.VehicleOccupantRole;
+import android.car.occupantawareness.SystemStatusEvent;
+import android.car.occupantawareness.SystemStatusEvent.DetectionTypeFlags;
+import android.content.Context;
+import android.hardware.automotive.occupant_awareness.IOccupantAwareness;
+import android.hardware.automotive.occupant_awareness.IOccupantAwarenessClientCallback;
+import android.os.RemoteCallbackList;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.Log;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.io.PrintWriter;
+import java.lang.ref.WeakReference;
+
+/**
+ * A service that listens to an Occupant Awareness Detection system across a HAL boundary and
+ * exposes the data to system clients in Android via a {@link
+ * android.car.occupantawareness.OccupantAwarenessManager}.
+ *
+ * <p>The service exposes the following detections types:
+ *
+ * <h1>Presence Detection</h1>
+ *
+ * Detects whether a person is present for each seat location.
+ *
+ * <h1>Gaze Detection</h1>
+ *
+ * Detects where an occupant is looking and for how long they have been looking at the specified
+ * target.
+ *
+ * <h1>Driver Monitoring</h1>
+ *
+ * Detects whether a driver is looking on or off-road and for how long they have been looking there.
+ */
+public class OccupantAwarenessService
+        extends android.car.occupantawareness.IOccupantAwarenessManager.Stub
+        implements CarServiceBase {
+    private static final String TAG = CarLog.TAG_OAS;
+    private static final boolean DBG = false;
+
+    // HAL service identifier name.
+    @VisibleForTesting
+    static final String OAS_SERVICE_ID =
+            "android.hardware.automotive.occupant_awareness.IOccupantAwareness/default";
+
+    private final Object mLock = new Object();
+    private final Context mContext;
+
+    @GuardedBy("mLock")
+    private IOccupantAwareness mOasHal;
+
+    private final ChangeListenerToHalService mHalListener = new ChangeListenerToHalService(this);
+
+    private class ChangeCallbackList extends RemoteCallbackList<IOccupantAwarenessEventCallback> {
+        private final WeakReference<OccupantAwarenessService> mOasService;
+
+        ChangeCallbackList(OccupantAwarenessService oasService) {
+            mOasService = new WeakReference<>(oasService);
+        }
+
+        /** Handle callback death. */
+        @Override
+        public void onCallbackDied(IOccupantAwarenessEventCallback listener) {
+            Log.i(TAG, "binderDied: " + listener.asBinder());
+
+            OccupantAwarenessService service = mOasService.get();
+            if (service != null) {
+                service.handleClientDisconnected();
+            }
+        }
+    }
+
+    @GuardedBy("mLock")
+    private final ChangeCallbackList mListeners = new ChangeCallbackList(this);
+
+    /** Creates an OccupantAwarenessService instance given a {@link Context}. */
+    public OccupantAwarenessService(Context context) {
+        mContext = context;
+    }
+
+    /** Creates an OccupantAwarenessService instance given a {@link Context}. */
+    @VisibleForTesting
+    OccupantAwarenessService(Context context, IOccupantAwareness oasInterface) {
+        mContext = context;
+        mOasHal = oasInterface;
+    }
+
+    @Override
+    public void init() {
+        logd("Initializing service");
+        connectToHalServiceIfNotConnected();
+    }
+
+    @Override
+    public void release() {
+        logd("Will stop detection and disconnect listeners");
+        stopDetectionGraph();
+        mListeners.kill();
+    }
+
+    @Override
+    public void dump(PrintWriter writer) {
+        writer.println("*OccupantAwarenessService*");
+        writer.println(
+                String.format(
+                        "%s to HAL service", mOasHal == null ? "NOT connected" : "Connected"));
+        writer.println(
+                String.format(
+                        "%d change listeners subscribed.",
+                        mListeners.getRegisteredCallbackCount()));
+    }
+
+    /** Attempts to connect to the HAL service if it is not already connected. */
+    private void connectToHalServiceIfNotConnected() {
+        logd("connectToHalServiceIfNotConnected()");
+
+        synchronized (mLock) {
+            // If already connected, nothing more needs to be done.
+            if (mOasHal != null) {
+                logd("Client is already connected, nothing more to do");
+                return;
+            }
+
+            // Attempt to find the HAL service.
+            logd("Attempting to connect to client at: " + OAS_SERVICE_ID);
+            mOasHal =
+                    android.hardware.automotive.occupant_awareness.IOccupantAwareness.Stub
+                            .asInterface(ServiceManager.getService(OAS_SERVICE_ID));
+
+            if (mOasHal == null) {
+                Log.e(TAG, "Failed to find OAS hal_service at: [" + OAS_SERVICE_ID + "]");
+                return;
+            }
+
+            // Register for callbacks.
+            try {
+                mOasHal.setCallback(mHalListener);
+            } catch (RemoteException e) {
+                mOasHal = null;
+                Log.e(TAG, "Failed to set callback: " + e);
+                return;
+            }
+
+            logd("Successfully connected to hal_service at: [" + OAS_SERVICE_ID + "]");
+        }
+    }
+
+    /** Sends a message via the HAL to start the detection graph. */
+    private void startDetectionGraph() {
+        logd("Attempting to start detection graph");
+
+        // Grab a copy of 'mOasHal' to avoid sitting on the lock longer than is necessary.
+        IOccupantAwareness hal;
+        synchronized (mLock) {
+            hal = mOasHal;
+        }
+
+        if (hal != null) {
+            try {
+                hal.startDetection();
+            } catch (RemoteException e) {
+                Log.e(TAG, "startDetection() HAL invocation failed: " + e, e);
+
+                synchronized (mLock) {
+                    mOasHal = null;
+                }
+            }
+        } else {
+            Log.e(TAG, "No HAL is connected. Cannot request graph start");
+        }
+    }
+
+    /** Sends a message via the HAL to stop the detection graph. */
+    private void stopDetectionGraph() {
+        logd("Attempting to stop detection graph.");
+
+        // Grab a copy of 'mOasHal' to avoid sitting on the lock longer than is necessary.
+        IOccupantAwareness hal;
+        synchronized (mLock) {
+            hal = mOasHal;
+        }
+
+        if (hal != null) {
+            try {
+                hal.stopDetection();
+            } catch (RemoteException e) {
+                Log.e(TAG, "stopDetection() HAL invocation failed: " + e, e);
+
+                synchronized (mLock) {
+                    mOasHal = null;
+                }
+            }
+        } else {
+            Log.e(TAG, "No HAL is connected. Cannot request graph stop");
+        }
+    }
+
+    /**
+     * Gets the vehicle capabilities for a given role.
+     *
+     * <p>Capabilities are static for a given vehicle configuration and need only be queried once
+     * per vehicle. Once capability is determined, clients should query system status to see if the
+     * subsystem is currently ready to serve.
+     *
+     * <p>Requires {@link android.car.Car.PERMISSION_READ_CAR_OCCUPANT_AWARENESS_STATE} read
+     * permissions to access.
+     *
+     * @param role {@link VehicleOccupantRole} to query for.
+     * @return Flags indicating supported capabilities for the role.
+     */
+    public @DetectionTypeFlags int getCapabilityForRole(@VehicleOccupantRole int role) {
+        ICarImpl.assertPermission(mContext, Car.PERMISSION_READ_CAR_OCCUPANT_AWARENESS_STATE);
+
+        connectToHalServiceIfNotConnected();
+
+        // Grab a copy of 'mOasHal' to avoid sitting on the lock longer than is necessary.
+        IOccupantAwareness hal;
+        synchronized (mLock) {
+            hal = mOasHal;
+        }
+
+        if (hal != null) {
+            try {
+                return hal.getCapabilityForRole(role);
+            } catch (RemoteException e) {
+
+                Log.e(TAG, "getCapabilityForRole() HAL invocation failed: " + e, e);
+
+                synchronized (mLock) {
+                    mOasHal = null;
+                }
+
+                return SystemStatusEvent.DETECTION_TYPE_NONE;
+            }
+        } else {
+            Log.e(
+                    TAG,
+                    "getCapabilityForRole(): No HAL interface has been provided. Cannot get"
+                            + " capabilities");
+            return SystemStatusEvent.DETECTION_TYPE_NONE;
+        }
+    }
+
+    /**
+     * Registers a {@link IOccupantAwarenessEventCallback} to be notified for changes in the system
+     * state.
+     *
+     * <p>Requires {@link android.car.Car.PERMISSION_READ_CAR_OCCUPANT_AWARENESS_STATE} read
+     * permissions to access.
+     *
+     * @param listener {@link IOccupantAwarenessEventCallback} listener to register.
+     */
+    @Override
+    public void registerEventListener(@NonNull IOccupantAwarenessEventCallback listener) {
+        ICarImpl.assertPermission(mContext, Car.PERMISSION_READ_CAR_OCCUPANT_AWARENESS_STATE);
+
+        connectToHalServiceIfNotConnected();
+
+        synchronized (mLock) {
+            if (mOasHal == null) {
+                Log.e(TAG, "Attempting to register a listener, but could not connect to HAL.");
+                return;
+            }
+
+            logd("Registering a new listener");
+            mListeners.register(listener);
+
+            // After the first client connects, request that the detection graph start.
+            if (mListeners.getRegisteredCallbackCount() == 1) {
+                startDetectionGraph();
+            }
+        }
+    }
+
+    /**
+     * Unregister the given {@link IOccupantAwarenessEventCallback} listener from receiving events.
+     *
+     * <p>Requires {@link android.car.Car.PERMISSION_READ_CAR_OCCUPANT_AWARENESS_STATE} read
+     * permissions to access.
+     *
+     * @param listener {@link IOccupantAwarenessEventCallback} client to unregister.
+     */
+    @Override
+    public void unregisterEventListener(@NonNull IOccupantAwarenessEventCallback listener) {
+        ICarImpl.assertPermission(mContext, Car.PERMISSION_READ_CAR_OCCUPANT_AWARENESS_STATE);
+
+        connectToHalServiceIfNotConnected();
+
+        synchronized (mLock) {
+            mListeners.unregister(listener);
+        }
+
+        // When the last client disconnects, request that the detection graph stop.
+        handleClientDisconnected();
+    }
+
+    /** Processes a detection event and propagates it to registered clients. */
+    @VisibleForTesting
+    void processStatusEvent(@NonNull SystemStatusEvent statusEvent) {
+        int idx = mListeners.beginBroadcast();
+        while (idx-- > 0) {
+            IOccupantAwarenessEventCallback listener = mListeners.getBroadcastItem(idx);
+            try {
+                listener.onStatusChanged(statusEvent);
+            } catch (RemoteException e) {
+                // It's likely the connection snapped. Let binder death handle the situation.
+                Log.e(TAG, "onStatusChanged() invocation failed: " + e, e);
+            }
+        }
+        mListeners.finishBroadcast();
+    }
+
+    /** Processes a detection event and propagates it to registered clients. */
+    @VisibleForTesting
+    void processDetectionEvent(@NonNull OccupantAwarenessDetection detection) {
+        int idx = mListeners.beginBroadcast();
+        while (idx-- > 0) {
+            IOccupantAwarenessEventCallback listener = mListeners.getBroadcastItem(idx);
+            try {
+                listener.onDetectionEvent(detection);
+            } catch (RemoteException e) {
+                // It's likely the connection snapped. Let binder death handle the situation.
+                Log.e(TAG, "onDetectionEvent() invocation failed: " + e, e);
+            }
+        }
+        mListeners.finishBroadcast();
+    }
+
+    /** Handle client disconnections, possibly stopping the detection graph. */
+    void handleClientDisconnected() {
+        // If the last client disconnects, requests that the graph stops.
+        synchronized (mLock) {
+            if (mListeners.getRegisteredCallbackCount() == 0) {
+                stopDetectionGraph();
+            }
+        }
+    }
+
+    private static void logd(String msg) {
+        if (DBG) {
+            Log.d(TAG, msg);
+        }
+    }
+
+    /**
+     * Class that implements the listener interface and gets called back from the {@link
+     * android.hardware.automotive.occupant_awareness.IOccupantAwarenessClientCallback} across the
+     * binder interface.
+     */
+    private static class ChangeListenerToHalService extends IOccupantAwarenessClientCallback.Stub {
+        private final WeakReference<OccupantAwarenessService> mOasService;
+
+        ChangeListenerToHalService(OccupantAwarenessService oasService) {
+            mOasService = new WeakReference<>(oasService);
+        }
+
+        @Override
+        public void onSystemStatusChanged(int inputDetectionFlags, byte inputStatus) {
+            OccupantAwarenessService service = mOasService.get();
+            if (service != null) {
+                service.processStatusEvent(
+                        OccupantAwarenessUtils.convertToStatusEvent(
+                                inputDetectionFlags, inputStatus));
+            }
+        }
+
+        @Override
+        public void onDetectionEvent(
+                android.hardware.automotive.occupant_awareness.OccupantDetections detections) {
+            OccupantAwarenessService service = mOasService.get();
+            if (service != null) {
+                for (android.hardware.automotive.occupant_awareness.OccupantDetection detection :
+                        detections.detections) {
+                    service.processDetectionEvent(
+                            OccupantAwarenessUtils.convertToDetectionEvent(
+                                    detections.timeStampMillis, detection));
+                }
+            }
+        }
+    }
+}
diff --git a/service/src/com/android/car/OccupantAwarenessUtils.java b/service/src/com/android/car/OccupantAwarenessUtils.java
new file mode 100644
index 0000000..3ecf008
--- /dev/null
+++ b/service/src/com/android/car/OccupantAwarenessUtils.java
@@ -0,0 +1,242 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car;
+
+import android.annotation.Nullable;
+import android.car.occupantawareness.DriverMonitoringDetection;
+import android.car.occupantawareness.GazeDetection;
+import android.car.occupantawareness.OccupantAwarenessDetection;
+import android.car.occupantawareness.OccupantAwarenessDetection.ConfidenceLevel;
+import android.car.occupantawareness.Point3D;
+import android.car.occupantawareness.SystemStatusEvent;
+import android.car.occupantawareness.SystemStatusEvent.DetectionTypeFlags;
+import android.car.occupantawareness.SystemStatusEvent.SystemStatus;
+import android.hardware.automotive.occupant_awareness.IOccupantAwareness;
+import android.hardware.automotive.occupant_awareness.OccupantAwarenessStatus;
+import android.util.Log;
+
+/*package*/ final class OccupantAwarenessUtils {
+    private static final String TAG = "OccupantAwarenessUtils";
+
+    private OccupantAwarenessUtils() {}
+
+    /** Converts status flags into to a SystemStatusEvent. */
+    static SystemStatusEvent convertToStatusEvent(int inputDetectionFlags, byte inputStatus) {
+        // Parse the system status from the hardware layer.
+        @SystemStatus int outputStatus = SystemStatusEvent.SYSTEM_STATUS_NOT_READY;
+
+        switch (inputStatus) {
+            case OccupantAwarenessStatus.READY:
+                outputStatus = SystemStatusEvent.SYSTEM_STATUS_READY;
+                break;
+
+            case OccupantAwarenessStatus.NOT_SUPPORTED:
+                outputStatus = SystemStatusEvent.SYSTEM_STATUS_NOT_SUPPORTED;
+                break;
+
+            case OccupantAwarenessStatus.NOT_INITIALIZED:
+                outputStatus = SystemStatusEvent.SYSTEM_STATUS_NOT_READY;
+                break;
+
+            case OccupantAwarenessStatus.FAILURE:
+                outputStatus = SystemStatusEvent.SYSTEM_STATUS_SYSTEM_FAILURE;
+                break;
+
+            default:
+                Log.e(TAG, "Invalid status code: " + inputStatus);
+                break;
+        }
+
+        // Parse the detection flags from the hardware layer.
+        @DetectionTypeFlags int outputFlags = SystemStatusEvent.DETECTION_TYPE_NONE;
+
+        switch (inputDetectionFlags) {
+            case IOccupantAwareness.CAP_NONE:
+                outputFlags = SystemStatusEvent.DETECTION_TYPE_NONE;
+                break;
+
+            case IOccupantAwareness.CAP_PRESENCE_DETECTION:
+                outputFlags = SystemStatusEvent.DETECTION_TYPE_PRESENCE;
+                break;
+
+            case IOccupantAwareness.CAP_GAZE_DETECTION:
+                outputFlags = SystemStatusEvent.DETECTION_TYPE_GAZE;
+                break;
+
+            case IOccupantAwareness.CAP_DRIVER_MONITORING_DETECTION:
+                outputFlags = SystemStatusEvent.DETECTION_TYPE_DRIVER_MONITORING;
+                break;
+
+            default:
+                Log.e(TAG, "Invalid status code: " + inputDetectionFlags);
+                break;
+        }
+
+        return new SystemStatusEvent(outputStatus, outputFlags);
+    }
+
+    /** Converts an input confidence to a ConfidenceLevel. */
+    static @ConfidenceLevel int convertToConfidenceScore(
+            @android.hardware.automotive.occupant_awareness.ConfidenceLevel int inputConfidence) {
+        switch (inputConfidence) {
+            case android.hardware.automotive.occupant_awareness.ConfidenceLevel.MAX:
+                return OccupantAwarenessDetection.CONFIDENCE_LEVEL_MAX;
+
+            case android.hardware.automotive.occupant_awareness.ConfidenceLevel.HIGH:
+                return OccupantAwarenessDetection.CONFIDENCE_LEVEL_HIGH;
+
+            case android.hardware.automotive.occupant_awareness.ConfidenceLevel.LOW:
+                return OccupantAwarenessDetection.CONFIDENCE_LEVEL_LOW;
+
+            case android.hardware.automotive.occupant_awareness.ConfidenceLevel.NONE:
+            default:
+                return OccupantAwarenessDetection.CONFIDENCE_LEVEL_NONE;
+        }
+    }
+
+    /**
+     * Converts an input point to a Point3D. Returns null if the source vector was null or empty.
+     */
+    static Point3D convertToPoint3D(@Nullable double[] vector) {
+        if (vector != null && vector.length == 3) {
+            return new Point3D(vector[0], vector[1], vector[2]);
+        } else {
+            return null;
+        }
+    }
+
+    /** Converts an input timestamp to milliseconds since boot. */
+    static long convertTimestamp(long inputTimestamp) {
+        return inputTimestamp;
+    }
+
+    /**
+     * Converts an input @android.hardware.automotive.occupant_awareness.Role to a
+     * VehicleOccupantRole.
+     */
+    static @OccupantAwarenessDetection.VehicleOccupantRole int convertToRole(int inputRole) {
+        if (inputRole == android.hardware.automotive.occupant_awareness.Role.INVALID
+                || inputRole == android.hardware.automotive.occupant_awareness.Role.UNKNOWN) {
+            return OccupantAwarenessDetection.VEHICLE_OCCUPANT_NONE;
+        } else if (inputRole == android.hardware.automotive.occupant_awareness.Role.ALL_OCCUPANTS) {
+            return OccupantAwarenessDetection.VEHICLE_OCCUPANT_ALL_OCCUPANTS;
+        }
+
+        int outputRole = OccupantAwarenessDetection.VEHICLE_OCCUPANT_NONE;
+
+        if ((inputRole & android.hardware.automotive.occupant_awareness.Role.DRIVER) > 0) {
+            outputRole |= OccupantAwarenessDetection.VEHICLE_OCCUPANT_DRIVER;
+        }
+
+        if ((inputRole & android.hardware.automotive.occupant_awareness.Role.FRONT_PASSENGER) > 0) {
+            outputRole |= OccupantAwarenessDetection.VEHICLE_OCCUPANT_FRONT_PASSENGER;
+        }
+
+        if ((inputRole & android.hardware.automotive.occupant_awareness.Role.ROW_2_PASSENGER_LEFT)
+                > 0) {
+            outputRole |= OccupantAwarenessDetection.VEHICLE_OCCUPANT_ROW_2_PASSENGER_LEFT;
+        }
+
+        if ((inputRole & android.hardware.automotive.occupant_awareness.Role.ROW_2_PASSENGER_CENTER)
+                > 0) {
+            outputRole |= OccupantAwarenessDetection.VEHICLE_OCCUPANT_ROW_2_PASSENGER_CENTER;
+        }
+
+        if ((inputRole & android.hardware.automotive.occupant_awareness.Role.ROW_2_PASSENGER_RIGHT)
+                > 0) {
+            outputRole |= OccupantAwarenessDetection.VEHICLE_OCCUPANT_ROW_2_PASSENGER_RIGHT;
+        }
+
+        if ((inputRole & android.hardware.automotive.occupant_awareness.Role.ROW_3_PASSENGER_LEFT)
+                > 0) {
+            outputRole |= OccupantAwarenessDetection.VEHICLE_OCCUPANT_ROW_3_PASSENGER_LEFT;
+        }
+
+        if ((inputRole & android.hardware.automotive.occupant_awareness.Role.ROW_3_PASSENGER_CENTER)
+                > 0) {
+            outputRole |= OccupantAwarenessDetection.VEHICLE_OCCUPANT_ROW_3_PASSENGER_CENTER;
+        }
+
+        if ((inputRole & android.hardware.automotive.occupant_awareness.Role.ROW_3_PASSENGER_RIGHT)
+                > 0) {
+            outputRole |= OccupantAwarenessDetection.VEHICLE_OCCUPANT_ROW_3_PASSENGER_RIGHT;
+        }
+
+        return outputRole;
+    }
+
+    /** Converts an input detection to a GazeDetection. */
+    static GazeDetection convertToGazeDetection(
+            android.hardware.automotive.occupant_awareness.GazeDetection inputDetection) {
+
+        return new GazeDetection(
+                convertToConfidenceScore(inputDetection.gazeConfidence),
+                convertToPoint3D(inputDetection.headPosition),
+                convertToPoint3D(inputDetection.headPosition),
+                convertToPoint3D(inputDetection.headAngleUnitVector),
+                convertToPoint3D(inputDetection.gazeAngleUnitVector),
+                inputDetection.gazeTarget,
+                inputDetection.timeOnTargetMillis);
+    }
+
+    /** Converts an input detection to a DriverMonitoringDetection. */
+    static DriverMonitoringDetection convertToDriverMonitoringDetection(
+            android.hardware.automotive.occupant_awareness.DriverMonitoringDetection
+                    inputDetection) {
+        return new DriverMonitoringDetection(
+                convertToConfidenceScore(inputDetection.confidenceScore),
+                inputDetection.isLookingOnRoad,
+                inputDetection.gazeDurationMillis);
+    }
+
+    /** Converts a PresenceDetection to a boolean indicating if an occupant was present. */
+    static boolean convertToPresence(
+            android.hardware.automotive.occupant_awareness.PresenceDetection inputDetection) {
+        return inputDetection.isOccupantDetected;
+    }
+
+    /** Converts an OccupantDetection to OccupantAwarenessDetection. */
+    static OccupantAwarenessDetection convertToDetectionEvent(
+            long timestamp,
+            android.hardware.automotive.occupant_awareness.OccupantDetection inputDetection) {
+        // Populate presence, if data is available for this frame.
+        boolean isPresent = false;
+        if (inputDetection.presenceData != null && inputDetection.presenceData.length > 0) {
+            isPresent = convertToPresence(inputDetection.presenceData[0]);
+        }
+
+        // Populate gaze, if data is available for this frame.
+        GazeDetection gazeDetection = null;
+        if (inputDetection.gazeData != null && inputDetection.gazeData.length > 0) {
+            gazeDetection = convertToGazeDetection(inputDetection.gazeData[0]);
+        }
+
+        // Populate driver monitoring, if data is available for this frame.
+        DriverMonitoringDetection driverMonitoringDetection = null;
+        if (inputDetection.attentionData != null && inputDetection.attentionData.length > 0) {
+            driverMonitoringDetection =
+                    convertToDriverMonitoringDetection(inputDetection.attentionData[0]);
+        }
+
+        return new OccupantAwarenessDetection(
+                convertToRole(inputDetection.role),
+                timestamp,
+                isPresent,
+                gazeDetection,
+                driverMonitoringDetection);
+    }
+}
diff --git a/service/src/com/android/car/PerUserCarService.java b/service/src/com/android/car/PerUserCarService.java
index dec2b3a..cb13649 100644
--- a/service/src/com/android/car/PerUserCarService.java
+++ b/service/src/com/android/car/PerUserCarService.java
@@ -17,8 +17,8 @@
 
 import android.app.Service;
 import android.car.ICarBluetoothUserService;
-import android.car.ICarUserService;
 import android.car.ILocationManagerProxy;
+import android.car.IPerUserCarService;
 import android.content.Intent;
 import android.os.IBinder;
 import android.util.Log;
@@ -33,20 +33,20 @@
  */
 public class PerUserCarService extends Service {
     private static final boolean DBG = true;
-    private static final String TAG = "CarUserService";
+    private static final String TAG = "PerUserCarService";
     private volatile CarBluetoothUserService mCarBluetoothUserService;
     private volatile LocationManagerProxy mLocationManagerProxy;
-    private CarUserServiceBinder mCarUserServiceBinder;
+    private PerUserCarServiceBinder mPerUserCarServiceBinder;
 
     @Override
     public IBinder onBind(Intent intent) {
         if (DBG) {
             Log.d(TAG, "onBind()");
         }
-        if (mCarUserServiceBinder == null) {
+        if (mPerUserCarServiceBinder == null) {
             Log.e(TAG, "UserSvcBinder null");
         }
-        return mCarUserServiceBinder;
+        return mPerUserCarServiceBinder;
     }
 
     @Override
@@ -62,7 +62,7 @@
         if (DBG) {
             Log.d(TAG, "onCreate()");
         }
-        mCarUserServiceBinder = new CarUserServiceBinder();
+        mPerUserCarServiceBinder = new PerUserCarServiceBinder();
         mCarBluetoothUserService = new CarBluetoothUserService(this);
         mLocationManagerProxy = new LocationManagerProxy(this);
         super.onCreate();
@@ -73,14 +73,14 @@
         if (DBG) {
             Log.d(TAG, "onDestroy()");
         }
-        mCarUserServiceBinder = null;
+        mPerUserCarServiceBinder = null;
     }
 
     /**
      * Other Services in CarService can create their own Binder interface and receive that interface
-     * through this CarUserService binder.
+     * through this PerUserCarService binder.
      */
-    private final class CarUserServiceBinder extends ICarUserService.Stub {
+    private final class PerUserCarServiceBinder extends IPerUserCarService.Stub {
         @Override
         public ICarBluetoothUserService getBluetoothUserService() {
             return mCarBluetoothUserService;
diff --git a/service/src/com/android/car/PerUserCarServiceHelper.java b/service/src/com/android/car/PerUserCarServiceHelper.java
index 73e41cf..a4148aa 100644
--- a/service/src/com/android/car/PerUserCarServiceHelper.java
+++ b/service/src/com/android/car/PerUserCarServiceHelper.java
@@ -16,22 +16,21 @@
 
 package com.android.car;
 
-import android.car.ICarUserService;
-import android.content.BroadcastReceiver;
+import android.car.IPerUserCarService;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
-import android.content.IntentFilter;
 import android.content.ServiceConnection;
 import android.os.IBinder;
 import android.os.UserHandle;
 import android.util.Log;
 
+import com.android.car.user.CarUserService;
 import com.android.internal.annotations.GuardedBy;
 
 import java.io.PrintWriter;
-import java.util.List;
 import java.util.ArrayList;
+import java.util.List;
 
 /**
  * A Helper class that helps with the following:
@@ -42,68 +41,56 @@
 public class PerUserCarServiceHelper implements CarServiceBase {
     private static final String TAG = "PerUserCarSvcHelper";
     private static boolean DBG = false;
-    private Context mContext;
-    private ICarUserService mCarUserService;
+    private final Context mContext;
+    private final CarUserService mUserService;
+    private IPerUserCarService mPerUserCarService;
     // listener to call on a ServiceConnection to PerUserCarService
     private List<ServiceCallback> mServiceCallbacks;
-    private UserSwitchBroadcastReceiver mReceiver;
-    private IntentFilter mUserSwitchFilter;
     private static final String EXTRA_USER_HANDLE = "android.intent.extra.user_handle";
     private final Object mServiceBindLock = new Object();
     @GuardedBy("mServiceBindLock")
     private boolean mBound = false;
 
-    public PerUserCarServiceHelper(Context context) {
+    public PerUserCarServiceHelper(Context context, CarUserService userService) {
         mContext = context;
         mServiceCallbacks = new ArrayList<>();
-        mReceiver = new UserSwitchBroadcastReceiver();
-        setupUserSwitchListener();
+        mUserService = userService;
+        mUserService.addUserCallback(mUserCallback);
     }
 
     @Override
-    public synchronized void init() {
-        bindToPerUserCarService();
-    }
-
-    @Override
-    public synchronized void release() {
-        unbindFromPerUserCarService();
-    }
-
-    /**
-     * Setting up the intent filter for
-     * 2. UserSwitch events
-     */
-    private void setupUserSwitchListener() {
-        mUserSwitchFilter = new IntentFilter();
-        mUserSwitchFilter.addAction(Intent.ACTION_USER_SWITCHED);
-        mContext.registerReceiver(mReceiver, mUserSwitchFilter);
-        if (DBG) {
-            Log.d(TAG, "UserSwitch Listener Registered");
+    public void init() {
+        synchronized (mServiceBindLock) {
+            bindToPerUserCarService();
         }
     }
 
-    /**
-     * UserSwitchBroadcastReceiver receives broadcasts on User account switches.
-     */
-    public class UserSwitchBroadcastReceiver extends BroadcastReceiver {
+    @Override
+    public void release() {
+        synchronized (mServiceBindLock) {
+            unbindFromPerUserCarService();
+            mUserService.removeUserCallback(mUserCallback);
+        }
+    }
+
+    private final CarUserService.UserCallback mUserCallback = new CarUserService.UserCallback() {
+
         @Override
-        public void onReceive(Context context, Intent intent) {
+        public void onUserLockChanged(int userId, boolean unlocked) {
+            // Do Nothing
+        }
+
+        @Override
+        public void onSwitchUser(int userId) {
             List<ServiceCallback> callbacks;
             if (DBG) {
-                Log.d(TAG, "User Switch Happened");
-                boolean userSwitched = intent.getAction().equals(
-                        Intent.ACTION_USER_SWITCHED);
-
-                int user = intent.getExtras().getInt(EXTRA_USER_HANDLE);
-                if (userSwitched) {
-                    Log.d(TAG, "New User " + user);
-                }
+                Log.d(TAG, "User Switch Happened. New User" + userId);
             }
+
             // Before unbinding, notify the callbacks about unbinding from the service
             // so the callbacks can clean up their state through the binder before the service is
             // killed.
-            synchronized (this) {
+            synchronized (mServiceBindLock) {
                 // copy the callbacks
                 callbacks = new ArrayList<>(mServiceCallbacks);
             }
@@ -116,7 +103,7 @@
             // bind to the service running as the new user
             bindToPerUserCarService();
         }
-    }
+    };
 
     /**
      * ServiceConnection to detect connecting/disconnecting to {@link PerUserCarService}
@@ -129,15 +116,15 @@
             if (DBG) {
                 Log.d(TAG, "Connected to User Service");
             }
-            mCarUserService = ICarUserService.Stub.asInterface(service);
-            if (mCarUserService != null) {
-                synchronized (this) {
+            mPerUserCarService = IPerUserCarService.Stub.asInterface(service);
+            if (mPerUserCarService != null) {
+                synchronized (mServiceBindLock) {
                     // copy the callbacks
                     callbacks = new ArrayList<>(mServiceCallbacks);
                 }
                 // call them
                 for (ServiceCallback callback : callbacks) {
-                    callback.onServiceConnected(mCarUserService);
+                    callback.onServiceConnected(mPerUserCarService);
                 }
             }
         }
@@ -148,7 +135,7 @@
             if (DBG) {
                 Log.d(TAG, "Disconnected from User Service");
             }
-            synchronized (this) {
+            synchronized (mServiceBindLock) {
                 // copy the callbacks
                 callbacks = new ArrayList<>(mServiceCallbacks);
             }
@@ -160,9 +147,8 @@
     };
 
     /**
-     * Bind to the CarUserService {@link PerUserCarService} which is created to run as the Current
-     * User.
-     *
+     * Bind to the PerUserCarService {@link PerUserCarService} which is created to run as the
+     * Current User.
      */
     private void bindToPerUserCarService() {
         if (DBG) {
@@ -207,7 +193,7 @@
             if (DBG) {
                 Log.d(TAG, "Registering PerUserCarService Listener");
             }
-            synchronized (this) {
+            synchronized (mServiceBindLock) {
                 mServiceCallbacks.add(listener);
             }
         }
@@ -222,7 +208,7 @@
             Log.d(TAG, "Unregistering PerUserCarService Listener");
         }
         if (listener != null) {
-            synchronized (this) {
+            synchronized (mServiceBindLock) {
                 mServiceCallbacks.remove(listener);
             }
         }
@@ -232,11 +218,21 @@
      * Listener to the PerUserCarService connection status that clients need to implement.
      */
     public interface ServiceCallback {
-        // When Service Connects
-        void onServiceConnected(ICarUserService carUserService);
-        // Before an unbind call is going to be made.
+        /**
+         * Invoked when a service connects.
+         *
+         * @param perUserCarService the instance of IPerUserCarService.
+         */
+        void onServiceConnected(IPerUserCarService perUserCarService);
+
+        /**
+         * Invoked before an unbind call is going to be made.
+         */
         void onPreUnbind();
-        // When Service crashed or disconnected
+
+        /**
+         * Invoked when a service is crashed or disconnected.
+         */
         void onServiceDisconnected();
     }
 
@@ -244,7 +240,4 @@
     public synchronized void dump(PrintWriter writer) {
 
     }
-
-
 }
-
diff --git a/service/src/com/android/car/Standards.kt b/service/src/com/android/car/Standards.kt
new file mode 100644
index 0000000..272b44f
--- /dev/null
+++ b/service/src/com/android/car/Standards.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car
+
+/**
+ * Identical to [also]. Use to communicate intent of guarding against null. Compatible with
+ * `?:` (elvis operator) if else block is needed.
+ *
+ * Example:
+ * ```
+ * array.firstOrNull()?.guard { Log.d(TAG, "$it is guaranteed to not be null here") }
+ * ```
+ *
+ */
+internal inline fun <T> T.guard(block: (T) -> Unit): T {
+    return this.also(block)
+}
\ No newline at end of file
diff --git a/service/src/com/android/car/SystemActivityMonitoringService.java b/service/src/com/android/car/SystemActivityMonitoringService.java
index d192727..b2f2863 100644
--- a/service/src/com/android/car/SystemActivityMonitoringService.java
+++ b/service/src/com/android/car/SystemActivityMonitoringService.java
@@ -268,6 +268,11 @@
             Log.e(CarLog.TAG_AM, "cannot getTasks", e);
             return;
         }
+
+        if (infos == null) {
+            return;
+        }
+
         int focusedStackId = INVALID_STACK_ID;
         try {
             // TODO(b/66955160): Someone on the Auto-team should probably re-work the code in the
diff --git a/service/src/com/android/car/VmsPublisherService.java b/service/src/com/android/car/VmsPublisherService.java
deleted file mode 100644
index def10dd..0000000
--- a/service/src/com/android/car/VmsPublisherService.java
+++ /dev/null
@@ -1,249 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.car;
-
-import android.car.vms.IVmsPublisherClient;
-import android.car.vms.IVmsPublisherService;
-import android.car.vms.IVmsSubscriberClient;
-import android.car.vms.VmsLayer;
-import android.car.vms.VmsLayersOffering;
-import android.car.vms.VmsSubscriptionState;
-import android.content.Context;
-import android.os.Binder;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.util.ArrayMap;
-import android.util.Log;
-
-import com.android.car.stats.CarStatsService;
-import com.android.car.vms.VmsBrokerService;
-import com.android.car.vms.VmsClientManager;
-import com.android.internal.annotations.VisibleForTesting;
-
-import java.io.PrintWriter;
-import java.util.Collections;
-import java.util.Map;
-import java.util.Set;
-import java.util.function.IntSupplier;
-
-
-/**
- * Receives HAL updates by implementing VmsHalService.VmsHalListener.
- * Binds to publishers and configures them to use this service.
- * Notifies publishers of subscription changes.
- */
-public class VmsPublisherService implements CarServiceBase {
-    private static final boolean DBG = false;
-    private static final String TAG = "VmsPublisherService";
-
-    private final Context mContext;
-    private final CarStatsService mStatsService;
-    private final VmsBrokerService mBrokerService;
-    private final VmsClientManager mClientManager;
-    private final IntSupplier mGetCallingUid;
-    private final Map<String, PublisherProxy> mPublisherProxies = Collections.synchronizedMap(
-            new ArrayMap<>());
-
-    VmsPublisherService(
-            Context context,
-            CarStatsService statsService,
-            VmsBrokerService brokerService,
-            VmsClientManager clientManager) {
-        this(context, statsService, brokerService, clientManager, Binder::getCallingUid);
-    }
-
-    @VisibleForTesting
-    VmsPublisherService(
-            Context context,
-            CarStatsService statsService,
-            VmsBrokerService brokerService,
-            VmsClientManager clientManager,
-            IntSupplier getCallingUid) {
-        mContext = context;
-        mStatsService = statsService;
-        mBrokerService = brokerService;
-        mClientManager = clientManager;
-        mGetCallingUid = getCallingUid;
-
-        mClientManager.setPublisherService(this);
-    }
-
-    @Override
-    public void init() {}
-
-    @Override
-    public void release() {
-        mPublisherProxies.values().forEach(PublisherProxy::unregister);
-        mPublisherProxies.clear();
-    }
-
-    @Override
-    public void dump(PrintWriter writer) {
-        writer.println("*" + getClass().getSimpleName() + "*");
-        writer.println("mPublisherProxies: " + mPublisherProxies.size());
-    }
-
-    /**
-     * Called when a client connection is established or re-established.
-     *
-     * @param publisherName    String that uniquely identifies the service and user.
-     * @param publisherClient The client's communication channel.
-     */
-    public void onClientConnected(String publisherName, IVmsPublisherClient publisherClient) {
-        if (DBG) Log.d(TAG, "onClientConnected: " + publisherName);
-        IBinder publisherToken = new Binder();
-
-        PublisherProxy publisherProxy = new PublisherProxy(publisherName, publisherToken,
-                publisherClient);
-        publisherProxy.register();
-        try {
-            publisherClient.setVmsPublisherService(publisherToken, publisherProxy);
-        } catch (Throwable e) {
-            Log.e(TAG, "unable to configure publisher: " + publisherName, e);
-            return;
-        }
-
-        PublisherProxy existingProxy = mPublisherProxies.put(publisherName, publisherProxy);
-        if (existingProxy != null) {
-            existingProxy.unregister();
-        }
-    }
-
-    /**
-     * Called when a client connection is terminated.
-     *
-     * @param publisherName String that uniquely identifies the service and user.
-     */
-    public void onClientDisconnected(String publisherName) {
-        if (DBG) Log.d(TAG, "onClientDisconnected: " + publisherName);
-        PublisherProxy proxy = mPublisherProxies.remove(publisherName);
-        if (proxy != null) {
-            proxy.unregister();
-        }
-    }
-
-    private class PublisherProxy extends IVmsPublisherService.Stub implements
-            VmsBrokerService.PublisherListener {
-        private final String mName;
-        private final IBinder mToken;
-        private final IVmsPublisherClient mPublisherClient;
-        private boolean mConnected;
-
-        PublisherProxy(String name, IBinder token,
-                IVmsPublisherClient publisherClient) {
-            this.mName = name;
-            this.mToken = token;
-            this.mPublisherClient = publisherClient;
-        }
-
-        void register() {
-            if (DBG) Log.d(TAG, "register: " + mName);
-            mConnected = true;
-            mBrokerService.addPublisherListener(this);
-        }
-
-        void unregister() {
-            if (DBG) Log.d(TAG, "unregister: " + mName);
-            mConnected = false;
-            mBrokerService.removePublisherListener(this);
-            mBrokerService.removeDeadPublisher(mToken);
-        }
-
-        @Override
-        public void setLayersOffering(IBinder token, VmsLayersOffering offering) {
-            assertPermission(token);
-            mBrokerService.setPublisherLayersOffering(token, offering);
-        }
-
-        @Override
-        public void publish(IBinder token, VmsLayer layer, int publisherId, byte[] payload) {
-            assertPermission(token);
-            if (DBG) {
-                Log.d(TAG, String.format("Publishing to %s as %d (%s)", layer, publisherId, mName));
-            }
-
-            if (layer == null) {
-                return;
-            }
-
-            int payloadLength = payload != null ? payload.length : 0;
-            mStatsService.getVmsClientLogger(mGetCallingUid.getAsInt())
-                    .logPacketSent(layer, payloadLength);
-
-            // Send the message to subscribers
-            Set<IVmsSubscriberClient> listeners =
-                    mBrokerService.getSubscribersForLayerFromPublisher(layer, publisherId);
-
-            if (DBG) Log.d(TAG, String.format("Number of subscribers: %d", listeners.size()));
-
-            if (listeners.size() == 0) {
-                // A negative UID signals that the packet had zero subscribers
-                mStatsService.getVmsClientLogger(-1)
-                        .logPacketDropped(layer, payloadLength);
-            }
-
-            for (IVmsSubscriberClient listener : listeners) {
-                int subscriberUid = mClientManager.getSubscriberUid(listener);
-                try {
-                    listener.onVmsMessageReceived(layer, payload);
-                    mStatsService.getVmsClientLogger(subscriberUid)
-                            .logPacketReceived(layer, payloadLength);
-                } catch (RemoteException ex) {
-                    mStatsService.getVmsClientLogger(subscriberUid)
-                            .logPacketDropped(layer, payloadLength);
-                    String subscriberName = mClientManager.getPackageName(listener);
-                    Log.e(TAG, String.format("Unable to publish to listener: %s", subscriberName));
-                }
-            }
-        }
-
-        @Override
-        public VmsSubscriptionState getSubscriptions() {
-            assertPermission();
-            return mBrokerService.getSubscriptionState();
-        }
-
-        @Override
-        public int getPublisherId(byte[] publisherInfo) {
-            assertPermission();
-            return mBrokerService.getPublisherId(publisherInfo);
-        }
-
-        @Override
-        public void onSubscriptionChange(VmsSubscriptionState subscriptionState) {
-            try {
-                mPublisherClient.onVmsSubscriptionChange(subscriptionState);
-            } catch (Throwable e) {
-                Log.e(TAG, String.format("Unable to send subscription state to: %s", mName), e);
-            }
-        }
-
-        private void assertPermission(IBinder publisherToken) {
-            if (mToken != publisherToken) {
-                throw new SecurityException("Invalid publisher token");
-            }
-            assertPermission();
-        }
-
-        private void assertPermission() {
-            if (!mConnected) {
-                throw new SecurityException("Publisher has been disconnected");
-            }
-            ICarImpl.assertVmsPublisherPermission(mContext);
-        }
-    }
-}
diff --git a/service/src/com/android/car/VmsPublishersInfo.java b/service/src/com/android/car/VmsPublishersInfo.java
index 94bc794..b99fc1e 100644
--- a/service/src/com/android/car/VmsPublishersInfo.java
+++ b/service/src/com/android/car/VmsPublishersInfo.java
@@ -16,6 +16,7 @@
 
 package com.android.car;
 
+import android.annotation.Nullable;
 import android.util.ArrayMap;
 
 import com.android.internal.annotations.GuardedBy;
@@ -88,8 +89,21 @@
      */
     public byte[] getPublisherInfo(int publisherId) {
         synchronized (mLock) {
+            byte[] result = getPublisherInfoOrNull(publisherId);
+            return result != null ? result : EMPTY_RESPONSE;
+        }
+    }
+
+    /**
+     * Returns the publisher info associated with the given publisher ID.
+     * @param publisherId Publisher ID to query.
+     * @return Publisher info associated with the ID, or null if publisher ID is unknown.
+     */
+    @Nullable
+    public byte[] getPublisherInfoOrNull(int publisherId) {
+        synchronized (mLock) {
             return publisherId < 1 || publisherId > mPublishersInfo.size()
-                    ? EMPTY_RESPONSE
+                    ? null
                     : mPublishersInfo.get(publisherId - 1).getInfo();
         }
     }
diff --git a/service/src/com/android/car/VmsRouting.java b/service/src/com/android/car/VmsRouting.java
deleted file mode 100644
index 97a928f..0000000
--- a/service/src/com/android/car/VmsRouting.java
+++ /dev/null
@@ -1,307 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.car;
-
-import android.car.vms.IVmsSubscriberClient;
-import android.car.vms.VmsAssociatedLayer;
-import android.car.vms.VmsLayer;
-import android.car.vms.VmsOperationRecorder;
-import android.car.vms.VmsSubscriptionState;
-import android.os.IBinder;
-import android.util.ArrayMap;
-import android.util.ArraySet;
-import android.util.Pair;
-
-import com.android.internal.annotations.GuardedBy;
-
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Objects;
-import java.util.Set;
-import java.util.stream.Collectors;
-
-/**
- * Manages all the VMS subscriptions:
- * + Subscriptions to data messages of individual layer + version.
- * + Subscriptions to all data messages.
- * + HAL subscriptions to layer + version.
- */
-
-public class VmsRouting {
-    private final Object mLock = new Object();
-
-    @GuardedBy("mLock")
-    private Map<IBinder, IVmsSubscriberClient> mSubscribers = new ArrayMap<>();
-
-    @GuardedBy("mLock")
-    private Set<IBinder> mPassiveSubscribers = new ArraySet<>();
-
-    @GuardedBy("mLock")
-    private Map<VmsLayer, Set<IBinder>> mLayerSubscriptions = new ArrayMap<>();
-
-    @GuardedBy("mLock")
-    private Map<VmsLayer, Map<Integer, Set<IBinder>>> mLayerSubscriptionsToPublishers =
-            new ArrayMap<>();
-
-    @GuardedBy("mLock")
-    private int mSequenceNumber = 0;
-
-    /**
-     * Add a passive subscription to all data messages.
-     *
-     * Passive subscribers receive all published data messages, but are not reflected in the
-     * subscription state sent to publishers.
-     *
-     * @param subscriber VMS subscriber to add
-     */
-    public void addSubscription(IVmsSubscriberClient subscriber) {
-        int sequenceNumber;
-        synchronized (mLock) {
-            if (!mPassiveSubscribers.add(addSubscriber(subscriber))) {
-                return;
-            }
-            sequenceNumber = mSequenceNumber;
-        }
-        VmsOperationRecorder.get().addPromiscuousSubscription(sequenceNumber);
-    }
-
-    /**
-     * Remove a passive subscription to all data messages.
-     *
-     * @param subscriber VMS subscriber to remove
-     */
-    public void removeSubscription(IVmsSubscriberClient subscriber) {
-        int sequenceNumber;
-        synchronized (mLock) {
-            if (!mPassiveSubscribers.remove(subscriber.asBinder())) {
-                return;
-            }
-            sequenceNumber = mSequenceNumber;
-        }
-        VmsOperationRecorder.get().removePromiscuousSubscription(sequenceNumber);
-    }
-
-    /**
-     * Add a subscription to data messages from a VMS layer.
-     *
-     * @param subscriber VMS subscriber to add
-     * @param layer      the layer to subscribe to
-     */
-    public void addSubscription(IVmsSubscriberClient subscriber, VmsLayer layer) {
-        int sequenceNumber;
-        synchronized (mLock) {
-            Set<IBinder> subscribers =
-                    mLayerSubscriptions.computeIfAbsent(layer, k -> new ArraySet<>());
-            if (!subscribers.add(addSubscriber(subscriber))) {
-                return;
-            }
-            sequenceNumber = ++mSequenceNumber;
-        }
-        VmsOperationRecorder.get().addSubscription(sequenceNumber, layer);
-    }
-
-    /**
-     * Remove a subscription to data messages from a VMS layer.
-     *
-     * @param subscriber VMS subscriber to remove
-     * @param layer      the subscribed layer
-     */
-    public void removeSubscription(IVmsSubscriberClient subscriber, VmsLayer layer) {
-        int sequenceNumber;
-        synchronized (mLock) {
-            Set<IBinder> subscribers =
-                    mLayerSubscriptions.getOrDefault(layer, Collections.emptySet());
-            if (!subscribers.remove(subscriber.asBinder())) {
-                return;
-            }
-            sequenceNumber = ++mSequenceNumber;
-
-            if (subscribers.isEmpty()) {
-                // If a layer has no subscribers, remove it
-                mLayerSubscriptions.remove(layer);
-            }
-        }
-        VmsOperationRecorder.get().removeSubscription(sequenceNumber, layer);
-    }
-
-    /**
-     * Add a subscription to data messages from a VMS layer and a specific publisher.
-     *
-     * @param subscriber  VMS subscriber to add
-     * @param layer       the layer to subscribe to
-     * @param publisherId the publisher ID
-     */
-    public void addSubscription(IVmsSubscriberClient subscriber, VmsLayer layer, int publisherId) {
-        int sequenceNumber;
-        synchronized (mLock) {
-            Set<IBinder> subscribers =
-                    mLayerSubscriptionsToPublishers.computeIfAbsent(layer, k -> new ArrayMap<>())
-                            .computeIfAbsent(publisherId, k -> new ArraySet<>());
-            if (!subscribers.add(addSubscriber(subscriber))) {
-                return;
-            }
-            sequenceNumber = ++mSequenceNumber;
-        }
-        VmsOperationRecorder.get().addSubscription(sequenceNumber, layer);
-    }
-
-    /**
-     * Remove a subscription to data messages from a VMS layer and a specific publisher.
-     *
-     * @param subscriber  VMS subscriber to remove
-     * @param layer       the subscribed layer
-     * @param publisherId the publisher ID
-     */
-    public void removeSubscription(IVmsSubscriberClient subscriber,
-            VmsLayer layer,
-            int publisherId) {
-        int sequenceNumber;
-        synchronized (mLock) {
-            Map<Integer, Set<IBinder>> subscribersToPublishers =
-                    mLayerSubscriptionsToPublishers.getOrDefault(layer, Collections.emptyMap());
-
-            Set<IBinder> subscribers =
-                    subscribersToPublishers.getOrDefault(publisherId, Collections.emptySet());
-            if (!subscribers.remove(subscriber.asBinder())) {
-                return;
-            }
-            sequenceNumber = ++mSequenceNumber;
-
-            // If a publisher has no subscribers, remove it
-            if (subscribers.isEmpty()) {
-                subscribersToPublishers.remove(publisherId);
-            }
-
-            // If a layer has no subscribers, remove it
-            if (subscribersToPublishers.isEmpty()) {
-                mLayerSubscriptionsToPublishers.remove(layer);
-            }
-        }
-        VmsOperationRecorder.get().removeSubscription(sequenceNumber, layer);
-    }
-
-    /**
-     * Remove all of a subscriber's subscriptions.
-     *
-     * @param subscriber VMS subscriber to remove
-     * @return {@code true} if the subscription state was modified
-     */
-    public boolean removeDeadSubscriber(IVmsSubscriberClient subscriber) {
-        IBinder subscriberBinder = subscriber.asBinder();
-        synchronized (mLock) {
-            int startSequenceNumber = mSequenceNumber;
-
-            // Remove the subscriber from the loggers.
-            removeSubscription(subscriber);
-
-            // Remove the subscriber from all layer-based subscriptions.
-            mLayerSubscriptions.entrySet().stream()
-                    .filter(e -> e.getValue().contains(subscriberBinder))
-                    .map(Map.Entry::getKey)
-                    .collect(Collectors.toSet())
-                    .forEach(layer -> removeSubscription(subscriber, layer));
-
-            // Remove the subscriber from all publisher-based subscriptions.
-            mLayerSubscriptionsToPublishers.entrySet().stream()
-                    .flatMap(layer -> layer.getValue().entrySet().stream()
-                            .filter(publisher -> publisher.getValue().contains(subscriberBinder))
-                            .map(publisher -> Pair.create(layer.getKey(), publisher.getKey())))
-                    .collect(Collectors.toSet())
-                    .forEach(layerAndPublisher -> removeSubscription(subscriber,
-                            layerAndPublisher.first, layerAndPublisher.second));
-
-            // Remove the subscriber from the subscriber index
-            mSubscribers.remove(subscriberBinder);
-
-            // If the sequence number was updated, then the subscription state was modified
-            return startSequenceNumber != mSequenceNumber;
-        }
-    }
-
-    /**
-     * Returns a list of all the subscribers a data message should be delivered to. This includes
-     * subscribers that subscribed to this layer from all publishers, subscribed to this layer
-     * from a specific publisher, and passive subscribers.
-     *
-     * @param layer       The layer of the message.
-     * @param publisherId the ID of the client that published the message to be routed.
-     * @return a list of the subscribers.
-     */
-    public Set<IVmsSubscriberClient> getSubscribersForLayerFromPublisher(VmsLayer layer,
-            int publisherId) {
-        Set<IBinder> subscribers = new HashSet<>();
-        synchronized (mLock) {
-            // Add the passive subscribers
-            subscribers.addAll(mPassiveSubscribers);
-
-            // Add the subscribers which explicitly subscribed to this layer
-            subscribers.addAll(mLayerSubscriptions.getOrDefault(layer, Collections.emptySet()));
-
-            // Add the subscribers which explicitly subscribed to this layer and publisher
-            subscribers.addAll(
-                    mLayerSubscriptionsToPublishers.getOrDefault(layer, Collections.emptyMap())
-                            .getOrDefault(publisherId, Collections.emptySet()));
-        }
-        return subscribers.stream()
-                .map(binder -> mSubscribers.get(binder))
-                .filter(Objects::nonNull)
-                .collect(Collectors.toSet());
-    }
-
-    /**
-     * @return {@code true} if there is an explicit subscription to the layer
-     */
-    public boolean hasLayerSubscriptions(VmsLayer layer) {
-        synchronized (mLock) {
-            return mLayerSubscriptions.containsKey(layer);
-        }
-    }
-
-    /**
-     * @return {@code true} if there is an explicit subscription to the layer and publisherId
-     */
-    public boolean hasLayerFromPublisherSubscriptions(VmsLayer layer, int publisherId) {
-        synchronized (mLock) {
-            return mLayerSubscriptionsToPublishers.containsKey(layer)
-                    && mLayerSubscriptionsToPublishers.getOrDefault(layer, Collections.emptyMap())
-                    .containsKey(publisherId);
-        }
-    }
-
-    /**
-     * @return a Set of layers and publishers which VMS clients are subscribed to.
-     */
-    public VmsSubscriptionState getSubscriptionState() {
-        synchronized (mLock) {
-            return new VmsSubscriptionState(mSequenceNumber,
-                    new ArraySet<>(mLayerSubscriptions.keySet()),
-                    mLayerSubscriptionsToPublishers.entrySet()
-                            .stream()
-                            .map(e -> new VmsAssociatedLayer(e.getKey(), e.getValue().keySet()))
-                            .collect(Collectors.toSet()));
-        }
-    }
-
-    private IBinder addSubscriber(IVmsSubscriberClient subscriber) {
-        IBinder subscriberBinder = subscriber.asBinder();
-        synchronized (mLock) {
-            mSubscribers.putIfAbsent(subscriberBinder, subscriber);
-        }
-        return subscriberBinder;
-    }
-}
diff --git a/service/src/com/android/car/VmsSubscriberService.java b/service/src/com/android/car/VmsSubscriberService.java
deleted file mode 100644
index 16cebf3..0000000
--- a/service/src/com/android/car/VmsSubscriberService.java
+++ /dev/null
@@ -1,149 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.car;
-
-import android.car.vms.IVmsSubscriberClient;
-import android.car.vms.IVmsSubscriberService;
-import android.car.vms.VmsAvailableLayers;
-import android.car.vms.VmsLayer;
-import android.content.Context;
-import android.os.RemoteException;
-import android.util.Log;
-
-import com.android.car.hal.VmsHalService;
-import com.android.car.vms.VmsBrokerService;
-import com.android.car.vms.VmsClientManager;
-
-import java.io.PrintWriter;
-
-/**
- * Offers subscriber services by implementing IVmsSubscriberService.Stub.
- */
-public class VmsSubscriberService extends IVmsSubscriberService.Stub implements CarServiceBase,
-        VmsBrokerService.SubscriberListener {
-    private static final String TAG = "VmsSubscriberService";
-
-    private final Context mContext;
-    private final VmsBrokerService mBrokerService;
-    private final VmsClientManager mClientManager;
-
-    /**
-     * Constructor for client manager.
-     *
-     * @param context           Context to use for registering receivers and binding services.
-     * @param brokerService     Service managing the VMS publisher/subscriber state.
-     * @param clientManager     Service for monitoring VMS subscriber clients.
-     * @param hal               Service providing the HAL client interface
-     */
-    VmsSubscriberService(Context context, VmsBrokerService brokerService,
-            VmsClientManager clientManager, VmsHalService hal) {
-        mContext = context;
-        mBrokerService = brokerService;
-        mClientManager = clientManager;
-        mBrokerService.addSubscriberListener(this);
-        hal.setVmsSubscriberService(this);
-    }
-
-    @Override
-    public void init() {}
-
-    @Override
-    public void release() {}
-
-    @Override
-    public void dump(PrintWriter writer) {
-    }
-
-    @Override
-    public void addVmsSubscriberToNotifications(IVmsSubscriberClient subscriber) {
-        ICarImpl.assertVmsSubscriberPermission(mContext);
-        mClientManager.addSubscriber(subscriber);
-    }
-
-    @Override
-    public void removeVmsSubscriberToNotifications(IVmsSubscriberClient subscriber) {
-        ICarImpl.assertVmsSubscriberPermission(mContext);
-        mClientManager.removeSubscriber(subscriber);
-    }
-
-    @Override
-    public void addVmsSubscriber(IVmsSubscriberClient subscriber, VmsLayer layer) {
-        ICarImpl.assertVmsSubscriberPermission(mContext);
-        mClientManager.addSubscriber(subscriber);
-        mBrokerService.addSubscription(subscriber, layer);
-    }
-
-    @Override
-    public void removeVmsSubscriber(IVmsSubscriberClient subscriber, VmsLayer layer) {
-        ICarImpl.assertVmsSubscriberPermission(mContext);
-        mBrokerService.removeSubscription(subscriber, layer);
-    }
-
-    @Override
-    public void addVmsSubscriberToPublisher(IVmsSubscriberClient subscriber,
-            VmsLayer layer,
-            int publisherId) {
-        ICarImpl.assertVmsSubscriberPermission(mContext);
-        mClientManager.addSubscriber(subscriber);
-        mBrokerService.addSubscription(subscriber, layer, publisherId);
-    }
-
-    @Override
-    public void removeVmsSubscriberToPublisher(IVmsSubscriberClient subscriber,
-            VmsLayer layer,
-            int publisherId) {
-        ICarImpl.assertVmsSubscriberPermission(mContext);
-        mBrokerService.removeSubscription(subscriber, layer, publisherId);
-    }
-
-    @Override
-    public void addVmsSubscriberPassive(IVmsSubscriberClient subscriber) {
-        ICarImpl.assertVmsSubscriberPermission(mContext);
-        mClientManager.addSubscriber(subscriber);
-        mBrokerService.addSubscription(subscriber);
-    }
-
-    @Override
-    public void removeVmsSubscriberPassive(IVmsSubscriberClient subscriber) {
-        ICarImpl.assertVmsSubscriberPermission(mContext);
-        mBrokerService.removeSubscription(subscriber);
-    }
-
-    @Override
-    public byte[] getPublisherInfo(int publisherId) {
-        ICarImpl.assertVmsSubscriberPermission(mContext);
-        return mBrokerService.getPublisherInfo(publisherId);
-    }
-
-    @Override
-    public VmsAvailableLayers getAvailableLayers() {
-        ICarImpl.assertVmsSubscriberPermission(mContext);
-        return mBrokerService.getAvailableLayers();
-    }
-
-    @Override
-    public void onLayersAvailabilityChange(VmsAvailableLayers availableLayers) {
-        for (IVmsSubscriberClient subscriber : mClientManager.getAllSubscribers()) {
-            try {
-                subscriber.onLayersAvailabilityChanged(availableLayers);
-            } catch (RemoteException e) {
-                Log.e(TAG, "onLayersAvailabilityChanged failed: "
-                        + mClientManager.getPackageName(subscriber), e);
-            }
-        }
-    }
-}
diff --git a/service/src/com/android/car/audio/CarAudioContext.java b/service/src/com/android/car/audio/CarAudioContext.java
new file mode 100644
index 0000000..fd5eda6
--- /dev/null
+++ b/service/src/com/android/car/audio/CarAudioContext.java
@@ -0,0 +1,225 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.audio;
+
+import android.media.AudioAttributes;
+import android.media.AudioAttributes.AttributeUsage;
+import android.util.SparseArray;
+import android.util.SparseIntArray;
+
+import androidx.annotation.IntDef;
+
+import com.android.internal.util.Preconditions;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Arrays;
+
+/**
+ * Groupings of {@link AttributeUsage}s to simplify configuration of car audio routing, volume
+ * groups, and focus interactions for similar usages.
+ */
+final class CarAudioContext {
+    /*
+     * Shouldn't be used
+     * ::android::hardware::automotive::audiocontrol::V1_0::ContextNumber.INVALID
+     */
+    static final int INVALID = 0;
+    /*
+     * Music playback
+     * ::android::hardware::automotive::audiocontrol::V1_0::ContextNumber.INVALID implicitly + 1
+     */
+    static final int MUSIC = 1;
+    /*
+     * Navigation directions
+     * ::android::hardware::automotive::audiocontrol::V1_0::ContextNumber.MUSIC implicitly + 1
+     */
+    static final int NAVIGATION = 2;
+    /*
+     * Voice command session
+     * ::android::hardware::automotive::audiocontrol::V1_0::ContextNumber.NAVIGATION implicitly + 1
+     */
+    static final int VOICE_COMMAND = 3;
+    /*
+     * Voice call ringing
+     * ::android::hardware::automotive::audiocontrol::V1_0::ContextNumber
+     *     .VOICE_COMMAND implicitly + 1
+     */
+    static final int CALL_RING = 4;
+    /*
+     * Voice call
+     * ::android::hardware::automotive::audiocontrol::V1_0::ContextNumber.CALL_RING implicitly + 1
+     */
+    static final int CALL = 5;
+    /*
+     * Alarm sound from Android
+     * ::android::hardware::automotive::audiocontrol::V1_0::ContextNumber.CALL implicitly + 1
+     */
+    static final int ALARM = 6;
+    /*
+     * Notifications
+     * ::android::hardware::automotive::audiocontrol::V1_0::ContextNumber.ALARM implicitly + 1
+     */
+    static final int NOTIFICATION = 7;
+    /*
+     * System sounds
+     * ::android::hardware::automotive::audiocontrol::V1_0::ContextNumber
+     *     .NOTIFICATION implicitly + 1
+     */
+    static final int SYSTEM_SOUND = 8;
+
+    static final int[] CONTEXTS = {
+            MUSIC,
+            NAVIGATION,
+            VOICE_COMMAND,
+            CALL_RING,
+            CALL,
+            ALARM,
+            NOTIFICATION,
+            SYSTEM_SOUND
+    };
+
+    private static final SparseArray<int[]> CONTEXT_TO_USAGES = new SparseArray<>();
+
+    static {
+        CONTEXT_TO_USAGES.put(MUSIC,
+                new int[]{
+                        AudioAttributes.USAGE_UNKNOWN,
+                        AudioAttributes.USAGE_GAME,
+                        AudioAttributes.USAGE_MEDIA
+                });
+
+        CONTEXT_TO_USAGES.put(NAVIGATION,
+                new int[]{
+                        AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE
+                });
+
+        CONTEXT_TO_USAGES.put(VOICE_COMMAND,
+                new int[]{
+                        AudioAttributes.USAGE_ASSISTANCE_ACCESSIBILITY,
+                        AudioAttributes.USAGE_ASSISTANT
+                });
+
+        CONTEXT_TO_USAGES.put(CALL_RING,
+                new int[]{
+                        AudioAttributes.USAGE_NOTIFICATION_RINGTONE
+                });
+
+        CONTEXT_TO_USAGES.put(CALL,
+                new int[]{
+                        AudioAttributes.USAGE_VOICE_COMMUNICATION,
+                        AudioAttributes.USAGE_VOICE_COMMUNICATION_SIGNALLING
+                });
+
+        CONTEXT_TO_USAGES.put(ALARM,
+                new int[]{
+                        AudioAttributes.USAGE_ALARM
+                });
+
+        CONTEXT_TO_USAGES.put(NOTIFICATION,
+                new int[]{
+                        AudioAttributes.USAGE_NOTIFICATION,
+                        AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_REQUEST,
+                        AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_INSTANT,
+                        AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_DELAYED,
+                        AudioAttributes.USAGE_NOTIFICATION_EVENT
+                });
+
+        CONTEXT_TO_USAGES.put(SYSTEM_SOUND,
+                new int[]{
+                        AudioAttributes.USAGE_ASSISTANCE_SONIFICATION
+                });
+
+        CONTEXT_TO_USAGES.put(INVALID,
+                new int[]{
+                        AudioAttributes.USAGE_VIRTUAL_SOURCE
+                });
+    }
+
+    private static final SparseIntArray USAGE_TO_CONTEXT = new SparseIntArray();
+
+    static {
+        for (int i = 0; i < CONTEXT_TO_USAGES.size(); i++) {
+            @AudioContext int audioContext = CONTEXT_TO_USAGES.keyAt(i);
+            @AttributeUsage int[] usages = CONTEXT_TO_USAGES.valueAt(i);
+            for (@AttributeUsage int usage : usages) {
+                USAGE_TO_CONTEXT.put(usage, audioContext);
+            }
+        }
+    }
+
+    /**
+     * Checks if the audio context is within the valid range from MUSIC to SYSTEM_SOUND
+     */
+    static void preconditionCheckAudioContext(@AudioContext int audioContext) {
+        Preconditions.checkArgument(Arrays.binarySearch(CONTEXTS, audioContext) >= 0,
+                "audioContext %d is invalid", audioContext);
+    }
+
+    static @AttributeUsage int[] getUsagesForContext(@AudioContext int carAudioContext) {
+        preconditionCheckAudioContext(carAudioContext);
+        return CONTEXT_TO_USAGES.get(carAudioContext);
+    }
+
+    /**
+     * @return Context number for a given audio usage, {@code INVALID} if the given usage is
+     * unrecognized.
+     */
+    static @AudioContext int getContextForUsage(@AttributeUsage int audioUsage) {
+        return USAGE_TO_CONTEXT.get(audioUsage, INVALID);
+    }
+
+    static String toString(@AudioContext int audioContext) {
+        switch (audioContext) {
+            case INVALID:
+                return "INVALID";
+            case MUSIC:
+                return "MUSIC";
+            case NAVIGATION:
+                return "NAVIGATION";
+            case VOICE_COMMAND:
+                return "VOICE_COMMAND";
+            case CALL_RING:
+                return "CALL_RING";
+            case CALL:
+                return "CALL";
+            case ALARM:
+                return "ALARM";
+            case NOTIFICATION:
+                return "NOTIFICATION";
+            case SYSTEM_SOUND:
+                return "SYSTEM_SOUND";
+            default:
+                return "0x" + Integer.toHexString(audioContext);
+        }
+    }
+
+    @IntDef({
+            INVALID,
+            MUSIC,
+            NAVIGATION,
+            VOICE_COMMAND,
+            CALL_RING,
+            CALL,
+            ALARM,
+            NOTIFICATION,
+            SYSTEM_SOUND,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @interface AudioContext {
+    }
+};
diff --git a/service/src/com/android/car/audio/CarAudioDeviceInfo.java b/service/src/com/android/car/audio/CarAudioDeviceInfo.java
index f45f48e..cc8788c 100644
--- a/service/src/com/android/car/audio/CarAudioDeviceInfo.java
+++ b/service/src/com/android/car/audio/CarAudioDeviceInfo.java
@@ -28,6 +28,7 @@
 import com.android.internal.util.Preconditions;
 
 import java.io.PrintWriter;
+import java.util.Objects;
 
 /**
  * A helper class wraps {@link AudioDeviceInfo}, and helps get/set the gain on a specific port
@@ -38,34 +39,14 @@
  */
 /* package */ class CarAudioDeviceInfo {
 
-    /**
-     * Parse device address. Expected format is BUS%d_%s, address, usage hint
-     * @return valid address (from 0 to positive) or -1 for invalid address.
-     */
-    static int parseDeviceAddress(String address) {
-        String[] words = address.split("_");
-        int addressParsed = -1;
-        if (words[0].toLowerCase().startsWith("bus")) {
-            try {
-                addressParsed = Integer.parseInt(words[0].substring(3));
-            } catch (NumberFormatException e) {
-                //ignore
-            }
-        }
-        if (addressParsed < 0) {
-            return -1;
-        }
-        return addressParsed;
-    }
-
     private final AudioDeviceInfo mAudioDeviceInfo;
-    private final int mBusNumber;
     private final int mSampleRate;
     private final int mEncodingFormat;
     private final int mChannelCount;
     private final int mDefaultGain;
     private final int mMaxGain;
     private final int mMinGain;
+    private final int mStepValue;
 
     /**
      * We need to store the current gain because it is not accessible from the current
@@ -76,15 +57,15 @@
 
     CarAudioDeviceInfo(AudioDeviceInfo audioDeviceInfo) {
         mAudioDeviceInfo = audioDeviceInfo;
-        mBusNumber = parseDeviceAddress(audioDeviceInfo.getAddress());
         mSampleRate = getMaxSampleRate(audioDeviceInfo);
         mEncodingFormat = getEncodingFormat(audioDeviceInfo);
         mChannelCount = getMaxChannels(audioDeviceInfo);
-        final AudioGain audioGain = Preconditions.checkNotNull(
+        final AudioGain audioGain = Objects.requireNonNull(
                 getAudioGain(), "No audio gain on device port " + audioDeviceInfo);
         mDefaultGain = audioGain.defaultValue();
         mMaxGain = audioGain.maxValue();
         mMinGain = audioGain.minValue();
+        mStepValue = audioGain.stepValue();
 
         mCurrentGain = -1; // Not initialized till explicitly set
     }
@@ -97,8 +78,8 @@
         return mAudioDeviceInfo.getPort();
     }
 
-    int getBusNumber() {
-        return mBusNumber;
+    String getAddress() {
+        return mAudioDeviceInfo.getAddress();
     }
 
     int getDefaultGain() {
@@ -125,6 +106,10 @@
         return mChannelCount;
     }
 
+    int getStepValue() {
+        return mStepValue;
+    }
+
     // Input is in millibels
     void setCurrentGain(int gainInMillibels) {
         // Clamp the incoming value to our valid range.  Out of range values ARE legal input
@@ -237,8 +222,7 @@
 
     @Override
     public String toString() {
-        return "bus number: " + mBusNumber
-                + " address: " + mAudioDeviceInfo.getAddress()
+        return "address: " + mAudioDeviceInfo.getAddress()
                 + " sampleRate: " + getSampleRate()
                 + " encodingFormat: " + getEncodingFormat()
                 + " channelCount: " + getChannelCount()
@@ -248,8 +232,8 @@
     }
 
     void dump(String indent, PrintWriter writer) {
-        writer.printf("%sCarAudioDeviceInfo Bus(%d: %s)\n ",
-                indent, mBusNumber, mAudioDeviceInfo.getAddress());
+        writer.printf("%sCarAudioDeviceInfo Device(%s)\n ",
+                indent, mAudioDeviceInfo.getAddress());
         writer.printf("%s\tsample rate / encoding format / channel count: %d %d %d\n",
                 indent, getSampleRate(), getEncodingFormat(), getChannelCount());
         writer.printf("%s\tGain values (min / max / default/ current): %d %d %d %d\n",
diff --git a/service/src/com/android/car/audio/CarAudioDynamicRouting.java b/service/src/com/android/car/audio/CarAudioDynamicRouting.java
index 73b810c..661f1f1 100644
--- a/service/src/com/android/car/audio/CarAudioDynamicRouting.java
+++ b/service/src/com/android/car/audio/CarAudioDynamicRouting.java
@@ -15,7 +15,6 @@
  */
 package com.android.car.audio;
 
-import android.hardware.automotive.audiocontrol.V1_0.ContextNumber;
 import android.media.AudioAttributes;
 import android.media.AudioFormat;
 import android.media.AudioManager;
@@ -23,32 +22,16 @@
 import android.media.audiopolicy.AudioMixingRule;
 import android.media.audiopolicy.AudioPolicy;
 import android.util.Log;
-import android.util.SparseIntArray;
 
 import com.android.car.CarLog;
 
-import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.List;
 
 /**
  * Builds dynamic audio routing in a car from audio zone configuration.
  */
 /* package */ class CarAudioDynamicRouting {
 
-    static final int[] CONTEXT_NUMBERS = new int[] {
-            ContextNumber.MUSIC,
-            ContextNumber.NAVIGATION,
-            ContextNumber.VOICE_COMMAND,
-            ContextNumber.CALL_RING,
-            ContextNumber.CALL,
-            ContextNumber.ALARM,
-            ContextNumber.NOTIFICATION,
-            ContextNumber.SYSTEM_SOUND
-    };
-
-    static final SparseIntArray USAGE_TO_CONTEXT = new SparseIntArray();
-
     static final int DEFAULT_AUDIO_USAGE = AudioAttributes.USAGE_MEDIA;
 
     // For legacy stream type based volume control.
@@ -64,33 +47,6 @@
             AudioAttributes.USAGE_NOTIFICATION_RINGTONE
     };
 
-    static {
-        USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_UNKNOWN, ContextNumber.MUSIC);
-        USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_MEDIA, ContextNumber.MUSIC);
-        USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_VOICE_COMMUNICATION, ContextNumber.CALL);
-        USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_VOICE_COMMUNICATION_SIGNALLING,
-                ContextNumber.CALL);
-        USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_ALARM, ContextNumber.ALARM);
-        USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_NOTIFICATION, ContextNumber.NOTIFICATION);
-        USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_NOTIFICATION_RINGTONE, ContextNumber.CALL_RING);
-        USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_REQUEST,
-                ContextNumber.NOTIFICATION);
-        USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_INSTANT,
-                ContextNumber.NOTIFICATION);
-        USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_DELAYED,
-                ContextNumber.NOTIFICATION);
-        USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_NOTIFICATION_EVENT, ContextNumber.NOTIFICATION);
-        USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_ASSISTANCE_ACCESSIBILITY,
-                ContextNumber.VOICE_COMMAND);
-        USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE,
-                ContextNumber.NAVIGATION);
-        USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION,
-                ContextNumber.SYSTEM_SOUND);
-        USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_GAME, ContextNumber.MUSIC);
-        USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_VIRTUAL_SOURCE, ContextNumber.INVALID);
-        USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_ASSISTANT, ContextNumber.VOICE_COMMAND);
-    }
-
     private final CarAudioZone[] mCarAudioZones;
 
     CarAudioDynamicRouting(CarAudioZone[] carAudioZones) {
@@ -113,31 +69,32 @@
     private void setupAudioDynamicRoutingForGroup(CarVolumeGroup group,
             AudioPolicy.Builder builder) {
         // Note that one can not register audio mix for same bus more than once.
-        for (int busNumber : group.getBusNumbers()) {
+        for (String address : group.getAddresses()) {
             boolean hasContext = false;
-            CarAudioDeviceInfo info = group.getCarAudioDeviceInfoForBus(busNumber);
+            CarAudioDeviceInfo info = group.getCarAudioDeviceInfoForAddress(address);
             AudioFormat mixFormat = new AudioFormat.Builder()
                     .setSampleRate(info.getSampleRate())
                     .setEncoding(info.getEncodingFormat())
                     .setChannelMask(info.getChannelCount())
                     .build();
             AudioMixingRule.Builder mixingRuleBuilder = new AudioMixingRule.Builder();
-            for (int contextNumber : group.getContextsForBus(busNumber)) {
+            for (int carAudioContext : group.getContextsForAddress(address)) {
                 hasContext = true;
-                int[] usages = getUsagesForContext(contextNumber);
+                int[] usages = CarAudioContext.getUsagesForContext(carAudioContext);
                 for (int usage : usages) {
                     mixingRuleBuilder.addRule(
                             new AudioAttributes.Builder().setUsage(usage).build(),
                             AudioMixingRule.RULE_MATCH_ATTRIBUTE_USAGE);
                 }
-                Log.d(CarLog.TAG_AUDIO, "Bus number: " + busNumber
-                        + " contextNumber: " + contextNumber
-                        + " sampleRate: " + info.getSampleRate()
-                        + " channels: " + info.getChannelCount()
-                        + " usages: " + Arrays.toString(usages));
+                if (Log.isLoggable(CarLog.TAG_AUDIO, Log.DEBUG)) {
+                    Log.d(CarLog.TAG_AUDIO, String.format(
+                            "Address: %s AudioContext: %s sampleRate: %d channels: %d usages: %s",
+                            address, carAudioContext, info.getSampleRate(), info.getChannelCount(),
+                            Arrays.toString(usages)));
+                }
             }
             if (hasContext) {
-                // It's a valid case that an audio output bus is defined in
+                // It's a valid case that an audio output address is defined in
                 // audio_policy_configuration and no context is assigned to it.
                 // In such case, do not build a policy mix with zero rules.
                 AudioMix audioMix = new AudioMix.Builder(mixingRuleBuilder.build())
@@ -149,14 +106,4 @@
             }
         }
     }
-
-    private int[] getUsagesForContext(int contextNumber) {
-        final List<Integer> usages = new ArrayList<>();
-        for (int i = 0; i < CarAudioDynamicRouting.USAGE_TO_CONTEXT.size(); i++) {
-            if (CarAudioDynamicRouting.USAGE_TO_CONTEXT.valueAt(i) == contextNumber) {
-                usages.add(CarAudioDynamicRouting.USAGE_TO_CONTEXT.keyAt(i));
-            }
-        }
-        return usages.stream().mapToInt(i -> i).toArray();
-    }
 }
diff --git a/service/src/com/android/car/audio/CarAudioFocus.java b/service/src/com/android/car/audio/CarAudioFocus.java
index 286a045..f4e85a1 100644
--- a/service/src/com/android/car/audio/CarAudioFocus.java
+++ b/service/src/com/android/car/audio/CarAudioFocus.java
@@ -15,15 +15,12 @@
  */
 package com.android.car.audio;
 
-import android.car.Car;
-import android.car.media.CarAudioManager;
 import android.content.pm.PackageManager;
-import android.hardware.automotive.audiocontrol.V1_0.ContextNumber;
 import android.media.AudioAttributes;
 import android.media.AudioFocusInfo;
 import android.media.AudioManager;
 import android.media.audiopolicy.AudioPolicy;
-import android.os.Bundle;
+import android.util.LocalLog;
 import android.util.Log;
 
 import java.io.PrintWriter;
@@ -36,88 +33,13 @@
 
     private static final String TAG = "CarAudioFocus";
 
+    private static final int FOCUS_EVENT_LOGGER_QUEUE_SIZE = 100;
+
     private final AudioManager mAudioManager;
     private final PackageManager mPackageManager;
-    private CarAudioService mCarAudioService; // Dynamically assigned just after construction
     private AudioPolicy mAudioPolicy; // Dynamically assigned just after construction
 
-
-    // Values for the internal interaction matrix we use to make focus decisions
-    static final int INTERACTION_REJECT     = 0;    // Focus not granted
-    static final int INTERACTION_EXCLUSIVE  = 1;    // Focus granted, others loose focus
-    static final int INTERACTION_CONCURRENT = 2;    // Focus granted, others keep focus
-
-
-    // TODO:  Make this an overlayable resource...
-    //  MUSIC           = 1,        // Music playback
-    //  NAVIGATION      = 2,        // Navigation directions
-    //  VOICE_COMMAND   = 3,        // Voice command session
-    //  CALL_RING       = 4,        // Voice call ringing
-    //  CALL            = 5,        // Voice call
-    //  ALARM           = 6,        // Alarm sound from Android
-    //  NOTIFICATION    = 7,        // Notifications
-    //  SYSTEM_SOUND    = 8,        // User interaction sounds (button clicks, etc)
-    private static int sInteractionMatrix[][] = {
-        // Row selected by playing sound (labels along the right)
-        // Column selected by incoming request (labels along the top)
-        // Cell value is one of INTERACTION_REJECT, INTERACTION_EXCLUSIVE, INTERACTION_CONCURRENT
-        // Invalid, Music, Nav, Voice, Ring, Call, Alarm, Notification, System
-        {  0,       0,     0,   0,     0,    0,    0,     0,            0 }, // Invalid
-        {  0,       1,     2,   1,     1,    1,    1,     2,            2 }, // Music
-        {  0,       2,     2,   1,     2,    1,    2,     2,            2 }, // Nav
-        {  0,       2,     0,   2,     1,    1,    0,     0,            0 }, // Voice
-        {  0,       0,     2,   2,     2,    2,    0,     0,            2 }, // Ring
-        {  0,       0,     2,   0,     2,    2,    2,     2,            0 }, // Context
-        {  0,       2,     2,   1,     1,    1,    2,     2,            2 }, // Alarm
-        {  0,       2,     2,   1,     1,    1,    2,     2,            2 }, // Notification
-        {  0,       2,     2,   1,     1,    1,    2,     2,            2 }, // System
-    };
-
-
-    private class FocusEntry {
-        // Requester info
-        final AudioFocusInfo mAfi;                      // never null
-
-        final int mAudioContext;                        // Which HAL level context does this affect
-        final ArrayList<FocusEntry> mBlockers;          // List of requests that block ours
-        boolean mReceivedLossTransientCanDuck;          // Whether holder has lost focus duckably
-
-        FocusEntry(AudioFocusInfo afi,
-                   int context) {
-            mAfi             = afi;
-            mAudioContext    = context;
-            mBlockers        = new ArrayList<FocusEntry>();
-        }
-
-        public String getClientId() {
-            return mAfi.getClientId();
-        }
-
-        public boolean wantsPauseInsteadOfDucking() {
-            return (mAfi.getFlags() & AudioManager.AUDIOFOCUS_FLAG_PAUSES_ON_DUCKABLE_LOSS) != 0;
-        }
-
-        public boolean receivesDuckEvents() {
-            Bundle bundle = mAfi.getAttributes().getBundle();
-
-            if (bundle == null) {
-                return false;
-            }
-
-            if (!bundle.getBoolean(CarAudioManager.AUDIOFOCUS_EXTRA_RECEIVE_DUCKING_EVENTS)) {
-                return false;
-            }
-
-            return (mPackageManager.checkPermission(
-                            Car.PERMISSION_RECEIVE_CAR_AUDIO_DUCKING_EVENTS,
-                            mAfi.getPackageName())
-                    == PackageManager.PERMISSION_GRANTED);
-        }
-
-        String getUsageName() {
-            return mAfi.getAttributes().usageToString();
-        }
-    }
+    private final LocalLog mFocusEventLogger;
 
 
     // We keep track of all the focus requesters in this map, with their clientId as the key.
@@ -134,32 +56,37 @@
     private final HashMap<String, FocusEntry> mFocusHolders = new HashMap<>();
     private final HashMap<String, FocusEntry> mFocusLosers = new HashMap<>();
 
+    private final Object mLock = new Object();
+
 
     CarAudioFocus(AudioManager audioManager, PackageManager packageManager) {
         mAudioManager = audioManager;
         mPackageManager = packageManager;
+        mFocusEventLogger = new LocalLog(FOCUS_EVENT_LOGGER_QUEUE_SIZE);
     }
 
 
     // This has to happen after the construction to avoid a chicken and egg problem when setting up
     // the AudioPolicy which must depend on this object.
-    public void setOwningPolicy(CarAudioService audioService, AudioPolicy parentPolicy) {
-        mCarAudioService = audioService;
+    public void setOwningPolicy(AudioPolicy parentPolicy) {
         mAudioPolicy     = parentPolicy;
     }
 
 
     // This sends a focus loss message to the targeted requester.
-    private void sendFocusLoss(FocusEntry loser, int lossType) {
-        Log.i(TAG, "sendFocusLoss (" + focusEventToString(lossType) + ") to "
-                + loser.getClientId());
-        int result = mAudioManager.dispatchAudioFocusChange(loser.mAfi, lossType, mAudioPolicy);
+    private void sendFocusLossLocked(FocusEntry loser, int lossType) {
+        int result = mAudioManager.dispatchAudioFocusChange(loser.getAudioFocusInfo(), lossType,
+                mAudioPolicy);
         if (result != AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
             // TODO:  Is this actually an error, or is it okay for an entry in the focus stack
             // to NOT have a listener?  If that's the case, should we even keep it in the focus
             // stack?
             Log.e(TAG, "Failure to signal loss of audio focus with error: " + result);
         }
+
+        logFocusEvent("sendFocusLoss for client " + loser.getClientId()
+                        + " with loss type " + focusEventToString(lossType)
+                        + " resulted in " + focusRequestResponseToString(result));
     }
 
 
@@ -174,7 +101,7 @@
     // If it is for the same USAGE, we replace the old request with the new one.
     // The default audio framework's behavior is to remove the previous entry in the stack (no-op
     // if the requester is already holding focus).
-    int evaluateFocusRequest(AudioFocusInfo afi) {
+    private int evaluateFocusRequestLocked(AudioFocusInfo afi) {
         Log.i(TAG, "Evaluating " + focusEventToString(afi.getGainRequest())
                 + " request for client " + afi.getClientId()
                 + " with usage " + afi.getAttributes().usageToString());
@@ -192,7 +119,7 @@
 
 
         // Convert from audio attributes "usage" to HAL level "context"
-        final int requestedContext = mCarAudioService.getContextForUsage(
+        final int requestedContext = CarAudioContext.getContextForUsage(
                 afi.getAttributes().getUsage());
 
         // If we happen to find entries that this new request should replace, we'll store them here.
@@ -214,16 +141,16 @@
             // This matches the hardwired behavior in the default audio policy engine which apps
             // might expect (The interaction matrix doesn't have any provision for dealing with
             // override flags like this).
-            if ((requestedContext == ContextNumber.NOTIFICATION) &&
-                    (entry.mAfi.getGainRequest() ==
-                            AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE)) {
+            if ((requestedContext == CarAudioContext.NOTIFICATION)
+                    && (entry.getAudioFocusInfo().getGainRequest()
+                    == AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE)) {
                 return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
             }
 
             // We don't allow sharing listeners (client IDs) between two concurrent requests
             // (because the app would have no way to know to which request a later event applied)
-            if (afi.getClientId().equals(entry.mAfi.getClientId())) {
-                if (entry.mAudioContext == requestedContext) {
+            if (afi.getClientId().equals(entry.getAudioFocusInfo().getClientId())) {
+                if (entry.getAudioContext() == requestedContext) {
                     // This is a request from a current focus holder.
                     // Abandon the previous request (without sending a LOSS notification to it),
                     // and don't check the interaction matrix for it.
@@ -232,58 +159,39 @@
                     continue;
                 } else {
                     // Trivially reject a request for a different USAGE
-                    Log.e(TAG, "Client " + entry.getClientId() + " has already requested focus "
-                            + "for " + entry.mAfi.getAttributes().usageToString() + " - cannot "
-                            + "request focus for " + afi.getAttributes().usageToString() + " on "
-                            + "same listener.");
+                    Log.e(TAG, String.format(
+                            "Client %s has already requested focus for %s - cannot request focus "
+                                    + "for %s on same listener.",
+                            entry.getClientId(),
+                            entry.getAudioFocusInfo().getAttributes().usageToString(),
+                            afi.getAttributes().usageToString()));
                     return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
                 }
             }
 
-            // Check the interaction matrix for the relationship between this entry and the request
-            switch (sInteractionMatrix[entry.mAudioContext][requestedContext]) {
-                case INTERACTION_REJECT:
-                    // This request is rejected, so nothing further to do
-                    return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
-                case INTERACTION_EXCLUSIVE:
-                    // The new request will cause this existing entry to lose focus
-                    losers.add(entry);
-                    break;
-                case INTERACTION_CONCURRENT:
-                    // If ducking isn't allowed by the focus requestor, then everybody else
-                    // must get a LOSS.
-                    // If a focus holder has set the AUDIOFOCUS_FLAG_PAUSES_ON_DUCKABLE_LOSS flag,
-                    // they must get a LOSS message even if ducking would otherwise be allowed.
-                    // If a focus holder holds the RECEIVE_CAR_AUDIO_DUCKING_EVENTS permission,
-                    // they must receive all audio focus losses.
-                    if (!allowDucking
-                            || entry.wantsPauseInsteadOfDucking()
-                            || entry.receivesDuckEvents()) {
-                        losers.add(entry);
-                    }
-                    break;
-                default:
-                    Log.e(TAG, "Bad interaction matrix value - rejecting");
-                    return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
+            @AudioManager.FocusRequestResult int interactionResult = FocusInteraction
+                    .evaluateRequest(requestedContext, entry, losers, allowDucking);
+            if (interactionResult == AudioManager.AUDIOFOCUS_REQUEST_FAILED) {
+                return interactionResult;
             }
         }
         Log.i(TAG, "Scanning those who've already lost focus...");
         final ArrayList<FocusEntry> blocked = new ArrayList<FocusEntry>();
         for (FocusEntry entry : mFocusLosers.values()) {
-            Log.i(TAG, entry.mAfi.getClientId());
+            Log.i(TAG, entry.getAudioFocusInfo().getClientId());
 
             // If this request is for Notifications and a pending focus holder has specified
             // AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE, then reject the request
-            if ((requestedContext == ContextNumber.NOTIFICATION) &&
-                    (entry.mAfi.getGainRequest() ==
-                            AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE)) {
+            if ((requestedContext == CarAudioContext.NOTIFICATION)
+                    && (entry.getAudioFocusInfo().getGainRequest()
+                    == AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE)) {
                 return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
             }
 
             // We don't allow sharing listeners (client IDs) between two concurrent requests
             // (because the app would have no way to know to which request a later event applied)
-            if (afi.getClientId().equals(entry.mAfi.getClientId())) {
-                if (entry.mAudioContext == requestedContext) {
+            if (afi.getClientId().equals(entry.getAudioFocusInfo().getClientId())) {
+                if (entry.getAudioContext() == requestedContext) {
                     // This is a repeat of a request that is currently blocked.
                     // Evaluate it as if it were a new request, but note that we should remove
                     // the old pending request, and move it.
@@ -293,45 +201,26 @@
                     continue;
                 } else {
                     // Trivially reject a request for a different USAGE
-                    Log.e(TAG, "Client " + entry.getClientId() + " has already requested focus "
-                            + "for " + entry.mAfi.getAttributes().usageToString() + " - cannot "
-                            + "request focus for " + afi.getAttributes().usageToString() + " on "
-                            + "same listener.");
+                    Log.e(TAG, String.format(
+                            "Client %s has already requested focus for %s - cannot request focus "
+                                    + "for %s on same listener.",
+                            entry.getClientId(),
+                            entry.getAudioFocusInfo().getAttributes().usageToString(),
+                            afi.getAttributes().usageToString()));
                     return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
                 }
             }
 
-            // Check the interaction matrix for the relationship between this entry and the request
-            switch (sInteractionMatrix[entry.mAudioContext][requestedContext]) {
-                case INTERACTION_REJECT:
-                    // Even though this entry has currently lost focus, the fact that it is
-                    // waiting to play means we'll reject this new conflicting request.
-                    return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
-                case INTERACTION_EXCLUSIVE:
-                    // The new request is yet another reason this entry cannot regain focus (yet)
-                    blocked.add(entry);
-                    break;
-                case INTERACTION_CONCURRENT:
-                    // If ducking is not allowed by the requester, or the pending focus holder had
-                    // set the AUDIOFOCUS_FLAG_PAUSES_ON_DUCKABLE_LOSS flag, or if the pending
-                    // focus holder has requested to receive all focus events, then the pending
-                    // holder must stay "lost" until this requester goes away.
-                    if (!allowDucking
-                            || entry.wantsPauseInsteadOfDucking()
-                            || entry.receivesDuckEvents()) {
-                        // The new request is yet another reason this entry cannot regain focus yet
-                        blocked.add(entry);
-                    }
-                    break;
-                default:
-                    Log.e(TAG, "Bad interaction matrix value - rejecting");
-                    return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
+            @AudioManager.FocusRequestResult int interactionResult = FocusInteraction
+                    .evaluateRequest(requestedContext, entry, blocked, allowDucking);
+            if (interactionResult == AudioManager.AUDIOFOCUS_REQUEST_FAILED) {
+                return interactionResult;
             }
         }
 
 
         // Now that we've decided we'll grant focus, construct our new FocusEntry
-        FocusEntry newEntry = new FocusEntry(afi, requestedContext);
+        FocusEntry newEntry = new FocusEntry(afi, requestedContext, mPackageManager);
 
         // These entries have permanently lost focus as a result of this request, so they
         // should be removed from all blocker lists.
@@ -351,54 +240,55 @@
         // block but are already out of focus but waiting to come back
         for (FocusEntry entry : blocked) {
             // If we're out of focus it must be because somebody is blocking us
-            assert !entry.mBlockers.isEmpty();
+            assert !entry.isUnblocked();
 
             if (permanent) {
                 // This entry has now lost focus forever
-                sendFocusLoss(entry, AudioManager.AUDIOFOCUS_LOSS);
-                entry.mReceivedLossTransientCanDuck = false;
-                final FocusEntry deadEntry = mFocusLosers.remove(entry.mAfi.getClientId());
+                sendFocusLossLocked(entry, AudioManager.AUDIOFOCUS_LOSS);
+                entry.setDucked(false);
+                final FocusEntry deadEntry = mFocusLosers.remove(
+                        entry.getAudioFocusInfo().getClientId());
                 assert deadEntry != null;
                 permanentlyLost.add(entry);
             } else {
-                if (!allowDucking && entry.mReceivedLossTransientCanDuck) {
+                if (!allowDucking && entry.isDucked()) {
                     // This entry was previously allowed to duck, but can no longer do so.
                     Log.i(TAG, "Converting duckable loss to non-duckable for "
                             + entry.getClientId());
-                    sendFocusLoss(entry, AudioManager.AUDIOFOCUS_LOSS_TRANSIENT);
-                    entry.mReceivedLossTransientCanDuck = false;
+                    sendFocusLossLocked(entry, AudioManager.AUDIOFOCUS_LOSS_TRANSIENT);
+                    entry.setDucked(false);
                 }
                 // Note that this new request is yet one more reason we can't (yet) have focus
-                entry.mBlockers.add(newEntry);
+                entry.addBlocker(newEntry);
             }
         }
 
         // Notify and update any requests which are now losing focus as a result of the new request
         for (FocusEntry entry : losers) {
             // If we have focus (but are about to loose it), nobody should be blocking us yet
-            assert entry.mBlockers.isEmpty();
+            assert entry.isUnblocked();
 
             int lossType;
             if (permanent) {
                 lossType = AudioManager.AUDIOFOCUS_LOSS;
             } else if (allowDucking && entry.receivesDuckEvents()) {
                 lossType = AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK;
-                entry.mReceivedLossTransientCanDuck = true;
+                entry.setDucked(true);
             } else {
                 lossType = AudioManager.AUDIOFOCUS_LOSS_TRANSIENT;
             }
-            sendFocusLoss(entry, lossType);
+            sendFocusLossLocked(entry, lossType);
 
             // The entry no longer holds focus, so take it out of the holders list
-            mFocusHolders.remove(entry.mAfi.getClientId());
+            mFocusHolders.remove(entry.getAudioFocusInfo().getClientId());
 
             if (permanent) {
                 permanentlyLost.add(entry);
             } else {
                 // Add ourselves to the list of requests waiting to get focus back and
                 // note why we lost focus so we can tell when it's time to get it back
-                mFocusLosers.put(entry.mAfi.getClientId(), entry);
-                entry.mBlockers.add(newEntry);
+                mFocusLosers.put(entry.getAudioFocusInfo().getClientId(), entry);
+                entry.addBlocker(newEntry);
             }
         }
 
@@ -409,7 +299,7 @@
         // GAIN_TRANSIENT request from the same listener.)
         for (FocusEntry entry : permanentlyLost) {
             Log.d(TAG, "Cleaning up entry " + entry.getClientId());
-            removeFocusEntryAndRestoreUnblockedWaiters(entry);
+            removeBlockerAndRestoreUnblockedWaitersLocked(entry);
         }
 
         // Finally, add the request we're granting to the focus holders' list
@@ -419,15 +309,19 @@
         return AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
     }
 
-
     @Override
-    public synchronized void onAudioFocusRequest(AudioFocusInfo afi, int requestResult) {
-        Log.i(TAG, "onAudioFocusRequest " + afi.getClientId());
-
-        int response = evaluateFocusRequest(afi);
-
+    public void onAudioFocusRequest(AudioFocusInfo afi, int requestResult) {
+        int response;
+        AudioPolicy policy;
+        synchronized (mLock) {
+            policy = mAudioPolicy;
+            response = evaluateFocusRequestLocked(afi);
+        }
         // Post our reply for delivery to the original focus requester
-        mAudioManager.setFocusRequestResult(afi, response, mAudioPolicy);
+        mAudioManager.setFocusRequestResult(afi, response, policy);
+        logFocusEvent("onAudioFocusRequest for client " + afi.getClientId()
+                + " with gain type " + focusEventToString(afi.getGainRequest())
+                + " resulted in " + focusRequestResponseToString(response));
     }
 
 
@@ -437,13 +331,14 @@
      * we don't need to watch for death notifications directly.
      * */
     @Override
-    public synchronized void onAudioFocusAbandon(AudioFocusInfo afi) {
-        Log.i(TAG, "onAudioFocusAbandon " + afi.getClientId());
+    public void onAudioFocusAbandon(AudioFocusInfo afi) {
+        logFocusEvent("onAudioFocusAbandon for client " + afi.getClientId());
+        synchronized (mLock) {
+            FocusEntry deadEntry = removeFocusEntryLocked(afi);
 
-        FocusEntry deadEntry = removeFocusEntry(afi);
-
-        if (deadEntry != null) {
-            removeFocusEntryAndRestoreUnblockedWaiters(deadEntry);
+            if (deadEntry != null) {
+                removeBlockerAndRestoreUnblockedWaitersLocked(deadEntry);
+            }
         }
     }
 
@@ -452,7 +347,7 @@
      * @param afi Audio Focus Info to remove
      * @return Removed Focus Entry
      */
-    private FocusEntry removeFocusEntry(AudioFocusInfo afi) {
+    private FocusEntry removeFocusEntryLocked(AudioFocusInfo afi) {
         Log.i(TAG, "removeFocusEntry " + afi.getClientId());
 
         // Remove this entry from our active or pending list
@@ -476,17 +371,17 @@
         return deadEntry;
     }
 
-    private void removeFocusEntryAndRestoreUnblockedWaiters(FocusEntry deadEntry) {
+    private void removeBlockerAndRestoreUnblockedWaitersLocked(FocusEntry deadEntry) {
         // Remove this entry from the blocking list of any pending requests
         Iterator<FocusEntry> it = mFocusLosers.values().iterator();
         while (it.hasNext()) {
             FocusEntry entry = it.next();
 
             // Remove the retiring entry from all blocker lists
-            entry.mBlockers.remove(deadEntry);
+            entry.removeBlocker(deadEntry);
 
             // Any entry whose blocking list becomes empty should regain focus
-            if (entry.mBlockers.isEmpty()) {
+            if (entry.isUnblocked()) {
                 Log.i(TAG, "Restoring unblocked entry " + entry.getClientId());
                 // Pull this entry out of the focus losers list
                 it.remove();
@@ -494,8 +389,7 @@
                 // Add it back into the focus holders list
                 mFocusHolders.put(entry.getClientId(), entry);
 
-                dispatchFocusGained(entry.mAfi);
-
+                dispatchFocusGainedLocked(entry.getAudioFocusInfo());
             }
         }
     }
@@ -505,7 +399,7 @@
      * @param afi Audio focus info
      * @return AudioManager.AUDIOFOCUS_REQUEST_GRANTED if focus is dispatched successfully
      */
-    private int dispatchFocusGained(AudioFocusInfo afi) {
+    private int dispatchFocusGainedLocked(AudioFocusInfo afi) {
         // Send the focus (re)gain notification
         int result = mAudioManager.dispatchAudioFocusChange(
                 afi,
@@ -517,10 +411,13 @@
             // it in the focus stack?
             Log.e(TAG, "Failure to signal gain of audio focus with error: " + result);
         }
+
+        logFocusEvent("dispatchFocusGainedLocked for client " + afi.getClientId()
+                        + " with gain type " + focusEventToString(afi.getGainRequest())
+                        + " resulted in " + focusRequestResponseToString(result));
         return result;
     }
 
-
     /**
      * Query the current list of focus loser for uid
      * @param uid uid to query current focus loser
@@ -548,10 +445,12 @@
     private ArrayList<AudioFocusInfo> getAudioFocusListForUid(int uid,
             HashMap<String, FocusEntry> mapToQuery) {
         ArrayList<AudioFocusInfo> matchingInfoList = new ArrayList<>();
-        for (String clientId : mapToQuery.keySet()) {
-            AudioFocusInfo afi = mapToQuery.get(clientId).mAfi;
-            if (afi.getClientUid() == uid) {
-                matchingInfoList.add(afi);
+        synchronized (mLock) {
+            for (String clientId : mapToQuery.keySet()) {
+                AudioFocusInfo afi = mapToQuery.get(clientId).getAudioFocusInfo();
+                if (afi.getClientUid() == uid) {
+                    matchingInfoList.add(afi);
+                }
             }
         }
         return matchingInfoList;
@@ -563,11 +462,12 @@
      * @param afi Audio Focus info to remove
      */
     void removeAudioFocusInfoAndTransientlyLoseFocus(AudioFocusInfo afi) {
-        FocusEntry deadEntry = removeFocusEntry(afi);
-
-        if (deadEntry != null) {
-            sendFocusLoss(deadEntry, AudioManager.AUDIOFOCUS_LOSS_TRANSIENT);
-            removeFocusEntryAndRestoreUnblockedWaiters(deadEntry);
+        synchronized (mLock) {
+            FocusEntry deadEntry = removeFocusEntryLocked(afi);
+            if (deadEntry != null) {
+                sendFocusLossLocked(deadEntry, AudioManager.AUDIOFOCUS_LOSS_TRANSIENT);
+                removeBlockerAndRestoreUnblockedWaitersLocked(deadEntry);
+            }
         }
     }
 
@@ -577,11 +477,14 @@
      * @return AudioManager.AUDIOFOCUS_REQUEST_GRANTED if focus is granted
      */
     int reevaluateAndRegainAudioFocus(AudioFocusInfo afi) {
-        int results = evaluateFocusRequest(afi);
-
-        if (results == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
-            return dispatchFocusGained(afi);
+        int results;
+        synchronized (mLock) {
+            results = evaluateFocusRequestLocked(afi);
+            if (results == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
+                results = dispatchFocusGainedLocked(afi);
+            }
         }
+
         return results;
     }
 
@@ -590,20 +493,25 @@
      * @param indent indent to add to each line in the current stream
      * @param writer stream to write to
      */
-    public synchronized void dump(String indent, PrintWriter writer) {
-        writer.printf("%s*CarAudioFocus*\n", indent);
+    public void dump(String indent, PrintWriter writer) {
+        synchronized (mLock) {
+            writer.printf("%s*CarAudioFocus*\n", indent);
 
-        String innerIndent = indent + "\t";
-        writer.printf("%sCurrent Focus Holders:\n", innerIndent);
-        for (String clientId : mFocusHolders.keySet()) {
-            writer.printf("%s\t%s - %s\n", innerIndent, clientId,
-                    mFocusHolders.get(clientId).getUsageName());
-        }
+            String innerIndent = indent + "\t";
+            writer.printf("%sCurrent Focus Holders:\n", innerIndent);
+            for (String clientId : mFocusHolders.keySet()) {
+                writer.printf("%s\t%s - %s\n", innerIndent, clientId,
+                        mFocusHolders.get(clientId).getUsageName());
+            }
 
-        writer.printf("%sTransient Focus Losers:\n", innerIndent);
-        for (String clientId : mFocusLosers.keySet()) {
-            writer.printf("%s\t%s - %s\n", innerIndent, clientId,
-                    mFocusLosers.get(clientId).getUsageName());
+            writer.printf("%sTransient Focus Losers:\n", innerIndent);
+            for (String clientId : mFocusLosers.keySet()) {
+                writer.printf("%s\t%s - %s\n", innerIndent, clientId,
+                        mFocusLosers.get(clientId).getUsageName());
+            }
+
+            writer.printf("%sFocus Events:\n", innerIndent);
+            mFocusEventLogger.dump(innerIndent + "\t", writer);
         }
     }
 
@@ -627,4 +535,18 @@
                 return "unknown event " + focusEvent;
         }
     }
+
+    private static String focusRequestResponseToString(int response) {
+        if (response == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
+            return "REQUEST_GRANTED";
+        } else if (response == AudioManager.AUDIOFOCUS_REQUEST_FAILED) {
+            return "REQUEST_FAILED";
+        }
+        return "REQUEST_DELAYED";
+    }
+
+    private void logFocusEvent(String log) {
+        mFocusEventLogger.log(log);
+        Log.i(TAG, log);
+    }
 }
diff --git a/service/src/com/android/car/audio/CarAudioService.java b/service/src/com/android/car/audio/CarAudioService.java
index 147cd1e..c6c98d8 100644
--- a/service/src/com/android/car/audio/CarAudioService.java
+++ b/service/src/com/android/car/audio/CarAudioService.java
@@ -15,6 +15,11 @@
  */
 package com.android.car.audio;
 
+import static android.media.AudioAttributes.USAGE_ANNOUNCEMENT;
+import static android.media.AudioAttributes.USAGE_EMERGENCY;
+import static android.media.AudioAttributes.USAGE_SAFETY;
+import static android.media.AudioAttributes.USAGE_VEHICLE_STATUS;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.car.Car;
@@ -27,8 +32,11 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.PackageManager;
+import android.hardware.automotive.audiocontrol.V1_0.ContextNumber;
 import android.hardware.automotive.audiocontrol.V1_0.IAudioControl;
 import android.media.AudioAttributes;
+import android.media.AudioAttributes.AttributeSystemUsage;
+import android.media.AudioDeviceAttributes;
 import android.media.AudioDeviceInfo;
 import android.media.AudioDevicePort;
 import android.media.AudioFocusInfo;
@@ -44,18 +52,17 @@
 import android.os.IBinder;
 import android.os.Looper;
 import android.os.RemoteException;
-import android.provider.Settings;
 import android.telephony.TelephonyManager;
 import android.text.TextUtils;
 import android.util.Log;
-import android.util.SparseArray;
 import android.view.DisplayAddress;
 import android.view.KeyEvent;
 
-import com.android.car.BinderInterfaceContainer;
+import com.android.car.CarLocalServices;
 import com.android.car.CarLog;
 import com.android.car.CarServiceBase;
 import com.android.car.R;
+import com.android.car.user.CarUserService;
 import com.android.internal.util.Preconditions;
 
 import org.xmlpull.v1.XmlPullParserException;
@@ -71,6 +78,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.NoSuchElementException;
+import java.util.Objects;
 import java.util.Set;
 import java.util.stream.Collectors;
 
@@ -89,10 +97,6 @@
     // Key to persist master mute state in system settings
     private static final String VOLUME_SETTINGS_KEY_MASTER_MUTE = "android.car.MASTER_MUTE";
 
-    // The trailing slash forms a directory-liked hierarchy and
-    // allows listening for both GROUP/MEDIA and GROUP/NAVIGATION.
-    private static final String VOLUME_SETTINGS_KEY_FOR_GROUP_PREFIX = "android.car.VOLUME_GROUP/";
-
     // CarAudioService reads configuration from the following paths respectively.
     // If the first one is found, all others are ignored.
     // If no one is found, it fallbacks to car_volume_groups.xml resource file.
@@ -101,17 +105,12 @@
             "/system/etc/car_audio_configuration.xml"
     };
 
-    /**
-     * Gets the key to persist volume for a volume group in settings
-     *
-     * @param zoneId The audio zone id
-     * @param groupId The volume group id
-     * @return Key to persist volume index for volume group in system settings
-     */
-    static String getVolumeSettingsKeyForGroup(int zoneId, int groupId) {
-        final int maskedGroupId = (zoneId << 8) + groupId;
-        return VOLUME_SETTINGS_KEY_FOR_GROUP_PREFIX + maskedGroupId;
-    }
+    private static final @AttributeSystemUsage int[] SYSTEM_USAGES = new int[] {
+            USAGE_EMERGENCY,
+            USAGE_SAFETY,
+            USAGE_VEHICLE_STATUS,
+            USAGE_ANNOUNCEMENT
+    };
 
     private final Object mImplLock = new Object();
 
@@ -120,6 +119,9 @@
     private final AudioManager mAudioManager;
     private final boolean mUseDynamicRouting;
     private final boolean mPersistMasterMuteState;
+    private final CarVolumeSettings mCarVolumeSettings;
+
+    private final CarUserService.UserCallback  mReceiver = new CarAudioServiceUserCallback();
 
     private final AudioPolicy.AudioPolicyVolumeCallback mAudioPolicyVolumeCallback =
             new AudioPolicy.AudioPolicyVolumeCallback() {
@@ -162,9 +164,6 @@
         }
     };
 
-    private final BinderInterfaceContainer<ICarVolumeCallback> mVolumeCallbackContainer =
-            new BinderInterfaceContainer<>();
-
     /**
      * Simulates {@link ICarVolumeCallback} when it's running in legacy mode.
      * This receiver assumes the intent is sent to {@link CarAudioManager#PRIMARY_AUDIO_ZONE}.
@@ -194,6 +193,7 @@
     private CarZonesAudioFocus mFocusHandler;
     private String mCarAudioConfigurationPath;
     private CarAudioZone[] mCarAudioZones;
+    private final CarVolumeCallbackHandler mCarVolumeCallbackHandler;
 
     // TODO do not store uid mapping here instead use the uid
     //  device affinity in audio policy when available
@@ -207,6 +207,8 @@
         mPersistMasterMuteState = mContext.getResources().getBoolean(
                 R.bool.audioPersistMasterMuteState);
         mUidToZoneMap = new HashMap<>();
+        mCarVolumeCallbackHandler = new CarVolumeCallbackHandler();
+        mCarVolumeSettings = new CarVolumeSettings(mContext);
     }
 
     /**
@@ -216,29 +218,9 @@
     @Override
     public void init() {
         synchronized (mImplLock) {
+            CarLocalServices.getService(CarUserService.class).addUserCallback(mReceiver);
             if (mUseDynamicRouting) {
-                // Enumerate all output bus device ports
-                AudioDeviceInfo[] deviceInfos = mAudioManager.getDevices(
-                        AudioManager.GET_DEVICES_OUTPUTS);
-                if (deviceInfos.length == 0) {
-                    Log.e(CarLog.TAG_AUDIO, "No output device available, ignore");
-                    return;
-                }
-                SparseArray<CarAudioDeviceInfo> busToCarAudioDeviceInfo = new SparseArray<>();
-                for (AudioDeviceInfo info : deviceInfos) {
-                    Log.v(CarLog.TAG_AUDIO, String.format("output id=%d address=%s type=%s",
-                            info.getId(), info.getAddress(), info.getType()));
-                    if (info.getType() == AudioDeviceInfo.TYPE_BUS) {
-                        final CarAudioDeviceInfo carInfo = new CarAudioDeviceInfo(info);
-                        // See also the audio_policy_configuration.xml,
-                        // the bus number should be no less than zero.
-                        if (carInfo.getBusNumber() >= 0) {
-                            busToCarAudioDeviceInfo.put(carInfo.getBusNumber(), carInfo);
-                            Log.i(CarLog.TAG_AUDIO, "Valid bus found " + carInfo);
-                        }
-                    }
-                }
-                setupDynamicRouting(busToCarAudioDeviceInfo);
+                setupDynamicRouting();
             } else {
                 Log.i(CarLog.TAG_AUDIO, "Audio dynamic routing not enabled, run in legacy mode");
                 setupLegacyVolumeChangedListener();
@@ -246,10 +228,11 @@
 
             // Restore master mute state if applicable
             if (mPersistMasterMuteState) {
-                boolean storedMasterMute = Settings.Global.getInt(mContext.getContentResolver(),
-                        VOLUME_SETTINGS_KEY_MASTER_MUTE, 0) != 0;
+                boolean storedMasterMute = mCarVolumeSettings.getMasterMute();
                 setMasterMute(storedMasterMute, 0);
             }
+
+            mAudioManager.setSupportedSystemUsages(SYSTEM_USAGES);
         }
     }
 
@@ -267,7 +250,7 @@
                 mContext.unregisterReceiver(mLegacyVolumeChangedReceiver);
             }
 
-            mVolumeCallbackContainer.clear();
+            mCarVolumeCallbackHandler.release();
         }
     }
 
@@ -327,14 +310,7 @@
     }
 
     private void callbackGroupVolumeChange(int zoneId, int groupId, int flags) {
-        for (BinderInterfaceContainer.BinderInterface<ICarVolumeCallback> callback :
-                mVolumeCallbackContainer.getInterfaces()) {
-            try {
-                callback.binderInterface.onGroupVolumeChanged(zoneId, groupId, flags);
-            } catch (RemoteException e) {
-                Log.e(CarLog.TAG_AUDIO, "Failed to callback onGroupVolumeChanged", e);
-            }
-        }
+        mCarVolumeCallbackHandler.onVolumeGroupChange(zoneId, groupId, flags);
     }
 
     private void setMasterMute(boolean mute, int flags) {
@@ -348,20 +324,11 @@
     }
 
     private void callbackMasterMuteChange(int zoneId, int flags) {
-        for (BinderInterfaceContainer.BinderInterface<ICarVolumeCallback> callback :
-                mVolumeCallbackContainer.getInterfaces()) {
-            try {
-                callback.binderInterface.onMasterMuteChanged(zoneId, flags);
-            } catch (RemoteException e) {
-                Log.e(CarLog.TAG_AUDIO, "Failed to callback onMasterMuteChanged", e);
-            }
-        }
+        mCarVolumeCallbackHandler.onMasterMuteChanged(zoneId, flags);
 
         // Persists master mute state if applicable
         if (mPersistMasterMuteState) {
-            Settings.Global.putInt(mContext.getContentResolver(),
-                    VOLUME_SETTINGS_KEY_MASTER_MUTE,
-                    mAudioManager.isMasterMute() ? 1 : 0);
+            mCarVolumeSettings.storeMasterMute(mAudioManager.isMasterMute());
         }
     }
 
@@ -423,7 +390,7 @@
     }
 
     private CarVolumeGroup getCarVolumeGroup(int zoneId, int groupId) {
-        Preconditions.checkNotNull(mCarAudioZones);
+        Objects.requireNonNull(mCarAudioZones);
         Preconditions.checkArgumentInRange(zoneId, 0, mCarAudioZones.length - 1,
                 "zoneId out of range: " + zoneId);
         return mCarAudioZones[zoneId].getVolumeGroup(groupId);
@@ -436,34 +403,65 @@
         mContext.registerReceiver(mLegacyVolumeChangedReceiver, intentFilter);
     }
 
-    private void setupDynamicRouting(SparseArray<CarAudioDeviceInfo> busToCarAudioDeviceInfo) {
-        final AudioPolicy.Builder builder = new AudioPolicy.Builder(mContext);
-        builder.setLooper(Looper.getMainLooper());
+    private List<CarAudioDeviceInfo> generateCarAudioDeviceInfos() {
+        AudioDeviceInfo[] deviceInfos = mAudioManager.getDevices(
+                AudioManager.GET_DEVICES_OUTPUTS);
+
+        return Arrays.stream(deviceInfos)
+                .filter(info -> info.getType() == AudioDeviceInfo.TYPE_BUS)
+                .map(CarAudioDeviceInfo::new)
+                .collect(Collectors.toList());
+    }
+
+    private AudioDeviceInfo[] getAllInputDevices() {
+        return mAudioManager.getDevices(
+                AudioManager.GET_DEVICES_INPUTS);
+    }
+
+    private CarAudioZone[] loadCarAudioConfiguration(List<CarAudioDeviceInfo> carAudioDeviceInfos) {
+        AudioDeviceInfo[] inputDevices = getAllInputDevices();
+        try (InputStream inputStream = new FileInputStream(mCarAudioConfigurationPath)) {
+            CarAudioZonesHelper zonesHelper = new CarAudioZonesHelper(mContext, inputStream,
+                    carAudioDeviceInfos, inputDevices);
+            return zonesHelper.loadAudioZones();
+        } catch (IOException | XmlPullParserException e) {
+            throw new RuntimeException("Failed to parse audio zone configuration", e);
+        }
+    }
+
+    private CarAudioZone[] loadVolumeGroupConfigurationWithAudioControl(
+            List<CarAudioDeviceInfo> carAudioDeviceInfos) {
+        // In legacy mode, context -> bus mapping is done by querying IAudioControl HAL.
+        final IAudioControl audioControl = getAudioControl();
+        if (audioControl == null) {
+            throw new RuntimeException(
+                    "Dynamic routing requested but audioControl HAL not available");
+        }
+        CarAudioZonesHelperLegacy legacyHelper = new CarAudioZonesHelperLegacy(mContext,
+                R.xml.car_volume_groups, carAudioDeviceInfos, audioControl);
+        return legacyHelper.loadAudioZones();
+    }
+
+    private void loadCarAudioZones() {
+        List<CarAudioDeviceInfo> carAudioDeviceInfos = generateCarAudioDeviceInfos();
 
         mCarAudioConfigurationPath = getAudioConfigurationPath();
         if (mCarAudioConfigurationPath != null) {
-            try (InputStream inputStream = new FileInputStream(mCarAudioConfigurationPath)) {
-                CarAudioZonesHelper zonesHelper = new CarAudioZonesHelper(mContext, inputStream,
-                        busToCarAudioDeviceInfo);
-                mCarAudioZones = zonesHelper.loadAudioZones();
-            } catch (IOException | XmlPullParserException e) {
-                throw new RuntimeException("Failed to parse audio zone configuration", e);
-            }
+            mCarAudioZones = loadCarAudioConfiguration(carAudioDeviceInfos);
         } else {
-            // In legacy mode, context -> bus mapping is done by querying IAudioControl HAL.
-            final IAudioControl audioControl = getAudioControl();
-            if (audioControl == null) {
-                throw new RuntimeException(
-                        "Dynamic routing requested but audioControl HAL not available");
-            }
-            CarAudioZonesHelperLegacy legacyHelper = new CarAudioZonesHelperLegacy(mContext,
-                    R.xml.car_volume_groups, busToCarAudioDeviceInfo, audioControl);
-            mCarAudioZones = legacyHelper.loadAudioZones();
+            mCarAudioZones = loadVolumeGroupConfigurationWithAudioControl(carAudioDeviceInfos);
         }
+
+        CarAudioZonesValidator.validate(mCarAudioZones);
+    }
+
+    private void setupDynamicRouting() {
+        final AudioPolicy.Builder builder = new AudioPolicy.Builder(mContext);
+        builder.setLooper(Looper.getMainLooper());
+
+        loadCarAudioZones();
+
         for (CarAudioZone zone : mCarAudioZones) {
-            if (!zone.validateVolumeGroups()) {
-                throw new RuntimeException("Invalid volume groups configuration");
-            }
             // Ensure HAL gets our initial value
             zone.synchronizeCurrentGainIndex();
             Log.v(CarLog.TAG_AUDIO, "Processed audio zone: " + zone);
@@ -514,13 +512,6 @@
         return null;
     }
 
-    /**
-     * @return Context number for a given audio usage, 0 if the given usage is unrecognized.
-     */
-    int getContextForUsage(int audioUsage) {
-        return CarAudioDynamicRouting.USAGE_TO_CONTEXT.get(audioUsage);
-    }
-
     @Override
     public void setFadeTowardFront(float value) {
         synchronized (mImplLock) {
@@ -624,11 +615,11 @@
                 break;
             }
         }
-        Preconditions.checkNotNull(sourcePortInfo,
+        Objects.requireNonNull(sourcePortInfo,
                 "Specified source is not available: " + sourceAddress);
 
         // Find the output port associated with the given carUsage
-        AudioDevicePort sinkPort = Preconditions.checkNotNull(getAudioPort(usage),
+        AudioDevicePort sinkPort = Objects.requireNonNull(getAudioPort(usage),
                 "Sink not available for usage: " + AudioAttributes.usageToString(usage));
 
         // {@link android.media.AudioPort#activeConfig()} is valid for mixer port only,
@@ -641,7 +632,7 @@
 
         // Configure the source port to match the output port except for a gain adjustment
         final CarAudioDeviceInfo helper = new CarAudioDeviceInfo(sourcePortInfo);
-        AudioGain audioGain = Preconditions.checkNotNull(helper.getAudioGain(),
+        AudioGain audioGain = Objects.requireNonNull(helper.getAudioGain(),
                 "Gain controller not available for source port");
 
         // size of gain values is 1 in MODE_JOINT
@@ -662,7 +653,7 @@
             throw new RuntimeException("createAudioPatch failed with code " + result);
         }
 
-        Preconditions.checkNotNull(patch[0],
+        Objects.requireNonNull(patch[0],
                 "createAudioPatch didn't provide expected single handle");
         Log.d(CarLog.TAG_AUDIO, "Audio patch created: " + patch[0]);
 
@@ -726,7 +717,7 @@
             for (int i = 0; i < groups.length; i++) {
                 int[] contexts = groups[i].getContexts();
                 for (int context : contexts) {
-                    if (getContextForUsage(usage) == context) {
+                    if (CarAudioContext.getContextForUsage(usage) == context) {
                         return i;
                     }
                 }
@@ -749,9 +740,10 @@
             Set<Integer> contexts =
                     Arrays.stream(group.getContexts()).boxed().collect(Collectors.toSet());
             final List<Integer> usages = new ArrayList<>();
-            for (int i = 0; i < CarAudioDynamicRouting.USAGE_TO_CONTEXT.size(); i++) {
-                if (contexts.contains(CarAudioDynamicRouting.USAGE_TO_CONTEXT.valueAt(i))) {
-                    usages.add(CarAudioDynamicRouting.USAGE_TO_CONTEXT.keyAt(i));
+            for (@CarAudioContext.AudioContext int context : contexts) {
+                int[] usagesForContext = CarAudioContext.getUsagesForContext(context);
+                for (@AudioAttributes.AttributeUsage int usage : usagesForContext) {
+                    usages.add(usage);
                 }
             }
             return usages.stream().mapToInt(i -> i).toArray();
@@ -857,6 +849,18 @@
         }
     }
 
+    @Override
+    public String getOutputDeviceAddressForUsage(int zoneId,
+            @AudioAttributes.AttributeUsage int usage) {
+        enforcePermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS);
+        Preconditions.checkArgumentInRange(zoneId, 0, mCarAudioZones.length - 1,
+                "zoneId (" + zoneId + ")");
+        int contextForUsage = CarAudioContext.getContextForUsage(usage);
+        Preconditions.checkArgument(contextForUsage != ContextNumber.INVALID,
+                "Invalid audio attribute usage %d", usage);
+        return mCarAudioZones[zoneId].getAddressForContext(contextForUsage);
+    }
+
     /**
      * Regain focus for the focus list passed in
      * @param afiList focus info list to regain
@@ -962,8 +966,7 @@
     public void registerVolumeCallback(@NonNull IBinder binder) {
         synchronized (mImplLock) {
             enforcePermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME);
-
-            mVolumeCallbackContainer.addBinder(ICarVolumeCallback.Stub.asInterface(binder));
+            mCarVolumeCallbackHandler.registerCallback(binder);
         }
     }
 
@@ -971,8 +974,7 @@
     public void unregisterVolumeCallback(@NonNull IBinder binder) {
         synchronized (mImplLock) {
             enforcePermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME);
-
-            mVolumeCallbackContainer.removeBinder(ICarVolumeCallback.Stub.asInterface(binder));
+            mCarVolumeCallbackHandler.unregisterCallback(binder);
         }
     }
 
@@ -990,11 +992,11 @@
     private @Nullable AudioDevicePort getAudioPort(@AudioAttributes.AttributeUsage int usage) {
         int zoneId = CarAudioManager.PRIMARY_AUDIO_ZONE;
         final int groupId = getVolumeGroupIdForUsage(zoneId, usage);
-        final CarVolumeGroup group = Preconditions.checkNotNull(
+        final CarVolumeGroup group = Objects.requireNonNull(
                 mCarAudioZones[zoneId].getVolumeGroup(groupId),
                 "Can not find CarVolumeGroup by usage: "
                         + AudioAttributes.usageToString(usage));
-        return group.getAudioDevicePortForContext(getContextForUsage(usage));
+        return group.getAudioDevicePortForContext(CarAudioContext.getContextForUsage(usage));
     }
 
     /**
@@ -1023,6 +1025,21 @@
     }
 
     /**
+     * Gets the input devices for zone zoneId
+     */
+    public @NonNull List<AudioDeviceAttributes> getInputDevicesForZoneId(int zoneId) {
+        enforcePermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS);
+        Preconditions.checkArgumentInRange(zoneId, 0, mCarAudioZones.length - 1,
+                "zoneId out of range: " + zoneId);
+        for (CarAudioZone zone : mCarAudioZones) {
+            if (zone.getId() == zoneId) {
+                return zone.getInputAudioDevices();
+            }
+        }
+        throw new IllegalArgumentException("zoneId does not exist" + zoneId);
+    }
+
+    /**
      * Gets volume group by a given legacy stream type
      * @param streamType Legacy stream type such as {@link AudioManager#STREAM_MUSIC}
      * @return volume group id mapped from stream type
@@ -1038,6 +1055,16 @@
         return groupId;
     }
 
+    /**
+     * Updates the volume group for user
+     * @param userId user id to update
+     */
+    private void updateVolumeGroupForUser(int userId) {
+        for (CarAudioZone zone : mCarAudioZones) {
+            zone.updateVolumeGroupsForUser(userId);
+        }
+    }
+
     @Nullable
     private static IAudioControl getAudioControl() {
         try {
@@ -1049,4 +1076,17 @@
         }
         return null;
     }
+
+    private class CarAudioServiceUserCallback implements  CarUserService.UserCallback {
+
+        @Override
+        public void onUserLockChanged(int userId, boolean unlocked) {
+            //Do nothing user is already loaded.
+        }
+
+        @Override
+        public void onSwitchUser(int userId) {
+            updateVolumeGroupForUser(userId);
+        }
+    }
 }
diff --git a/service/src/com/android/car/audio/CarAudioZone.java b/service/src/com/android/car/audio/CarAudioZone.java
index 4bdf0b7..a87b4b1 100644
--- a/service/src/com/android/car/audio/CarAudioZone.java
+++ b/service/src/com/android/car/audio/CarAudioZone.java
@@ -16,6 +16,7 @@
 package com.android.car.audio;
 
 import android.car.media.CarAudioManager;
+import android.media.AudioDeviceAttributes;
 import android.media.AudioDeviceInfo;
 import android.util.Log;
 import android.view.DisplayAddress;
@@ -45,12 +46,14 @@
     private final String mName;
     private final List<CarVolumeGroup> mVolumeGroups;
     private final List<DisplayAddress.Physical> mPhysicalDisplayAddresses;
+    private List<AudioDeviceAttributes> mInputAudioDevice;
 
     CarAudioZone(int id, String name) {
         mId = id;
         mName = name;
         mVolumeGroups = new ArrayList<>();
         mPhysicalDisplayAddresses = new ArrayList<>();
+        mInputAudioDevice = new ArrayList<>();
     }
 
     int getId() {
@@ -81,8 +84,8 @@
     List<AudioDeviceInfo> getAudioDeviceInfos() {
         final List<AudioDeviceInfo> devices = new ArrayList<>();
         for (CarVolumeGroup group : mVolumeGroups) {
-            for (int busNumber : group.getBusNumbers()) {
-                devices.add(group.getCarAudioDeviceInfoForBus(busNumber).getAudioDeviceInfo());
+            for (String address : group.getAddresses()) {
+                devices.add(group.getCarAudioDeviceInfoForAddress(address).getAudioDeviceInfo());
             }
         }
         return devices;
@@ -121,43 +124,40 @@
      *
      * - One context should not appear in two groups
      * - All contexts are assigned
-     * - One bus should not appear in two groups
+     * - One device should not appear in two groups
      * - All gain controllers in the same group have same step value
      *
-     * Note that it is fine that there are buses not appear in any group, those buses may be
-     * reserved for other usages.
-     * Step value validation is done in {@link CarVolumeGroup#bind(int, int, CarAudioDeviceInfo)}
+     * Note that it is fine that there are devices which do not appear in any group. Those devices
+     * may be reserved for other purposes.
+     * Step value validation is done in {@link CarVolumeGroup#bind(int, CarAudioDeviceInfo)}
      */
     boolean validateVolumeGroups() {
         Set<Integer> contextSet = new HashSet<>();
-        Set<Integer> busNumberSet = new HashSet<>();
+        Set<String> addresses = new HashSet<>();
         for (CarVolumeGroup group : mVolumeGroups) {
             // One context should not appear in two groups
             for (int context : group.getContexts()) {
-                if (contextSet.contains(context)) {
+                if (!contextSet.add(context)) {
                     Log.e(CarLog.TAG_AUDIO, "Context appears in two groups: " + context);
                     return false;
                 }
-                contextSet.add(context);
             }
 
-            // One bus should not appear in two groups
-            for (int busNumber : group.getBusNumbers()) {
-                if (busNumberSet.contains(busNumber)) {
-                    Log.e(CarLog.TAG_AUDIO, "Bus appears in two groups: " + busNumber);
+            // One address should not appear in two groups
+            for (String address : group.getAddresses()) {
+                if (!addresses.add(address)) {
+                    Log.e(CarLog.TAG_AUDIO, "Address appears in two groups: " + address);
                     return false;
                 }
-                busNumberSet.add(busNumber);
             }
         }
 
         // All contexts are assigned
-        if (contextSet.size() != CarAudioDynamicRouting.CONTEXT_NUMBERS.length) {
+        if (contextSet.size() != CarAudioContext.CONTEXTS.length) {
             Log.e(CarLog.TAG_AUDIO, "Some contexts are not assigned to group");
-            Log.e(CarLog.TAG_AUDIO, "Assigned contexts "
-                    + Arrays.toString(contextSet.toArray(new Integer[0])));
+            Log.e(CarLog.TAG_AUDIO, "Assigned contexts " + contextSet);
             Log.e(CarLog.TAG_AUDIO,
-                    "All contexts " + Arrays.toString(CarAudioDynamicRouting.CONTEXT_NUMBERS));
+                    "All contexts " + Arrays.toString(CarAudioContext.CONTEXTS));
             return false;
         }
 
@@ -171,16 +171,57 @@
     }
 
     void dump(String indent, PrintWriter writer) {
+        String internalIndent = indent + "\t";
         writer.printf("%sCarAudioZone(%s:%d) isPrimary? %b\n", indent, mName, mId, isPrimaryZone());
         for (DisplayAddress.Physical physical: mPhysicalDisplayAddresses) {
             long port = (long) physical.getPort();
-            writer.printf("%sDisplayAddress.Physical(%d)\n", indent + "\t", port);
+            writer.printf("%sDisplayAddress.Physical(%d)\n", internalIndent, port);
         }
         writer.println();
 
         for (CarVolumeGroup group : mVolumeGroups) {
-            group.dump(indent + "\t", writer);
+            group.dump(internalIndent, writer);
+        }
+
+        writer.printf("%sInput Audio Device Addresses\n", internalIndent);
+        String devicesIndent = internalIndent + "\t";
+        for (AudioDeviceAttributes audioDevice : mInputAudioDevice) {
+            writer.printf("%sDevice Address(%s)\n", devicesIndent,
+                    audioDevice.getAddress());
         }
         writer.println();
     }
+
+    String getAddressForContext(int audioContext) {
+        CarAudioContext.preconditionCheckAudioContext(audioContext);
+        String deviceAddress = null;
+        for (CarVolumeGroup volumeGroup : getVolumeGroups()) {
+            deviceAddress = volumeGroup.getAddressForContext(audioContext);
+            if (deviceAddress != null) {
+                return deviceAddress;
+            }
+        }
+        // This should not happen unless something went wrong.
+        // Device address are unique per zone and all contexts are assigned in a zone.
+        throw new IllegalStateException("Could not find output device in zone " + mId
+                + " for audio context " + audioContext);
+    }
+
+    /**
+     * Update the volume groups for the new user
+     * @param userId user id to update to
+     */
+    public void updateVolumeGroupsForUser(int userId) {
+        for (CarVolumeGroup group : mVolumeGroups) {
+            group.loadVolumesForUser(userId);
+        }
+    }
+
+    void addInputAudioDevice(AudioDeviceAttributes device) {
+        mInputAudioDevice.add(device);
+    }
+
+    List<AudioDeviceAttributes> getInputAudioDevices() {
+        return mInputAudioDevice;
+    }
 }
diff --git a/service/src/com/android/car/audio/CarAudioZonesHelper.java b/service/src/com/android/car/audio/CarAudioZonesHelper.java
index 17d9393..053c71a 100644
--- a/service/src/com/android/car/audio/CarAudioZonesHelper.java
+++ b/service/src/com/android/car/audio/CarAudioZonesHelper.java
@@ -18,8 +18,10 @@
 import android.annotation.NonNull;
 import android.car.media.CarAudioManager;
 import android.content.Context;
-import android.hardware.automotive.audiocontrol.V1_0.ContextNumber;
-import android.util.SparseArray;
+import android.media.AudioDeviceAttributes;
+import android.media.AudioDeviceInfo;
+import android.text.TextUtils;
+import android.util.SparseIntArray;
 import android.util.Xml;
 import android.view.DisplayAddress;
 
@@ -36,12 +38,15 @@
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Set;
+import java.util.stream.Collectors;
 
 /**
  * A helper class loads all audio zones from the configuration XML file.
  */
 /* package */ class CarAudioZonesHelper {
+    private static final int INVALID_ZONE_ID = -1;
 
     private static final String NAMESPACE = null;
     private static final String TAG_ROOT = "carAudioConfiguration";
@@ -59,46 +64,98 @@
     private static final String ATTR_DEVICE_ADDRESS = "address";
     private static final String ATTR_CONTEXT_NAME = "context";
     private static final String ATTR_PHYSICAL_PORT = "port";
-    private static final int SUPPORTED_VERSION = 1;
+    private static final String ATTR_ZONE_ID = "audioZoneId";
+    private static final String ATTR_OCCUPANT_ZONE_ID = "occupantZoneId";
+    private static final String TAG_INPUT_DEVICES = "inputDevices";
+    private static final String TAG_INPUT_DEVICE = "inputDevice";
+    private static final int INVALID_VERSION = -1;
+    private static final int SUPPORTED_VERSION_1 = 1;
+    private static final int SUPPORTED_VERSION_2 = 2;
+    private static final SparseIntArray SUPPORTED_VERSIONS;
+
 
     private static final Map<String, Integer> CONTEXT_NAME_MAP;
 
     static {
-        CONTEXT_NAME_MAP = new HashMap<>();
-        CONTEXT_NAME_MAP.put("music", ContextNumber.MUSIC);
-        CONTEXT_NAME_MAP.put("navigation", ContextNumber.NAVIGATION);
-        CONTEXT_NAME_MAP.put("voice_command", ContextNumber.VOICE_COMMAND);
-        CONTEXT_NAME_MAP.put("call_ring", ContextNumber.CALL_RING);
-        CONTEXT_NAME_MAP.put("call", ContextNumber.CALL);
-        CONTEXT_NAME_MAP.put("alarm", ContextNumber.ALARM);
-        CONTEXT_NAME_MAP.put("notification", ContextNumber.NOTIFICATION);
-        CONTEXT_NAME_MAP.put("system_sound", ContextNumber.SYSTEM_SOUND);
+        CONTEXT_NAME_MAP = new HashMap<>(8);
+        CONTEXT_NAME_MAP.put("music", CarAudioContext.MUSIC);
+        CONTEXT_NAME_MAP.put("navigation", CarAudioContext.NAVIGATION);
+        CONTEXT_NAME_MAP.put("voice_command", CarAudioContext.VOICE_COMMAND);
+        CONTEXT_NAME_MAP.put("call_ring", CarAudioContext.CALL_RING);
+        CONTEXT_NAME_MAP.put("call", CarAudioContext.CALL);
+        CONTEXT_NAME_MAP.put("alarm", CarAudioContext.ALARM);
+        CONTEXT_NAME_MAP.put("notification", CarAudioContext.NOTIFICATION);
+        CONTEXT_NAME_MAP.put("system_sound", CarAudioContext.SYSTEM_SOUND);
+
+        SUPPORTED_VERSIONS = new SparseIntArray(2);
+        SUPPORTED_VERSIONS.put(SUPPORTED_VERSION_1, SUPPORTED_VERSION_1);
+        SUPPORTED_VERSIONS.put(SUPPORTED_VERSION_2, SUPPORTED_VERSION_2);
     }
 
     private final Context mContext;
-    private final SparseArray<CarAudioDeviceInfo> mBusToCarAudioDeviceInfo;
+    private final Map<String, CarAudioDeviceInfo> mAddressToCarAudioDeviceInfo;
+    private final Map<String, AudioDeviceInfo> mAddressToInputAudioDeviceInfo;
     private final InputStream mInputStream;
     private final Set<Long> mPortIds;
+    private final SparseIntArray mZoneIdToOccupantZoneIdMapping;
+    private final Set<Integer> mAudioZoneIds;
+    private final Set<String> mInputAudioDevices;
 
     private boolean mHasPrimaryZone;
     private int mNextSecondaryZoneId;
+    private int mCurrentVersion;
 
+    /**
+     * <p><b>Note: <b/> CarAudioZonesHelper is expected to be used from a single thread. This
+     * should be the same thread that originally called new CarAudioZonesHelper.
+     */
     CarAudioZonesHelper(Context context, @NonNull InputStream inputStream,
-            @NonNull SparseArray<CarAudioDeviceInfo> busToCarAudioDeviceInfo) {
+            @NonNull List<CarAudioDeviceInfo> carAudioDeviceInfos,
+            @NonNull AudioDeviceInfo[] inputDeviceInfo) {
         mContext = context;
         mInputStream = inputStream;
-        mBusToCarAudioDeviceInfo = busToCarAudioDeviceInfo;
-
+        mAddressToCarAudioDeviceInfo = CarAudioZonesHelper.generateAddressToInfoMap(
+                carAudioDeviceInfos);
+        mAddressToInputAudioDeviceInfo =
+                CarAudioZonesHelper.generateAddressToInputAudioDeviceInfoMap(inputDeviceInfo);
         mNextSecondaryZoneId = CarAudioManager.PRIMARY_AUDIO_ZONE + 1;
         mPortIds = new HashSet<>();
+        mZoneIdToOccupantZoneIdMapping = new SparseIntArray();
+        mAudioZoneIds = new HashSet<>();
+        mInputAudioDevices = new HashSet<>();
     }
 
+    SparseIntArray getCarAudioZoneIdToOccupantZoneIdMapping() {
+        return mZoneIdToOccupantZoneIdMapping;
+    }
+
+    // TODO: refactor this method to return List<CarAudioZone>
     CarAudioZone[] loadAudioZones() throws IOException, XmlPullParserException {
         List<CarAudioZone> carAudioZones = new ArrayList<>();
         parseCarAudioZones(carAudioZones, mInputStream);
         return carAudioZones.toArray(new CarAudioZone[0]);
     }
 
+    private static Map<String, CarAudioDeviceInfo> generateAddressToInfoMap(
+            List<CarAudioDeviceInfo> carAudioDeviceInfos) {
+        return carAudioDeviceInfos.stream()
+                .filter(info -> !TextUtils.isEmpty(info.getAddress()))
+                .collect(Collectors.toMap(CarAudioDeviceInfo::getAddress, info -> info));
+    }
+
+    private static Map<String, AudioDeviceInfo> generateAddressToInputAudioDeviceInfoMap(
+            @NonNull AudioDeviceInfo[] inputAudioDeviceInfos) {
+        HashMap<String, AudioDeviceInfo> deviceAddressToInputDeviceMap =
+                new HashMap<>(inputAudioDeviceInfos.length);
+        for (int i = 0; i < inputAudioDeviceInfos.length; ++i) {
+            AudioDeviceInfo device = inputAudioDeviceInfos[i];
+            if (device.isSource()) {
+                deviceAddressToInputDeviceMap.put(device.getAddress(), device);
+            }
+        }
+        return deviceAddressToInputDeviceMap;
+    }
+
     private void parseCarAudioZones(List<CarAudioZone> carAudioZones, InputStream stream)
             throws XmlPullParserException, IOException {
         final XmlPullParser parser = Xml.newPullParser();
@@ -112,11 +169,14 @@
         // Version check
         final int versionNumber = Integer.parseInt(
                 parser.getAttributeValue(NAMESPACE, ATTR_VERSION));
-        if (versionNumber != SUPPORTED_VERSION) {
-            throw new RuntimeException("Support version:"
-                    + SUPPORTED_VERSION + " only, got version:" + versionNumber);
+
+        if (SUPPORTED_VERSIONS.get(versionNumber, INVALID_VERSION) == INVALID_VERSION) {
+            throw new IllegalArgumentException("Latest Supported version:"
+                    + SUPPORTED_VERSION_2 + " , got version:" + versionNumber);
         }
 
+        mCurrentVersion = versionNumber;
+
         // Get all zones configured under <zones> tag
         while (parser.next() != XmlPullParser.END_TAG) {
             if (parser.getEventType() != XmlPullParser.START_TAG) continue;
@@ -151,10 +211,9 @@
             mHasPrimaryZone = true;
         }
         final String zoneName = parser.getAttributeValue(NAMESPACE, ATTR_ZONE_NAME);
-
-        CarAudioZone zone = new CarAudioZone(
-                isPrimary ? CarAudioManager.PRIMARY_AUDIO_ZONE : getNextSecondaryZoneId(),
-                zoneName);
+        final int audioZoneId = getZoneId(isPrimary, parser);
+        parseOccupantZoneId(audioZoneId, parser);
+        final CarAudioZone zone = new CarAudioZone(audioZoneId, zoneName);
         while (parser.next() != XmlPullParser.END_TAG) {
             if (parser.getEventType() != XmlPullParser.START_TAG) continue;
             // Expect one <volumeGroups> in one audio zone
@@ -162,6 +221,8 @@
                 parseVolumeGroups(parser, zone);
             } else if (TAG_DISPLAYS.equals(parser.getName())) {
                 parseDisplays(parser, zone);
+            } else if (TAG_INPUT_DEVICES.equals(parser.getName())) {
+                parseInputAudioDevices(parser, zone);
             } else {
                 skip(parser);
             }
@@ -169,6 +230,101 @@
         return zone;
     }
 
+    private int getZoneId(boolean isPrimary, XmlPullParser parser) {
+        String audioZoneIdString = parser.getAttributeValue(NAMESPACE, ATTR_ZONE_ID);
+        if (mCurrentVersion == SUPPORTED_VERSION_1) {
+            Preconditions.checkArgument(audioZoneIdString == null,
+                    "Invalid audio attribute %s"
+                            + ", Please update car audio configurations file "
+                            + "to version to 2 to use it.", ATTR_ZONE_ID);
+            return isPrimary ? CarAudioManager.PRIMARY_AUDIO_ZONE
+                    : getNextSecondaryZoneId();
+        }
+        // Primary zone does not need to define it
+        if (isPrimary && audioZoneIdString == null) {
+            return CarAudioManager.PRIMARY_AUDIO_ZONE;
+        }
+        Objects.requireNonNull(audioZoneIdString, () ->
+                "Requires " + ATTR_ZONE_ID + " for all audio zones.");
+        int zoneId = parsePositiveIntAttribute(ATTR_ZONE_ID, audioZoneIdString);
+        //Verify that primary zone id is PRIMARY_AUDIO_ZONE
+        if (isPrimary) {
+            Preconditions.checkArgument(zoneId == CarAudioManager.PRIMARY_AUDIO_ZONE,
+                    "Primary zone %s must be %d or it can be left empty.",
+                    ATTR_ZONE_ID, CarAudioManager.PRIMARY_AUDIO_ZONE);
+        } else {
+            Preconditions.checkArgument(zoneId != CarAudioManager.PRIMARY_AUDIO_ZONE,
+                    "%s can only be %d for primary zone.",
+                    ATTR_ZONE_ID, CarAudioManager.PRIMARY_AUDIO_ZONE);
+        }
+        validateAudioZoneIdIsUnique(zoneId);
+        return zoneId;
+    }
+
+    private void parseOccupantZoneId(int audioZoneId, XmlPullParser parser) {
+        String occupantZoneIdString = parser.getAttributeValue(NAMESPACE, ATTR_OCCUPANT_ZONE_ID);
+        if (mCurrentVersion == SUPPORTED_VERSION_1) {
+            Preconditions.checkArgument(occupantZoneIdString == null,
+                    "Invalid audio attribute %s"
+                            + ", Please update car audio configurations file "
+                            + "to version to 2 to use it.", ATTR_OCCUPANT_ZONE_ID);
+            return;
+        }
+        //Occupant id not required for all zones
+        if (occupantZoneIdString == null) {
+            return;
+        }
+        int occupantZoneId = parsePositiveIntAttribute(ATTR_OCCUPANT_ZONE_ID, occupantZoneIdString);
+        validateOccupantZoneIdIsUnique(occupantZoneId);
+        mZoneIdToOccupantZoneIdMapping.put(audioZoneId, occupantZoneId);
+    }
+
+    private int parsePositiveIntAttribute(String attribute, String integerString) {
+        try {
+            return Integer.parseUnsignedInt(integerString);
+        } catch (NumberFormatException | IndexOutOfBoundsException e) {
+            throw new IllegalArgumentException(attribute + " must be a positive integer, but was \""
+                    + integerString + "\" instead.", e);
+        }
+    }
+
+    private void parseInputAudioDevices(XmlPullParser parser, CarAudioZone zone)
+            throws IOException, XmlPullParserException {
+        if (mCurrentVersion == SUPPORTED_VERSION_1) {
+            throw new IllegalStateException(
+                    TAG_INPUT_DEVICES + " are not supported in car_audio_configuration.xml version "
+                            + SUPPORTED_VERSION_1);
+        }
+        while (parser.next() != XmlPullParser.END_TAG) {
+            if (parser.getEventType() != XmlPullParser.START_TAG) continue;
+            if (TAG_INPUT_DEVICE.equals(parser.getName())) {
+                String audioDeviceAddress =
+                        parser.getAttributeValue(NAMESPACE, ATTR_DEVICE_ADDRESS);
+                validateInputAudioDeviceAddress(audioDeviceAddress);
+                AudioDeviceInfo info = mAddressToInputAudioDeviceInfo.get(audioDeviceAddress);
+                Preconditions.checkArgument(info != null,
+                        "%s %s of %s does not exist, add input device to"
+                                + " audio_policy_configuration.xml.",
+                        ATTR_DEVICE_ADDRESS, audioDeviceAddress, TAG_INPUT_DEVICE);
+                zone.addInputAudioDevice(new AudioDeviceAttributes(info));
+            }
+            skip(parser);
+        }
+    }
+
+    private void validateInputAudioDeviceAddress(String audioDeviceAddress) {
+        Objects.requireNonNull(audioDeviceAddress, () ->
+                TAG_INPUT_DEVICE + " " + ATTR_DEVICE_ADDRESS + " attribute must be present.");
+        Preconditions.checkArgument(!audioDeviceAddress.isEmpty(),
+                "%s %s attribute can not be empty.",
+                TAG_INPUT_DEVICE, ATTR_DEVICE_ADDRESS);
+        if (mInputAudioDevices.contains(audioDeviceAddress)) {
+            throw new IllegalArgumentException(TAG_INPUT_DEVICE + " " + audioDeviceAddress
+                    + " repeats, " + TAG_INPUT_DEVICES + " can not repeat.");
+        }
+        mInputAudioDevices.add(audioDeviceAddress);
+    }
+
     private void parseDisplays(XmlPullParser parser, CarAudioZone zone)
             throws IOException, XmlPullParserException {
         while (parser.next() != XmlPullParser.END_TAG) {
@@ -186,7 +342,7 @@
         try {
             portId = Long.parseLong(port);
         } catch (NumberFormatException e) {
-            throw new RuntimeException("Port " +  port + " is not a number", e);
+            throw new IllegalArgumentException(String.format("Port %s is not a number", port), e);
         }
         validatePortIsUnique(portId);
         return DisplayAddress.fromPhysicalDisplayId(portId);
@@ -194,11 +350,27 @@
 
     private void validatePortIsUnique(Long portId) {
         if (mPortIds.contains(portId)) {
-            throw new RuntimeException("Port Id " + portId + " is already associated with a zone");
+            throw new IllegalArgumentException(
+                    String.format("Port Id %d is already associated with a zone", portId));
         }
         mPortIds.add(portId);
     }
 
+    private void validateOccupantZoneIdIsUnique(int occupantZoneId) {
+        if (mZoneIdToOccupantZoneIdMapping.indexOfValue(occupantZoneId) > -1) {
+            throw new IllegalArgumentException(ATTR_OCCUPANT_ZONE_ID + " " + occupantZoneId
+                    + " is already associated with a zone");
+        }
+    }
+
+    private void validateAudioZoneIdIsUnique(int audioZoneId) {
+        if (mAudioZoneIds.contains(audioZoneId)) {
+            throw new IllegalArgumentException(ATTR_ZONE_ID + " " + audioZoneId
+                    + " is already associated with a zone");
+        }
+        mAudioZoneIds.add(audioZoneId);
+    }
+
     private void parseVolumeGroups(XmlPullParser parser, CarAudioZone zone)
             throws XmlPullParserException, IOException {
         int groupId = 0;
@@ -215,13 +387,14 @@
 
     private CarVolumeGroup parseVolumeGroup(XmlPullParser parser, int zoneId, int groupId)
             throws XmlPullParserException, IOException {
-        final CarVolumeGroup group = new CarVolumeGroup(mContext, zoneId, groupId);
+        final CarVolumeSettings settings = new CarVolumeSettings(mContext);
+        final CarVolumeGroup group = new CarVolumeGroup(settings, zoneId, groupId);
         while (parser.next() != XmlPullParser.END_TAG) {
             if (parser.getEventType() != XmlPullParser.START_TAG) continue;
             if (TAG_AUDIO_DEVICE.equals(parser.getName())) {
                 String address = parser.getAttributeValue(NAMESPACE, ATTR_DEVICE_ADDRESS);
-                parseVolumeGroupContexts(parser, group,
-                        CarAudioDeviceInfo.parseDeviceAddress(address));
+                validateOutputDeviceExist(address);
+                parseVolumeGroupContexts(parser, group, address);
             } else {
                 skip(parser);
             }
@@ -229,15 +402,23 @@
         return group;
     }
 
+    private void validateOutputDeviceExist(String address) {
+        if (!mAddressToCarAudioDeviceInfo.containsKey(address)) {
+            throw new IllegalStateException(String.format(
+                    "Output device address %s does not belong to any configured output device.",
+                    address));
+        }
+    }
+
     private void parseVolumeGroupContexts(
-            XmlPullParser parser, CarVolumeGroup group, int busNumber)
+            XmlPullParser parser, CarVolumeGroup group, String address)
             throws XmlPullParserException, IOException {
         while (parser.next() != XmlPullParser.END_TAG) {
             if (parser.getEventType() != XmlPullParser.START_TAG) continue;
             if (TAG_CONTEXT.equals(parser.getName())) {
-                group.bind(
-                        parseContextNumber(parser.getAttributeValue(NAMESPACE, ATTR_CONTEXT_NAME)),
-                        busNumber, mBusToCarAudioDeviceInfo.get(busNumber));
+                int audioContext = parseCarAudioContext(
+                        parser.getAttributeValue(NAMESPACE, ATTR_CONTEXT_NAME));
+                group.bind(audioContext, mAddressToCarAudioDeviceInfo.get(address));
             }
             // Always skip to upper level since we're at the lowest.
             skip(parser);
@@ -261,8 +442,8 @@
         }
     }
 
-    private int parseContextNumber(String context) {
-        return CONTEXT_NAME_MAP.getOrDefault(context.toLowerCase(), ContextNumber.INVALID);
+    private int parseCarAudioContext(String context) {
+        return CONTEXT_NAME_MAP.getOrDefault(context.toLowerCase(), CarAudioContext.INVALID);
     }
 
     private int getNextSecondaryZoneId() {
diff --git a/service/src/com/android/car/audio/CarAudioZonesHelperLegacy.java b/service/src/com/android/car/audio/CarAudioZonesHelperLegacy.java
index 7f11275..cde16be 100644
--- a/service/src/com/android/car/audio/CarAudioZonesHelperLegacy.java
+++ b/service/src/com/android/car/audio/CarAudioZonesHelperLegacy.java
@@ -56,16 +56,16 @@
     private final SparseArray<CarAudioDeviceInfo> mBusToCarAudioDeviceInfo;
 
     CarAudioZonesHelperLegacy(Context context, @XmlRes int xmlConfiguration,
-            @NonNull SparseArray<CarAudioDeviceInfo> busToCarAudioDeviceInfo,
+            @NonNull List<CarAudioDeviceInfo> carAudioDeviceInfos,
             @NonNull IAudioControl audioControl) {
         mContext = context;
         mXmlConfiguration = xmlConfiguration;
-        mBusToCarAudioDeviceInfo = busToCarAudioDeviceInfo;
+        mBusToCarAudioDeviceInfo = generateBusToCarAudioDeviceInfo(carAudioDeviceInfos);
 
         // Initialize context => bus mapping once.
         mContextToBus = new SparseIntArray();
         try {
-            for (int contextNumber : CarAudioDynamicRouting.CONTEXT_NUMBERS) {
+            for (int contextNumber : CarAudioContext.CONTEXTS) {
                 mContextToBus.put(contextNumber, audioControl.getBusForContext(contextNumber));
             }
         } catch (RemoteException e) {
@@ -74,6 +74,26 @@
         }
     }
 
+    private static SparseArray<CarAudioDeviceInfo> generateBusToCarAudioDeviceInfo(
+            List<CarAudioDeviceInfo> carAudioDeviceInfos) {
+        SparseArray<CarAudioDeviceInfo> busToCarAudioDeviceInfo = new SparseArray<>();
+
+        for (CarAudioDeviceInfo carAudioDeviceInfo : carAudioDeviceInfos) {
+            int busNumber = parseDeviceAddress(carAudioDeviceInfo.getAddress());
+            if (busNumber >= 0) {
+                if (busToCarAudioDeviceInfo.get(busNumber) != null) {
+                    throw new RuntimeException("Two addresses map to same bus number: "
+                            + carAudioDeviceInfo.getAddress()
+                            + " and "
+                    + busToCarAudioDeviceInfo.get(busNumber).getAddress());
+                }
+                busToCarAudioDeviceInfo.put(busNumber, carAudioDeviceInfo);
+            }
+        }
+
+        return busToCarAudioDeviceInfo;
+    }
+
     CarAudioZone[] loadAudioZones() {
         final CarAudioZone zone = new CarAudioZone(CarAudioManager.PRIMARY_AUDIO_ZONE,
                 "Primary zone");
@@ -82,7 +102,7 @@
             // Binding audio device to volume group.
             for (int contextNumber : group.getContexts()) {
                 int busNumber = mContextToBus.get(contextNumber);
-                group.bind(contextNumber, busNumber, mBusToCarAudioDeviceInfo.get(busNumber));
+                group.bind(contextNumber, mBusToCarAudioDeviceInfo.get(busNumber));
             }
         }
         return new CarAudioZone[] { zone };
@@ -141,7 +161,29 @@
             }
         }
 
-        return new CarVolumeGroup(mContext, CarAudioManager.PRIMARY_AUDIO_ZONE, id,
+        final CarVolumeSettings settings = new CarVolumeSettings(mContext);
+
+        return new CarVolumeGroup(settings, CarAudioManager.PRIMARY_AUDIO_ZONE, id,
                 contexts.stream().mapToInt(i -> i).filter(i -> i >= 0).toArray());
     }
+
+    /**
+     * Parse device address. Expected format is BUS%d_%s, address, usage hint
+     * @return valid address (from 0 to positive) or -1 for invalid address.
+     */
+    private static int parseDeviceAddress(String address) {
+        String[] words = address.split("_");
+        int addressParsed = -1;
+        if (words[0].toLowerCase().startsWith("bus")) {
+            try {
+                addressParsed = Integer.parseInt(words[0].substring(3));
+            } catch (NumberFormatException e) {
+                //ignore
+            }
+        }
+        if (addressParsed < 0) {
+            return -1;
+        }
+        return addressParsed;
+    }
 }
diff --git a/service/src/com/android/car/audio/CarAudioZonesValidator.java b/service/src/com/android/car/audio/CarAudioZonesValidator.java
new file mode 100644
index 0000000..1bb0234
--- /dev/null
+++ b/service/src/com/android/car/audio/CarAudioZonesValidator.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.car.audio;
+
+
+import java.util.HashSet;
+import java.util.Set;
+
+class CarAudioZonesValidator {
+    static void validate(CarAudioZone[] carAudioZones) {
+        validateAtLeastOneZoneDefined(carAudioZones);
+        validateVolumeGroupsForEachZone(carAudioZones);
+        validateEachAddressAppearsAtMostOnce(carAudioZones);
+    }
+
+    private static void validateAtLeastOneZoneDefined(CarAudioZone[] carAudioZones) {
+        if (carAudioZones.length == 0) {
+            throw new RuntimeException("At least one zone should be defined");
+        }
+    }
+
+    private static void validateVolumeGroupsForEachZone(CarAudioZone[] carAudioZones) {
+        for (CarAudioZone zone : carAudioZones) {
+            if (!zone.validateVolumeGroups()) {
+                throw new RuntimeException(
+                        "Invalid volume groups configuration for zone " + zone.getId());
+            }
+        }
+    }
+
+    private static void validateEachAddressAppearsAtMostOnce(CarAudioZone[] carAudioZones) {
+        Set<String> addresses = new HashSet<>();
+        for (CarAudioZone zone : carAudioZones) {
+            for (CarVolumeGroup carVolumeGroup : zone.getVolumeGroups()) {
+                for (String address : carVolumeGroup.getAddresses()) {
+                    if (!addresses.add(address)) {
+                        throw new RuntimeException("Device with address "
+                                + address + " appears in multiple volume groups or audio zones");
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/service/src/com/android/car/audio/CarVolumeCallbackHandler.java b/service/src/com/android/car/audio/CarVolumeCallbackHandler.java
new file mode 100644
index 0000000..71cb844
--- /dev/null
+++ b/service/src/com/android/car/audio/CarVolumeCallbackHandler.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.car.audio;
+
+import android.car.media.ICarVolumeCallback;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+
+import com.android.car.BinderInterfaceContainer;
+import com.android.car.CarLog;
+
+/**
+ * Manages callbacks for changes in car volume
+ */
+class CarVolumeCallbackHandler {
+    private final BinderInterfaceContainer<ICarVolumeCallback> mVolumeCallbackContainer =
+            new BinderInterfaceContainer<>();
+
+    void release() {
+        mVolumeCallbackContainer.clear();
+    }
+
+    void onVolumeGroupChange(int zoneId, int groupId, int flags) {
+        for (BinderInterfaceContainer.BinderInterface<ICarVolumeCallback> callback :
+                mVolumeCallbackContainer.getInterfaces()) {
+            try {
+                callback.binderInterface.onGroupVolumeChanged(zoneId, groupId, flags);
+            } catch (RemoteException e) {
+                Log.e(CarLog.TAG_AUDIO, "Failed to callback onGroupVolumeChanged", e);
+            }
+        }
+    }
+
+    void onMasterMuteChanged(int zoneId, int flags) {
+        for (BinderInterfaceContainer.BinderInterface<ICarVolumeCallback> callback :
+                mVolumeCallbackContainer.getInterfaces()) {
+            try {
+                callback.binderInterface.onMasterMuteChanged(zoneId, flags);
+            } catch (RemoteException e) {
+                Log.e(CarLog.TAG_AUDIO, "Failed to callback onMasterMuteChanged", e);
+            }
+        }
+    }
+
+    public void registerCallback(@NonNull IBinder binder) {
+        mVolumeCallbackContainer.addBinder(ICarVolumeCallback.Stub.asInterface(binder));
+    }
+
+    public void unregisterCallback(@NonNull IBinder binder) {
+        mVolumeCallbackContainer.removeBinder(ICarVolumeCallback.Stub.asInterface(binder));
+    }
+}
diff --git a/service/src/com/android/car/audio/CarVolumeGroup.java b/service/src/com/android/car/audio/CarVolumeGroup.java
index eb3cc4f..0e100db 100644
--- a/service/src/com/android/car/audio/CarVolumeGroup.java
+++ b/service/src/com/android/car/audio/CarVolumeGroup.java
@@ -17,21 +17,22 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.app.ActivityManager;
 import android.car.media.CarAudioManager;
-import android.content.ContentResolver;
 import android.content.Context;
-import android.hardware.automotive.audiocontrol.V1_0.ContextNumber;
 import android.media.AudioDevicePort;
-import android.provider.Settings;
+import android.util.Log;
 import android.util.SparseArray;
-import android.util.SparseIntArray;
 
+import com.android.car.CarLog;
 import com.android.internal.util.Preconditions;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 
 /**
  * A class encapsulates a volume group in car.
@@ -42,11 +43,11 @@
  */
 /* package */ final class CarVolumeGroup {
 
-    private final ContentResolver mContentResolver;
+    private CarVolumeSettings mSettingsManager;
     private final int mZoneId;
     private final int mId;
-    private final SparseIntArray mContextToBus = new SparseIntArray();
-    private final SparseArray<CarAudioDeviceInfo> mBusToCarAudioDeviceInfo = new SparseArray<>();
+    private final SparseArray<String> mContextToAddress = new SparseArray<>();
+    private final Map<String, CarAudioDeviceInfo> mAddressToCarAudioDeviceInfo = new HashMap<>();
 
     private int mDefaultGain = Integer.MIN_VALUE;
     private int mMaxGain = Integer.MIN_VALUE;
@@ -57,16 +58,16 @@
 
     /**
      * Constructs a {@link CarVolumeGroup} instance
-     * @param context {@link Context} instance
+     * @param Settings {@link CarVolumeSettings} instance
      * @param zoneId Audio zone this volume group belongs to
      * @param id ID of this volume group
      */
-    CarVolumeGroup(Context context, int zoneId, int id) {
-        mContentResolver = context.getContentResolver();
+    CarVolumeGroup(CarVolumeSettings settings, int zoneId, int id) {
+        mSettingsManager = settings;
         mZoneId = zoneId;
         mId = id;
-        mStoredGainIndex = Settings.Global.getInt(mContentResolver,
-                CarAudioService.getVolumeSettingsKeyForGroup(mZoneId, mId), -1);
+
+        updateUserId(ActivityManager.getCurrentUser());
     }
 
     /**
@@ -78,83 +79,92 @@
      * @deprecated In favor of {@link #CarVolumeGroup(Context, int, int)}
      */
     @Deprecated
-    CarVolumeGroup(Context context, int zoneId, int id, @NonNull int[] contexts) {
-        this(context, zoneId, id);
+    CarVolumeGroup(CarVolumeSettings settings, int zoneId, int id, @NonNull int[] contexts) {
+        this(settings, zoneId, id);
         // Deal with the pre-populated car audio contexts
         for (int audioContext : contexts) {
-            mContextToBus.put(audioContext, -1);
+            mContextToAddress.put(audioContext, null);
         }
     }
 
     /**
-     * @param busNumber Physical bus number for the audio device port
-     * @return {@link CarAudioDeviceInfo} associated with a given bus number
+     * @param address Physical address for the audio device
+     * @return {@link CarAudioDeviceInfo} associated with a given address
      */
-    CarAudioDeviceInfo getCarAudioDeviceInfoForBus(int busNumber) {
-        return mBusToCarAudioDeviceInfo.get(busNumber);
+    CarAudioDeviceInfo getCarAudioDeviceInfoForAddress(String address) {
+        return mAddressToCarAudioDeviceInfo.get(address);
     }
 
     /**
-     * @return Array of context numbers in this {@link CarVolumeGroup}
+     * @return Array of car audio contexts {@link CarAudioContext} in this {@link CarVolumeGroup}
      */
     int[] getContexts() {
-        final int[] contextNumbers = new int[mContextToBus.size()];
-        for (int i = 0; i < contextNumbers.length; i++) {
-            contextNumbers[i] = mContextToBus.keyAt(i);
+        final int[] carAudioContexts = new int[mContextToAddress.size()];
+        for (int i = 0; i < carAudioContexts.length; i++) {
+            carAudioContexts[i] = mContextToAddress.keyAt(i);
         }
-        return contextNumbers;
+        return carAudioContexts;
     }
 
     /**
-     * @param busNumber Physical bus number for the audio device port
-     * @return Array of context numbers assigned to a given bus number
+     * Returns the devices address for the given context
+     * or {@code null} if the context does not exist in the volume group
      */
-    int[] getContextsForBus(int busNumber) {
-        List<Integer> contextNumbers = new ArrayList<>();
-        for (int i = 0; i < mContextToBus.size(); i++) {
-            int value = mContextToBus.valueAt(i);
-            if (value == busNumber) {
-                contextNumbers.add(mContextToBus.keyAt(i));
+    @Nullable
+    String getAddressForContext(int audioContext) {
+        return mContextToAddress.get(audioContext);
+    }
+
+    /**
+     * @param address Physical address for the audio device
+     * @return Array of car audio contexts {@link CarAudioContext} assigned to a given address
+     */
+    int[] getContextsForAddress(@NonNull String address) {
+        List<Integer> carAudioContexts = new ArrayList<>();
+        for (int i = 0; i < mContextToAddress.size(); i++) {
+            String value = mContextToAddress.valueAt(i);
+            if (address.equals(value)) {
+                carAudioContexts.add(mContextToAddress.keyAt(i));
             }
         }
-        return contextNumbers.stream().mapToInt(i -> i).toArray();
+        return carAudioContexts.stream().mapToInt(i -> i).toArray();
     }
 
     /**
-     * @return Array of bus numbers in this {@link CarVolumeGroup}
+     * @return Array of addresses in this {@link CarVolumeGroup}
      */
-    int[] getBusNumbers() {
-        final int[] busNumbers = new int[mBusToCarAudioDeviceInfo.size()];
-        for (int i = 0; i < busNumbers.length; i++) {
-            busNumbers[i] = mBusToCarAudioDeviceInfo.keyAt(i);
-        }
-        return busNumbers;
+    List<String> getAddresses() {
+        return new ArrayList<>(mAddressToCarAudioDeviceInfo.keySet());
     }
 
     /**
-     * Binds the context number to physical bus number and audio device port information.
+     * Binds the context number to physical address and audio device port information.
      * Because this may change the groups min/max values, thus invalidating an index computed from
      * a gain before this call, all calls to this function must happen at startup before any
      * set/getGainIndex calls.
      *
-     * @param contextNumber Context number as defined in audio control HAL
-     * @param busNumber Physical bus number for the audio device port
-     * @param info {@link CarAudioDeviceInfo} instance relates to the physical bus
+     * @param carAudioContext Context to bind audio to {@link CarAudioContext}
+     * @param info {@link CarAudioDeviceInfo} instance relates to the physical address
      */
-    void bind(int contextNumber, int busNumber, CarAudioDeviceInfo info) {
-        if (mBusToCarAudioDeviceInfo.size() == 0) {
-            mStepSize = info.getAudioGain().stepValue();
+    void bind(int carAudioContext, CarAudioDeviceInfo info) {
+        Preconditions.checkArgument(mContextToAddress.get(carAudioContext) == null,
+                String.format("Context %s has already been bound to %s",
+                        CarAudioContext.toString(carAudioContext),
+                        mContextToAddress.get(carAudioContext)));
+
+        if (mAddressToCarAudioDeviceInfo.size() == 0) {
+            mStepSize = info.getStepValue();
         } else {
             Preconditions.checkArgument(
-                    info.getAudioGain().stepValue() == mStepSize,
+                    info.getStepValue() == mStepSize,
                     "Gain controls within one group must have same step value");
         }
 
-        mContextToBus.put(contextNumber, busNumber);
-        mBusToCarAudioDeviceInfo.put(busNumber, info);
+        mAddressToCarAudioDeviceInfo.put(info.getAddress(), info);
+        mContextToAddress.put(carAudioContext, info.getAddress());
 
         if (info.getDefaultGain() > mDefaultGain) {
-            // We're arbitrarily selecting the highest bus default gain as the group's default.
+            // We're arbitrarily selecting the highest device default gain as the group's default.
             mDefaultGain = info.getDefaultGain();
         }
         if (info.getMaxGain() > mMaxGain) {
@@ -163,6 +173,24 @@
         if (info.getMinGain() < mMinGain) {
             mMinGain = info.getMinGain();
         }
+        updateCurrentGainIndex();
+    }
+
+    /**
+     * Update the user with the a new user
+     * @param userId new user
+     * @note also reloads the store gain index for the user
+     */
+    private void updateUserId(int userId) {
+        mStoredGainIndex = mSettingsManager.getStoredVolumeGainIndexForUser(userId, mZoneId, mId);
+        Log.i(CarLog.TAG_AUDIO, "updateUserId userId " + userId
+                + " mStoredGainIndex " + mStoredGainIndex);
+    }
+
+    /**
+     * Update the current gain index based on the stored gain index
+     */
+    private void updateCurrentGainIndex() {
         if (mStoredGainIndex < getMinGainIndex() || mStoredGainIndex > getMaxGainIndex()) {
             // We expected to load a value from last boot, but if we didn't (perhaps this is the
             // first boot ever?), then use the highest "default" we've seen to initialize
@@ -192,7 +220,7 @@
     }
 
     /**
-     * Sets the gain on this group, gain will be set on all buses within same bus.
+     * Sets the gain on this group, gain will be set on all devices within volume group.
      * @param gainIndex The gain index
      */
     void setCurrentGainIndex(int gainIndex) {
@@ -206,14 +234,14 @@
                         + gainInMillibels + "index "
                         + gainIndex);
 
-        for (int i = 0; i < mBusToCarAudioDeviceInfo.size(); i++) {
-            CarAudioDeviceInfo info = mBusToCarAudioDeviceInfo.valueAt(i);
+        for (String address : mAddressToCarAudioDeviceInfo.keySet()) {
+            CarAudioDeviceInfo info = mAddressToCarAudioDeviceInfo.get(address);
             info.setCurrentGain(gainInMillibels);
         }
 
         mCurrentGainIndex = gainIndex;
-        Settings.Global.putInt(mContentResolver,
-                CarAudioService.getVolumeSettingsKeyForGroup(mZoneId, mId), gainIndex);
+        mSettingsManager.storeVolumeGainIndexForUser(ActivityManager.getCurrentUser(),
+                mZoneId, mId, gainIndex);
     }
 
     // Given a group level gain index, return the computed gain in millibells
@@ -234,12 +262,13 @@
      * Gets {@link AudioDevicePort} from a context number
      */
     @Nullable
-    AudioDevicePort getAudioDevicePortForContext(int contextNumber) {
-        final int busNumber = mContextToBus.get(contextNumber, -1);
-        if (busNumber < 0 || mBusToCarAudioDeviceInfo.get(busNumber) == null) {
+    AudioDevicePort getAudioDevicePortForContext(int carAudioContext) {
+        final String address = mContextToAddress.get(carAudioContext);
+        if (address == null || mAddressToCarAudioDeviceInfo.get(address) == null) {
             return null;
         }
-        return mBusToCarAudioDeviceInfo.get(busNumber).getAudioDevicePort();
+
+        return mAddressToCarAudioDeviceInfo.get(address).getAudioDevicePort();
     }
 
     @Override
@@ -247,26 +276,42 @@
         return "CarVolumeGroup id: " + mId
                 + " currentGainIndex: " + mCurrentGainIndex
                 + " contexts: " + Arrays.toString(getContexts())
-                + " buses: " + Arrays.toString(getBusNumbers());
+                + " addresses: " + String.join(", ", getAddresses());
     }
 
     /** Writes to dumpsys output */
     void dump(String indent, PrintWriter writer) {
         writer.printf("%sCarVolumeGroup(%d)\n", indent, mId);
+        writer.printf("%sUserId(%d)\n", indent, ActivityManager.getCurrentUser());
         writer.printf("%sGain values (min / max / default/ current): %d %d %d %d\n",
                 indent, mMinGain, mMaxGain,
                 mDefaultGain, getGainForIndex(mCurrentGainIndex));
         writer.printf("%sGain indexes (min / max / default / current): %d %d %d %d\n",
                 indent, getMinGainIndex(), getMaxGainIndex(),
                 getDefaultGainIndex(), mCurrentGainIndex);
-        for (int i = 0; i < mContextToBus.size(); i++) {
-            writer.printf("%sContext: %s -> Bus: %d\n", indent,
-                    ContextNumber.toString(mContextToBus.keyAt(i)), mContextToBus.valueAt(i));
+        for (int i = 0; i < mContextToAddress.size(); i++) {
+            writer.printf("%sContext: %s -> Address: %s\n", indent,
+                    CarAudioContext.toString(mContextToAddress.keyAt(i)),
+                    mContextToAddress.valueAt(i));
         }
-        for (int i = 0; i < mBusToCarAudioDeviceInfo.size(); i++) {
-            mBusToCarAudioDeviceInfo.valueAt(i).dump(indent, writer);
-        }
+        mAddressToCarAudioDeviceInfo.keySet().stream()
+                .map(mAddressToCarAudioDeviceInfo::get)
+                .forEach((info -> info.dump(indent, writer)));
+
         // Empty line for comfortable reading
         writer.println();
     }
+
+    /**
+     * Load volumes for new user
+     * @param userId new user to load
+     */
+    void loadVolumesForUser(int userId) {
+        //Update the volume for the new user
+        updateUserId(userId);
+        //Update the current gain index
+        updateCurrentGainIndex();
+        //Reset devices with current gain index
+        setCurrentGainIndex(getCurrentGainIndex());
+    }
 }
diff --git a/service/src/com/android/car/audio/CarVolumeSettings.java b/service/src/com/android/car/audio/CarVolumeSettings.java
new file mode 100644
index 0000000..8b08997
--- /dev/null
+++ b/service/src/com/android/car/audio/CarVolumeSettings.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.car.audio;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.provider.Settings;
+
+/**
+ * Use to save/load car volume settings
+ */
+public class CarVolumeSettings {
+
+    // The trailing slash forms a directory-liked hierarchy and
+    // allows listening for both GROUP/MEDIA and GROUP/NAVIGATION.
+    private static final String VOLUME_SETTINGS_KEY_FOR_GROUP_PREFIX = "android.car.VOLUME_GROUP/";
+
+    // Key to persist master mute state in system settings
+    private static final String VOLUME_SETTINGS_KEY_MASTER_MUTE = "android.car.MASTER_MUTE";
+
+    /**
+     * Gets the key to persist volume for a volume group in settings
+     *
+     * @param zoneId The audio zone id
+     * @param groupId The volume group id
+     * @return Key to persist volume index for volume group in system settings
+     */
+    private static String getVolumeSettingsKeyForGroup(int zoneId, int groupId) {
+        final int maskedGroupId = (zoneId << 8) + groupId;
+        return VOLUME_SETTINGS_KEY_FOR_GROUP_PREFIX + maskedGroupId;
+    }
+
+    private final ContentResolver mContentResolver;
+
+    CarVolumeSettings(Context context) {
+        mContentResolver = context.getContentResolver();
+    }
+
+    int getStoredVolumeGainIndexForUser(int userId, int zoneId, int id) {
+        return Settings.System.getIntForUser(mContentResolver,
+                getVolumeSettingsKeyForGroup(zoneId, id), -1, userId);
+    }
+
+    void storeVolumeGainIndexForUser(int userId, int zoneId, int id, int gainIndex) {
+        Settings.System.putIntForUser(mContentResolver,
+                getVolumeSettingsKeyForGroup(zoneId, id),
+                gainIndex, userId);
+    }
+
+    void storeMasterMute(Boolean masterMuteValue) {
+        Settings.Global.putInt(mContentResolver,
+                VOLUME_SETTINGS_KEY_MASTER_MUTE,
+                masterMuteValue ? 1 : 0);
+    }
+
+    boolean getMasterMute() {
+        return Settings.Global.getInt(mContentResolver,
+                VOLUME_SETTINGS_KEY_MASTER_MUTE, 0) != 0;
+    }
+}
diff --git a/service/src/com/android/car/audio/CarZonesAudioFocus.java b/service/src/com/android/car/audio/CarZonesAudioFocus.java
index a7441ba..ec4ffdd 100644
--- a/service/src/com/android/car/audio/CarZonesAudioFocus.java
+++ b/service/src/com/android/car/audio/CarZonesAudioFocus.java
@@ -33,11 +33,12 @@
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.Map;
+import java.util.Objects;
 
 /**
  * Implements {@link AudioPolicy.AudioPolicyFocusListener}
  *
- * @note Manages audio focus on a per zone basis.
+ * <p><b>Note:</b> Manages audio focus on a per zone basis.
  */
 class CarZonesAudioFocus extends AudioPolicy.AudioPolicyFocusListener {
 
@@ -52,7 +53,7 @@
         //Create the zones here, the policy will be set setOwningPolicy,
         // which is called right after this constructor.
 
-        Preconditions.checkNotNull(carAudioZones);
+        Objects.requireNonNull(carAudioZones);
         Preconditions.checkArgument(carAudioZones.length != 0,
                 "There must be a minimum of one audio zone");
 
@@ -111,17 +112,18 @@
     /**
      * Sets the owning policy of the audio focus
      *
-     * @param audioService owning car audio service
-     * @param parentPolicy owning parent car audio policy
-     * @Note This has to happen after the construction to avoid a chicken and egg
+     * <p><b>Note:</b> This has to happen after the construction to avoid a chicken and egg
      * problem when setting up the AudioPolicy which must depend on this object.
+
+     * @param carAudioService owning car audio service
+     * @param parentPolicy owning parent car audio policy
      */
-    void setOwningPolicy(CarAudioService audioService, AudioPolicy parentPolicy) {
-        mCarAudioService = audioService;
+    void setOwningPolicy(CarAudioService carAudioService, AudioPolicy parentPolicy) {
         mAudioPolicy = parentPolicy;
+        mCarAudioService = carAudioService;
 
         for (int zoneId : mFocusZones.keySet()) {
-            mFocusZones.get(zoneId).setOwningPolicy(mCarAudioService, mAudioPolicy);
+            mFocusZones.get(zoneId).setOwningPolicy(mAudioPolicy);
         }
     }
 
@@ -173,13 +175,13 @@
      * @param indent indent to append to each new line
      * @param writer stream to write current state
      */
-    synchronized void dump(String indent, PrintWriter writer) {
+    void dump(String indent, PrintWriter writer) {
         writer.printf("%s*CarZonesAudioFocus*\n", indent);
 
         writer.printf("%s\tCar Zones Audio Focus Listeners:\n", indent);
         Integer[] keys = mFocusZones.keySet().stream().sorted().toArray(Integer[]::new);
         for (Integer zoneId : keys) {
-            writer.printf("%s\tZone Id: %s", indent, zoneId.toString());
+            writer.printf("%s\tZone Id: %s\n", indent, zoneId.toString());
             mFocusZones.get(zoneId).dump(indent + "\t", writer);
         }
     }
diff --git a/service/src/com/android/car/audio/FocusEntry.java b/service/src/com/android/car/audio/FocusEntry.java
new file mode 100644
index 0000000..6be453e
--- /dev/null
+++ b/service/src/com/android/car/audio/FocusEntry.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.car.audio;
+
+import android.car.Car;
+import android.car.media.CarAudioManager;
+import android.content.pm.PackageManager;
+import android.media.AudioFocusInfo;
+import android.media.AudioManager;
+import android.os.Bundle;
+
+import androidx.annotation.NonNull;
+
+import com.android.car.audio.CarAudioContext.AudioContext;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+final class FocusEntry {
+    private final AudioFocusInfo mAudioFocusInfo;
+    private final int mAudioContext;
+
+    private final List<FocusEntry> mBlockers;
+    private final PackageManager mPackageManager;
+    private boolean mIsDucked;
+
+    FocusEntry(@NonNull AudioFocusInfo audioFocusInfo, @AudioContext int context,
+            @NonNull PackageManager packageManager) {
+        Objects.requireNonNull(audioFocusInfo, "AudioFocusInfo cannot be null");
+        Objects.requireNonNull(packageManager, "PackageManager cannot be null");
+        mAudioFocusInfo = audioFocusInfo;
+        mAudioContext = context;
+        mBlockers = new ArrayList<>();
+        mPackageManager = packageManager;
+    }
+
+    @AudioContext
+    int getAudioContext() {
+        return mAudioContext;
+    }
+
+    AudioFocusInfo getAudioFocusInfo() {
+        return mAudioFocusInfo;
+    }
+
+    boolean isUnblocked() {
+        return mBlockers.isEmpty();
+    }
+
+    void addBlocker(FocusEntry blocker) {
+        mBlockers.add(blocker);
+    }
+
+    void removeBlocker(FocusEntry blocker) {
+        mBlockers.remove(blocker);
+    }
+
+    String getClientId() {
+        return mAudioFocusInfo.getClientId();
+    }
+
+    boolean isDucked() {
+        return mIsDucked;
+    }
+
+    void setDucked(boolean ducked) {
+        mIsDucked = ducked;
+    }
+
+    boolean wantsPauseInsteadOfDucking() {
+        return (mAudioFocusInfo.getFlags() & AudioManager.AUDIOFOCUS_FLAG_PAUSES_ON_DUCKABLE_LOSS)
+                != 0;
+    }
+
+    boolean receivesDuckEvents() {
+        Bundle bundle = mAudioFocusInfo.getAttributes().getBundle();
+
+        if (bundle == null) {
+            return false;
+        }
+
+        if (!bundle.getBoolean(CarAudioManager.AUDIOFOCUS_EXTRA_RECEIVE_DUCKING_EVENTS)) {
+            return false;
+        }
+
+        return (mPackageManager.checkPermission(
+                Car.PERMISSION_RECEIVE_CAR_AUDIO_DUCKING_EVENTS,
+                mAudioFocusInfo.getPackageName())
+                == PackageManager.PERMISSION_GRANTED);
+    }
+
+    String getUsageName() {
+        return mAudioFocusInfo.getAttributes().usageToString();
+    }
+}
diff --git a/service/src/com/android/car/audio/FocusInteraction.java b/service/src/com/android/car/audio/FocusInteraction.java
new file mode 100644
index 0000000..144d7a1
--- /dev/null
+++ b/service/src/com/android/car/audio/FocusInteraction.java
@@ -0,0 +1,219 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.car.audio;
+
+import android.media.AudioManager;
+import android.media.AudioManager.FocusRequestResult;
+import android.util.Log;
+
+import androidx.annotation.VisibleForTesting;
+
+import com.android.car.audio.CarAudioContext.AudioContext;
+import com.android.internal.util.Preconditions;
+
+import java.util.List;
+
+/**
+ * FocusInteraction is responsible for evaluating how incoming focus requests should be handled
+ * based on pre-defined interaction behaviors for each incoming {@link AudioContext} in relation to
+ * a {@link AudioContext} that is currently holding focus.
+ */
+final class FocusInteraction {
+
+    private static final String TAG = FocusInteraction.class.getSimpleName();
+
+    // Values for the internal interaction matrix we use to make focus decisions
+    @VisibleForTesting
+    static final int INTERACTION_REJECT = 0; // Focus not granted
+    @VisibleForTesting
+    static final int INTERACTION_EXCLUSIVE = 1; // Focus granted, others loose focus
+    @VisibleForTesting
+    static final int INTERACTION_CONCURRENT = 2; // Focus granted, others keep focus
+
+    private static final int[][] sInteractionMatrix = {
+            // Each Row represents CarAudioContext of current focus holder
+            // Each Column represents CarAudioContext of incoming request (labels along the right)
+            // Cell value is one of INTERACTION_REJECT, INTERACTION_EXCLUSIVE,
+            // or INTERACTION_CONCURRENT
+
+            // Focus holder: INVALID
+            {
+                    INTERACTION_REJECT, // INVALID
+                    INTERACTION_REJECT, // MUSIC
+                    INTERACTION_REJECT, // NAVIGATION
+                    INTERACTION_REJECT, // VOICE_COMMAND
+                    INTERACTION_REJECT, // CALL_RING
+                    INTERACTION_REJECT, // CALL
+                    INTERACTION_REJECT, // ALARM
+                    INTERACTION_REJECT, // NOTIFICATION
+                    INTERACTION_REJECT, // SYSTEM_SOUND
+            },
+            // Focus holder: MUSIC
+            {
+                    INTERACTION_REJECT, // INVALID
+                    INTERACTION_EXCLUSIVE, // MUSIC
+                    INTERACTION_CONCURRENT, // NAVIGATION
+                    INTERACTION_EXCLUSIVE, // VOICE_COMMAND
+                    INTERACTION_EXCLUSIVE, // CALL_RING
+                    INTERACTION_EXCLUSIVE, // CALL
+                    INTERACTION_EXCLUSIVE, // ALARM
+                    INTERACTION_CONCURRENT, // NOTIFICATION
+                    INTERACTION_CONCURRENT,  // SYSTEM_SOUND
+            },
+            // Focus holder: NAVIGATION
+            {
+                    INTERACTION_REJECT, // INVALID
+                    INTERACTION_CONCURRENT, // MUSIC
+                    INTERACTION_CONCURRENT, // NAVIGATION
+                    INTERACTION_EXCLUSIVE, // VOICE_COMMAND
+                    INTERACTION_CONCURRENT, // CALL_RING
+                    INTERACTION_EXCLUSIVE, // CALL
+                    INTERACTION_CONCURRENT, // ALARM
+                    INTERACTION_CONCURRENT, // NOTIFICATION
+                    INTERACTION_CONCURRENT,  // SYSTEM_SOUND
+            },
+            // Focus holder: VOICE_COMMAND
+            {
+                    INTERACTION_REJECT, // INVALID
+                    INTERACTION_CONCURRENT, // MUSIC
+                    INTERACTION_REJECT, // NAVIGATION
+                    INTERACTION_CONCURRENT, // VOICE_COMMAND
+                    INTERACTION_EXCLUSIVE, // CALL_RING
+                    INTERACTION_EXCLUSIVE, // CALL
+                    INTERACTION_REJECT, // ALARM
+                    INTERACTION_REJECT, // NOTIFICATION
+                    INTERACTION_REJECT, // SYSTEM_SOUND
+            },
+            // Focus holder: CALL_RING
+            {
+                    INTERACTION_REJECT, // INVALID
+                    INTERACTION_REJECT, // MUSIC
+                    INTERACTION_CONCURRENT, // NAVIGATION
+                    INTERACTION_CONCURRENT, // VOICE_COMMAND
+                    INTERACTION_CONCURRENT, // CALL_RING
+                    INTERACTION_CONCURRENT, // CALL
+                    INTERACTION_REJECT, // ALARM
+                    INTERACTION_REJECT, // NOTIFICATION
+                    INTERACTION_CONCURRENT, // SYSTEM_SOUND
+            },
+            // Focus holder: CALL
+            {
+                    INTERACTION_REJECT, // INVALID
+                    INTERACTION_REJECT, // MUSIC
+                    INTERACTION_CONCURRENT, // NAVIGATION
+                    INTERACTION_REJECT, // VOICE_COMMAND
+                    INTERACTION_CONCURRENT, // CALL_RING
+                    INTERACTION_CONCURRENT, // CALL
+                    INTERACTION_CONCURRENT, // ALARM
+                    INTERACTION_CONCURRENT, // NOTIFICATION
+                    INTERACTION_REJECT, // SYSTEM_SOUND
+            },
+            // Focus holder: ALARM
+            {
+                    INTERACTION_REJECT, // INVALID
+                    INTERACTION_CONCURRENT, // MUSIC
+                    INTERACTION_CONCURRENT, // NAVIGATION
+                    INTERACTION_EXCLUSIVE, // VOICE_COMMAND
+                    INTERACTION_EXCLUSIVE, // CALL_RING
+                    INTERACTION_EXCLUSIVE, // CALL
+                    INTERACTION_CONCURRENT, // ALARM
+                    INTERACTION_CONCURRENT, // NOTIFICATION
+                    INTERACTION_CONCURRENT, // SYSTEM_SOUND
+            },
+            // Focus holder: NOTIFICATION
+            {
+                    INTERACTION_REJECT, // INVALID
+                    INTERACTION_CONCURRENT, // MUSIC
+                    INTERACTION_CONCURRENT, // NAVIGATION
+                    INTERACTION_EXCLUSIVE, // VOICE_COMMAND
+                    INTERACTION_EXCLUSIVE, // CALL_RING
+                    INTERACTION_EXCLUSIVE, // CALL
+                    INTERACTION_CONCURRENT, // ALARM
+                    INTERACTION_CONCURRENT, // NOTIFICATION
+                    INTERACTION_CONCURRENT, // SYSTEM_SOUND
+            },
+            // Focus holder: SYSTEM_SOUND
+            {
+                    INTERACTION_REJECT, // INVALID
+                    INTERACTION_CONCURRENT, // MUSIC
+                    INTERACTION_CONCURRENT, // NAVIGATION
+                    INTERACTION_EXCLUSIVE, // VOICE_COMMAND
+                    INTERACTION_EXCLUSIVE, // CALL_RING
+                    INTERACTION_EXCLUSIVE, // CALL
+                    INTERACTION_CONCURRENT, // ALARM
+                    INTERACTION_CONCURRENT, // NOTIFICATION
+                    INTERACTION_CONCURRENT, // SYSTEM_SOUND
+            },
+    };
+
+    /**
+     * Evaluates interaction between incoming focus {@link AudioContext} and the current focus
+     * request based on interaction matrix.
+     *
+     * <p>Note: In addition to returning the {@link FocusRequestResult}
+     * for the incoming request based on this interaction, this method also adds the current {@code
+     * focusHolder} to the {@code focusLosers} list when appropriate.
+     *
+     * @param requestedContext CarAudioContextType of incoming focus request
+     * @param focusHolder      {@link FocusEntry} for current focus holder
+     * @param focusLosers      Mutable array to add focusHolder to if it should lose focus
+     * @return {@link FocusRequestResult} result of focus interaction
+     */
+    static @FocusRequestResult int evaluateRequest(@AudioContext int requestedContext,
+            FocusEntry focusHolder, List<FocusEntry> focusLosers, boolean allowDucking) {
+        @AudioContext int holderContext = focusHolder.getAudioContext();
+        Preconditions.checkArgumentInRange(holderContext, 0, sInteractionMatrix.length - 1,
+                "holderContext");
+        int[] holderRow = sInteractionMatrix[holderContext];
+        Preconditions.checkArgumentInRange(requestedContext, 0, holderRow.length - 1,
+                "requestedContext");
+
+        switch (holderRow[requestedContext]) {
+            case INTERACTION_REJECT:
+                return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
+            case INTERACTION_EXCLUSIVE:
+                focusLosers.add(focusHolder);
+                return AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
+            case INTERACTION_CONCURRENT:
+                // If ducking isn't allowed by the focus requester, then everybody else
+                // must get a LOSS.
+                // If a focus holder has set the AUDIOFOCUS_FLAG_PAUSES_ON_DUCKABLE_LOSS flag,
+                // they must get a LOSS message even if ducking would otherwise be allowed.
+                // If a focus holder holds the RECEIVE_CAR_AUDIO_DUCKING_EVENTS permission,
+                // they must receive all audio focus losses.
+                if (!allowDucking
+                        || focusHolder.wantsPauseInsteadOfDucking()
+                        || focusHolder.receivesDuckEvents()) {
+                    focusLosers.add(focusHolder);
+                }
+                return AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
+            default:
+                Log.e(TAG, String.format("Unsupported CarAudioContext %d - rejecting request",
+                        holderRow[requestedContext]));
+                return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
+        }
+    }
+
+    @VisibleForTesting
+    static int[][] getInteractionMatrix() {
+        int[][] interactionMatrixClone =
+                new int[sInteractionMatrix.length][sInteractionMatrix.length];
+        for (int audioContext = 0; audioContext < sInteractionMatrix.length; audioContext++) {
+            interactionMatrixClone[audioContext] = sInteractionMatrix[audioContext].clone();
+        }
+        return interactionMatrixClone;
+    }
+}
diff --git a/service/src/com/android/car/audio/OWNERS b/service/src/com/android/car/audio/OWNERS
new file mode 100644
index 0000000..8d34cdd
--- /dev/null
+++ b/service/src/com/android/car/audio/OWNERS
@@ -0,0 +1,3 @@
+# Audio owners
+haydengomes@google.com
+oscarazu@google.com
\ No newline at end of file
diff --git a/service/src/com/android/car/cluster/InstrumentClusterService.java b/service/src/com/android/car/cluster/InstrumentClusterService.java
index cc0a6b7..6be45b5 100644
--- a/service/src/com/android/car/cluster/InstrumentClusterService.java
+++ b/service/src/com/android/car/cluster/InstrumentClusterService.java
@@ -16,6 +16,7 @@
 package com.android.car.cluster;
 
 import static android.car.cluster.renderer.InstrumentClusterRenderingService.EXTRA_BUNDLE_KEY_FOR_INSTRUMENT_CLUSTER_HELPER;
+import static android.car.settings.CarSettings.Global.DISABLE_INSTRUMENTATION_SERVICE;
 
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
@@ -37,6 +38,7 @@
 import android.os.Message;
 import android.os.RemoteException;
 import android.os.UserHandle;
+import android.provider.Settings;
 import android.text.TextUtils;
 import android.util.Log;
 import android.view.KeyEvent;
@@ -242,6 +244,13 @@
     }
 
     private boolean bindInstrumentClusterRendererService() {
+        boolean explicitlyDisabled = "true".equals(Settings.Global
+                .getString(mContext.getContentResolver(), DISABLE_INSTRUMENTATION_SERVICE));
+        if (explicitlyDisabled) {
+            Log.i(TAG, "Instrument cluster renderer explicitly disabled by settings");
+            return false;
+        }
+
         String rendererService = mContext.getString(R.string.instrumentClusterRendererService);
         if (TextUtils.isEmpty(rendererService)) {
             Log.i(TAG, "Instrument cluster renderer was not configured");
diff --git a/service/src/com/android/car/garagemode/Controller.java b/service/src/com/android/car/garagemode/Controller.java
index 9380409..d7cda0a 100644
--- a/service/src/com/android/car/garagemode/Controller.java
+++ b/service/src/com/android/car/garagemode/Controller.java
@@ -24,8 +24,10 @@
 import android.content.Intent;
 import android.os.Handler;
 import android.os.Looper;
+import android.os.UserHandle;
 
 import com.android.car.CarLocalServices;
+import com.android.car.systeminterface.SystemInterface;
 import com.android.internal.annotations.VisibleForTesting;
 
 import java.util.List;
@@ -118,8 +120,9 @@
      * @param i intent that contains broadcast data
      */
     void sendBroadcast(Intent i) {
+        SystemInterface systemInterface = CarLocalServices.getService(SystemInterface.class);
         LOG.d("Sending broadcast with action: " + i.getAction());
-        mContext.sendBroadcast(i);
+        systemInterface.sendBroadcastAsUser(i, UserHandle.ALL);
     }
 
     /**
diff --git a/service/src/com/android/car/garagemode/GarageMode.java b/service/src/com/android/car/garagemode/GarageMode.java
index bdb7a82..a91d0b5 100644
--- a/service/src/com/android/car/garagemode/GarageMode.java
+++ b/service/src/com/android/car/garagemode/GarageMode.java
@@ -25,8 +25,9 @@
 import android.util.ArraySet;
 
 import com.android.car.CarLocalServices;
-import com.android.car.CarStatsLog;
+import com.android.car.CarStatsLogHelper;
 import com.android.car.user.CarUserService;
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 
 import java.util.ArrayList;
@@ -64,36 +65,49 @@
     private static final int ADDITIONAL_CHECKS_TO_DO = 1;
 
     private final Controller mController;
+    private final JobScheduler mJobScheduler;
+    private final Object mLock = new Object();
+    private final Handler mHandler;
 
+    @GuardedBy("mLock")
     private boolean mGarageModeActive;
+    @GuardedBy("mLock")
     private int mAdditionalChecksToDo = ADDITIONAL_CHECKS_TO_DO;
-    private JobScheduler mJobScheduler;
-    private Handler mHandler;
-    private Runnable mRunnable = new Runnable() {
+    @GuardedBy("mLock")
+    private List<String> mPendingJobs = new ArrayList<>();
+    private final Runnable mRunnable = new Runnable() {
         @Override
         public void run() {
             int numberRunning = numberOfIdleJobsRunning();
             if (numberRunning > 0) {
                 LOG.d("" + numberRunning + " jobs are still running. Need to wait more ...");
-                mAdditionalChecksToDo = ADDITIONAL_CHECKS_TO_DO;
+                synchronized (mLock) {
+                    mAdditionalChecksToDo = ADDITIONAL_CHECKS_TO_DO;
+                }
             } else {
                 // No idle-mode jobs are running.
                 // Are there any scheduled idle jobs that could run now?
-                int numberReadyToRun = numberOfJobsPending();
+                int numberReadyToRun = numberOfPendingJobs();
                 if (numberReadyToRun == 0) {
                     LOG.d("No jobs are running. No jobs are pending. Exiting Garage Mode.");
                     finish();
                     return;
                 }
-                if (mAdditionalChecksToDo == 0) {
+                int numAdditionalChecks;
+                synchronized (mLock) {
+                    numAdditionalChecks = mAdditionalChecksToDo;
+                    if (mAdditionalChecksToDo > 0) {
+                        mAdditionalChecksToDo--;
+                    }
+                }
+                if (numAdditionalChecks == 0) {
                     LOG.d("No jobs are running. Waited too long for "
                             + numberReadyToRun + " pending jobs. Exiting Garage Mode.");
                     finish();
                     return;
                 }
-                LOG.d("No jobs are running. Waiting " + mAdditionalChecksToDo
+                LOG.d("No jobs are running. Waiting " + numAdditionalChecks
                         + " more cycles for " + numberReadyToRun + " pending jobs.");
-                mAdditionalChecksToDo--;
             }
             mHandler.postDelayed(mRunnable, JOB_SNAPSHOT_UPDATE_FREQUENCY_MS);
         }
@@ -104,13 +118,12 @@
         public void run() {
             int userToStop = UserHandle.USER_SYSTEM; // BG user never becomes system user.
             int remainingUsersToStop = 0;
-            synchronized (this) {
+            synchronized (mLock) {
                 remainingUsersToStop = mStartedBackgroundUsers.size();
-                if (remainingUsersToStop > 0) {
-                    userToStop = mStartedBackgroundUsers.valueAt(0);
-                } else {
+                if (remainingUsersToStop < 1) {
                     return;
                 }
+                userToStop = mStartedBackgroundUsers.valueAt(0);
             }
             if (numberOfIdleJobsRunning() == 0) { // all jobs done or stopped.
                 // Keep user until job scheduling is stopped. Otherwise, it can crash jobs.
@@ -120,10 +133,10 @@
                     LOG.i("Stopping background user:" + userToStop + " remaining users:"
                             + (remainingUsersToStop - 1));
                 }
-                synchronized (this) {
+                synchronized (mLock) {
                     mStartedBackgroundUsers.remove(userToStop);
                     if (mStartedBackgroundUsers.size() == 0) {
-                        LOG.i("all background users stopped");
+                        LOG.i("All background users have stopped");
                         return;
                     }
                 }
@@ -135,8 +148,9 @@
         }
     };
 
-
+    @GuardedBy("mLock")
     private CompletableFuture<Void> mFuture;
+    @GuardedBy("mLock")
     private ArraySet<Integer> mStartedBackgroundUsers = new ArraySet<>();
 
     GarageMode(Controller controller) {
@@ -147,7 +161,9 @@
     }
 
     boolean isGarageModeActive() {
-        return mGarageModeActive;
+        synchronized (mLock) {
+            return mGarageModeActive;
+        }
     }
 
     List<String> dump() {
@@ -176,95 +192,97 @@
 
     void enterGarageMode(CompletableFuture<Void> future) {
         LOG.d("Entering GarageMode");
-        synchronized (this) {
+        synchronized (mLock) {
             mGarageModeActive = true;
         }
         updateFuture(future);
-        broadcastSignalToJobSchedulerTo(true);
-        CarStatsLog.logGarageModeStart();
+        broadcastSignalToJobScheduler(true);
+        CarStatsLogHelper.logGarageModeStart();
         startMonitoringThread();
         ArrayList<Integer> startedUsers =
                 CarLocalServices.getService(CarUserService.class).startAllBackgroundUsers();
-        synchronized (this) {
+        synchronized (mLock) {
             mStartedBackgroundUsers.addAll(startedUsers);
         }
     }
 
-    synchronized void cancel() {
-        broadcastSignalToJobSchedulerTo(false);
-        if (mFuture != null && !mFuture.isDone()) {
-            mFuture.cancel(true);
+    void cancel() {
+        broadcastSignalToJobScheduler(false);
+        synchronized (mLock) {
+            if (mFuture == null) {
+                cleanupGarageMode();
+            } else if (!mFuture.isDone()) {
+                mFuture.cancel(true);
+            }
+            mFuture = null;
+            startBackgroundUserStoppingLocked();
         }
-        mFuture = null;
-        startBackgroundUserStopping();
     }
 
-    synchronized void finish() {
-        broadcastSignalToJobSchedulerTo(false);
-        CarStatsLog.logGarageModeStop();
+    void finish() {
+        broadcastSignalToJobScheduler(false);
+        CarStatsLogHelper.logGarageModeStop();
         mController.scheduleNextWakeup();
-        synchronized (this) {
-            if (mFuture != null && !mFuture.isDone()) {
+        synchronized (mLock) {
+            if (mFuture == null) {
+                cleanupGarageMode();
+            } else if (!mFuture.isDone()) {
                 mFuture.complete(null);
             }
             mFuture = null;
+            startBackgroundUserStoppingLocked();
         }
-        startBackgroundUserStopping();
     }
 
     private void cleanupGarageMode() {
         LOG.d("Cleaning up GarageMode");
-        synchronized (this) {
+        synchronized (mLock) {
             mGarageModeActive = false;
+            stopMonitoringThread();
+            mHandler.removeCallbacks(mRunnable);
+            startBackgroundUserStoppingLocked();
         }
-        stopMonitoringThread();
-        mHandler.removeCallbacks(mRunnable);
-        startBackgroundUserStopping();
     }
 
-    private void startBackgroundUserStopping() {
-        synchronized (this) {
-            if (mStartedBackgroundUsers.size() > 0) {
-                mHandler.postDelayed(mStopUserCheckRunnable, USER_STOP_CHECK_INTERVAL);
-            }
+    private void startBackgroundUserStoppingLocked() {
+        if (mStartedBackgroundUsers.size() > 0) {
+            mHandler.postDelayed(mStopUserCheckRunnable, USER_STOP_CHECK_INTERVAL);
         }
     }
 
     private void updateFuture(CompletableFuture<Void> future) {
-        synchronized (this) {
+        synchronized (mLock) {
             mFuture = future;
-        }
-        if (mFuture != null) {
-            mFuture.whenComplete((result, exception) -> {
-                if (exception == null) {
-                    LOG.d("GarageMode completed normally");
-                } else if (exception instanceof CancellationException) {
-                    LOG.d("GarageMode was canceled");
-                } else {
-                    LOG.e("GarageMode ended due to exception: ", exception);
-                }
-                cleanupGarageMode();
-            });
+            if (mFuture != null) {
+                mFuture.whenComplete((result, exception) -> {
+                    if (exception == null) {
+                        LOG.d("GarageMode completed normally");
+                    } else if (exception instanceof CancellationException) {
+                        LOG.d("GarageMode was canceled");
+                    } else {
+                        LOG.e("GarageMode ended due to exception: ", exception);
+                    }
+                    cleanupGarageMode();
+                });
+            }
         }
     }
 
-    private void broadcastSignalToJobSchedulerTo(boolean enableGarageMode) {
+    private void broadcastSignalToJobScheduler(boolean enableGarageMode) {
         Intent i = new Intent();
-        if (enableGarageMode) {
-            i.setAction(ACTION_GARAGE_MODE_ON);
-        } else {
-            i.setAction(ACTION_GARAGE_MODE_OFF);
-        }
+        i.setAction(enableGarageMode ? ACTION_GARAGE_MODE_ON : ACTION_GARAGE_MODE_OFF);
         i.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY | Intent.FLAG_RECEIVER_NO_ABORT);
         mController.sendBroadcast(i);
     }
 
-    private synchronized void startMonitoringThread() {
-        mAdditionalChecksToDo = ADDITIONAL_CHECKS_TO_DO;
+    private void startMonitoringThread() {
+        synchronized (mLock) {
+            mAdditionalChecksToDo = ADDITIONAL_CHECKS_TO_DO;
+        }
         mHandler.postDelayed(mRunnable, JOB_SNAPSHOT_INITIAL_UPDATE_MS);
     }
 
-    private synchronized void stopMonitoringThread() {
+    private void stopMonitoringThread() {
         mHandler.removeCallbacks(mRunnable);
     }
 
@@ -293,7 +311,7 @@
         return count;
     }
 
-    private int numberOfJobsPending() {
+    private int numberOfPendingJobs() {
         return getListOfPendingJobs(null);
     }
 
diff --git a/service/src/com/android/car/hal/CarPropertyUtils.java b/service/src/com/android/car/hal/CarPropertyUtils.java
index 2d0e6ad..92258c2 100644
--- a/service/src/com/android/car/hal/CarPropertyUtils.java
+++ b/service/src/com/android/car/hal/CarPropertyUtils.java
@@ -28,6 +28,7 @@
 import android.hardware.automotive.vehicle.V2_0.VehiclePropValue;
 import android.hardware.automotive.vehicle.V2_0.VehiclePropertyType;
 
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
 
@@ -39,6 +40,10 @@
     /* Utility class has no public constructor */
     private CarPropertyUtils() {}
 
+    private static final int[] DEFAULT_AREAIDS = {VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL};
+    // Length of mixed type properties' configArray should always be 9
+    private static final int CONFIG_ARRAY_LENGTH = 9;
+
     /** Converts {@link VehiclePropValue} to {@link CarPropertyValue} */
     static CarPropertyValue<?> toCarPropertyValue(
             VehiclePropValue halValue, int propertyId) {
@@ -84,13 +89,34 @@
         } else if (byte[].class == clazz) {
             byte[] halData = toByteArray(v.bytes);
             return new CarPropertyValue<>(propertyId, areaId, status, timestamp, halData);
-        } else /* Object.class */ {
-            Object[] values = getRawValueList(clazz, v).toArray();
-            return new CarPropertyValue<>(propertyId, areaId, status, timestamp,
-                    values.length == 1 ? values[0] : values);
+        } else {
+            throw new IllegalArgumentException("Unexpected type in: " + propertyId);
         }
     }
 
+    /** Converts {@link VehiclePropValue} to {@link CarPropertyValue} for MIXED type properties*/
+    static CarPropertyValue<?> toMixedCarPropertyValue(
+            VehiclePropValue halValue, int propertyId, boolean containBoolean) {
+        int areaId = halValue.areaId;
+        int status = halValue.status;
+        long timestamp = halValue.timestamp;
+        VehiclePropValue.RawValue value = halValue.value;
+
+        List<Object> valuesList = new ArrayList<>();
+        valuesList.add(value.stringValue);
+        if (containBoolean) {
+            boolean boolValue = value.int32Values.get(0) == 1;
+            valuesList.add(boolValue);
+            valuesList.addAll(value.int32Values.subList(1, value.int32Values.size()));
+        } else {
+            valuesList.addAll(value.int32Values);
+        }
+        valuesList.addAll(value.int64Values);
+        valuesList.addAll(value.floatValues);
+        valuesList.addAll(value.bytes);
+        return new CarPropertyValue<>(propertyId, areaId, status, timestamp, valuesList.toArray());
+    }
+
     /** Converts {@link CarPropertyValue} to {@link VehiclePropValue} */
     static VehiclePropValue toVehiclePropValue(CarPropertyValue carProp, int halPropId) {
         VehiclePropValue vehicleProp = new VehiclePropValue();
@@ -102,10 +128,6 @@
 
         if (o instanceof Boolean) {
             v.int32Values.add(((Boolean) o) ? 1 : 0);
-        } else if (o instanceof Boolean[]) {
-            for (Boolean b : (Boolean[]) o) {
-                v.int32Values.add(b ? 1 : 0);
-            }
         } else if (o instanceof Integer) {
             v.int32Values.add((Integer) o);
         } else if (o instanceof Integer[]) {
@@ -132,21 +154,92 @@
     }
 
     /**
+     * Converts {@link CarPropertyValue} to {@link VehiclePropValue} for MIXED type properties
+     * configArray[0], 1 indicates the property has a String value
+     * configArray[1], 1 indicates the property has a Boolean value .
+     * configArray[2], 1 indicates the property has a Integer value.
+     * configArray[3], the number indicates the size of Integer[]  in the property.
+     * configArray[4], 1 indicates the property has a Long value .
+     * configArray[5], the number indicates the size of Long[]  in the property.
+     * configArray[6], 1 indicates the property has a Float value .
+     * configArray[7], the number indicates the size of Float[] in the property.
+     * configArray[8], the number indicates the size of byte[] in the property.
+     *
+     * For example:
+     * configArray = {1, 1, 1, 3, 0, 0, 0, 0, 0} indicates the property has a String value, a
+     * Boolean value, an Integer value, an Integer array with 3 enums.
+     */
+    static VehiclePropValue toMixedVehiclePropValue(CarPropertyValue carProp,
+            int halPropId, int[] configArray) {
+        if (configArray.length != CONFIG_ARRAY_LENGTH) {
+            throw new IllegalArgumentException("Unexpected configArray in:" + carProp);
+        }
+        VehiclePropValue vehicleProp = new VehiclePropValue();
+        vehicleProp.prop = halPropId;
+        vehicleProp.areaId = carProp.getAreaId();
+        VehiclePropValue.RawValue v = vehicleProp.value;
+
+        Object[] values = (Object[]) carProp.getValue();
+        int indexOfValues = 0;
+        if (configArray[0] != 0) {
+            // Add a string value
+            v.stringValue = (String) values[indexOfValues];
+            indexOfValues++;
+        }
+
+        if (configArray[1] != 0) {
+            // Add a boolean value
+            v.int32Values.add((Boolean) values[indexOfValues] ? 1 : 0); // in HAL, 1 indicates true
+            indexOfValues++;
+        }
+
+        /*
+         * configArray[2], 1 indicates the property has a Integer value.
+         * configArray[3], the number indicates the size of Integer[]  in the property.
+         */
+        int integerSize = configArray[2] + configArray[3];
+        while (integerSize != 0) {
+            v.int32Values.add((Integer) values[indexOfValues]);
+            indexOfValues++;
+            integerSize--;
+        }
+        /* configArray[4], 1 indicates the property has a Long value .
+         * configArray[5], the number indicates the size of Long[]  in the property.
+         */
+        int longSize = configArray[4] + configArray[5];
+        while (longSize != 0) {
+            v.int64Values.add((Long) values[indexOfValues]);
+            indexOfValues++;
+            longSize--;
+        }
+        /* configArray[6], 1 indicates the property has a Float value .
+         * configArray[7], the number indicates the size of Float[] in the property.
+         */
+        int floatSize = configArray[6] + configArray[7];
+        while (floatSize != 0) {
+            v.floatValues.add((Float) values[indexOfValues]);
+            indexOfValues++;
+            floatSize--;
+        }
+
+        /* configArray[8], the number indicates the size of byte[] in the property. */
+        if (configArray[8] != 0) {
+            Collections.addAll(v.bytes, (Byte[]) values[indexOfValues]);
+        }
+        return vehicleProp;
+    }
+
+    /**
      * Converts {@link VehiclePropConfig} to {@link CarPropertyConfig}.
      */
     static CarPropertyConfig<?> toCarPropertyConfig(VehiclePropConfig p, int propertyId) {
         int areaType = getVehicleAreaType(p.prop & VehicleArea.MASK);
-        // Create list of areaIds for this property
-        int[] areas = new int[p.areaConfigs.size()];
-        for (int i=0; i<p.areaConfigs.size(); i++) {
-            areas[i] = p.areaConfigs.get(i).areaId;
-        }
 
         Class<?> clazz = getJavaClass(p.prop & VehiclePropertyType.MASK);
         if (p.areaConfigs.isEmpty()) {
             return CarPropertyConfig
                     .newBuilder(clazz, propertyId, areaType, /* capacity */ 1)
-                    .addAreas(areas)
+                    .addAreas(DEFAULT_AREAIDS)
                     .setAccess(p.access)
                     .setChangeMode(p.changeMode)
                     .setConfigArray(p.configArray)
@@ -177,14 +270,13 @@
                            classMatched(Long[].class, clazz) ||
                            classMatched(String.class, clazz) ||
                            classMatched(byte[].class, clazz) ||
-                           classMatched(Object.class, clazz)) {
+                           classMatched(Object[].class, clazz)) {
                     // These property types do not have min/max values
                     builder.addArea(area.areaId);
                 } else {
                     throw new IllegalArgumentException("Unexpected type: " + clazz);
                 }
             }
-
             return builder.build();
         }
     }
@@ -229,24 +321,12 @@
             case VehiclePropertyType.BYTES:
                 return byte[].class;
             case VehiclePropertyType.MIXED:
-                return Object.class;
+                return Object[].class;
             default:
                 throw new IllegalArgumentException("Unexpected type: " + toHexString(halType));
         }
     }
 
-    private static List getRawValueList(Class<?> clazz, VehiclePropValue.RawValue value) {
-        if (classMatched(Float.class, clazz) || classMatched(Float[].class, clazz)) {
-            return value.floatValues;
-        } else if (classMatched(Integer.class, clazz) || classMatched(Integer[].class, clazz)) {
-            return value.int32Values;
-        } else if (classMatched(Long.class, clazz) || classMatched(Long[].class, clazz)) {
-            return value.int64Values;
-        } else {
-            throw new IllegalArgumentException("Unexpected type: " + clazz);
-        }
-    }
-
     private static boolean classMatched(Class<?> class1, Class<?> class2) {
         return class1 == class2 || class1.getComponentType() == class2;
     }
diff --git a/service/src/com/android/car/hal/DiagnosticHalService.java b/service/src/com/android/car/hal/DiagnosticHalService.java
index d61c88a..1e56d22 100644
--- a/service/src/com/android/car/hal/DiagnosticHalService.java
+++ b/service/src/com/android/car/hal/DiagnosticHalService.java
@@ -49,7 +49,7 @@
  */
 public class DiagnosticHalService extends  HalServiceBase{
     static final int OBD2_SELECTIVE_FRAME_CLEAR = 1;
-    static final boolean DEBUG = true;
+    static final boolean DEBUG = false;
     private final Object mLock = new Object();
     private final VehicleHal mVehicleHal;
 
@@ -140,7 +140,7 @@
      * Returns a unique token to be used to map this property to a higher-level sensor
      * This token will be stored in {@link DiagnosticHalService#mSensorTypeToConfig} to allow
      * callers to go from unique sensor identifiers to VehiclePropConfig objects
-     * @param config
+     * @param propConfig
      * @return SENSOR_TYPE_INVALID or a locally unique token
      */
     protected int getTokenForProperty(VehiclePropConfig propConfig) {
@@ -396,7 +396,7 @@
     private final LinkedList<CarDiagnosticEvent> mEventsToDispatch = new LinkedList<>();
 
     @Override
-    public void handleHalEvents(List<VehiclePropValue> values) {
+    public void onHalEvents(List<VehiclePropValue> values) {
         for (VehiclePropValue value : values) {
             CarDiagnosticEvent event = createCarDiagnosticEvent(value);
             if (event != null) {
diff --git a/service/src/com/android/car/hal/HalClient.java b/service/src/com/android/car/hal/HalClient.java
index 383499f..06d9666 100644
--- a/service/src/com/android/car/hal/HalClient.java
+++ b/service/src/com/android/car/hal/HalClient.java
@@ -28,6 +28,7 @@
 import android.os.Looper;
 import android.os.Message;
 import android.os.RemoteException;
+import android.os.ServiceSpecificException;
 import android.util.Log;
 
 import com.android.car.CarLog;
@@ -98,9 +99,9 @@
         }
 
         if (StatusCode.OK != status) {
-            throw new IllegalStateException(
-                    String.format("Failed to set property: 0x%x, areaId: 0x%x, "
-                            + "code: %d", propValue.prop, propValue.areaId, status));
+            Log.i(CarLog.TAG_HAL, String.format("Failed to set property: 0x%x, areaId: 0x%x, "
+                    + "code: %d", propValue.prop, propValue.areaId, status));
+            throw new ServiceSpecificException(status);
         }
     }
 
@@ -124,9 +125,9 @@
         }
 
         if (StatusCode.OK != status || valueWrapper.object == null) {
-            throw new IllegalStateException(
-                    String.format("Failed to get property: 0x%x, areaId: 0x%x, "
-                            + "code: %d", propId, areaId, status));
+            Log.i(CarLog.TAG_HAL, String.format("Failed to get property: 0x%x, areaId: 0x%x, "
+                    + "code: %d", requestedPropValue.prop, requestedPropValue.areaId, status));
+            throw new ServiceSpecificException(status);
         }
 
         return valueWrapper.object;
diff --git a/service/src/com/android/car/hal/HalServiceBase.java b/service/src/com/android/car/hal/HalServiceBase.java
index 40700a9..1e2b6e4 100644
--- a/service/src/com/android/car/hal/HalServiceBase.java
+++ b/service/src/com/android/car/hal/HalServiceBase.java
@@ -20,6 +20,7 @@
 import android.annotation.Nullable;
 import android.hardware.automotive.vehicle.V2_0.VehiclePropConfig;
 import android.hardware.automotive.vehicle.V2_0.VehiclePropValue;
+import android.util.Log;
 
 import java.io.PrintWriter;
 import java.util.Collection;
@@ -32,6 +33,9 @@
  * and will translate HAL data into car api specific format.
  */
 public abstract class HalServiceBase {
+
+    private static final String MY_TAG = HalServiceBase.class.getSimpleName();
+
     /** For dispatching events. Kept here to avoid alloc every time */
     private final LinkedList<VehiclePropValue> mDispatchList = new LinkedList<VehiclePropValue>();
 
@@ -48,13 +52,8 @@
     public abstract void release();
 
     /**
-     * return supported properties among all properties.
-     * @return null if no properties are supported
-     */
-    /**
-     * Take supported properties from given allProperties and return List of supported properties.
-     * @param allProperties
-     * @return null if no properties are supported.
+     * Takes the supported properties from given {@code allProperties} and return List of supported
+     * properties (or {@code null} are supported.
      */
     @Nullable
     public Collection<VehiclePropConfig> takeSupportedProperties(
@@ -62,9 +61,18 @@
         return null;
     }
 
-    public abstract void handleHalEvents(List<VehiclePropValue> values);
+    /**
+     * Handles property changes from HAL.
+     */
+    public abstract void onHalEvents(List<VehiclePropValue> values);
 
-    public void handlePropertySetError(int property, int area) {}
+    /**
+     * Handles errors and pass error codes  when setting properties.
+     */
+    public void onPropertySetError(int property, int area, int errorCode) {
+        Log.d(MY_TAG, getClass().getSimpleName() + ".onPropertySetError(): property=" + property
+                + ", area=" + area + " , errorCode = " + errorCode);
+    }
 
     public abstract void dump(PrintWriter writer);
 
diff --git a/service/src/com/android/car/hal/InputHalService.java b/service/src/com/android/car/hal/InputHalService.java
index d719510..1c8fb8d 100644
--- a/service/src/com/android/car/hal/InputHalService.java
+++ b/service/src/com/android/car/hal/InputHalService.java
@@ -121,7 +121,7 @@
     }
 
     @Override
-    public void handleHalEvents(List<VehiclePropValue> values) {
+    public void onHalEvents(List<VehiclePropValue> values) {
         InputListener listener;
         synchronized (this) {
             listener = mListener;
diff --git a/service/src/com/android/car/hal/PowerHalService.java b/service/src/com/android/car/hal/PowerHalService.java
index 0eaa5cc..518931a 100644
--- a/service/src/com/android/car/hal/PowerHalService.java
+++ b/service/src/com/android/car/hal/PowerHalService.java
@@ -32,6 +32,7 @@
 import android.util.Log;
 
 import com.android.car.CarLog;
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 
 import java.io.PrintWriter;
@@ -69,6 +70,8 @@
     @VisibleForTesting
     public static final int SHUTDOWN_ONLY = VehicleApPowerStateShutdownParam.SHUTDOWN_ONLY;
 
+    private final Object mLock = new Object();
+
     private static String powerStateReportName(int state) {
         String baseName;
         switch(state) {
@@ -145,7 +148,8 @@
             if (mState != VehicleApPowerStateReq.SHUTDOWN_PREPARE) {
                 throw new IllegalStateException("wrong state");
             }
-            return (mParam != VehicleApPowerStateShutdownParam.SHUTDOWN_IMMEDIATELY);
+            return (mParam != VehicleApPowerStateShutdownParam.SHUTDOWN_IMMEDIATELY
+                    && mParam != VehicleApPowerStateShutdownParam.SLEEP_IMMEDIATELY);
         }
 
         @Override
@@ -166,10 +170,14 @@
         }
     }
 
+    @GuardedBy("mLock")
     private final HashMap<Integer, VehiclePropConfig> mProperties = new HashMap<>();
     private final VehicleHal mHal;
+    @GuardedBy("mLock")
     private LinkedList<VehiclePropValue> mQueuedEvents;
+    @GuardedBy("mLock")
     private PowerEventListener mListener;
+    @GuardedBy("mLock")
     private int mMaxDisplayBrightness;
 
     public PowerHalService(VehicleHal hal) {
@@ -178,7 +186,7 @@
 
     public void setListener(PowerEventListener listener) {
         LinkedList<VehiclePropValue> eventsToDispatch = null;
-        synchronized (this) {
+        synchronized (mLock) {
             mListener = listener;
             if (mQueuedEvents != null && mQueuedEvents.size() > 0) {
                 eventsToDispatch = mQueuedEvents;
@@ -274,7 +282,7 @@
         try {
             mHal.set(VehicleProperty.DISPLAY_BRIGHTNESS, 0).to(brightness);
             Log.i(CarLog.TAG_POWER, "send display brightness = " + brightness);
-        } catch (PropertyTimeoutException e) {
+        } catch (PropertyTimeoutException | IllegalArgumentException e) {
             Log.e(CarLog.TAG_POWER, "cannot set DISPLAY_BRIGHTNESS", e);
         }
     }
@@ -305,16 +313,23 @@
                 state[VehicleApPowerStateReqIndex.ADDITIONAL]);
     }
 
-    public synchronized boolean isPowerStateSupported() {
-        return (mProperties.get(VehicleProperty.AP_POWER_STATE_REQ) != null)
-                && (mProperties.get(VehicleProperty.AP_POWER_STATE_REPORT) != null);
+    /**
+     * Determines if the current properties describe a valid power state
+     * @return true if both the power state request and power state report are valid
+     */
+    public boolean isPowerStateSupported() {
+        synchronized (mLock) {
+            return (mProperties.get(VehicleProperty.AP_POWER_STATE_REQ) != null)
+                    && (mProperties.get(VehicleProperty.AP_POWER_STATE_REPORT) != null);
+        }
     }
 
-    private synchronized boolean isConfigFlagSet(int flag) {
-        VehiclePropConfig config = mProperties.get(VehicleProperty.AP_POWER_STATE_REQ);
-        if (config == null) {
-            return false;
-        } else if (config.configArray.size() < 1) {
+    private boolean isConfigFlagSet(int flag) {
+        VehiclePropConfig config;
+        synchronized (mLock) {
+            config = mProperties.get(VehicleProperty.AP_POWER_STATE_REQ);
+        }
+        if (config == null || config.configArray.size() < 1) {
             return false;
         }
         return (config.configArray.get(0) & flag) != 0;
@@ -329,48 +344,54 @@
     }
 
     @Override
-    public synchronized void init() {
-        for (VehiclePropConfig config : mProperties.values()) {
-            if (VehicleHal.isPropertySubscribable(config)) {
-                mHal.subscribeProperty(this, config.prop);
+    public void init() {
+        synchronized (mLock) {
+            for (VehiclePropConfig config : mProperties.values()) {
+                if (VehicleHal.isPropertySubscribable(config)) {
+                    mHal.subscribeProperty(this, config.prop);
+                }
             }
-        }
-        VehiclePropConfig brightnessProperty = mProperties.get(DISPLAY_BRIGHTNESS);
-        if (brightnessProperty != null) {
-            mMaxDisplayBrightness = brightnessProperty.areaConfigs.size() > 0
-                    ? brightnessProperty.areaConfigs.get(0).maxInt32Value : 0;
-            if (mMaxDisplayBrightness <= 0) {
-                Log.w(CarLog.TAG_POWER, "Max display brightness from vehicle HAL is invalid:" +
-                        mMaxDisplayBrightness);
-                mMaxDisplayBrightness = 1;
+            VehiclePropConfig brightnessProperty = mProperties.get(DISPLAY_BRIGHTNESS);
+            if (brightnessProperty != null) {
+                mMaxDisplayBrightness = brightnessProperty.areaConfigs.size() > 0
+                        ? brightnessProperty.areaConfigs.get(0).maxInt32Value : 0;
+                if (mMaxDisplayBrightness <= 0) {
+                    Log.w(CarLog.TAG_POWER, "Max display brightness from vehicle HAL is invalid:"
+                            + mMaxDisplayBrightness);
+                    mMaxDisplayBrightness = 1;
+                }
             }
         }
     }
 
     @Override
-    public synchronized void release() {
-        mProperties.clear();
+    public void release() {
+        synchronized (mLock) {
+            mProperties.clear();
+        }
     }
 
     @Override
-    public synchronized Collection<VehiclePropConfig> takeSupportedProperties(
+    public Collection<VehiclePropConfig> takeSupportedProperties(
             Collection<VehiclePropConfig> allProperties) {
-        for (VehiclePropConfig config : allProperties) {
-            switch (config.prop) {
-                case AP_POWER_STATE_REQ:
-                case AP_POWER_STATE_REPORT:
-                case DISPLAY_BRIGHTNESS:
-                    mProperties.put(config.prop, config);
-                    break;
+        synchronized (mLock) {
+            for (VehiclePropConfig config : allProperties) {
+                switch (config.prop) {
+                    case AP_POWER_STATE_REQ:
+                    case AP_POWER_STATE_REPORT:
+                    case DISPLAY_BRIGHTNESS:
+                        mProperties.put(config.prop, config);
+                        break;
+                }
             }
+            return new LinkedList<>(mProperties.values());
         }
-        return new LinkedList<>(mProperties.values());
     }
 
     @Override
-    public void handleHalEvents(List<VehiclePropValue> values) {
+    public void onHalEvents(List<VehiclePropValue> values) {
         PowerEventListener listener;
-        synchronized (this) {
+        synchronized (mLock) {
             if (mListener == null) {
                 if (mQueuedEvents == null) {
                     mQueuedEvents = new LinkedList<>();
@@ -387,7 +408,7 @@
         for (VehiclePropValue v : values) {
             switch (v.prop) {
                 case AP_POWER_STATE_REPORT:
-                    // Should never see this; write-only property
+                    // Ignore this property event. It was generated inside of CarService.
                     break;
                 case AP_POWER_STATE_REQ:
                     int state = v.value.int32Values.get(VehicleApPowerStateReqIndex.STATE);
@@ -399,7 +420,7 @@
                 case DISPLAY_BRIGHTNESS:
                 {
                     int maxBrightness;
-                    synchronized (this) {
+                    synchronized (mLock) {
                         maxBrightness = mMaxDisplayBrightness;
                     }
                     int brightness = v.value.int32Values.get(0) * MAX_BRIGHTNESS / maxBrightness;
diff --git a/service/src/com/android/car/hal/PropertyHalService.java b/service/src/com/android/car/hal/PropertyHalService.java
index 484e667..047aa56 100644
--- a/service/src/com/android/car/hal/PropertyHalService.java
+++ b/service/src/com/android/car/hal/PropertyHalService.java
@@ -16,6 +16,8 @@
 package com.android.car.hal;
 
 import static com.android.car.hal.CarPropertyUtils.toCarPropertyValue;
+import static com.android.car.hal.CarPropertyUtils.toMixedCarPropertyValue;
+import static com.android.car.hal.CarPropertyUtils.toMixedVehiclePropValue;
 import static com.android.car.hal.CarPropertyUtils.toVehiclePropValue;
 
 import static java.lang.Integer.toHexString;
@@ -24,8 +26,13 @@
 import android.car.hardware.CarPropertyConfig;
 import android.car.hardware.CarPropertyValue;
 import android.car.hardware.property.CarPropertyEvent;
+import android.car.hardware.property.CarPropertyManager;
+import android.car.hardware.property.VehicleHalStatusCode;
 import android.hardware.automotive.vehicle.V2_0.VehiclePropConfig;
 import android.hardware.automotive.vehicle.V2_0.VehiclePropValue;
+import android.hardware.automotive.vehicle.V2_0.VehicleProperty;
+import android.hardware.automotive.vehicle.V2_0.VehiclePropertyType;
+import android.os.ServiceSpecificException;
 import android.util.Log;
 import android.util.SparseArray;
 
@@ -34,12 +41,12 @@
 
 import java.io.PrintWriter;
 import java.util.Collection;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
-import java.util.concurrent.ConcurrentHashMap;
 
 /**
  * Common interface for HAL services that send Vehicle Properties back and forth via ICarProperty.
@@ -49,16 +56,17 @@
 public class PropertyHalService extends HalServiceBase {
     private final boolean mDbg = true;
     private final LinkedList<CarPropertyEvent> mEventsToDispatch = new LinkedList<>();
-    private final Map<Integer, CarPropertyConfig<?>> mProps =
-            new ConcurrentHashMap<>();
-    private final SparseArray<Float> mRates = new SparseArray<Float>();
+    @GuardedBy("mLock")
+    private final Map<Integer, CarPropertyConfig<?>> mProps = new HashMap<>();
+    @GuardedBy("mLock")
+    private final SparseArray<VehiclePropConfig> mPropConfigSparseArray = new SparseArray<>();
     private static final String TAG = "PropertyHalService";
     private final VehicleHal mVehicleHal;
     private final PropertyHalServiceIds mPropIds;
 
     @GuardedBy("mLock")
     private PropertyHalListener mListener;
-
+    @GuardedBy("mLock")
     private Set<Integer> mSubscribedPropIds;
 
     private final Object mLock = new Object();
@@ -68,10 +76,12 @@
      * If property is not supported, it will return {@link #NOT_SUPPORTED_PROPERTY}.
      */
     private int managerToHalPropId(int propId) {
-        if (mProps.containsKey(propId)) {
-            return propId;
-        } else {
-            return NOT_SUPPORTED_PROPERTY;
+        synchronized (mLock) {
+            if (mPropConfigSparseArray.get(propId) != null) {
+                return propId;
+            } else {
+                return NOT_SUPPORTED_PROPERTY;
+            }
         }
     }
 
@@ -80,10 +90,12 @@
      * If property is not supported, it will return {@link #NOT_SUPPORTED_PROPERTY}.
      */
     private int halToManagerPropId(int halPropId) {
-        if (mProps.containsKey(halPropId)) {
-            return halPropId;
-        } else {
-            return NOT_SUPPORTED_PROPERTY;
+        synchronized (mLock) {
+            if (mPropConfigSparseArray.get(halPropId) != null) {
+                return halPropId;
+            } else {
+                return NOT_SUPPORTED_PROPERTY;
+            }
         }
     }
 
@@ -93,7 +105,7 @@
     public interface PropertyHalListener {
         /**
          * This event is sent whenever the property value is updated
-         * @param event
+         * @param events
          */
         void onPropertyChange(List<CarPropertyEvent> events);
         /**
@@ -101,7 +113,9 @@
          * @param property
          * @param area
          */
-        void onPropertySetError(int property, int area);
+        void onPropertySetError(int property, int area,
+                @CarPropertyManager.CarSetPropertyErrorCode int errorCode);
+
     }
 
     public PropertyHalService(VehicleHal vehicleHal) {
@@ -131,6 +145,15 @@
         if (mDbg) {
             Log.d(TAG, "getPropertyList");
         }
+        synchronized (mLock) {
+            if (mProps.size() == 0) {
+                for (int i = 0; i < mPropConfigSparseArray.size(); i++) {
+                    VehiclePropConfig p = mPropConfigSparseArray.valueAt(i);
+                    CarPropertyConfig config = CarPropertyUtils.toCarPropertyConfig(p, p.prop);
+                    mProps.put(p.prop, config);
+                }
+            }
+        }
         return mProps;
     }
 
@@ -153,6 +176,15 @@
             Log.e(CarLog.TAG_PROPERTY, "get, property not ready 0x" + toHexString(halPropId), e);
         }
 
+        if (isMixedTypeProperty(halPropId)) {
+            VehiclePropConfig propConfig;
+            synchronized (mLock) {
+                propConfig = mPropConfigSparseArray.get(halPropId);
+            }
+            boolean containBooleanType = propConfig.configArray.get(1) == 1;
+            return value == null ? null : toMixedCarPropertyValue(value,
+                    mgrPropId, containBooleanType);
+        }
         return value == null ? null : toCarPropertyValue(value, mgrPropId);
     }
 
@@ -200,12 +232,25 @@
             throw new IllegalArgumentException("Invalid property Id : 0x"
                     + toHexString(prop.getPropertyId()));
         }
-        VehiclePropValue halProp = toVehiclePropValue(prop, halPropId);
+
+        VehiclePropValue halProp;
+        if (isMixedTypeProperty(halPropId)) {
+            // parse mixed type property value.
+            VehiclePropConfig propConfig;
+            synchronized (mLock) {
+                propConfig = mPropConfigSparseArray.get(prop.getPropertyId());
+            }
+            int[] configArray = propConfig.configArray.stream().mapToInt(i->i).toArray();
+            halProp = toMixedVehiclePropValue(prop, halPropId, configArray);
+        } else {
+            halProp = toVehiclePropValue(prop, halPropId);
+        }
         try {
             mVehicleHal.set(halProp);
         } catch (PropertyTimeoutException e) {
+            // TODO(b/147896616): throw ServiceSpecificException at first place.
             Log.e(CarLog.TAG_PROPERTY, "set, property not ready 0x" + toHexString(halPropId), e);
-            throw new RuntimeException(e);
+            throw new ServiceSpecificException(VehicleHalStatusCode.STATUS_TRY_AGAIN);
         }
     }
 
@@ -223,16 +268,16 @@
             throw new IllegalArgumentException("Invalid property Id : 0x"
                     + toHexString(propId));
         }
-        // Validate the min/max rate
-        CarPropertyConfig cfg = mProps.get(propId);
-        if (rate > cfg.getMaxSampleRate()) {
-            rate = cfg.getMaxSampleRate();
-        } else if (rate < cfg.getMinSampleRate()) {
-            rate = cfg.getMinSampleRate();
-        }
-        synchronized (mSubscribedPropIds) {
+        synchronized (mLock) {
+            VehiclePropConfig cfg = mPropConfigSparseArray.get(propId);
+            if (rate > cfg.maxSampleRate) {
+                rate = cfg.maxSampleRate;
+            } else if (rate < cfg.minSampleRate) {
+                rate = cfg.minSampleRate;
+            }
             mSubscribedPropIds.add(halPropId);
         }
+
         mVehicleHal.subscribeProperty(this, halPropId, rate);
     }
 
@@ -249,7 +294,7 @@
             throw new IllegalArgumentException("Invalid property Id : 0x"
                     + toHexString(propId));
         }
-        synchronized (mSubscribedPropIds) {
+        synchronized (mLock) {
             if (mSubscribedPropIds.contains(halPropId)) {
                 mSubscribedPropIds.remove(halPropId);
                 mVehicleHal.unsubscribeProperty(this, halPropId);
@@ -269,15 +314,13 @@
         if (mDbg) {
             Log.d(TAG, "release()");
         }
-        synchronized (mSubscribedPropIds) {
+        synchronized (mLock) {
             for (Integer prop : mSubscribedPropIds) {
                 mVehicleHal.unsubscribeProperty(this, prop);
             }
             mSubscribedPropIds.clear();
-        }
-        mProps.clear();
-
-        synchronized (mLock) {
+            mPropConfigSparseArray.clear();
+            mProps.clear();
             mListener = null;
         }
     }
@@ -286,12 +329,12 @@
     public Collection<VehiclePropConfig> takeSupportedProperties(
             Collection<VehiclePropConfig> allProperties) {
         List<VehiclePropConfig> taken = new LinkedList<>();
-
         for (VehiclePropConfig p : allProperties) {
             if (mPropIds.isSupportedProperty(p.prop)) {
-                CarPropertyConfig config = CarPropertyUtils.toCarPropertyConfig(p, p.prop);
                 taken.add(p);
-                mProps.put(p.prop, config);
+                synchronized (mLock) {
+                    mPropConfigSparseArray.put(p.prop, p);
+                }
                 if (mDbg) {
                     Log.d(TAG, "takeSupportedProperties: " + toHexString(p.prop));
                 }
@@ -300,11 +343,20 @@
         if (mDbg) {
             Log.d(TAG, "takeSupportedProperties() took " + taken.size() + " properties");
         }
+        // If vehicle hal support to select permission for vendor properties.
+        VehiclePropConfig customizePermission;
+        synchronized (mLock) {
+            customizePermission = mPropConfigSparseArray.get(
+                    VehicleProperty.SUPPORT_CUSTOMIZE_VENDOR_PERMISSION);
+        }
+        if (customizePermission != null) {
+            mPropIds.customizeVendorPermission(customizePermission.configArray);
+        }
         return taken;
     }
 
     @Override
-    public void handleHalEvents(List<VehiclePropValue> values) {
+    public void onHalEvents(List<VehiclePropValue> values) {
         PropertyHalListener listener;
         synchronized (mLock) {
             listener = mListener;
@@ -319,12 +371,21 @@
                     Log.e(TAG, "Property is not supported: 0x" + toHexString(v.prop));
                     continue;
                 }
-                CarPropertyValue<?> propVal = toCarPropertyValue(v, mgrPropId);
+                CarPropertyValue<?> propVal;
+                if (isMixedTypeProperty(v.prop)) {
+                    // parse mixed type property value.
+                    VehiclePropConfig propConfig;
+                    synchronized (mLock) {
+                        propConfig = mPropConfigSparseArray.get(v.prop);
+                    }
+                    boolean containBooleanType = propConfig.configArray.get(1) == 1;
+                    propVal = toMixedCarPropertyValue(v, mgrPropId, containBooleanType);
+                } else {
+                    propVal = toCarPropertyValue(v, mgrPropId);
+                }
                 CarPropertyEvent event = new CarPropertyEvent(
                         CarPropertyEvent.PROPERTY_EVENT_PROPERTY_CHANGE, propVal);
-                if (event != null) {
-                    mEventsToDispatch.add(event);
-                }
+                mEventsToDispatch.add(event);
             }
             listener.onPropertyChange(mEventsToDispatch);
             mEventsToDispatch.clear();
@@ -332,13 +393,14 @@
     }
 
     @Override
-    public void handlePropertySetError(int property, int area) {
+    public void onPropertySetError(int property, int area,
+            @CarPropertyManager.CarSetPropertyErrorCode int errorCode) {
         PropertyHalListener listener;
         synchronized (mLock) {
             listener = mListener;
         }
         if (listener != null) {
-            listener.onPropertySetError(property, area);
+            listener.onPropertySetError(property, area, errorCode);
         }
     }
 
@@ -346,8 +408,15 @@
     public void dump(PrintWriter writer) {
         writer.println(TAG);
         writer.println("  Properties available:");
-        for (CarPropertyConfig prop : mProps.values()) {
-            writer.println("    " + prop.toString());
+        synchronized (mLock) {
+            for (int i = 0; i < mPropConfigSparseArray.size(); i++) {
+                VehiclePropConfig p = mPropConfigSparseArray.valueAt(i);
+                writer.println("    " + p);
+            }
         }
     }
+
+    private static boolean isMixedTypeProperty(int propId) {
+        return (propId & VehiclePropertyType.MASK) == VehiclePropertyType.MIXED;
+    }
 }
diff --git a/service/src/com/android/car/hal/PropertyHalServiceIds.java b/service/src/com/android/car/hal/PropertyHalServiceIds.java
index 82a89d7..b9f3134 100644
--- a/service/src/com/android/car/hal/PropertyHalServiceIds.java
+++ b/service/src/com/android/car/hal/PropertyHalServiceIds.java
@@ -18,8 +18,10 @@
 
 import static java.lang.Integer.toHexString;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.car.Car;
+import android.car.hardware.property.VehicleVendorPermission;
 import android.hardware.automotive.vehicle.V2_0.VehicleProperty;
 import android.hardware.automotive.vehicle.V2_0.VehiclePropertyGroup;
 import android.util.Log;
@@ -27,6 +29,7 @@
 import android.util.SparseArray;
 
 import java.util.HashSet;
+import java.util.List;
 
 /**
  * Helper class to define which property IDs are used by PropertyHalService.  This class binds the
@@ -44,6 +47,59 @@
     private final HashSet<Integer> mPropForUnits;
     private static final String TAG = "PropertyHalServiceIds";
 
+    // default vendor permission
+    private static final int PERMISSION_CAR_VENDOR_DEFAULT = 0x00000000;
+
+    // permissions for the property related with window
+    private static final int PERMISSION_SET_CAR_VENDOR_CATEGORY_WINDOW = 0X00000001;
+    private static final int PERMISSION_GET_CAR_VENDOR_CATEGORY_WINDOW = 0x00000002;
+    // permissions for the property related with door
+    private static final int PERMISSION_SET_CAR_VENDOR_CATEGORY_DOOR = 0x00000003;
+    private static final int PERMISSION_GET_CAR_VENDOR_CATEGORY_DOOR = 0x00000004;
+    // permissions for the property related with seat
+    private static final int PERMISSION_SET_CAR_VENDOR_CATEGORY_SEAT = 0x00000005;
+    private static final int PERMISSION_GET_CAR_VENDOR_CATEGORY_SEAT = 0x00000006;
+    // permissions for the property related with mirror
+    private static final int PERMISSION_SET_CAR_VENDOR_CATEGORY_MIRROR = 0x00000007;
+    private static final int PERMISSION_GET_CAR_VENDOR_CATEGORY_MIRROR = 0x00000008;
+
+    // permissions for the property related with car's information
+    private static final int PERMISSION_SET_CAR_VENDOR_CATEGORY_INFO = 0x00000009;
+    private static final int PERMISSION_GET_CAR_VENDOR_CATEGORY_INFO = 0x0000000A;
+    // permissions for the property related with car's engine
+    private static final int PERMISSION_SET_CAR_VENDOR_CATEGORY_ENGINE = 0x0000000B;
+    private static final int PERMISSION_GET_CAR_VENDOR_CATEGORY_ENGINE = 0x0000000C;
+    // permissions for the property related with car's HVAC
+    private static final int PERMISSION_SET_CAR_VENDOR_CATEGORY_HVAC = 0x0000000D;
+    private static final int PERMISSION_GET_CAR_VENDOR_CATEGORY_HVAC = 0x0000000E;
+    // permissions for the property related with car's light
+    private static final int PERMISSION_SET_CAR_VENDOR_CATEGORY_LIGHT = 0x0000000F;
+    private static final int PERMISSION_GET_CAR_VENDOR_CATEGORY_LIGHT = 0x00000010;
+
+    // permissions reserved for other vendor permission
+    private static final int PERMISSION_SET_CAR_VENDOR_CATEGORY_1 = 0x00010000;
+    private static final int PERMISSION_GET_CAR_VENDOR_CATEGORY_1 = 0x00011000;
+    private static final int PERMISSION_SET_CAR_VENDOR_CATEGORY_2 = 0x00020000;
+    private static final int PERMISSION_GET_CAR_VENDOR_CATEGORY_2 = 0x00021000;
+    private static final int PERMISSION_SET_CAR_VENDOR_CATEGORY_3 = 0x00030000;
+    private static final int PERMISSION_GET_CAR_VENDOR_CATEGORY_3 = 0x00031000;
+    private static final int PERMISSION_SET_CAR_VENDOR_CATEGORY_4 = 0x00040000;
+    private static final int PERMISSION_GET_CAR_VENDOR_CATEGORY_4 = 0x00041000;
+    private static final int PERMISSION_SET_CAR_VENDOR_CATEGORY_5 = 0x00050000;
+    private static final int PERMISSION_GET_CAR_VENDOR_CATEGORY_5 = 0x00051000;
+    private static final int PERMISSION_SET_CAR_VENDOR_CATEGORY_6 = 0x00060000;
+    private static final int PERMISSION_GET_CAR_VENDOR_CATEGORY_6 = 0x00061000;
+    private static final int PERMISSION_SET_CAR_VENDOR_CATEGORY_7 = 0x00070000;
+    private static final int PERMISSION_GET_CAR_VENDOR_CATEGORY_7 = 0x00071000;
+    private static final int PERMISSION_SET_CAR_VENDOR_CATEGORY_8 = 0x00080000;
+    private static final int PERMISSION_GET_CAR_VENDOR_CATEGORY_8 = 0x00081000;
+    private static final int PERMISSION_SET_CAR_VENDOR_CATEGORY_9 = 0x00090000;
+    private static final int PERMISSION_GET_CAR_VENDOR_CATEGORY_9 = 0x00091000;
+    private static final int PERMISSION_SET_CAR_VENDOR_CATEGORY_10 = 0x000A0000;
+    private static final int PERMISSION_GET_CAR_VENDOR_CATEGORY_10 = 0x000A1000;
+    // Not available for android
+    private static final int PERMISSION_CAR_VENDOR_NOT_ACCESSIBLE = 0xF0000000;
+
     public PropertyHalServiceIds() {
         mProps = new SparseArray<>();
         mPropForUnits = new HashSet<>();
@@ -186,6 +242,9 @@
         mProps.put(VehicleProperty.HVAC_DEFROSTER, new Pair<>(
                     Car.PERMISSION_CONTROL_CAR_CLIMATE,
                     Car.PERMISSION_CONTROL_CAR_CLIMATE));
+        mProps.put(VehicleProperty.HVAC_ELECTRIC_DEFROSTER_ON, new Pair<>(
+                    Car.PERMISSION_CONTROL_CAR_CLIMATE,
+                    Car.PERMISSION_CONTROL_CAR_CLIMATE));
         mProps.put(VehicleProperty.HVAC_AC_ON, new Pair<>(
                     Car.PERMISSION_CONTROL_CAR_CLIMATE,
                     Car.PERMISSION_CONTROL_CAR_CLIMATE));
@@ -260,12 +319,18 @@
         mProps.put(VehicleProperty.INFO_FUEL_DOOR_LOCATION, new Pair<>(
                     Car.PERMISSION_CAR_INFO,
                     null));
+        mProps.put(VehicleProperty.INFO_MULTI_EV_PORT_LOCATIONS, new Pair<>(
+                    Car.PERMISSION_CAR_INFO,
+                    null));
         mProps.put(VehicleProperty.INFO_EV_PORT_LOCATION, new Pair<>(
                     Car.PERMISSION_CAR_INFO,
                     null));
         mProps.put(VehicleProperty.INFO_DRIVER_SEAT, new Pair<>(
                     Car.PERMISSION_CAR_INFO,
                     null));
+        mProps.put(VehicleProperty.INFO_EXTERIOR_DIMENSIONS, new Pair<>(
+                    Car.PERMISSION_CAR_INFO,
+                    null));
 
         // Sensor properties
         mProps.put(VehicleProperty.PERF_ODOMETER, new Pair<>(
@@ -297,13 +362,13 @@
                 null));
         mProps.put(VehicleProperty.FUEL_DOOR_OPEN, new Pair<>(
                 Car.PERMISSION_ENERGY_PORTS,
-                null));
+                Car.PERMISSION_CONTROL_ENERGY_PORTS));
         mProps.put(VehicleProperty.EV_BATTERY_LEVEL, new Pair<>(
                 Car.PERMISSION_ENERGY,
                 null));
         mProps.put(VehicleProperty.EV_CHARGE_PORT_OPEN, new Pair<>(
                 Car.PERMISSION_ENERGY_PORTS,
-                null));
+                Car.PERMISSION_CONTROL_ENERGY_PORTS));
         mProps.put(VehicleProperty.EV_CHARGE_PORT_CONNECTED, new Pair<>(
                 Car.PERMISSION_ENERGY_PORTS,
                 null));
@@ -312,13 +377,16 @@
                 null));
         mProps.put(VehicleProperty.RANGE_REMAINING, new Pair<>(
                 Car.PERMISSION_ENERGY,
-                null));
+                Car.PERMISSION_ADJUST_RANGE_REMAINING));
         mProps.put(VehicleProperty.TIRE_PRESSURE, new Pair<>(
                 Car.PERMISSION_TIRES,
                 null));
         mProps.put(VehicleProperty.PERF_STEERING_ANGLE, new Pair<>(
                 Car.PERMISSION_READ_STEERING_STATE,
                 null));
+        mProps.put(VehicleProperty.PERF_REAR_STEERING_ANGLE, new Pair<>(
+                Car.PERMISSION_READ_STEERING_STATE,
+                null));
         mProps.put(VehicleProperty.GEAR_SELECTION, new Pair<>(
                 Car.PERMISSION_POWERTRAIN,
                 null));
@@ -413,11 +481,15 @@
                 Car.PERMISSION_READ_DISPLAY_UNITS,
                 Car.PERMISSION_CONTROL_DISPLAY_UNITS));
         mPropForUnits.add(VehicleProperty.VEHICLE_SPEED_DISPLAY_UNITS);
+
+        mProps.put(VehicleProperty.SUPPORT_CUSTOMIZE_VENDOR_PERMISSION, new Pair<>(
+                Car.PERMISSION_READ_CAR_VENDOR_PERMISSION_INFO,
+                null));
     }
 
     /**
      * @param propId Property ID
-     * @return Read permission string for given property ID. NULL if property ID dose not exist or
+     * @return Read permission string for given property ID. NULL if property ID does not exist or
      * the property is not available for reading.
      */
     @Nullable
@@ -429,6 +501,9 @@
                 Log.e(TAG, "propId is not available for reading : 0x" + toHexString(propId));
             }
             return p.first;
+        } else if (isVendorProperty(propId)) {
+            // if property is vendor property and do not have specific permission.
+            return Car.PERMISSION_VENDOR_EXTENSION;
         } else {
             return null;
         }
@@ -436,7 +511,7 @@
 
     /**
      * @param propId Property ID
-     * @return Write permission string for given property ID. NULL if property ID dose not exist or
+     * @return Write permission string for given property ID. NULL if property ID does not exist or
      * the property is not writable.
      */
     @Nullable
@@ -448,36 +523,23 @@
                 Log.e(TAG, "propId is not writable : 0x" + toHexString(propId));
             }
             return p.second;
+        } else if (isVendorProperty(propId)) {
+            // if property is vendor property and do not have specific permission.
+            return Car.PERMISSION_VENDOR_EXTENSION;
         } else {
             return null;
         }
     }
 
-    /**
-     * Return true if property is a vendor property and was added
-     */
-    public boolean insertVendorProperty(int propId) {
-        if ((propId & VehiclePropertyGroup.MASK) == VehiclePropertyGroup.VENDOR) {
-            mProps.put(propId, new Pair<>(
-                    Car.PERMISSION_VENDOR_EXTENSION, Car.PERMISSION_VENDOR_EXTENSION));
-            return true;
-        } else {
-            // This is not a vendor extension property, it is not added
-            return false;
-        }
+    private static boolean isVendorProperty(int propId) {
+        return (propId & VehiclePropertyGroup.MASK) == VehiclePropertyGroup.VENDOR;
     }
-
     /**
      * Check if property ID is in the list of known IDs that PropertyHalService is interested it.
      */
     public boolean isSupportedProperty(int propId) {
-        if (mProps.get(propId) != null) {
-            // Property is in the list of supported properties
-            return true;
-        } else {
-            // If it's a vendor property, insert it into the propId list and handle it
-            return insertVendorProperty(propId);
-        }
+        // Property is in the list of supported properties
+        return mProps.get(propId) != null || isVendorProperty(propId);
     }
 
     /**
@@ -486,4 +548,122 @@
     public boolean isPropertyToChangeUnits(int propertyId) {
         return mPropForUnits.contains(propertyId);
     }
+
+    /**
+     * Overrides the permission map for vendor properties
+     *
+     * @param configArray the configArray for
+     * {@link VehicleProperty#SUPPORT_CUSTOMIZE_VENDOR_PERMISSION}
+     */
+    public void customizeVendorPermission(@NonNull List<Integer> configArray) {
+        if (configArray == null || configArray.size() % 3 != 0) {
+            throw new IllegalArgumentException(
+                    "ConfigArray for SUPPORT_CUSTOMIZE_VENDOR_PERMISSION is wrong");
+        }
+        int index = 0;
+        while (index < configArray.size()) {
+            int propId = configArray.get(index++);
+            if (!isVendorProperty(propId)) {
+                throw new IllegalArgumentException("Property Id: " + propId
+                        + " is not in vendor range");
+            }
+            int readPermission = configArray.get(index++);
+            int writePermission = configArray.get(index++);
+            mProps.put(propId, new Pair<>(
+                    toPermissionString(readPermission, propId),
+                    toPermissionString(writePermission, propId)));
+        }
+
+    }
+
+    /**
+     * Map VehicleVendorPermission enums in VHAL to android permissions.
+     *
+     * @return permission string, return null if vendor property is not available.
+     */
+    @Nullable
+    private String toPermissionString(int permissionEnum, int propId) {
+        switch (permissionEnum) {
+            case PERMISSION_CAR_VENDOR_DEFAULT:
+                return Car.PERMISSION_VENDOR_EXTENSION;
+            case PERMISSION_SET_CAR_VENDOR_CATEGORY_WINDOW:
+                return VehicleVendorPermission.PERMISSION_SET_CAR_VENDOR_CATEGORY_WINDOW;
+            case PERMISSION_GET_CAR_VENDOR_CATEGORY_WINDOW:
+                return VehicleVendorPermission.PERMISSION_GET_CAR_VENDOR_CATEGORY_WINDOW;
+            case PERMISSION_SET_CAR_VENDOR_CATEGORY_DOOR:
+                return VehicleVendorPermission.PERMISSION_SET_CAR_VENDOR_CATEGORY_DOOR;
+            case PERMISSION_GET_CAR_VENDOR_CATEGORY_DOOR:
+                return VehicleVendorPermission.PERMISSION_GET_CAR_VENDOR_CATEGORY_DOOR;
+            case PERMISSION_SET_CAR_VENDOR_CATEGORY_SEAT:
+                return VehicleVendorPermission.PERMISSION_SET_CAR_VENDOR_CATEGORY_SEAT;
+            case PERMISSION_GET_CAR_VENDOR_CATEGORY_SEAT:
+                return VehicleVendorPermission.PERMISSION_GET_CAR_VENDOR_CATEGORY_SEAT;
+            case PERMISSION_SET_CAR_VENDOR_CATEGORY_MIRROR:
+                return VehicleVendorPermission.PERMISSION_SET_CAR_VENDOR_CATEGORY_MIRROR;
+            case PERMISSION_GET_CAR_VENDOR_CATEGORY_MIRROR:
+                return VehicleVendorPermission.PERMISSION_GET_CAR_VENDOR_CATEGORY_MIRROR;
+            case PERMISSION_SET_CAR_VENDOR_CATEGORY_INFO:
+                return VehicleVendorPermission.PERMISSION_SET_CAR_VENDOR_CATEGORY_INFO;
+            case PERMISSION_GET_CAR_VENDOR_CATEGORY_INFO:
+                return VehicleVendorPermission.PERMISSION_GET_CAR_VENDOR_CATEGORY_INFO;
+            case PERMISSION_SET_CAR_VENDOR_CATEGORY_ENGINE:
+                return VehicleVendorPermission.PERMISSION_SET_CAR_VENDOR_CATEGORY_ENGINE;
+            case PERMISSION_GET_CAR_VENDOR_CATEGORY_ENGINE:
+                return VehicleVendorPermission.PERMISSION_GET_CAR_VENDOR_CATEGORY_ENGINE;
+            case PERMISSION_SET_CAR_VENDOR_CATEGORY_HVAC:
+                return VehicleVendorPermission.PERMISSION_SET_CAR_VENDOR_CATEGORY_HVAC;
+            case PERMISSION_GET_CAR_VENDOR_CATEGORY_HVAC:
+                return VehicleVendorPermission.PERMISSION_GET_CAR_VENDOR_CATEGORY_HVAC;
+            case PERMISSION_SET_CAR_VENDOR_CATEGORY_LIGHT:
+                return VehicleVendorPermission.PERMISSION_SET_CAR_VENDOR_CATEGORY_LIGHT;
+            case PERMISSION_GET_CAR_VENDOR_CATEGORY_LIGHT:
+                return VehicleVendorPermission.PERMISSION_GET_CAR_VENDOR_CATEGORY_LIGHT;
+            case PERMISSION_SET_CAR_VENDOR_CATEGORY_1:
+                return VehicleVendorPermission.PERMISSION_SET_CAR_VENDOR_CATEGORY_1;
+            case PERMISSION_GET_CAR_VENDOR_CATEGORY_1:
+                return VehicleVendorPermission.PERMISSION_GET_CAR_VENDOR_CATEGORY_1;
+            case PERMISSION_SET_CAR_VENDOR_CATEGORY_2:
+                return VehicleVendorPermission.PERMISSION_SET_CAR_VENDOR_CATEGORY_2;
+            case PERMISSION_GET_CAR_VENDOR_CATEGORY_2:
+                return VehicleVendorPermission.PERMISSION_GET_CAR_VENDOR_CATEGORY_2;
+            case PERMISSION_SET_CAR_VENDOR_CATEGORY_3:
+                return VehicleVendorPermission.PERMISSION_SET_CAR_VENDOR_CATEGORY_3;
+            case PERMISSION_GET_CAR_VENDOR_CATEGORY_3:
+                return VehicleVendorPermission.PERMISSION_GET_CAR_VENDOR_CATEGORY_3;
+            case PERMISSION_SET_CAR_VENDOR_CATEGORY_4:
+                return VehicleVendorPermission.PERMISSION_SET_CAR_VENDOR_CATEGORY_4;
+            case PERMISSION_GET_CAR_VENDOR_CATEGORY_4:
+                return VehicleVendorPermission.PERMISSION_GET_CAR_VENDOR_CATEGORY_4;
+            case PERMISSION_SET_CAR_VENDOR_CATEGORY_5:
+                return VehicleVendorPermission.PERMISSION_SET_CAR_VENDOR_CATEGORY_5;
+            case PERMISSION_GET_CAR_VENDOR_CATEGORY_5:
+                return VehicleVendorPermission.PERMISSION_GET_CAR_VENDOR_CATEGORY_5;
+            case PERMISSION_SET_CAR_VENDOR_CATEGORY_6:
+                return VehicleVendorPermission.PERMISSION_SET_CAR_VENDOR_CATEGORY_6;
+            case PERMISSION_GET_CAR_VENDOR_CATEGORY_6:
+                return VehicleVendorPermission.PERMISSION_GET_CAR_VENDOR_CATEGORY_6;
+            case PERMISSION_SET_CAR_VENDOR_CATEGORY_7:
+                return VehicleVendorPermission.PERMISSION_SET_CAR_VENDOR_CATEGORY_7;
+            case PERMISSION_GET_CAR_VENDOR_CATEGORY_7:
+                return VehicleVendorPermission.PERMISSION_GET_CAR_VENDOR_CATEGORY_7;
+            case PERMISSION_SET_CAR_VENDOR_CATEGORY_8:
+                return VehicleVendorPermission.PERMISSION_SET_CAR_VENDOR_CATEGORY_8;
+            case PERMISSION_GET_CAR_VENDOR_CATEGORY_8:
+                return VehicleVendorPermission.PERMISSION_GET_CAR_VENDOR_CATEGORY_8;
+            case PERMISSION_SET_CAR_VENDOR_CATEGORY_9:
+                return VehicleVendorPermission.PERMISSION_SET_CAR_VENDOR_CATEGORY_9;
+            case PERMISSION_GET_CAR_VENDOR_CATEGORY_9:
+                return VehicleVendorPermission.PERMISSION_GET_CAR_VENDOR_CATEGORY_9;
+            case PERMISSION_SET_CAR_VENDOR_CATEGORY_10:
+                return VehicleVendorPermission.PERMISSION_SET_CAR_VENDOR_CATEGORY_10;
+            case PERMISSION_GET_CAR_VENDOR_CATEGORY_10:
+                return VehicleVendorPermission.PERMISSION_GET_CAR_VENDOR_CATEGORY_10;
+            case PERMISSION_CAR_VENDOR_NOT_ACCESSIBLE:
+                return null;
+            default:
+                throw new IllegalArgumentException("permission Id: " + permissionEnum
+                    + " for property:" + propId + " is invalid vendor permission Id");
+        }
+    }
+
 }
diff --git a/service/src/com/android/car/hal/UserHalHelper.java b/service/src/com/android/car/hal/UserHalHelper.java
new file mode 100644
index 0000000..d5311dc
--- /dev/null
+++ b/service/src/com/android/car/hal/UserHalHelper.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.car.hal;
+
+import android.annotation.NonNull;
+import android.content.pm.UserInfo;
+import android.hardware.automotive.vehicle.V2_0.InitialUserInfoRequestType;
+import android.hardware.automotive.vehicle.V2_0.UserFlags;
+import android.os.UserHandle;
+
+import com.android.car.hal.UserHalService.HalCallback;
+import com.android.car.hal.UserHalService.HalCallback.HalCallbackStatus;
+
+/**
+ * Provides utility methods for User HAL related functionalities.
+ */
+public final class UserHalHelper {
+
+    /**
+     * Gets user-friendly representation of the status.
+     */
+    public static String halCallbackStatusToString(@HalCallbackStatus int status) {
+        switch (status) {
+            case HalCallback.STATUS_OK:
+                return "OK";
+            case HalCallback.STATUS_HAL_SET_TIMEOUT:
+                return "HAL_SET_TIMEOUT";
+            case HalCallback.STATUS_HAL_RESPONSE_TIMEOUT:
+                return "HAL_RESPONSE_TIMEOUT";
+            case HalCallback.STATUS_WRONG_HAL_RESPONSE:
+                return "WRONG_HAL_RESPONSE";
+            case HalCallback.STATUS_CONCURRENT_OPERATION:
+                return "CONCURRENT_OPERATION";
+            default:
+                return "UNKNOWN-" + status;
+        }
+    }
+
+    /**
+     * Converts a string to a {@link InitialUserInfoRequestType}.
+     *
+     * @return valid type or numeric value if passed "as is"
+     *
+     * @throws IllegalArgumentException if type is not valid neither a number
+     */
+    public static int parseInitialUserInfoRequestType(@NonNull String type) {
+        // TODO(b/146207078): add unit test
+        switch(type) {
+            case "FIRST_BOOT":
+                return InitialUserInfoRequestType.FIRST_BOOT;
+            case "FIRST_BOOT_AFTER_OTA":
+                return InitialUserInfoRequestType.FIRST_BOOT_AFTER_OTA;
+            case "COLD_BOOT":
+                return InitialUserInfoRequestType.COLD_BOOT;
+            case "RESUME":
+                return InitialUserInfoRequestType.RESUME;
+            default:
+                try {
+                    return Integer.parseInt(type);
+                } catch (NumberFormatException e) {
+                    throw new IllegalArgumentException("invalid type: " + type);
+                }
+        }
+    }
+
+    /**
+     * Converts Android user flags to HALs.
+     */
+    public static int convertFlags(@NonNull UserInfo user) {
+        // TODO(b/146207078): add unit test
+        int flags = UserFlags.NONE;
+        if (user.id == UserHandle.USER_SYSTEM) {
+            flags |= UserFlags.SYSTEM;
+        }
+        if (user.isAdmin()) {
+            flags |= UserFlags.ADMIN;
+        }
+        if (user.isGuest()) {
+            flags |= UserFlags.GUEST;
+        }
+        if (user.isEphemeral()) {
+            flags |= UserFlags.EPHEMERAL;
+        }
+
+        return flags;
+    }
+
+    private UserHalHelper() {
+        throw new UnsupportedOperationException("contains only static methods");
+    }
+}
diff --git a/service/src/com/android/car/hal/UserHalService.java b/service/src/com/android/car/hal/UserHalService.java
new file mode 100644
index 0000000..336f28d
--- /dev/null
+++ b/service/src/com/android/car/hal/UserHalService.java
@@ -0,0 +1,460 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.car.hal;
+
+import static android.car.VehiclePropertyIds.INITIAL_USER_INFO;
+
+import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.car.hardware.property.CarPropertyManager;
+import android.hardware.automotive.vehicle.V2_0.InitialUserInfoResponse;
+import android.hardware.automotive.vehicle.V2_0.InitialUserInfoResponseAction;
+import android.hardware.automotive.vehicle.V2_0.UserFlags;
+import android.hardware.automotive.vehicle.V2_0.UserInfo;
+import android.hardware.automotive.vehicle.V2_0.UsersInfo;
+import android.hardware.automotive.vehicle.V2_0.VehiclePropConfig;
+import android.hardware.automotive.vehicle.V2_0.VehiclePropValue;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.util.Log;
+import android.util.Pair;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.util.SparseIntArray;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.Preconditions;
+
+import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Service used to integrate the OEM's custom user management with Android's.
+ */
+public final class UserHalService extends HalServiceBase {
+
+    private static final String UNSUPPORTED_MSG = "Vehicle HAL does not support user management";
+
+    private static final String TAG = UserHalService.class.getSimpleName();
+
+    // TODO(b/146207078): STOPSHIP - change to false before R is launched
+    private static final boolean DBG = true;
+
+    private static final int REQUEST_TYPE_GET_INITIAL_INFO = 1;
+
+    /** @hide */
+    @IntDef(prefix = { "REQUEST_TYPE_" }, value = {
+            REQUEST_TYPE_GET_INITIAL_INFO
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @interface RequestType{}
+
+    private final Object mLock = new Object();
+
+    private final VehicleHal mHal;
+
+    @GuardedBy("mLock")
+    @Nullable
+    private SparseArray<VehiclePropConfig> mProperties;
+
+    // This handler handles 2 types of messages:
+    // - "Anonymous" messages (what=0) containing runnables.
+    // - "Identifiable" messages used to check for timeouts (whose 'what' is the request id).
+    private final Handler mHandler = new Handler(Looper.getMainLooper());
+
+    /**
+     * Value used on the next request.
+     */
+    @GuardedBy("mLock")
+    private int mNextRequestId = 1;
+
+    /**
+     * Map of callbacks by request id.
+     */
+    @GuardedBy("mHandler")
+    private SparseArray<Pair<Class<?>, HalCallback<?>>> mPendingCallbacks = new SparseArray<>();
+
+    /**
+     * Map of request ids by {@link RequestType}.
+     */
+    @GuardedBy("mLock")
+    private SparseIntArray mPendingRequests = new SparseIntArray();
+
+    public UserHalService(VehicleHal hal) {
+        mHal = hal;
+    }
+
+    @Override
+    public void init() {
+        if (DBG) Log.d(TAG, "init()");
+
+        int size = mProperties.size();
+        for (int i = 0; i < size; i++) {
+            VehiclePropConfig config = mProperties.valueAt(i);
+            if (VehicleHal.isPropertySubscribable(config)) {
+                if (DBG) Log.d(TAG, "subscribing to property " + config.prop);
+                mHal.subscribeProperty(this, config.prop);
+            }
+        }
+    }
+
+    @Override
+    public void release() {
+        if (DBG) Log.d(TAG, "release()");
+    }
+
+    @Override
+    public void onHalEvents(List<VehiclePropValue> values) {
+        if (DBG) Log.d(TAG, "handleHalEvents(): " + values);
+
+        for (int i = 0; i < values.size(); i++) {
+            VehiclePropValue value = values.get(i);
+            switch (value.prop) {
+                case INITIAL_USER_INFO:
+                    mHandler.sendMessage(obtainMessage(
+                            UserHalService::handleOnInitialUserInfoResponse, this, value));
+                    break;
+                default:
+                    Slog.w(TAG, "received unsupported event from HAL: " + value);
+            }
+        }
+    }
+
+    @Override
+    public void onPropertySetError(int property, int area,
+            @CarPropertyManager.CarSetPropertyErrorCode int errorCode) {
+        if (DBG)Log.d(TAG, "handlePropertySetError(" + property + "/" + area + ")");
+    }
+
+    @Override
+    @Nullable
+    public Collection<VehiclePropConfig> takeSupportedProperties(
+            Collection<VehiclePropConfig> allProperties) {
+        boolean supported = false;
+        // TODO(b/146207078): increase capacity once it supports more
+        SparseArray<VehiclePropConfig> properties = new SparseArray<>(1);
+        ArrayList<VehiclePropConfig> taken = new ArrayList<>();
+        for (VehiclePropConfig config : allProperties) {
+            switch (config.prop) {
+                case INITIAL_USER_INFO:
+                    supported = true;
+                    taken.add(config);
+                    properties.put(config.prop, config);
+                    break;
+            }
+
+        }
+        if (!supported) {
+            Log.w(TAG, UNSUPPORTED_MSG);
+            return null;
+        }
+        synchronized (mLock) {
+            mProperties = properties;
+        }
+        return taken;
+    }
+
+    /**
+     * Callback used on async methods.
+     *
+     * @param <R> response type.
+     */
+    public interface HalCallback<R> {
+
+        int STATUS_OK = 1;
+        int STATUS_HAL_SET_TIMEOUT = 2;
+        int STATUS_HAL_RESPONSE_TIMEOUT = 3;
+        int STATUS_WRONG_HAL_RESPONSE = 4;
+        int STATUS_CONCURRENT_OPERATION = 5;
+
+        /** @hide */
+        @IntDef(prefix = { "STATUS_" }, value = {
+                STATUS_OK,
+                STATUS_HAL_SET_TIMEOUT,
+                STATUS_HAL_RESPONSE_TIMEOUT,
+                STATUS_WRONG_HAL_RESPONSE,
+                STATUS_CONCURRENT_OPERATION
+        })
+        @Retention(RetentionPolicy.SOURCE)
+        @interface HalCallbackStatus{}
+
+        /**
+         * Called when the HAL generated an event responding to that callback (or when an error
+         * occurred).
+         *
+         * @param status status of the request.
+         * @param response HAL response (or {@code null} in case of error).
+         */
+        void onResponse(@HalCallbackStatus int status, @Nullable R response);
+    }
+
+    /**
+     * Checks if the Vehicle HAL supports user management.
+     */
+    public boolean isSupported() {
+        synchronized (mLock) {
+            return mProperties != null;
+        }
+    }
+
+    @GuardedBy("mLock")
+    private void checkSupportedLocked() {
+        Preconditions.checkState(isSupported(), UNSUPPORTED_MSG);
+    }
+
+    /**
+     * Calls HAL to asynchronously get info about the initial user.
+     *
+     * @param requestType type of request (as defined by
+     * {@link android.hardware.automotive.vehicle.V2_0.InitialUserInfoRequestType}).
+     * @param timeoutMs how long to wait (in ms) for the property change event.
+     * @param usersInfo current state of Android users.
+     * @param callback callback to handle the response.
+     *
+     * @throws IllegalStateException if the HAL does not support user management (callers should
+     * call {@link #isSupported()} first to avoid this exception).
+     */
+    public void getInitialUserInfo(int requestType, int timeoutMs, @NonNull UsersInfo usersInfo,
+            @NonNull HalCallback<InitialUserInfoResponse> callback) {
+        Preconditions.checkArgumentPositive(timeoutMs, "timeout must be positive");
+        Objects.requireNonNull(usersInfo);
+        // TODO(b/146207078): use helper method to convert request to prop value and check usersInfo
+        // is valid
+        Objects.requireNonNull(callback);
+
+        VehiclePropValue propRequest = new VehiclePropValue();
+        propRequest.prop = INITIAL_USER_INFO;
+        int requestId;
+        synchronized (mLock) {
+            checkSupportedLocked();
+            if (hasPendingRequestLocked(REQUEST_TYPE_GET_INITIAL_INFO, callback)) return;
+            requestId = addPendingRequestLocked(REQUEST_TYPE_GET_INITIAL_INFO);
+            // TODO(b/146207078): use helper method to convert request to prop value
+            propRequest.value.int32Values.add(requestId);
+            propRequest.value.int32Values.add(requestType);
+            propRequest.value.int32Values.add(usersInfo.currentUser.userId);
+            propRequest.value.int32Values.add(usersInfo.currentUser.flags);
+            propRequest.value.int32Values.add(usersInfo.numberUsers);
+            for (int i = 0; i < usersInfo.numberUsers; i++) {
+                UserInfo userInfo = usersInfo.existingUsers.get(i);
+                propRequest.value.int32Values.add(userInfo.userId);
+                propRequest.value.int32Values.add(userInfo.flags);
+            }
+            setTimestamp(propRequest);
+        }
+        mHandler.sendMessage(obtainMessage(
+                UserHalService::handleAddPendingRequest, this, requestId,
+                InitialUserInfoResponse.class, callback));
+
+        mHandler.sendMessageDelayed(obtainMessage(
+                UserHalService::handleCheckIfRequestTimedOut, this, requestId).setWhat(requestId),
+                timeoutMs);
+        try {
+            if (DBG) Log.d(TAG, "Calling hal.set(): " + propRequest);
+            mHal.set(propRequest);
+        } catch (PropertyTimeoutException e) {
+            Log.w(TAG, "Failed to set INITIAL_USER_INFO", e);
+            callback.onResponse(HalCallback.STATUS_HAL_SET_TIMEOUT, null);
+        }
+    }
+
+    private void handleAddPendingRequest(int requestId, @NonNull Class<?> responseClass,
+            @NonNull HalCallback<?> callback) {
+        if (DBG) {
+            Log.d(TAG, "adding pending callback (of type " + responseClass.getName()
+                    + ") for request " + requestId);
+        }
+        mPendingCallbacks.put(requestId, new Pair<>(responseClass, callback));
+    }
+
+    /**
+     * Checks if there is a pending request of type {@code requestType}, calling {@code callback}
+     * with {@link HalCallback#STATUS_CONCURRENT_OPERATION} when there is.
+     */
+    private boolean hasPendingRequestLocked(@RequestType int requestType,
+            @NonNull HalCallback<?> callback) {
+        int index = mPendingRequests.indexOfKey(requestType);
+        if (index < 0) return false;
+
+        int requestId = mPendingRequests.valueAt(index);
+        Log.w(TAG, "Already have pending request of type " + requestTypeToString(requestType)
+                + ": id=" + requestId);
+
+        callback.onResponse(HalCallback.STATUS_CONCURRENT_OPERATION, null);
+        return true;
+    }
+
+    /**
+     * Adds a new pending request to the queue, returning its request id.
+     */
+    @GuardedBy("mLock")
+    private int addPendingRequestLocked(@RequestType int requestType) {
+        int requestId = mNextRequestId++;
+        mPendingRequests.put(requestType, requestId);
+        return requestId;
+    }
+
+    /**
+     * Removes the pending request and its timeout callback.
+     */
+    private void handleRemovePendingRequest(int requestId) {
+        if (DBG) Log.d(TAG, "Removing pending request #" + requestId);
+        mHandler.removeMessages(requestId);
+        mPendingCallbacks.remove(requestId);
+    }
+
+    private void handleCheckIfRequestTimedOut(int requestId) {
+        Pair<Class<?>, HalCallback<?>> pair = mPendingCallbacks.get(requestId);
+        if (pair == null) return;
+
+        Log.w(TAG, "Request #" + requestId + " timed out");
+        handleRemovePendingRequest(requestId);
+        pair.second.onResponse(HalCallback.STATUS_HAL_RESPONSE_TIMEOUT, null);
+    }
+
+    @GuardedBy("mHandle")
+    private void handleOnInitialUserInfoResponse(VehiclePropValue value) {
+        // TODO(b/146207078): record (for dumping()) the last N responses.
+        int requestId = value.value.int32Values.get(0);
+        HalCallback<InitialUserInfoResponse> callback = handleGetPendingCallback(requestId,
+                InitialUserInfoResponse.class);
+        if (callback == null) {
+            Log.w(TAG, "no callback for requestId " + requestId + ": " + value);
+            return;
+        }
+        handleRemovePendingRequest(requestId);
+        InitialUserInfoResponse response = new InitialUserInfoResponse();
+        // TODO(b/146207078): use helper method to convert prop value to proper response
+        response.requestId = requestId;
+        response.action = value.value.int32Values.get(1);
+        switch (response.action) {
+            case InitialUserInfoResponseAction.DEFAULT:
+                response.userToSwitchOrCreate.userId = UserHandle.USER_NULL;
+                response.userToSwitchOrCreate.flags = UserFlags.NONE;
+                break;
+            case InitialUserInfoResponseAction.SWITCH:
+                response.userToSwitchOrCreate.userId = value.value.int32Values.get(2);
+                response.userToSwitchOrCreate.flags = UserFlags.NONE;
+                break;
+            case InitialUserInfoResponseAction.CREATE:
+                response.userToSwitchOrCreate.userId = UserHandle.USER_NULL;
+                response.userToSwitchOrCreate.flags = value.value.int32Values.get(2);
+                response.userNameToCreate = value.value.stringValue;
+                break;
+            default:
+                Log.e(TAG, "invalid action (" + response.action + ") from HAL: " + value);
+                callback.onResponse(HalCallback.STATUS_WRONG_HAL_RESPONSE, null);
+                return;
+        }
+
+        if (DBG) Log.d(TAG, "replying to request " + requestId + " with " + response);
+        callback.onResponse(HalCallback.STATUS_OK, response);
+    }
+
+    @GuardedBy("mHandle")
+    private <T> HalCallback<T> handleGetPendingCallback(int requestId, Class<T> clazz) {
+        Pair<Class<?>, HalCallback<?>> pair = mPendingCallbacks.get(requestId);
+        if (pair == null) return null;
+
+        if (pair.first != clazz) {
+            Slog.e(TAG, "Invalid callback class for request " + requestId + ": expected" + clazz
+                    + ", but got is " + pair.first);
+            // TODO(b/146207078): add unit test for this scenario once it supports other properties
+            return null;
+        }
+        @SuppressWarnings("unchecked")
+        HalCallback<T> callback = (HalCallback<T>) pair.second;
+        return callback;
+    }
+
+    private void setTimestamp(@NonNull VehiclePropValue propRequest) {
+        propRequest.timestamp = SystemClock.elapsedRealtime();
+    }
+
+    @Override
+    public void dump(PrintWriter writer) {
+        writer.printf("*User HAL*\n");
+        synchronized (mLock) {
+            if (!isSupported()) {
+                writer.println(UNSUPPORTED_MSG);
+                return;
+            }
+            int numberProperties = mProperties.size();
+            String indent = "  ";
+            writer.printf("%d supported properties\n", numberProperties);
+            for (int i = 0; i < numberProperties; i++) {
+                writer.printf("%s%s\n", indent, mProperties.valueAt(i));
+            }
+            writer.printf("next request id: %d\n", mNextRequestId);
+            int size = mPendingRequests.size();
+            if (size == 0) {
+                writer.println("no pending requests");
+            } else {
+                writer.printf("%d pending requests\n", size);
+                for (int i = 0; i < size; i++) {
+                    String type = requestTypeToString(mPendingRequests.keyAt(i));
+                    int requestId = mPendingRequests.valueAt(i);
+                    writer.printf("%s#%d: %s=req_id(%d)\n", indent, i, type, requestId);
+                }
+            }
+        }
+
+        CountDownLatch latch = new CountDownLatch(1);
+        mHandler.sendMessage(obtainMessage(UserHalService::handleDump, this, writer, latch));
+        try {
+            if (!latch.await(500, TimeUnit.SECONDS)) {
+                writer.println("\nTIMED OUT");
+            }
+        } catch (InterruptedException e) {
+            Thread.currentThread().interrupt();
+            writer.println("\nINTERRUPTED");
+        }
+    }
+
+    /**
+     * Dumps the state that's guarded by {@code mHandler}.
+     */
+    private void handleDump(@NonNull PrintWriter writer, @NonNull CountDownLatch latch) {
+        if (mPendingCallbacks.size() == 0) {
+            writer.println("no pending callbacks");
+        } else {
+            writer.printf("pending callbacks: %s\n", mPendingCallbacks);
+        }
+        latch.countDown();
+    }
+
+    @NonNull
+    private static String requestTypeToString(@RequestType int type) {
+        switch (type) {
+            case REQUEST_TYPE_GET_INITIAL_INFO:
+                return "TYPE_GET_INITIAL_INFO";
+            default:
+                return "UNKNOWN-" + type;
+        }
+    }
+}
diff --git a/service/src/com/android/car/hal/VehicleHal.java b/service/src/com/android/car/hal/VehicleHal.java
index d85a357..f69d23d 100644
--- a/service/src/com/android/car/hal/VehicleHal.java
+++ b/service/src/com/android/car/hal/VehicleHal.java
@@ -23,6 +23,7 @@
 import static java.lang.Integer.toHexString;
 
 import android.annotation.CheckResult;
+import android.car.hardware.property.CarPropertyManager;
 import android.content.Context;
 import android.hardware.automotive.vehicle.V2_0.IVehicle;
 import android.hardware.automotive.vehicle.V2_0.IVehicleCallback;
@@ -55,7 +56,9 @@
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
+import java.util.stream.Collectors;
 
 /**
  * Abstraction for vehicle HAL. This class handles interface with native HAL and do basic parsing
@@ -74,6 +77,7 @@
     private final PropertyHalService mPropertyHal;
     private final InputHalService mInputHal;
     private final VmsHalService mVmsHal;
+    private final UserHalService mUserHal;
     private DiagnosticHalService mDiagnosticHal = null;
 
     /** Might be re-assigned if Vehicle HAL is reconnected. */
@@ -99,11 +103,13 @@
         mInputHal = new InputHalService(this);
         mVmsHal = new VmsHalService(context, this);
         mDiagnosticHal = new DiagnosticHalService(this);
+        mUserHal = new UserHalService(this);
         mAllServices.addAll(Arrays.asList(mPowerHal,
                 mInputHal,
                 mPropertyHal,
                 mDiagnosticHal,
-                mVmsHal));
+                mVmsHal,
+                mUserHal));
 
         mHalClient = new HalClient(vehicle, mHandlerThread.getLooper(), this /*IVehicleCallback*/);
     }
@@ -120,6 +126,7 @@
         mVmsHal = null;
         mHalClient = halClient;
         mDiagnosticHal = diagnosticHal;
+        mUserHal = null;
     }
 
     public void vehicleHalReconnected(IVehicle vehicle) {
@@ -156,10 +163,12 @@
         for (HalServiceBase service: mAllServices) {
             Collection<VehiclePropConfig> taken = service.takeSupportedProperties(properties);
             if (taken == null) {
+                Log.w(CarLog.TAG_HAL, "HalService " + service + " didn't take any property");
                 continue;
             }
             if (DBG) {
-                Log.i(CarLog.TAG_HAL, "HalService " + service + " take properties " + taken.size());
+                Log.i(CarLog.TAG_HAL, "HalService " + service + " took " + taken.size()
+                        + " properties ");
             }
             synchronized (this) {
                 for (VehiclePropConfig p: taken) {
@@ -205,6 +214,10 @@
         return mInputHal;
     }
 
+    public UserHalService getUserHal() {
+        return mUserHal;
+    }
+
     public VmsHalService getVmsHal() { return mVmsHal; }
 
     private void assertServiceOwnerLocked(HalServiceBase service, int property) {
@@ -440,7 +453,7 @@
             }
         }
         for (HalServiceBase s : mServicesToDispatch) {
-            s.handleHalEvents(s.getDispatchList());
+            s.onHalEvents(s.getDispatchList());
             s.getDispatchList().clear();
         }
         mServicesToDispatch.clear();
@@ -452,13 +465,14 @@
     }
 
     @Override
-    public void onPropertySetError(int errorCode, int propId, int areaId) {
+    public void onPropertySetError(@CarPropertyManager.CarSetPropertyErrorCode int errorCode,
+            int propId, int areaId) {
         Log.e(CarLog.TAG_HAL, String.format("onPropertySetError, errorCode: %d, prop: 0x%x, "
                 + "area: 0x%x", errorCode, propId, areaId));
         if (propId != VehicleProperty.INVALID) {
             HalServiceBase service = mPropertyHandlers.get(propId);
             if (service != null) {
-                service.handlePropertySetError(propId, areaId);
+                service.onPropertySetError(propId, areaId, errorCode);
             }
         }
     }
@@ -486,6 +500,31 @@
     }
 
     /**
+     * Dumps the list of HALs.
+     */
+    public void dumpListHals(PrintWriter writer) {
+        for (HalServiceBase service: mAllServices) {
+            writer.println(service.getClass().getName());
+        }
+    }
+
+    /**
+     * Dumps the given HALs.
+     */
+    public void dumpSpecificHals(PrintWriter writer, String... halNames) {
+        Map<String, HalServiceBase> byName = mAllServices.stream()
+                .collect(Collectors.toMap(s -> s.getClass().getSimpleName(), s -> s));
+        for (String halName : halNames) {
+            HalServiceBase service = byName.get(halName);
+            if (service == null) {
+                writer.printf("No HAL named %s. Valid options are: %s\n", halName, byName.keySet());
+                continue;
+            }
+            service.dump(writer);
+        }
+    }
+
+    /**
      * Dumps vehicle property values.
      * @param writer
      * @param propId property id, dump all properties' value if it is empty string.
@@ -500,6 +539,10 @@
             }
         } else if (areaId.equals("")) {
             VehiclePropConfig config = mAllProperties.get(Integer.parseInt(propId, 16));
+            if (config == null) {
+                writer.printf("Property 0x%s not supported by HAL\n", propId);
+                return;
+            }
             dumpPropertyValueByConfig(writer, config);
         } else {
             int id = Integer.parseInt(propId, 16);
@@ -569,20 +612,20 @@
     private static String dumpPropertyConfigsHelp(VehiclePropConfig config) {
         StringBuilder builder = new StringBuilder()
                 .append("Property:0x").append(toHexString(config.prop))
-                .append(",Property name:").append(VehicleProperty.toString(config.prop))
-                .append(",access:0x").append(toHexString(config.access))
-                .append(",changeMode:0x").append(toHexString(config.changeMode))
-                .append(",config:0x").append(Arrays.toString(config.configArray.toArray()))
-                .append(",fs min:").append(config.minSampleRate)
-                .append(",fs max:").append(config.maxSampleRate);
+                .append(", Property name:").append(VehicleProperty.toString(config.prop))
+                .append(", access:0x").append(toHexString(config.access))
+                .append(", changeMode:0x").append(toHexString(config.changeMode))
+                .append(", config:0x").append(Arrays.toString(config.configArray.toArray()))
+                .append(", fs min:").append(config.minSampleRate)
+                .append(", fs max:").append(config.maxSampleRate);
         for (VehicleAreaConfig area : config.areaConfigs) {
-            builder.append(",areaId :").append(toHexString(area.areaId))
-                    .append(",f min:").append(area.minFloatValue)
-                    .append(",f max:").append(area.maxFloatValue)
-                    .append(",i min:").append(area.minInt32Value)
-                    .append(",i max:").append(area.maxInt32Value)
-                    .append(",i64 min:").append(area.minInt64Value)
-                    .append(",i64 max:").append(area.maxInt64Value);
+            builder.append("\n\tareaId:0x").append(toHexString(area.areaId))
+                    .append(", f min:").append(area.minFloatValue)
+                    .append(", f max:").append(area.maxFloatValue)
+                    .append(", i min:").append(area.minInt32Value)
+                    .append(", i max:").append(area.maxInt32Value)
+                    .append(", i64 min:").append(area.minInt64Value)
+                    .append(", i64 max:").append(area.maxInt64Value);
         }
         return builder.toString();
     }
diff --git a/service/src/com/android/car/hal/VmsHalService.java b/service/src/com/android/car/hal/VmsHalService.java
index 99263d7..c206a01 100644
--- a/service/src/com/android/car/hal/VmsHalService.java
+++ b/service/src/com/android/car/hal/VmsHalService.java
@@ -18,16 +18,13 @@
 import static com.android.car.CarServiceUtils.toByteArray;
 
 import android.car.VehicleAreaType;
-import android.car.vms.IVmsPublisherClient;
-import android.car.vms.IVmsPublisherService;
-import android.car.vms.IVmsSubscriberClient;
-import android.car.vms.IVmsSubscriberService;
 import android.car.vms.VmsAssociatedLayer;
 import android.car.vms.VmsAvailableLayers;
+import android.car.vms.VmsClient;
+import android.car.vms.VmsClientManager.VmsClientCallback;
 import android.car.vms.VmsLayer;
 import android.car.vms.VmsLayerDependency;
-import android.car.vms.VmsLayersOffering;
-import android.car.vms.VmsOperationRecorder;
+import android.car.vms.VmsSubscriptionHelper;
 import android.car.vms.VmsSubscriptionState;
 import android.content.Context;
 import android.hardware.automotive.vehicle.V2_0.VehiclePropConfig;
@@ -43,29 +40,30 @@
 import android.hardware.automotive.vehicle.V2_0.VmsStartSessionMessageIntegerValuesIndex;
 import android.os.Build;
 import android.os.Handler;
+import android.os.HandlerExecutor;
 import android.os.HandlerThread;
-import android.os.IBinder;
-import android.os.Message;
 import android.os.RemoteException;
 import android.os.SystemClock;
 import android.util.ArraySet;
 import android.util.Log;
 
+import androidx.annotation.GuardedBy;
 import androidx.annotation.VisibleForTesting;
 
-import com.android.car.vms.VmsClientManager;
+import com.android.car.CarLocalServices;
+import com.android.car.vms.VmsNewBrokerService;
 
 import java.io.FileDescriptor;
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.PrintWriter;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
+import java.util.function.BiFunction;
 import java.util.function.Supplier;
 
 /**
@@ -80,129 +78,68 @@
     private static final int HAL_PROPERTY_ID = VehicleProperty.VEHICLE_MAP_SERVICE;
     private static final int NUM_INTEGERS_IN_VMS_LAYER = 3;
     private static final int UNKNOWN_CLIENT_ID = -1;
+    private static final byte[] DEFAULT_PUBLISHER_INFO = new byte[0];
 
     private final VehicleHal mVehicleHal;
+    private final HandlerThread mHandlerThread;
     private final int mCoreId;
-    private final MessageQueue mMessageQueue;
+    private final BiFunction<Handler, VmsClientCallback, VmsClient> mInitVmsClient;
     private final int mClientMetricsProperty;
     private final boolean mPropagatePropertyException;
-    private volatile boolean mIsSupported = false;
+    private final VmsSubscriptionHelper mSubscriptionHelper =
+            new VmsSubscriptionHelper(this::setSubscriptions);
 
-    private VmsClientManager mClientManager;
-    private IVmsPublisherService mPublisherService;
-    private IBinder mPublisherToken;
-    private IVmsSubscriberService mSubscriberService;
+    private final Object mLock = new Object();
+    @GuardedBy("mLock")
+    private boolean mIsSupported;
+    @GuardedBy("mLock")
+    private VmsClient mClient;
+    @GuardedBy("mLock")
+    private Handler mHandler;
 
-    private int mSubscriptionStateSequence = -1;
-    private int mAvailableLayersSequence = -1;
-
-    private final IVmsPublisherClient.Stub mPublisherClient = new IVmsPublisherClient.Stub() {
+    private final VmsClientCallback mClientCallback = new VmsClientCallback() {
         @Override
-        public void setVmsPublisherService(IBinder token, IVmsPublisherService service) {
-            mPublisherToken = token;
-            mPublisherService = service;
+        public void onClientConnected(VmsClient client) {
+            Log.wtf(TAG, "onClientConnnected triggered for local client");
         }
 
         @Override
-        public void onVmsSubscriptionChange(VmsSubscriptionState subscriptionState) {
+        public void onSubscriptionStateChanged(VmsSubscriptionState subscriptionState) {
             if (DBG) Log.d(TAG, "Handling a subscription state change");
-            // Drop out-of-order notifications
-            if (subscriptionState.getSequenceNumber() <= mSubscriptionStateSequence) {
-                Log.w(TAG,
-                        String.format("Out of order subscription state received: %d (expecting %d)",
-                                subscriptionState.getSequenceNumber(),
-                                mSubscriptionStateSequence + 1));
-                return;
-            }
-            mSubscriptionStateSequence = subscriptionState.getSequenceNumber();
-            mMessageQueue.enqueue(VmsMessageType.SUBSCRIPTIONS_CHANGE,
-                    createSubscriptionStateMessage(VmsMessageType.SUBSCRIPTIONS_CHANGE,
+            setPropertyValue(createSubscriptionStateMessage(VmsMessageType.SUBSCRIPTIONS_CHANGE,
                             subscriptionState));
         }
-    };
-
-    private final IVmsSubscriberClient.Stub mSubscriberClient = new IVmsSubscriberClient.Stub() {
-        @Override
-        public void onVmsMessageReceived(VmsLayer layer, byte[] payload) {
-            if (DBG) Log.d(TAG, "Handling a data message for Layer: " + layer);
-            mMessageQueue.enqueue(VmsMessageType.DATA, createDataMessage(layer, payload));
-        }
 
         @Override
-        public void onLayersAvailabilityChanged(VmsAvailableLayers availableLayers) {
+        public void onLayerAvailabilityChanged(VmsAvailableLayers availableLayers) {
             if (DBG) Log.d(TAG, "Handling a layer availability change");
-            // Drop out-of-order notifications
-            if (availableLayers.getSequence() <= mAvailableLayersSequence) {
-                Log.w(TAG,
-                        String.format("Out of order layer availability received: %d (expecting %d)",
-                                availableLayers.getSequence(),
-                                mAvailableLayersSequence + 1));
-                return;
-            }
-            mAvailableLayersSequence = availableLayers.getSequence();
-            mMessageQueue.enqueue(VmsMessageType.AVAILABILITY_CHANGE,
-                    createAvailableLayersMessage(VmsMessageType.AVAILABILITY_CHANGE,
+            setPropertyValue(createAvailableLayersMessage(VmsMessageType.AVAILABILITY_CHANGE,
                             availableLayers));
         }
-    };
-
-    private class MessageQueue implements Handler.Callback {
-        private final Set<Integer> mSupportedMessageTypes = new ArraySet<>(Arrays.asList(
-                VmsMessageType.DATA,
-                VmsMessageType.START_SESSION,
-                VmsMessageType.AVAILABILITY_CHANGE,
-                VmsMessageType.SUBSCRIPTIONS_CHANGE
-        ));
-        private HandlerThread mHandlerThread;
-        private Handler mHandler;
-
-        synchronized void init() {
-            mHandlerThread = new HandlerThread(TAG);
-            mHandlerThread.start();
-            mHandler = new Handler(mHandlerThread.getLooper(), this);
-        }
-
-        synchronized void release() {
-            if (mHandlerThread != null) {
-                mHandlerThread.quitSafely();
-            }
-        }
-
-        synchronized void enqueue(int messageType, Object message) {
-            if (mSupportedMessageTypes.contains(messageType)) {
-                Message.obtain(mHandler, messageType, message).sendToTarget();
-            } else {
-                Log.e(TAG, "Unexpected message type: " + VmsMessageType.toString(messageType));
-            }
-        }
-
-        synchronized void clear() {
-            mSupportedMessageTypes.forEach(mHandler::removeMessages);
-        }
 
         @Override
-        public boolean handleMessage(Message msg) {
-            int messageType = msg.what;
-            VehiclePropValue vehicleProp = (VehiclePropValue) msg.obj;
-            if (DBG) Log.d(TAG, "Sending " + VmsMessageType.toString(messageType) + " message");
-            setPropertyValue(vehicleProp);
-            return true;
+        public void onPacketReceived(int providerId, VmsLayer layer, byte[] packet) {
+            if (DBG) Log.d(TAG, "Handling a data message for Layer: " + layer);
+            setPropertyValue(createDataMessage(layer, providerId, packet));
         }
-    }
+    };
 
     /**
      * Constructor used by {@link VehicleHal}
      */
     VmsHalService(Context context, VehicleHal vehicleHal) {
-        this(context, vehicleHal, SystemClock::uptimeMillis, (Build.IS_ENG || Build.IS_USERDEBUG));
+        this(context, vehicleHal, SystemClock::uptimeMillis, VmsHalService::initVmsClient,
+                Build.IS_DEBUGGABLE);
     }
 
     @VisibleForTesting
     VmsHalService(Context context, VehicleHal vehicleHal, Supplier<Long> getCoreId,
+            BiFunction<Handler, VmsClientCallback, VmsClient> initVmsClient,
             boolean propagatePropertyException) {
         mVehicleHal = vehicleHal;
+        mHandlerThread = new HandlerThread(TAG);
         mCoreId = (int) (getCoreId.get() % Integer.MAX_VALUE);
-        mMessageQueue = new MessageQueue();
+        mInitVmsClient = initVmsClient;
         mClientMetricsProperty = getClientMetricsProperty(context);
         mPropagatePropertyException = propagatePropertyException;
     }
@@ -229,21 +166,7 @@
      */
     @VisibleForTesting
     Handler getHandler() {
-        return mMessageQueue.mHandler;
-    }
-
-    /**
-     * Sets a reference to the {@link VmsClientManager} implementation for use by the HAL.
-     */
-    public void setClientManager(VmsClientManager clientManager) {
-        mClientManager = clientManager;
-    }
-
-    /**
-     * Sets a reference to the {@link IVmsSubscriberService} implementation for use by the HAL.
-     */
-    public void setVmsSubscriberService(IVmsSubscriberService service) {
-        mSubscriberService = service;
+        return mHandler;
     }
 
     @Override
@@ -251,7 +174,9 @@
             Collection<VehiclePropConfig> allProperties) {
         for (VehiclePropConfig p : allProperties) {
             if (p.prop == HAL_PROPERTY_ID) {
-                mIsSupported = true;
+                synchronized (mLock) {
+                    mIsSupported = true;
+                }
                 return Collections.singleton(p);
             }
         }
@@ -260,53 +185,52 @@
 
     @Override
     public void init() {
-        if (mIsSupported) {
-            Log.i(TAG, "Initializing VmsHalService VHAL property");
-            mVehicleHal.subscribeProperty(this, HAL_PROPERTY_ID);
-        } else {
-            Log.i(TAG, "VmsHalService VHAL property not supported");
-            return; // Do not continue initialization
+        synchronized (mLock) {
+            if (!mIsSupported) {
+                Log.i(TAG, "VmsHalService VHAL property not supported");
+                return; // Do not continue initialization
+            }
+            mHandlerThread.start();
+            mHandler = new Handler(mHandlerThread.getLooper());
+            connectVmsClient();
         }
 
-        mMessageQueue.init();
-        mMessageQueue.enqueue(VmsMessageType.START_SESSION,
-                createStartSessionMessage(mCoreId, UNKNOWN_CLIENT_ID));
+        Log.i(TAG, "Initializing VmsHalService VHAL property");
+        mVehicleHal.subscribeProperty(this, HAL_PROPERTY_ID);
+
+        mHandler.post(() ->
+                setPropertyValue(createStartSessionMessage(mCoreId, UNKNOWN_CLIENT_ID)));
     }
 
     @Override
     public void release() {
-        mMessageQueue.release();
-        mSubscriptionStateSequence = -1;
-        mAvailableLayersSequence = -1;
-
-        if (mIsSupported) {
-            if (DBG) Log.d(TAG, "Releasing VmsHalService VHAL property");
-            mVehicleHal.unsubscribeProperty(this, HAL_PROPERTY_ID);
-        } else {
-            return;
-        }
-
-        if (mSubscriberService != null) {
-            try {
-                mSubscriberService.removeVmsSubscriberToNotifications(mSubscriberClient);
-            } catch (RemoteException e) {
-                Log.e(TAG, "While removing subscriber callback", e);
+        synchronized (mLock) {
+            disconnectVmsClient();
+            mHandlerThread.quitSafely();
+            if (!mIsSupported) {
+                return;
             }
         }
+        if (DBG) {
+            Log.d(TAG, "Releasing VmsHalService VHAL property");
+        }
+        mVehicleHal.unsubscribeProperty(this, HAL_PROPERTY_ID);
     }
 
     @Override
     public void dump(PrintWriter writer) {
-        writer.println("*VMS HAL*");
-
-        writer.println("VmsProperty: " + (mIsSupported ? "supported" : "unsupported"));
-        writer.println("VmsPublisherService: "
-                + (mPublisherService != null ? "registered " : "unregistered"));
-        writer.println("mSubscriptionStateSequence: " + mSubscriptionStateSequence);
-
-        writer.println("VmsSubscriberService: "
-                + (mSubscriberService != null ? "registered" : "unregistered"));
-        writer.println("mAvailableLayersSequence: " + mAvailableLayersSequence);
+        synchronized (mLock) {
+            writer.println("*VMS HAL*");
+            writer.printf("VmsProperty: %s\n", mIsSupported ? "supported" : "unsupported");
+            if (mClient == null) {
+                writer.println("VmsClient: disconnected");
+                return;
+            }
+            writer.println("VmsClient: connected");
+            writer.printf("Subscriptions: %s\n", mSubscriptionHelper.getSubscriptions());
+            writer.printf("AvailableLayers: %s\n", mClient.getAvailableLayers());
+            writer.printf("SubscriptionState: %s\n", mClient.getSubscriptionState());
+        }
     }
 
     /**
@@ -332,12 +256,11 @@
             return;
         }
 
-        FileOutputStream fout = new FileOutputStream(fd);
-        try {
+        try (FileOutputStream fout = new FileOutputStream(fd)) {
             fout.write(toByteArray(vehicleProp.value.bytes));
             fout.flush();
         } catch (IOException e) {
-            Log.e(TAG, "Error writing metrics to output stream");
+            Log.e(TAG, "Error writing metrics to output stream", e);
         }
     }
 
@@ -348,7 +271,7 @@
      * hardware/interfaces/automotive/vehicle/2.0/types.hal
      */
     @Override
-    public void handleHalEvents(List<VehiclePropValue> values) {
+    public void onHalEvents(List<VehiclePropValue> values) {
         if (DBG) Log.d(TAG, "Handling a VMS property change");
         for (VehiclePropValue v : values) {
             ArrayList<Integer> vec = v.value.int32Values;
@@ -392,12 +315,56 @@
                     default:
                         Log.e(TAG, "Unexpected message type: " + messageType);
                 }
-            } catch (IndexOutOfBoundsException | RemoteException e) {
+            } catch (IndexOutOfBoundsException e) {
                 Log.e(TAG, "While handling " + VmsMessageType.toString(messageType), e);
             }
         }
     }
 
+    private void connectVmsClient() {
+        synchronized (mLock) {
+            mClient = mInitVmsClient.apply(mHandler, mClientCallback);
+        }
+    }
+
+    private void disconnectVmsClient() {
+        synchronized (mLock) {
+            if (mClient != null) {
+                try {
+                    mClient.unregister();
+                } catch (RemoteException e) {
+                    Log.wtf(TAG, "Local broker should not throw RemoteException", e);
+                }
+                mClient = null;
+            }
+        }
+    }
+
+    private static VmsClient initVmsClient(Handler handler, VmsClientCallback callback) {
+        VmsNewBrokerService brokerService = CarLocalServices.getService(VmsNewBrokerService.class);
+        if (brokerService == null) {
+            Log.e(TAG, "Broker service is not enabled");
+            return null;
+        }
+        VmsClient client = new VmsClient(brokerService, new HandlerExecutor(handler), callback,
+                /* legacyClient= */ true, /* exceptionHandler= */ ignored -> { });
+        try {
+            client.register();
+        } catch (RemoteException e) {
+            Log.wtf(TAG, "Local broker should not throw RemoteException", e);
+        }
+        return client;
+    }
+
+    private VmsClient getVmsClient() {
+        synchronized (mLock) {
+            if (mClient == null) {
+                throw new IllegalStateException("VmsClient is not connected");
+            }
+            return mClient;
+        }
+    }
+
     /**
      * SESSION_START message format:
      * <ul>
@@ -412,39 +379,13 @@
         Log.i(TAG, "Starting new session with coreId: " + coreId + " client: " + clientId);
 
         if (coreId != mCoreId) {
-            if (mClientManager != null) {
-                mClientManager.onHalDisconnected();
-            } else {
-                Log.w(TAG, "Client manager not registered");
-            }
-
-            // Drop all queued messages and client state
-            mMessageQueue.clear();
-            mSubscriptionStateSequence = -1;
-            mAvailableLayersSequence = -1;
-
+            // Reset VmsClient
+            disconnectVmsClient();
+            connectVmsClient();
             // Send acknowledgement message
             setPropertyValue(createStartSessionMessage(mCoreId, clientId));
         }
-
-        // Notify client manager of connection
-        if (mClientManager != null) {
-            mClientManager.onHalConnected(mPublisherClient, mSubscriberClient);
-        } else {
-            Log.w(TAG, "Client manager not registered");
-        }
-
-        if (mSubscriberService != null) {
-            // Publish layer availability to HAL clients (this triggers HAL client initialization)
-            try {
-                mSubscriberClient.onLayersAvailabilityChanged(
-                        mSubscriberService.getAvailableLayers());
-            } catch (RemoteException e) {
-                Log.e(TAG, "While publishing layer availability", e);
-            }
-        } else {
-            Log.w(TAG, "Subscriber connect callback not registered");
-        }
+        mClientCallback.onLayerAvailabilityChanged(getVmsClient().getAvailableLayers());
     }
 
     /**
@@ -458,15 +399,14 @@
      * <li>Payload
      * </ul>
      */
-    private void handleDataEvent(List<Integer> message, byte[] payload)
-            throws RemoteException {
+    private void handleDataEvent(List<Integer> message, byte[] payload) {
         VmsLayer vmsLayer = parseVmsLayerFromMessage(message);
         int publisherId = parsePublisherIdFromMessage(message);
         if (DBG) {
             Log.d(TAG,
                     "Handling a data event for Layer: " + vmsLayer + " Publisher: " + publisherId);
         }
-        mPublisherService.publish(mPublisherToken, vmsLayer, publisherId, payload);
+        getVmsClient().publishPacket(publisherId, vmsLayer, payload);
     }
 
     /**
@@ -478,10 +418,10 @@
      * <li>Layer version
      * </ul>
      */
-    private void handleSubscribeEvent(List<Integer> message) throws RemoteException {
+    private void handleSubscribeEvent(List<Integer> message) {
         VmsLayer vmsLayer = parseVmsLayerFromMessage(message);
         if (DBG) Log.d(TAG, "Handling a subscribe event for Layer: " + vmsLayer);
-        mSubscriberService.addVmsSubscriber(mSubscriberClient, vmsLayer);
+        mSubscriptionHelper.subscribe(vmsLayer);
     }
 
     /**
@@ -494,16 +434,14 @@
      * <li>Publisher ID
      * </ul>
      */
-    private void handleSubscribeToPublisherEvent(List<Integer> message)
-            throws RemoteException {
+    private void handleSubscribeToPublisherEvent(List<Integer> message) {
         VmsLayer vmsLayer = parseVmsLayerFromMessage(message);
         int publisherId = parsePublisherIdFromMessage(message);
         if (DBG) {
-            Log.d(TAG,
-                    "Handling a subscribe event for Layer: " + vmsLayer + " Publisher: "
-                            + publisherId);
+            Log.d(TAG, "Handling a subscribe event for Layer: " + vmsLayer
+                    + " Publisher: " + publisherId);
         }
-        mSubscriberService.addVmsSubscriberToPublisher(mSubscriberClient, vmsLayer, publisherId);
+        mSubscriptionHelper.subscribe(vmsLayer, publisherId);
     }
 
     /**
@@ -515,10 +453,10 @@
      * <li>Layer version
      * </ul>
      */
-    private void handleUnsubscribeEvent(List<Integer> message) throws RemoteException {
+    private void handleUnsubscribeEvent(List<Integer> message) {
         VmsLayer vmsLayer = parseVmsLayerFromMessage(message);
         if (DBG) Log.d(TAG, "Handling an unsubscribe event for Layer: " + vmsLayer);
-        mSubscriberService.removeVmsSubscriber(mSubscriberClient, vmsLayer);
+        mSubscriptionHelper.unsubscribe(vmsLayer);
     }
 
     /**
@@ -531,15 +469,18 @@
      * <li>Publisher ID
      * </ul>
      */
-    private void handleUnsubscribeFromPublisherEvent(List<Integer> message)
-            throws RemoteException {
+    private void handleUnsubscribeFromPublisherEvent(List<Integer> message) {
         VmsLayer vmsLayer = parseVmsLayerFromMessage(message);
         int publisherId = parsePublisherIdFromMessage(message);
         if (DBG) {
-            Log.d(TAG, "Handling an unsubscribe event for Layer: " + vmsLayer + " Publisher: "
-                    + publisherId);
+            Log.d(TAG, "Handling an unsubscribe event for Layer: " + vmsLayer
+                    + " Publisher: " + publisherId);
         }
-        mSubscriberService.removeVmsSubscriberToPublisher(mSubscriberClient, vmsLayer, publisherId);
+        mSubscriptionHelper.unsubscribe(vmsLayer, publisherId);
+    }
+
+    private void setSubscriptions(Set<VmsAssociatedLayer> subscriptions) {
+        getVmsClient().setSubscriptions(subscriptions);
     }
 
     /**
@@ -555,14 +496,15 @@
      * <li>Publisher ID
      * </ul>
      */
-    private void handlePublisherIdRequest(byte[] payload)
-            throws RemoteException {
-        if (DBG) Log.d(TAG, "Handling a publisher id request event");
+    private void handlePublisherIdRequest(byte[] payload) {
+        if (DBG) {
+            Log.d(TAG, "Handling a publisher id request event");
+        }
 
         VehiclePropValue vehicleProp = createVmsMessage(VmsMessageType.PUBLISHER_ID_RESPONSE);
-        // Publisher ID
-        vehicleProp.value.int32Values.add(mPublisherService.getPublisherId(payload));
 
+        // Publisher ID
+        vehicleProp.value.int32Values.add(getVmsClient().registerProvider(payload));
         setPropertyValue(vehicleProp);
     }
 
@@ -580,16 +522,17 @@
      * <li>Publisher info (bytes)
      * </ul>
      */
-    private void handlePublisherInfoRequest(List<Integer> message)
-            throws RemoteException {
+    private void handlePublisherInfoRequest(List<Integer> message) {
         if (DBG) Log.d(TAG, "Handling a publisher info request event");
         int publisherId = message.get(VmsPublisherInformationIntegerValuesIndex.PUBLISHER_ID);
 
         VehiclePropValue vehicleProp =
                 createVmsMessage(VmsMessageType.PUBLISHER_INFORMATION_RESPONSE);
-        // Publisher Info
-        appendBytes(vehicleProp.value.bytes, mSubscriberService.getPublisherInfo(publisherId));
 
+        // Publisher Info
+        byte[] publisherInfo = getVmsClient().getProviderDescription(publisherId);
+        appendBytes(vehicleProp.value.bytes,
+                publisherInfo != null ? publisherInfo : DEFAULT_PUBLISHER_INFO);
         setPropertyValue(vehicleProp);
     }
 
@@ -614,7 +557,7 @@
      * </ul>
      * </ul>
      */
-    private void handleOfferingEvent(List<Integer> message) throws RemoteException {
+    private void handleOfferingEvent(List<Integer> message) {
         // Publisher ID for OFFERING is stored at a different index than in other message types
         int publisherId = message.get(VmsOfferingMessageIntegerValuesIndex.PUBLISHER_ID);
         int numLayerDependencies =
@@ -645,10 +588,7 @@
                 offeredLayers.add(new VmsLayerDependency(offeredLayer, dependencies));
             }
         }
-
-        VmsLayersOffering offering = new VmsLayersOffering(offeredLayers, publisherId);
-        VmsOperationRecorder.get().setHalPublisherLayersOffering(offering);
-        mPublisherService.setLayersOffering(mPublisherToken, offering);
+        getVmsClient().setProviderOfferings(publisherId, offeredLayers);
     }
 
     /**
@@ -657,10 +597,9 @@
      * <li>Message type
      * </ul>
      */
-    private void handleAvailabilityRequestEvent() throws RemoteException {
-        setPropertyValue(
-                createAvailableLayersMessage(VmsMessageType.AVAILABILITY_RESPONSE,
-                        mSubscriberService.getAvailableLayers()));
+    private void handleAvailabilityRequestEvent() {
+        setPropertyValue(createAvailableLayersMessage(VmsMessageType.AVAILABILITY_RESPONSE,
+                getVmsClient().getAvailableLayers()));
     }
 
     /**
@@ -669,26 +608,27 @@
      * <li>Message type
      * </ul>
      */
-    private void handleSubscriptionsRequestEvent() throws RemoteException {
-        setPropertyValue(
-                createSubscriptionStateMessage(VmsMessageType.SUBSCRIPTIONS_RESPONSE,
-                        mPublisherService.getSubscriptions()));
+    private void handleSubscriptionsRequestEvent() {
+        setPropertyValue(createSubscriptionStateMessage(VmsMessageType.SUBSCRIPTIONS_RESPONSE,
+                getVmsClient().getSubscriptionState()));
     }
 
     private void setPropertyValue(VehiclePropValue vehicleProp) {
         int messageType = vehicleProp.value.int32Values.get(
                 VmsBaseMessageIntegerValuesIndex.MESSAGE_TYPE);
 
-        if (!mIsSupported) {
-            Log.w(TAG, "HAL unsupported while attempting to send "
-                    + VmsMessageType.toString(messageType));
-            return;
+        synchronized (mLock) {
+            if (!mIsSupported) {
+                Log.w(TAG, "HAL unsupported while attempting to send "
+                        + VmsMessageType.toString(messageType));
+                return;
+            }
         }
 
         try {
             mVehicleHal.set(vehicleProp);
         } catch (PropertyTimeoutException | RuntimeException e) {
-            Log.e(TAG, "While sending " + VmsMessageType.toString(messageType), e.getCause());
+            Log.e(TAG, "While sending " + VmsMessageType.toString(messageType), e);
             if (mPropagatePropertyException) {
                 throw new IllegalStateException(e);
             }
@@ -733,16 +673,18 @@
      * </ul>
      *
      * @param layer Layer for which message was published.
+     * @param publisherId Publisher of message
+     * @param payload Data message
      */
-    private static VehiclePropValue createDataMessage(VmsLayer layer, byte[] payload) {
+    private static VehiclePropValue createDataMessage(VmsLayer layer, int publisherId,
+            byte[] payload) {
         // Message type + layer
         VehiclePropValue vehicleProp = createVmsMessage(VmsMessageType.DATA);
         appendLayer(vehicleProp.value.int32Values, layer);
         List<Integer> message = vehicleProp.value.int32Values;
 
         // Publisher ID
-        // TODO(b/124130256): Set publisher ID of data message
-        message.add(0);
+        message.add(publisherId);
 
         // Payload
         appendBytes(vehicleProp.value.bytes, payload);
@@ -879,8 +821,8 @@
         message.add(layer.getVmsLayer().getType());
         message.add(layer.getVmsLayer().getSubtype());
         message.add(layer.getVmsLayer().getVersion());
-        message.add(layer.getPublisherIds().size());
-        message.addAll(layer.getPublisherIds());
+        message.add(layer.getProviderIds().size());
+        message.addAll(layer.getProviderIds());
     }
 
     private static void appendBytes(ArrayList<Byte> dst, byte[] src) {
diff --git a/service/src/com/android/car/pm/ActivityBlockingActivity.java b/service/src/com/android/car/pm/ActivityBlockingActivity.java
index a9f0d6a..db50f85 100644
--- a/service/src/com/android/car/pm/ActivityBlockingActivity.java
+++ b/service/src/com/android/car/pm/ActivityBlockingActivity.java
@@ -21,6 +21,8 @@
 import static com.android.car.pm.CarPackageManagerService.BLOCKING_INTENT_EXTRA_ROOT_ACTIVITY_NAME;
 
 import android.app.Activity;
+import android.app.ActivityManager;
+import android.app.IActivityManager;
 import android.car.Car;
 import android.car.content.pm.CarPackageManager;
 import android.car.drivingstate.CarUxRestrictions;
@@ -29,6 +31,7 @@
 import android.content.Intent;
 import android.os.Build;
 import android.os.Bundle;
+import android.os.RemoteException;
 import android.text.TextUtils;
 import android.util.Log;
 import android.view.View;
@@ -39,20 +42,25 @@
 import com.android.car.CarLog;
 import com.android.car.R;
 
+import java.util.List;
+
 /**
  * Default activity that will be launched when the current foreground activity is not allowed.
  * Additional information on blocked Activity should be passed as intent extras.
  */
 public class ActivityBlockingActivity extends Activity {
     private static final int INVALID_TASK_ID = -1;
+    private final Object mLock = new Object();
 
     private Car mCar;
     private CarUxRestrictionsManager mUxRManager;
+    private CarPackageManager mCarPackageManager;
 
     private Button mExitButton;
     private Button mToggleDebug;
 
     private int mBlockedTaskId;
+    private IActivityManager mAm;
 
     private final View.OnClickListener mOnExitButtonClickedListener =
             v -> {
@@ -78,6 +86,7 @@
         setContentView(R.layout.activity_blocking);
 
         mExitButton = findViewById(R.id.exit_button);
+        mAm = ActivityManager.getService();
 
         // Listen to the CarUxRestrictions so this blocking activity can be dismissed when the
         // restrictions are lifted.
@@ -88,6 +97,8 @@
                     if (!ready) {
                         return;
                     }
+                    mCarPackageManager = (CarPackageManager) car.getCarManager(
+                            Car.PACKAGE_SERVICE);
                     mUxRManager = (CarUxRestrictionsManager) car.getCarManager(
                             Car.CAR_UX_RESTRICTION_SERVICE);
                     // This activity would have been launched only in a restricted state.
@@ -111,6 +122,12 @@
         String blockedActivity = getIntent().getStringExtra(
                 BLOCKING_INTENT_EXTRA_BLOCKED_ACTIVITY_NAME);
         if (!TextUtils.isEmpty(blockedActivity)) {
+            if (isTopActivityBehindAbaDistractionOptimized()) {
+                Log.e(CarLog.TAG_AM, "Top activity is already DO, so finishing");
+                finish();
+                return;
+            }
+
             if (Log.isLoggable(CarLog.TAG_AM, Log.DEBUG)) {
                 Log.d(CarLog.TAG_AM, "Blocking activity " + blockedActivity);
             }
@@ -144,6 +161,58 @@
                 : getString(R.string.exit_button_go_back);
     }
 
+    /**
+     * It is possible that the stack info has changed between when the intent to launch this
+     * activity was initiated and when this activity is started. Check whether the activity behind
+     * the ABA is distraction optimized.
+     */
+    private boolean isTopActivityBehindAbaDistractionOptimized() {
+        List<ActivityManager.StackInfo> stackInfos;
+        try {
+            stackInfos = mAm.getAllStackInfos();
+        } catch (RemoteException e) {
+            Log.e(CarLog.TAG_AM, "Unable to get stack info from ActivityManager");
+            // assume that the state is still correct, the activity behind is not DO
+            return false;
+        }
+
+        ActivityManager.StackInfo topStackBehindAba = null;
+        for (ActivityManager.StackInfo stackInfo : stackInfos) {
+            if (stackInfo.displayId != getDisplayId()) {
+                // ignore stacks on other displays
+                continue;
+            }
+
+            if (getComponentName().equals(stackInfo.topActivity)) {
+                // ignore stack with the blocking activity
+                continue;
+            }
+
+            if (topStackBehindAba == null || topStackBehindAba.position < stackInfo.position) {
+                topStackBehindAba = stackInfo;
+            }
+        }
+
+        if (Log.isLoggable(CarLog.TAG_AM, Log.DEBUG)) {
+            Log.d(CarLog.TAG_AM, String.format("Top stack behind ABA is: %s", topStackBehindAba));
+        }
+
+        if (topStackBehindAba != null && topStackBehindAba.topActivity != null) {
+            boolean isDo = mCarPackageManager.isActivityDistractionOptimized(
+                    topStackBehindAba.topActivity.getPackageName(),
+                    topStackBehindAba.topActivity.getClassName());
+            if (Log.isLoggable(CarLog.TAG_AM, Log.DEBUG)) {
+                Log.d(CarLog.TAG_AM,
+                        String.format("Top activity (%s) is DO: %s", topStackBehindAba.topActivity,
+                                isDo));
+            }
+            return isDo;
+        }
+
+        // unknown top stack / activity, default to considering it non-DO
+        return false;
+    }
+
     private void displayDebugInfo() {
         String blockedActivity = getIntent().getStringExtra(
                 BLOCKING_INTENT_EXTRA_BLOCKED_ACTIVITY_NAME);
@@ -218,6 +287,7 @@
     @Override
     protected void onDestroy() {
         super.onDestroy();
+        mCar.disconnect();
         mUxRManager.unregisterListener();
         mToggleDebug.getViewTreeObserver().removeOnGlobalLayoutListener(mOnGlobalLayoutListener);
         mCar.disconnect();
@@ -247,18 +317,16 @@
     }
 
     private void handleRestartingTask() {
-        if (isFinishing()) {
-            return;
-        }
-
         // Lock on self to avoid restarting the same task twice.
-        synchronized (this) {
+        synchronized (mLock) {
+            if (isFinishing()) {
+                return;
+            }
+
             if (Log.isLoggable(CarLog.TAG_AM, Log.INFO)) {
                 Log.i(CarLog.TAG_AM, "Restarting task " + mBlockedTaskId);
             }
-            CarPackageManager carPm = (CarPackageManager)
-                    mCar.getCarManager(Car.PACKAGE_SERVICE);
-            carPm.restartTask(mBlockedTaskId);
+            mCarPackageManager.restartTask(mBlockedTaskId);
             finish();
         }
     }
diff --git a/service/src/com/android/car/pm/AppBlockingPolicyProxy.java b/service/src/com/android/car/pm/AppBlockingPolicyProxy.java
index 38b5a5a..22d4e42 100644
--- a/service/src/com/android/car/pm/AppBlockingPolicyProxy.java
+++ b/service/src/com/android/car/pm/AppBlockingPolicyProxy.java
@@ -38,9 +38,10 @@
     private final Context mContext;
     private final ServiceInfo mServiceInfo;
     private final ICarAppBlockingPolicySetterImpl mSetter;
+    private final Object mLock = new Object();
 
-    @GuardedBy("this")
-    private ICarAppBlockingPolicy mPolicyService = null;
+    @GuardedBy("mLock")
+    private ICarAppBlockingPolicy mPolicyService;
 
     /**
      * policy not set within this time after binding will be treated as failure and will be
@@ -48,10 +49,10 @@
      */
     private static final long TIMEOUT_MS = 5000;
     private static final int MAX_CRASH_RETRY = 2;
-    @GuardedBy("this")
-    private int mCrashCount = 0;
-    @GuardedBy("this")
-    private boolean mBound = false;
+    @GuardedBy("mLock")
+    private int mCrashCount;
+    @GuardedBy("mLock")
+    private boolean mBound;
 
     private final Handler mHandler;
     private final Runnable mTimeoutRunnable = new Runnable() {
@@ -81,14 +82,14 @@
         intent.setComponent(mServiceInfo.getComponentName());
         mContext.bindServiceAsUser(intent, this, Context.BIND_AUTO_CREATE | Context.BIND_IMPORTANT,
                 UserHandle.CURRENT_OR_SELF);
-        synchronized (this) {
+        synchronized (mLock) {
             mBound = true;
         }
         mHandler.postDelayed(mTimeoutRunnable, TIMEOUT_MS);
     }
 
     public void disconnect() {
-        synchronized (this) {
+        synchronized (mLock) {
             if (!mBound) {
                 return;
             }
@@ -107,7 +108,7 @@
     public void onServiceConnected(ComponentName name, IBinder service) {
         ICarAppBlockingPolicy policy = null;
         boolean failed = false;
-        synchronized (this) {
+        synchronized (mLock) {
             mPolicyService = ICarAppBlockingPolicy.Stub.asInterface(service);
             policy = mPolicyService;
             if (policy == null) {
@@ -120,7 +121,7 @@
             return;
         }
         try {
-            mPolicyService.setAppBlockingPolicySetter(mSetter);
+            policy.setAppBlockingPolicySetter(mSetter);
         } catch (RemoteException e) {
             // let retry handle this
         }
@@ -129,7 +130,7 @@
     @Override
     public void onServiceDisconnected(ComponentName name) {
         boolean failed = false;
-        synchronized (this) {
+        synchronized (mLock) {
             mCrashCount++;
             if (mCrashCount > MAX_CRASH_RETRY) {
                 mPolicyService = null;
diff --git a/service/src/com/android/car/pm/CarPackageManagerService.java b/service/src/com/android/car/pm/CarPackageManagerService.java
index 54d577a..bc2d253 100644
--- a/service/src/com/android/car/pm/CarPackageManagerService.java
+++ b/service/src/com/android/car/pm/CarPackageManagerService.java
@@ -20,6 +20,7 @@
 import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.app.ActivityManager.StackInfo;
+import android.app.PendingIntent;
 import android.car.Car;
 import android.car.content.pm.AppBlockingPackageInfo;
 import android.car.content.pm.CarAppBlockingPolicy;
@@ -28,7 +29,6 @@
 import android.car.content.pm.ICarPackageManager;
 import android.car.drivingstate.CarUxRestrictions;
 import android.car.drivingstate.ICarUxRestrictionsChangeListener;
-import android.car.userlib.CarUserManagerHelper;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.Context;
@@ -66,6 +66,7 @@
 import com.android.car.R;
 import com.android.car.SystemActivityMonitoringService;
 import com.android.car.SystemActivityMonitoringService.TopTaskInfoContainer;
+import com.android.car.user.CarUserService;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 
@@ -91,6 +92,7 @@
     private static final int LOG_SIZE = 20;
 
     private final Context mContext;
+    private final CarUserService mUserService;
     private final SystemActivityMonitoringService mSystemActivityMonitoringService;
     private final PackageManager mPackageManager;
     private final ActivityManager mActivityManager;
@@ -98,6 +100,7 @@
 
     private final HandlerThread mHandlerThread;
     private final PackageHandler mHandler;
+    private final Object mLock = new Object();
 
     // For dumpsys logging.
     private final LinkedList<String> mBlockedActivityLogs = new LinkedList<>();
@@ -112,14 +115,14 @@
      * Hold policy set from policy service or client.
      * Key: packageName of policy service
      */
-    @GuardedBy("this")
+    @GuardedBy("mLock")
     private final HashMap<String, ClientPolicy> mClientPolicies = new HashMap<>();
-    @GuardedBy("this")
+    @GuardedBy("mLock")
     private HashMap<String, AppBlockingPackageInfoWrapper> mActivityWhitelistMap = new HashMap<>();
-    @GuardedBy("this")
+    @GuardedBy("mLock")
     private LinkedList<AppBlockingPolicyProxy> mProxies;
 
-    @GuardedBy("this")
+    @GuardedBy("mLock")
     private final LinkedList<CarAppBlockingPolicy> mWaitingPolicies = new LinkedList<>();
 
     private final CarUxRestrictionsManagerService mCarUxRestrictionsService;
@@ -143,8 +146,6 @@
 
     private final PackageParsingEventReceiver mPackageParsingEventReceiver =
             new PackageParsingEventReceiver();
-    private final UserSwitchedEventReceiver mUserSwitchedEventReceiver =
-            new UserSwitchedEventReceiver();
 
     // To track if the packages have been parsed for building white/black lists. If we haven't had
     // received any intents (boot complete or package changed), then the white list is null leading
@@ -187,8 +188,9 @@
     public CarPackageManagerService(Context context,
             CarUxRestrictionsManagerService uxRestrictionsService,
             SystemActivityMonitoringService systemActivityMonitoringService,
-            CarUserManagerHelper carUserManagerHelper) {
+            CarUserService userService) {
         mContext = context;
+        mUserService = userService;
         mCarUxRestrictionsService = uxRestrictionsService;
         mSystemActivityMonitoringService = systemActivityMonitoringService;
         mPackageManager = mContext.getPackageManager();
@@ -204,7 +206,7 @@
         mAllowedAppInstallSources = Arrays.asList(
                 res.getStringArray(R.array.allowedAppInstallSources));
         mVendorServiceController = new VendorServiceController(
-                mContext, mHandler.getLooper(), carUserManagerHelper);
+                mContext, mHandler.getLooper());
     }
 
 
@@ -240,17 +242,17 @@
             throw new IllegalArgumentException(
                     "Cannot set both FLAG_SET_POLICY_ADD and FLAG_SET_POLICY_REMOVE flag");
         }
-        synchronized (this) {
+        synchronized (mLock) {
             if ((flags & CarPackageManager.FLAG_SET_POLICY_WAIT_FOR_CHANGE) != 0) {
                 mWaitingPolicies.add(policy);
             }
         }
         mHandler.requestUpdatingPolicy(packageName, policy, flags);
         if ((flags & CarPackageManager.FLAG_SET_POLICY_WAIT_FOR_CHANGE) != 0) {
-            synchronized (this) {
+            synchronized (mLock) {
                 try {
                     while (mWaitingPolicies.contains(policy)) {
-                        wait();
+                        mLock.wait();
                     }
                 } catch (InterruptedException e) {
                     // Pass it over binder call
@@ -264,7 +266,7 @@
     @Override
     public boolean isActivityDistractionOptimized(String packageName, String className) {
         assertPackageAndClassName(packageName, className);
-        synchronized (this) {
+        synchronized (mLock) {
             if (DBG_POLICY_CHECK) {
                 Log.i(CarLog.TAG_PACKAGE, "isActivityDistractionOptimized"
                         + dumpPoliciesLocked(false));
@@ -278,11 +280,20 @@
     }
 
     @Override
+    public boolean isPendingIntentDistractionOptimized(PendingIntent pendingIntent) {
+        ResolveInfo info = mPackageManager.resolveActivity(
+                pendingIntent.getIntent(), PackageManager.MATCH_DEFAULT_ONLY);
+        if (info == null) return false;
+        ActivityInfo activityInfo = info.activityInfo;
+        return isActivityDistractionOptimized(activityInfo.packageName, activityInfo.name);
+    }
+
+    @Override
     public boolean isServiceDistractionOptimized(String packageName, String className) {
         if (packageName == null) {
             throw new IllegalArgumentException("Package name null");
         }
-        synchronized (this) {
+        synchronized (mLock) {
             if (DBG_POLICY_CHECK) {
                 Log.i(CarLog.TAG_PACKAGE, "isServiceDistractionOptimized"
                         + dumpPoliciesLocked(false));
@@ -331,7 +342,7 @@
         }
     }
 
-    @GuardedBy("this")
+    @GuardedBy("mLock")
     private AppBlockingPackageInfo searchFromBlacklistsLocked(String packageName) {
         for (ClientPolicy policy : mClientPolicies.values()) {
             AppBlockingPackageInfoWrapper wrapper = policy.blacklistsMap.get(packageName);
@@ -342,7 +353,7 @@
         return null;
     }
 
-    @GuardedBy("this")
+    @GuardedBy("mLock")
     private AppBlockingPackageInfo searchFromWhitelistsLocked(String packageName) {
         for (ClientPolicy policy : mClientPolicies.values()) {
             AppBlockingPackageInfoWrapper wrapper = policy.whitelistsMap.get(packageName);
@@ -354,7 +365,7 @@
         return (wrapper != null) ? wrapper.info : null;
     }
 
-    @GuardedBy("this")
+    @GuardedBy("mLock")
     private boolean isActivityInWhitelistsLocked(String packageName, String className) {
         for (ClientPolicy policy : mClientPolicies.values()) {
             if (isActivityInMapAndMatching(policy.whitelistsMap, packageName, className)) {
@@ -378,19 +389,22 @@
 
     @Override
     public void init() {
-        synchronized (this) {
+        synchronized (mLock) {
             mHandler.requestInit();
         }
     }
 
     @Override
     public void release() {
-        synchronized (this) {
+        synchronized (mLock) {
             mHandler.requestRelease();
             // wait for release do be done. This guarantees that init is done.
             try {
-                wait();
+                mLock.wait();
             } catch (InterruptedException e) {
+                Log.e(CarLog.TAG_PACKAGE,
+                        "Interrupted wait during release");
+                Thread.currentThread().interrupt();
             }
             mHasParsedPackages = false;
             mActivityWhitelistMap.clear();
@@ -402,10 +416,10 @@
                 mProxies.clear();
             }
             mWaitingPolicies.clear();
-            notifyAll();
+            mLock.notifyAll();
         }
         mContext.unregisterReceiver(mPackageParsingEventReceiver);
-        mContext.unregisterReceiver(mUserSwitchedEventReceiver);
+        mUserService.removeUserCallback(mUserCallback);
         mSystemActivityMonitoringService.registerActivityLaunchListener(null);
         for (int i = 0; i < mUxRestrictionsListeners.size(); i++) {
             UxRestrictionsListener listener = mUxRestrictionsListeners.valueAt(i);
@@ -413,12 +427,24 @@
         }
     }
 
+    private final CarUserService.UserCallback mUserCallback = new CarUserService.UserCallback() {
+
+        @Override
+        public void onUserLockChanged(int userId, boolean unlocked) {
+            // Do Nothing
+        }
+
+        @Override
+        public void onSwitchUser(int userId) {
+            mHandler.requestParsingInstalledPkgs(0);
+        }
+
+    };
+
     // run from HandlerThread
     private void doHandleInit() {
         startAppBlockingPolicies();
-        IntentFilter intent = new IntentFilter();
-        intent.addAction(Intent.ACTION_USER_SWITCHED);
-        mContext.registerReceiver(mUserSwitchedEventReceiver, intent);
+        mUserService.addUserCallback(mUserCallback);
         IntentFilter pkgParseIntent = new IntentFilter();
         for (String action : mPackageManagerActions) {
             pkgParseIntent.addAction(action);
@@ -449,7 +475,7 @@
     private void doParseInstalledPackages() {
         int userId = mActivityManager.getCurrentUser();
         generateActivityWhitelistMap(userId);
-        synchronized (this) {
+        synchronized (mLock) {
             mHasParsedPackages = true;
         }
         // Once the activity launch listener is registered we attempt to block any non-whitelisted
@@ -459,9 +485,11 @@
         blockTopActivitiesIfNecessary();
     }
 
-    private synchronized void doHandleRelease() {
-        mVendorServiceController.release();
-        notifyAll();
+    private void doHandleRelease() {
+        synchronized (mLock) {
+            mVendorServiceController.release();
+            mLock.notifyAll();
+        }
     }
 
     private void doUpdatePolicy(String packageName, CarAppBlockingPolicy policy, int flags) {
@@ -471,7 +499,7 @@
         }
         AppBlockingPackageInfoWrapper[] blacklistWrapper = verifyList(policy.blacklists);
         AppBlockingPackageInfoWrapper[] whitelistWrapper = verifyList(policy.whitelists);
-        synchronized (this) {
+        synchronized (mLock) {
             ClientPolicy clientPolicy = mClientPolicies.get(packageName);
             if (clientPolicy == null) {
                 clientPolicy = new ClientPolicy();
@@ -489,7 +517,7 @@
             }
             if ((flags & CarPackageManager.FLAG_SET_POLICY_WAIT_FOR_CHANGE) != 0) {
                 mWaitingPolicies.remove(policy);
-                notifyAll();
+                mLock.notifyAll();
             }
             if (DBG_POLICY_SET) {
                 Log.i(CarLog.TAG_PACKAGE, "policy set:" + dumpPoliciesLocked(false));
@@ -598,7 +626,7 @@
                 activityWhitelist.put(packageName, userWhitelistedPackages.get(packageName));
             }
         }
-        synchronized (this) {
+        synchronized (mLock) {
             mActivityWhitelistMap.clear();
             mActivityWhitelistMap.putAll(activityWhitelist);
         }
@@ -662,11 +690,10 @@
             Map<String, Set<String>> configWhitelist, Map<String, Set<String>> configBlacklist) {
         HashMap<String, AppBlockingPackageInfoWrapper> activityWhitelist = new HashMap<>();
 
-        List<PackageInfo> packages = mPackageManager.getInstalledPackagesAsUser(
-                PackageManager.GET_SIGNATURES | PackageManager.GET_ACTIVITIES
-                        | PackageManager.MATCH_DIRECT_BOOT_AWARE
-                        | PackageManager.MATCH_DIRECT_BOOT_UNAWARE,
-                userId);
+        List<PackageInfo> packages = mPackageManager
+                .getInstalledPackagesAsUser(PackageManager.GET_SIGNATURES
+                        | PackageManager.GET_ACTIVITIES | PackageManager.MATCH_DIRECT_BOOT_AWARE
+                        | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, userId);
         for (PackageInfo info : packages) {
             if (info.applicationInfo == null) {
                 continue;
@@ -743,9 +770,8 @@
                     // Some of the activities in this app are Distraction Optimized.
                     if (DBG_POLICY_CHECK) {
                         for (String activity : doActivities) {
-                            Log.d(CarLog.TAG_PACKAGE,
-                                    "adding " + activity + " from " + info.packageName
-                                            + " to whitelist");
+                            Log.d(CarLog.TAG_PACKAGE, "adding " + activity + " from "
+                                    + info.packageName + " to whitelist");
                         }
                     }
                     activities.addAll(Arrays.asList(doActivities));
@@ -867,7 +893,7 @@
                 proxies.add(proxy);
             }
         }
-        synchronized (this) {
+        synchronized (mLock) {
             mProxies = proxies;
         }
     }
@@ -883,7 +909,7 @@
 
     private void doHandlePolicyConnection(AppBlockingPolicyProxy proxy,
             CarAppBlockingPolicy policy) {
-        synchronized (this) {
+        synchronized (mLock) {
             if (mProxies == null) {
                 proxy.disconnect();
                 return;
@@ -908,7 +934,7 @@
 
     @Override
     public void dump(PrintWriter writer) {
-        synchronized (this) {
+        synchronized (mLock) {
             writer.println("*CarPackageManagerService*");
             writer.println("mEnableActivityBlocking:" + mEnableActivityBlocking);
             writer.println("mHasParsedPackages:" + mHasParsedPackages);
@@ -926,7 +952,7 @@
         }
     }
 
-    @GuardedBy("this")
+    @GuardedBy("mLock")
     private String dumpPoliciesLocked(boolean dumpAll) {
         StringBuilder sb = new StringBuilder();
         if (dumpAll) {
@@ -1038,7 +1064,7 @@
         if (allowed) {
             return;
         }
-        synchronized (this) {
+        synchronized (mLock) {
             if (!mEnableActivityBlocking) {
                 Log.d(CarLog.TAG_PACKAGE, "Current activity " + topTask.topActivity +
                         " not allowed, blocking disabled. Number of tasks in stack:"
@@ -1120,17 +1146,18 @@
      * engineering builds for development convenience.
      */
     @Override
-    public synchronized void setEnableActivityBlocking(boolean enable) {
+    public void setEnableActivityBlocking(boolean enable) {
         if (!isDebugBuild()) {
             Log.e(CarLog.TAG_PACKAGE, "Cannot enable/disable activity blocking");
             return;
         }
+
         // Check if the caller has the same signature as that of the car service.
         if (mPackageManager.checkSignatures(Process.myUid(), Binder.getCallingUid())
                 != PackageManager.SIGNATURE_MATCH) {
             throw new SecurityException(
                     "Caller " + mPackageManager.getNameForUid(Binder.getCallingUid())
-                            + " does not have the right signature");
+                    + " does not have the right signature");
         }
         mCarUxRestrictionsService.setUxRChangeBroadcastEnabled(enable);
     }
@@ -1187,7 +1214,6 @@
         }
 
         private void requestRelease() {
-            removeMessages(MSG_INIT);
             removeMessages(MSG_UPDATE_POLICY);
             Message msg = obtainMessage(MSG_RELEASE);
             sendMessage(msg);
@@ -1335,7 +1361,7 @@
      * checking if the foreground Activity should be blocked.
      */
     private class UxRestrictionsListener extends ICarUxRestrictionsChangeListener.Stub {
-        @GuardedBy("this")
+        @GuardedBy("mLock")
         @Nullable
         private CarUxRestrictions mCurrentUxRestrictions;
         private final CarUxRestrictionsManagerService uxRestrictionsService;
@@ -1358,7 +1384,7 @@
                 return;
             }
 
-            synchronized (this) {
+            synchronized (mLock) {
                 mCurrentUxRestrictions = new CarUxRestrictions(restrictions);
             }
             checkIfTopActivityNeedsBlocking();
@@ -1366,7 +1392,7 @@
 
         private void checkIfTopActivityNeedsBlocking() {
             boolean shouldCheck = false;
-            synchronized (this) {
+            synchronized (mLock) {
                 if (mCurrentUxRestrictions != null
                         && mCurrentUxRestrictions.isRequiresDistractionOptimization()) {
                     shouldCheck = true;
@@ -1381,14 +1407,17 @@
             }
         }
 
-        private synchronized boolean isRestricted() {
+        private boolean isRestricted() {
             // if current restrictions is null, try querying the service, once.
-            if (mCurrentUxRestrictions == null) {
-                mCurrentUxRestrictions = uxRestrictionsService.getCurrentUxRestrictions();
+            synchronized (mLock) {
+                if (mCurrentUxRestrictions == null) {
+                    mCurrentUxRestrictions = uxRestrictionsService.getCurrentUxRestrictions();
+                }
+                if (mCurrentUxRestrictions != null) {
+                    return mCurrentUxRestrictions.isRequiresDistractionOptimization();
+                }
             }
-            if (mCurrentUxRestrictions != null) {
-                return mCurrentUxRestrictions.isRequiresDistractionOptimization();
-            }
+
             // If restriction information is still not available (could happen during bootup),
             // return not restricted.  This maintains parity with previous implementation but needs
             // a revisit as we test more.
@@ -1397,21 +1426,6 @@
     }
 
     /**
-     * Listens to the Boot intent to initiate parsing installed packages.
-     */
-    private class UserSwitchedEventReceiver extends BroadcastReceiver {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            if (intent == null || intent.getAction() == null) {
-                return;
-            }
-            if (Intent.ACTION_USER_SWITCHED.equals(intent.getAction())) {
-                mHandler.requestParsingInstalledPkgs(0);
-            }
-        }
-    }
-
-    /**
      * Listens to the package install/uninstall events to know when to initiate parsing
      * installed packages.
      */
diff --git a/service/src/com/android/car/pm/TEST_MAPPING b/service/src/com/android/car/pm/TEST_MAPPING
new file mode 100644
index 0000000..d37fe3d
--- /dev/null
+++ b/service/src/com/android/car/pm/TEST_MAPPING
@@ -0,0 +1,34 @@
+{
+  "auto-postsubmit": [
+    {
+      "name": "CarServiceTest",
+      "options" : [
+        {
+          "include-filter": "com.android.car.pm.CarPackageManagerServiceTest"
+        },
+        {
+          "include-filter": "com.android.car.CarPackageManagerTest"
+        }
+      ]
+    },
+    {
+      "name": "CarServiceUnitTest",
+      "options" : [
+        {
+          "include-filter": "com.android.car.pm.VendorServiceControllerTest"
+        },
+        {
+          "include-filter": "com.android.car.pm.VendorServiceInfoTest"
+        }
+      ]
+    },
+    {
+      "name": "AndroidCarApiTest",
+      "options": [
+        {
+          "include-filter": "android.car.apitest.CarPackageManagerTest"
+        }
+      ]
+    }
+  ]
+}
\ No newline at end of file
diff --git a/service/src/com/android/car/pm/VendorServiceController.java b/service/src/com/android/car/pm/VendorServiceController.java
index b20dc89..a14a2e6 100644
--- a/service/src/com/android/car/pm/VendorServiceController.java
+++ b/service/src/com/android/car/pm/VendorServiceController.java
@@ -18,7 +18,7 @@
 
 import static android.content.Context.BIND_AUTO_CREATE;
 
-import android.car.userlib.CarUserManagerHelper;
+import android.app.ActivityManager;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -64,15 +64,12 @@
     private final Context mContext;
     private final UserManager mUserManager;
     private final Handler mHandler;
-    private final CarUserManagerHelper mUserManagerHelper;
     private CarUserService mCarUserService;
 
 
-    VendorServiceController(Context context, Looper looper,
-            CarUserManagerHelper userManagerHelper) {
+    VendorServiceController(Context context, Looper looper) {
         mContext = context;
         mUserManager = context.getSystemService(UserManager.class);
-        mUserManagerHelper = userManagerHelper;
         mHandler = new Handler(looper) {
             @Override
             public void handleMessage(Message msg) {
@@ -124,7 +121,7 @@
 
     private void doSwitchUser(int userId) {
         // Stop all services which which do not run under foreground or system user.
-        final int fgUser = mUserManagerHelper.getCurrentForegroundUserId();
+        final int fgUser = ActivityManager.getCurrentUser();
         if (fgUser != userId) {
             Log.w(CarLog.TAG_PACKAGE, "Received userSwitch event for user " + userId
                     + " while current foreground user is " + fgUser + "."
@@ -147,7 +144,7 @@
     }
 
     private void doUserLockChanged(int userId, boolean unlocked) {
-        final int currentUserId = mUserManagerHelper.getCurrentForegroundUserId();
+        final int currentUserId = ActivityManager.getCurrentUser();
 
         if (DBG) {
             Log.i(CarLog.TAG_PACKAGE, "onUserLockedChanged, user: " + userId
@@ -180,7 +177,7 @@
     }
 
     private void startOrBindServicesIfNeeded() {
-        int userId = mUserManagerHelper.getCurrentForegroundUserId();
+        int userId = ActivityManager.getCurrentUser();
         startOrBindServicesForUser(UserHandle.SYSTEM);
         if (userId > 0) {
             startOrBindServicesForUser(UserHandle.of(userId));
@@ -220,8 +217,8 @@
     private VendorServiceConnection getOrCreateConnection(ConnectionKey key) {
         VendorServiceConnection connection = mConnections.get(key);
         if (connection == null) {
-            connection = new VendorServiceConnection(mContext, mHandler, mUserManagerHelper,
-                    key.mVendorServiceInfo, key.mUserHandle);
+            connection = new VendorServiceConnection(mContext, mHandler, key.mVendorServiceInfo,
+                    key.mUserHandle);
             mConnections.put(key, connection);
         }
 
@@ -266,14 +263,11 @@
         private final UserHandle mUser;
         private final Handler mHandler;
         private final Handler mFailureHandler;
-        private final CarUserManagerHelper mUserManagerHelper;
 
         VendorServiceConnection(Context context, Handler handler,
-                CarUserManagerHelper userManagerHelper, VendorServiceInfo vendorServiceInfo,
-                UserHandle user) {
+                VendorServiceInfo vendorServiceInfo, UserHandle user) {
             mContext = context;
             mHandler = handler;
-            mUserManagerHelper = userManagerHelper;
             mVendorServiceInfo = vendorServiceInfo;
             mUser = user;
 
@@ -351,7 +345,7 @@
                 return;
             }
 
-            if (UserHandle.of(mUserManagerHelper.getCurrentForegroundUserId()).equals(mUser)
+            if (UserHandle.of(ActivityManager.getCurrentUser()).equals(mUser)
                     || UserHandle.SYSTEM.equals(mUser)) {
                 mFailureHandler.sendMessageDelayed(
                         mFailureHandler.obtainMessage(MSG_REBIND), REBIND_DELAY_MS);
diff --git a/service/src/com/android/car/stats/CarStatsService.java b/service/src/com/android/car/stats/CarStatsService.java
index d74fb8c..1e25a62 100644
--- a/service/src/com/android/car/stats/CarStatsService.java
+++ b/service/src/com/android/car/stats/CarStatsService.java
@@ -16,22 +16,21 @@
 
 package com.android.car.stats;
 
-import android.Manifest;
+import android.app.StatsManager;
+import android.app.StatsManager.PullAtomMetadata;
 import android.content.Context;
 import android.content.pm.PackageManager;
-import android.os.StatsLogEventWrapper;
-import android.os.SystemClock;
 import android.util.ArrayMap;
 import android.util.Log;
-import android.util.StatsLog;
+import android.util.StatsEvent;
 
+import com.android.car.CarStatsLog;
 import com.android.car.stats.VmsClientLogger.ConnectionState;
 import com.android.internal.annotations.GuardedBy;
-import com.android.internal.car.ICarStatsService;
+import com.android.internal.os.BackgroundThread;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
-import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Comparator;
 import java.util.List;
@@ -41,11 +40,11 @@
 import java.util.function.Function;
 
 /**
- * Implementation of {@link ICarStatsService}, for reporting pulled atoms via statsd.
+ * Registers pulled atoms with statsd via StatsManager.
  *
  * Also implements collection and dumpsys reporting of atoms in CSV format.
  */
-public class CarStatsService extends ICarStatsService.Stub {
+public class CarStatsService {
     private static final boolean DEBUG = false;
     private static final String TAG = "CarStatsService";
     private static final String VMS_CONNECTION_STATS_DUMPSYS_HEADER =
@@ -82,6 +81,7 @@
 
     private final Context mContext;
     private final PackageManager mPackageManager;
+    private final StatsManager mStatsManager;
 
     @GuardedBy("mVmsClientStats")
     private final Map<Integer, VmsClientLogger> mVmsClientStats = new ArrayMap<>();
@@ -89,6 +89,22 @@
     public CarStatsService(Context context) {
         mContext = context;
         mPackageManager = context.getPackageManager();
+        mStatsManager = (StatsManager) mContext.getSystemService(Context.STATS_MANAGER);
+    }
+
+    /**
+     * Registers VmsClientStats puller with StatsManager.
+     */
+    public void init() {
+        PullAtomMetadata metadata = new PullAtomMetadata.Builder()
+                .setAdditiveFields(new int[] {5, 6, 7, 8, 9, 10})
+                .build();
+        mStatsManager.registerPullAtomCallback(
+                CarStatsLog.VMS_CLIENT_STATS,
+                metadata,
+                BackgroundThread.getExecutor(),
+                (atomTag, data) -> pullVmsClientStats(atomTag, data)
+        );
     }
 
     /**
@@ -108,7 +124,6 @@
         }
     }
 
-    @Override
     public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
         List<String> flags = Arrays.asList(args);
         if (args.length == 0 || flags.contains("--vms-client")) {
@@ -116,21 +131,6 @@
         }
     }
 
-    @Override
-    public StatsLogEventWrapper[] pullData(int tagId) {
-        mContext.enforceCallingPermission(Manifest.permission.DUMP, null);
-        if (tagId != StatsLog.VMS_CLIENT_STATS) {
-            Log.w(TAG, "Unexpected tagId: " + tagId);
-            return null;
-        }
-
-        List<StatsLogEventWrapper> ret = new ArrayList<>();
-        long elapsedNanos = SystemClock.elapsedRealtimeNanos();
-        long wallClockNanos = SystemClock.currentTimeMicro() * 1000L;
-        pullVmsClientStats(tagId, elapsedNanos, wallClockNanos, ret);
-        return ret.toArray(new StatsLogEventWrapper[0]);
-    }
-
     private void dumpVmsStats(PrintWriter writer) {
         synchronized (mVmsClientStats) {
             writer.println(VMS_CONNECTION_STATS_DUMPSYS_HEADER);
@@ -149,25 +149,31 @@
         }
     }
 
-    private void pullVmsClientStats(int tagId, long elapsedNanos, long wallClockNanos,
-            List<StatsLogEventWrapper> pulledData) {
+    private int pullVmsClientStats(int atomTag, List<StatsEvent> pulledData) {
+        if (atomTag != CarStatsLog.VMS_CLIENT_STATS) {
+            Log.w(TAG, "Unexpected atom tag: " + atomTag);
+            return StatsManager.PULL_SKIP;
+        }
+
         dumpVmsClientStats((entry) -> {
-            StatsLogEventWrapper e =
-                    new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos);
-            e.writeInt(entry.getUid());
+            StatsEvent e = StatsEvent.newBuilder()
+                    .setAtomId(atomTag)
+                    .writeInt(entry.getUid())
 
-            e.writeInt(entry.getLayerType());
-            e.writeInt(entry.getLayerChannel());
-            e.writeInt(entry.getLayerVersion());
+                    .writeInt(entry.getLayerType())
+                    .writeInt(entry.getLayerChannel())
+                    .writeInt(entry.getLayerVersion())
 
-            e.writeLong(entry.getTxBytes());
-            e.writeLong(entry.getTxPackets());
-            e.writeLong(entry.getRxBytes());
-            e.writeLong(entry.getRxPackets());
-            e.writeLong(entry.getDroppedBytes());
-            e.writeLong(entry.getDroppedPackets());
+                    .writeLong(entry.getTxBytes())
+                    .writeLong(entry.getTxPackets())
+                    .writeLong(entry.getRxBytes())
+                    .writeLong(entry.getRxPackets())
+                    .writeLong(entry.getDroppedBytes())
+                    .writeLong(entry.getDroppedPackets())
+                    .build();
             pulledData.add(e);
         });
+        return StatsManager.PULL_SUCCESS;
     }
 
     private void dumpVmsClientStats(Consumer<VmsClientStats> dumpFn) {
diff --git a/service/src/com/android/car/stats/VmsClientLogger.java b/service/src/com/android/car/stats/VmsClientLogger.java
index 948db05..39cade1 100644
--- a/service/src/com/android/car/stats/VmsClientLogger.java
+++ b/service/src/com/android/car/stats/VmsClientLogger.java
@@ -19,8 +19,8 @@
 import android.annotation.Nullable;
 import android.car.vms.VmsLayer;
 import android.util.ArrayMap;
-import android.util.StatsLog;
 
+import com.android.car.CarStatsLog;
 import com.android.internal.annotations.GuardedBy;
 
 import java.util.Collection;
@@ -38,19 +38,19 @@
     public static class ConnectionState {
         // Attempting to connect to the client
         public static final int CONNECTING =
-                StatsLog.VMS_CLIENT_CONNECTION_STATE_CHANGED__STATE__CONNECTING;
+                CarStatsLog.VMS_CLIENT_CONNECTION_STATE_CHANGED__STATE__CONNECTING;
         // Client connection established
         public static final int CONNECTED =
-                StatsLog.VMS_CLIENT_CONNECTION_STATE_CHANGED__STATE__CONNECTED;
+                CarStatsLog.VMS_CLIENT_CONNECTION_STATE_CHANGED__STATE__CONNECTED;
         // Client connection closed unexpectedly
         public static final int DISCONNECTED =
-                StatsLog.VMS_CLIENT_CONNECTION_STATE_CHANGED__STATE__DISCONNECTED;
+                CarStatsLog.VMS_CLIENT_CONNECTION_STATE_CHANGED__STATE__DISCONNECTED;
         // Client connection closed by VMS
         public static final int TERMINATED =
-                StatsLog.VMS_CLIENT_CONNECTION_STATE_CHANGED__STATE__TERMINATED;
+                CarStatsLog.VMS_CLIENT_CONNECTION_STATE_CHANGED__STATE__TERMINATED;
         // Error establishing the client connection
         public static final int CONNECTION_ERROR =
-                StatsLog.VMS_CLIENT_CONNECTION_STATE_CHANGED__STATE__CONNECTION_ERROR;
+                CarStatsLog.VMS_CLIENT_CONNECTION_STATE_CHANGED__STATE__CONNECTION_ERROR;
     }
 
     private final Object mLock = new Object();
@@ -83,8 +83,8 @@
      * @param connectionState New connection state
      */
     public void logConnectionState(int connectionState) {
-        StatsLog.write(StatsLog.VMS_CLIENT_CONNECTION_STATE_CHANGED,
-                mUid, mPackageName, connectionState);
+        CarStatsLog.write(CarStatsLog.VMS_CLIENT_CONNECTION_STATE_CHANGED,
+                mUid, connectionState);
 
         AtomicLong counter;
         synchronized (mLock) {
diff --git a/service/src/com/android/car/storagemonitoring/IoStatsTracker.java b/service/src/com/android/car/storagemonitoring/IoStatsTracker.java
index 724cd08..fee1bbf 100644
--- a/service/src/com/android/car/storagemonitoring/IoStatsTracker.java
+++ b/service/src/com/android/car/storagemonitoring/IoStatsTracker.java
@@ -13,24 +13,31 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 package com.android.car.storagemonitoring;
 
 import android.car.storagemonitoring.IoStatsEntry;
 import android.car.storagemonitoring.UidIoRecord;
 import android.util.SparseArray;
+
+import androidx.annotation.GuardedBy;
+
 import com.android.car.SparseArrayStream;
 import com.android.car.procfsinspector.ProcessInfo;
 import com.android.car.systeminterface.SystemStateInterface;
+
 import java.util.List;
 import java.util.Optional;
 
 public class IoStatsTracker {
+
+    // NOTE: this class is not thread safe
     private abstract class Lazy<T> {
         protected Optional<T> mLazy = Optional.empty();
 
         protected abstract T supply();
 
-        public synchronized T get() {
+        public T get() {
             if (!mLazy.isPresent()) {
                 mLazy = Optional.of(supply());
             }
@@ -38,9 +45,12 @@
         }
     }
 
+    private final Object mLock = new Object();
     private final long mSampleWindowMs;
     private final SystemStateInterface mSystemStateInterface;
+    @GuardedBy("mLock")
     private SparseArray<IoStatsEntry> mTotal;
+    @GuardedBy("mLock")
     private SparseArray<IoStatsEntry> mCurrentSample;
 
     public IoStatsTracker(List<IoStatsEntry> initialValue,
@@ -52,7 +62,10 @@
         mSystemStateInterface = systemStateInterface;
     }
 
-    public synchronized void update(SparseArray<UidIoRecord> newMetrics) {
+    /**
+     * Updates the tracker information with new metrics.
+     */
+    public void update(SparseArray<UidIoRecord> newMetrics) {
         final Lazy<List<ProcessInfo>> processTable = new Lazy<List<ProcessInfo>>() {
             @Override
             protected List<ProcessInfo> supply() {
@@ -63,56 +76,68 @@
         SparseArray<IoStatsEntry> newSample = new SparseArray<>();
         SparseArray<IoStatsEntry> newTotal = new SparseArray<>();
 
-        // prepare the new values
-        SparseArrayStream.valueStream(newMetrics).forEach( newRecord -> {
-            final int uid = newRecord.uid;
-            final IoStatsEntry oldRecord = mTotal.get(uid);
+        synchronized (mLock) {
+            // prepare the new values
+            SparseArrayStream.valueStream(newMetrics).forEach(newRecord -> {
+                final int uid = newRecord.uid;
+                final IoStatsEntry oldRecord = mTotal.get(uid);
 
-            IoStatsEntry newStats = null;
+                IoStatsEntry newStats = null;
 
-            if (oldRecord == null) {
-                // this user id has just showed up, so just add it to the current sample
-                // and its runtime is the size of our sample window
-                newStats = new IoStatsEntry(newRecord, mSampleWindowMs);
-            } else {
-                // this user id has already been detected
+                if (oldRecord == null) {
+                    // this user id has just showed up, so just add it to the current sample
+                    // and its runtime is the size of our sample window
+                    newStats = new IoStatsEntry(newRecord, mSampleWindowMs);
+                } else {
+                    // this user id has already been detected
 
-                if (oldRecord.representsSameMetrics(newRecord)) {
-                    // if no new I/O happened, try to figure out if any process on behalf
-                    // of this user has happened, and use that to update the runtime metrics
-                    if (processTable.get().stream().anyMatch(pi -> pi.uid == uid)) {
+                    if (oldRecord.representsSameMetrics(newRecord)) {
+                        // if no new I/O happened, try to figure out if any process on behalf
+                        // of this user has happened, and use that to update the runtime metrics
+                        if (processTable.get().stream().anyMatch(pi -> pi.uid == uid)) {
+                            newStats = new IoStatsEntry(newRecord.delta(oldRecord),
+                                    oldRecord.runtimeMillis + mSampleWindowMs);
+                        }
+                        // if no new I/O happened and no process is running for this user
+                        // then do not prepare a new sample, as nothing has changed
+                    } else {
+                        // but if new I/O happened, assume something was running for the entire
+                        // sample window and compute the delta
                         newStats = new IoStatsEntry(newRecord.delta(oldRecord),
                                 oldRecord.runtimeMillis + mSampleWindowMs);
                     }
-                    // if no new I/O happened and no process is running for this user
-                    // then do not prepare a new sample, as nothing has changed
-                } else {
-                    // but if new I/O happened, assume something was running for the entire
-                    // sample window and compute the delta
-                    newStats = new IoStatsEntry(newRecord.delta(oldRecord),
-                            oldRecord.runtimeMillis + mSampleWindowMs);
                 }
-            }
 
-            if (newStats != null) {
-                newSample.put(uid, newStats);
-                newTotal.append(uid, new IoStatsEntry(newRecord, newStats.runtimeMillis));
-            } else {
-                // if oldRecord were null, newStats would be != null and we wouldn't be here
-                newTotal.append(uid, oldRecord);
-            }
-        });
+                if (newStats != null) {
+                    newSample.put(uid, newStats);
+                    newTotal.append(uid, new IoStatsEntry(newRecord, newStats.runtimeMillis));
+                } else {
+                    // if oldRecord were null, newStats would be != null and we wouldn't be here
+                    newTotal.append(uid, oldRecord);
+                }
+            });
 
-        // now update the stored values
-        mCurrentSample = newSample;
-        mTotal = newTotal;
+            // now update the stored values
+            mCurrentSample = newSample;
+            mTotal = newTotal;
+        }
     }
 
-    public synchronized SparseArray<IoStatsEntry> getTotal() {
-        return mTotal;
+    /**
+     * Returns all IO stats entries.
+     */
+    public SparseArray<IoStatsEntry> getTotal() {
+        synchronized (mLock) {
+            return mTotal.clone();
+        }
     }
 
-    public synchronized SparseArray<IoStatsEntry> getCurrentSample() {
-        return mCurrentSample;
+    /**
+     * Return newly added IO stats entries.
+     */
+    public SparseArray<IoStatsEntry> getCurrentSample() {
+        synchronized (mLock) {
+            return mCurrentSample.clone();
+        }
     }
 }
diff --git a/service/src/com/android/car/storagemonitoring/TEST_MAPPING b/service/src/com/android/car/storagemonitoring/TEST_MAPPING
new file mode 100644
index 0000000..a43eed0
--- /dev/null
+++ b/service/src/com/android/car/storagemonitoring/TEST_MAPPING
@@ -0,0 +1,23 @@
+{
+  "auto-postsubmit": [
+    {
+      "name": "CarServiceTest",
+      "options" : [
+        {
+          "include-filter": "com.android.car.CarStorageMonitoringTest"
+        }
+      ]
+    },
+    {
+      "name": "CarServiceUnitTest",
+      "options" : [
+        {
+          "include-filter": "com.android.car.storagemonitoring.CarStorageMonitoringTest"
+        },
+        {
+          "include-filter": "com.android.car.storagemonitoring.IoStatsTrackerTest"
+        }
+      ]
+    }
+  ]
+}
\ No newline at end of file
diff --git a/service/src/com/android/car/systeminterface/ActivityManagerInterface.java b/service/src/com/android/car/systeminterface/ActivityManagerInterface.java
new file mode 100644
index 0000000..c17ca48
--- /dev/null
+++ b/service/src/com/android/car/systeminterface/ActivityManagerInterface.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.systeminterface;
+
+import android.annotation.RequiresPermission;
+import android.content.Context;
+import android.content.Intent;
+import android.os.UserHandle;
+
+/**
+ * Interface that abstracts activity manager operations
+ */
+public interface ActivityManagerInterface {
+    /**
+     * Sends a broadcast
+     */
+    void sendBroadcastAsUser(Intent intent, UserHandle user);
+
+    /**
+     * Default implementation of ActivityManagerInterface
+     */
+    class DefaultImpl implements ActivityManagerInterface {
+        private final Context mContext;
+
+        DefaultImpl(Context context) {
+            mContext = context;
+        }
+
+        @Override
+        public void sendBroadcastAsUser(@RequiresPermission Intent intent, UserHandle user) {
+            mContext.sendBroadcastAsUser(intent, user);
+        }
+    }
+}
diff --git a/service/src/com/android/car/systeminterface/DisplayInterface.java b/service/src/com/android/car/systeminterface/DisplayInterface.java
index d20a177..301a48a 100644
--- a/service/src/com/android/car/systeminterface/DisplayInterface.java
+++ b/service/src/com/android/car/systeminterface/DisplayInterface.java
@@ -21,10 +21,11 @@
 import static com.android.settingslib.display.BrightnessUtils.convertLinearToGamma;
 
 import android.app.ActivityManager;
-import android.car.userlib.CarUserManagerHelper;
-import android.car.userlib.CarUserManagerHelper.OnUsersUpdateListener;
+import android.content.BroadcastReceiver;
 import android.content.ContentResolver;
 import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
 import android.database.ContentObserver;
 import android.hardware.display.DisplayManager;
 import android.hardware.display.DisplayManager.DisplayListener;
@@ -32,20 +33,17 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.os.PowerManager;
-import android.os.RemoteException;
-import android.os.ServiceManager;
 import android.os.SystemClock;
 import android.os.UserHandle;
 import android.provider.Settings.SettingNotFoundException;
 import android.provider.Settings.System;
 import android.util.Log;
 import android.view.Display;
-import android.view.DisplayAddress;
-import android.view.IWindowManager;
 import android.view.InputDevice;
 
 import com.android.car.CarLog;
 import com.android.car.CarPowerManagementService;
+import com.android.internal.annotations.GuardedBy;
 
 /**
  * Interface that abstracts display operations
@@ -65,30 +63,27 @@
     void refreshDisplayBrightness();
 
     /**
-     * Reconfigure all secondary displays due to b/131909551
-     */
-    void reconfigureSecondaryDisplays();
-    /**
      * Default implementation of display operations
      */
-    class DefaultImpl implements DisplayInterface, OnUsersUpdateListener {
-        static final String TAG = DisplayInterface.class.getSimpleName();
-
+    class DefaultImpl implements DisplayInterface {
         private final ActivityManager mActivityManager;
         private final ContentResolver mContentResolver;
         private final Context mContext;
         private final DisplayManager mDisplayManager;
         private final InputManager mInputManager;
+        private final Object mLock = new Object();
         private final int mMaximumBacklight;
         private final int mMinimumBacklight;
         private final PowerManager mPowerManager;
         private final WakeLockInterface mWakeLockInterface;
+        @GuardedBy("mLock")
         private CarPowerManagementService mService;
+        @GuardedBy("mLock")
         private boolean mDisplayStateSet;
-        private CarUserManagerHelper mCarUserManagerHelper;
+        @GuardedBy("mLock")
         private int mLastBrightnessLevel = -1;
 
-        private ContentObserver mBrightnessObserver =
+        private final ContentObserver mBrightnessObserver =
                 new ContentObserver(new Handler(Looper.getMainLooper())) {
                     @Override
                     public void onChange(boolean selfChange) {
@@ -115,6 +110,13 @@
             }
         };
 
+        private final BroadcastReceiver mUserChangeReceiver = new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                onUsersUpdate();
+            }
+        };
+
         DefaultImpl(Context context, WakeLockInterface wakeLockInterface) {
             mActivityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
             mContext = context;
@@ -125,30 +127,42 @@
             mMaximumBacklight = mPowerManager.getMaximumScreenBrightnessSetting();
             mMinimumBacklight = mPowerManager.getMinimumScreenBrightnessSetting();
             mWakeLockInterface = wakeLockInterface;
-            mCarUserManagerHelper = new CarUserManagerHelper(context);
-            mCarUserManagerHelper.registerOnUsersUpdateListener(this);
+
+            mContext.registerReceiverAsUser(
+                    mUserChangeReceiver,
+                    UserHandle.ALL,
+                    new IntentFilter(Intent.ACTION_USER_SWITCHED),
+                    null,
+                    null);
         }
 
         @Override
-        public synchronized void refreshDisplayBrightness() {
-            int gamma = GAMMA_SPACE_MAX;
-            try {
-                int linear = System.getIntForUser(
-                        mContentResolver,
-                        System.SCREEN_BRIGHTNESS,
-                        mActivityManager.getCurrentUser());
-                gamma = convertLinearToGamma(linear, mMinimumBacklight, mMaximumBacklight);
-            } catch (SettingNotFoundException e) {
-                Log.e(CarLog.TAG_POWER, "Could not get SCREEN_BRIGHTNESS:  " + e);
+        public void refreshDisplayBrightness() {
+            synchronized (mLock) {
+                if (mService == null) {
+                    Log.e(CarLog.TAG_POWER,
+                            "Could not set brightness: no CarPowerManagementService");
+                    return;
+                }
+                int gamma = GAMMA_SPACE_MAX;
+                try {
+                    int linear = System.getIntForUser(
+                            mContentResolver,
+                            System.SCREEN_BRIGHTNESS,
+                            ActivityManager.getCurrentUser());
+                    gamma = convertLinearToGamma(linear, mMinimumBacklight, mMaximumBacklight);
+                } catch (SettingNotFoundException e) {
+                    Log.e(CarLog.TAG_POWER, "Could not get SCREEN_BRIGHTNESS: " + e);
+                }
+                int percentBright = (gamma * 100 + ((GAMMA_SPACE_MAX + 1) / 2)) / GAMMA_SPACE_MAX;
+                mService.sendDisplayBrightness(percentBright);
             }
-            int percentBright = (gamma * 100 + ((GAMMA_SPACE_MAX + 1) / 2)) / GAMMA_SPACE_MAX;
-            mService.sendDisplayBrightness(percentBright);
         }
 
         private void handleMainDisplayChanged() {
             boolean isOn = isMainDisplayOn();
             CarPowerManagementService service;
-            synchronized (this) {
+            synchronized (mLock) {
                 if (mDisplayStateSet == isOn) { // same as what is set
                     return;
                 }
@@ -164,23 +178,25 @@
 
         @Override
         public void setDisplayBrightness(int percentBright) {
-            if (percentBright == mLastBrightnessLevel) {
-                // We have already set the value last time. Skipping
-                return;
+            synchronized (mLock) {
+                if (percentBright == mLastBrightnessLevel) {
+                    // We have already set the value last time. Skipping
+                    return;
+                }
+                mLastBrightnessLevel = percentBright;
             }
-            mLastBrightnessLevel = percentBright;
             int gamma = (percentBright * GAMMA_SPACE_MAX + 50) / 100;
             int linear = convertGammaToLinear(gamma, mMinimumBacklight, mMaximumBacklight);
             System.putIntForUser(
                     mContentResolver,
                     System.SCREEN_BRIGHTNESS,
                     linear,
-                    mActivityManager.getCurrentUser());
+                    ActivityManager.getCurrentUser());
         }
 
         @Override
         public void startDisplayStateMonitoring(CarPowerManagementService service) {
-            synchronized (this) {
+            synchronized (mLock) {
                 mService = service;
                 mDisplayStateSet = isMainDisplayOn();
             }
@@ -201,7 +217,7 @@
 
         @Override
         public void setDisplayState(boolean on) {
-            synchronized (this) {
+            synchronized (mLock) {
                 mDisplayStateSet = on;
             }
             if (on) {
@@ -228,42 +244,16 @@
             }
         }
 
-        @Override
-        public void onUsersUpdate() {
-            if (mService == null) {
-                // CarPowerManagementService is not connected yet
-                return;
+        private void onUsersUpdate() {
+            synchronized (mLock) {
+                if (mService == null) {
+                    // CarPowerManagementService is not connected yet
+                    return;
+                }
+                // We need to reset last value
+                mLastBrightnessLevel = -1;
             }
-            // We need to reset last value
-            mLastBrightnessLevel = -1;
             refreshDisplayBrightness();
         }
-
-        @Override
-        public void reconfigureSecondaryDisplays() {
-            IWindowManager wm = IWindowManager.Stub
-                    .asInterface(ServiceManager.getService(Context.WINDOW_SERVICE));
-            if (wm == null) {
-                Log.e(TAG, "reconfigureSecondaryDisplays IWindowManager not available");
-                return;
-            }
-            Display[] displays = mDisplayManager.getDisplays();
-            for (Display display : displays) {
-                if (display.getDisplayId() == Display.DEFAULT_DISPLAY) { // skip main
-                    continue;
-                }
-                // Only use physical secondary displays
-                if (display.getAddress() instanceof DisplayAddress.Physical) {
-                    int displayId = display.getDisplayId();
-                    try {
-                        // Do not change the mode but this triggers reconfiguring.
-                        int windowingMode = wm.getWindowingMode(displayId);
-                        wm.setWindowingMode(displayId, windowingMode);
-                    } catch (RemoteException e) {
-                        Log.e(CarLog.TAG_SERVICE, "cannot access IWindowManager", e);
-                    }
-                }
-            }
-        }
     }
 }
diff --git a/service/src/com/android/car/systeminterface/SystemInterface.java b/service/src/com/android/car/systeminterface/SystemInterface.java
index 8e7f863..ff3dcb0 100644
--- a/service/src/com/android/car/systeminterface/SystemInterface.java
+++ b/service/src/com/android/car/systeminterface/SystemInterface.java
@@ -17,6 +17,8 @@
 package com.android.car.systeminterface;
 
 import android.content.Context;
+import android.content.Intent;
+import android.os.UserHandle;
 
 import com.android.car.CarPowerManagementService;
 import com.android.car.procfsinspector.ProcessInfo;
@@ -34,9 +36,11 @@
  * This class contains references to all the different wrapper interfaces between
  * CarService and the Android OS APIs.
  */
-public class SystemInterface implements DisplayInterface, IOInterface,
-        StorageMonitoringInterface, SystemStateInterface, TimeInterface,
+public class SystemInterface implements ActivityManagerInterface,
+        DisplayInterface, IOInterface, StorageMonitoringInterface,
+        SystemStateInterface, TimeInterface,
         WakeLockInterface {
+    private final ActivityManagerInterface mActivityManagerInterface;
     private final DisplayInterface mDisplayInterface;
     private final IOInterface mIOInterface;
     private final StorageMonitoringInterface mStorageMonitoringInterface;
@@ -44,12 +48,14 @@
     private final TimeInterface mTimeInterface;
     private final WakeLockInterface mWakeLockInterface;
 
-    SystemInterface(DisplayInterface displayInterface,
+    SystemInterface(ActivityManagerInterface activityManagerInterface,
+            DisplayInterface displayInterface,
             IOInterface ioInterface,
             StorageMonitoringInterface storageMonitoringInterface,
             SystemStateInterface systemStateInterface,
             TimeInterface timeInterface,
             WakeLockInterface wakeLockInterface) {
+        mActivityManagerInterface = activityManagerInterface;
         mDisplayInterface = displayInterface;
         mIOInterface = ioInterface;
         mStorageMonitoringInterface = storageMonitoringInterface;
@@ -58,6 +64,9 @@
         mWakeLockInterface = wakeLockInterface;
     }
 
+    public ActivityManagerInterface getActivityManagerInterface() {
+        return mActivityManagerInterface;
+    }
     public DisplayInterface getDisplayInterface() { return mDisplayInterface; }
     public IOInterface getIOInterface() { return mIOInterface; }
     public SystemStateInterface getSystemStateInterface() { return mSystemStateInterface; }
@@ -68,6 +77,11 @@
     }
 
     @Override
+    public void sendBroadcastAsUser(Intent intent, UserHandle user) {
+        mActivityManagerInterface.sendBroadcastAsUser(intent, user);
+    }
+
+    @Override
     public File getSystemCarDir() {
         return mIOInterface.getSystemCarDir();
     }
@@ -123,11 +137,6 @@
     }
 
     @Override
-    public void reconfigureSecondaryDisplays() {
-        mDisplayInterface.reconfigureSecondaryDisplays();
-    }
-
-    @Override
     public void startDisplayStateMonitoring(CarPowerManagementService service) {
         mDisplayInterface.startDisplayStateMonitoring(service);
     }
@@ -183,6 +192,7 @@
     }
 
     public final static class Builder {
+        private ActivityManagerInterface mActivityManagerInterface;
         private DisplayInterface mDisplayInterface;
         private IOInterface mIOInterface;
         private StorageMonitoringInterface mStorageMonitoringInterface;
@@ -199,6 +209,7 @@
         public static Builder defaultSystemInterface(Context context) {
             Objects.requireNonNull(context);
             Builder builder = newSystemInterface();
+            builder.withActivityManagerInterface(new ActivityManagerInterface.DefaultImpl(context));
             builder.withWakeLockInterface(new WakeLockInterface.DefaultImpl(context));
             builder.withDisplayInterface(new DisplayInterface.DefaultImpl(context,
                     builder.mWakeLockInterface));
@@ -210,6 +221,7 @@
 
         public static Builder fromBuilder(Builder otherBuilder) {
             return newSystemInterface()
+                    .withActivityManagerInterface(otherBuilder.mActivityManagerInterface)
                     .withDisplayInterface(otherBuilder.mDisplayInterface)
                     .withIOInterface(otherBuilder.mIOInterface)
                     .withStorageMonitoringInterface(otherBuilder.mStorageMonitoringInterface)
@@ -218,6 +230,12 @@
                     .withWakeLockInterface(otherBuilder.mWakeLockInterface);
         }
 
+        public Builder withActivityManagerInterface(ActivityManagerInterface
+                activityManagerInterface) {
+            mActivityManagerInterface = activityManagerInterface;
+            return this;
+        }
+
         public Builder withDisplayInterface(DisplayInterface displayInterface) {
             mDisplayInterface = displayInterface;
             return this;
@@ -250,7 +268,8 @@
         }
 
         public SystemInterface build() {
-            return new SystemInterface(Objects.requireNonNull(mDisplayInterface),
+            return new SystemInterface(Objects.requireNonNull(mActivityManagerInterface),
+                Objects.requireNonNull(mDisplayInterface),
                 Objects.requireNonNull(mIOInterface),
                 Objects.requireNonNull(mStorageMonitoringInterface),
                 Objects.requireNonNull(mSystemStateInterface),
diff --git a/service/src/com/android/car/trust/BLEMessageV1Factory.java b/service/src/com/android/car/trust/BLEMessageV1Factory.java
index 4afe877..8bebc90 100644
--- a/service/src/com/android/car/trust/BLEMessageV1Factory.java
+++ b/service/src/com/android/car/trust/BLEMessageV1Factory.java
@@ -113,7 +113,8 @@
      * @param operation The operation this message represents
      * @return The generated {@link com.android.car.trust.BLEStream.BLEMessage}
      */
-    private static BLEMessage makeBLEMessage(byte[] payload, OperationType operation,
+    @VisibleForTesting
+    static BLEMessage makeBLEMessage(byte[] payload, OperationType operation,
             boolean isPayloadEncrypted) {
         return BLEMessage.newBuilder()
                 .setVersion(PROTOCOL_VERSION)
diff --git a/service/src/com/android/car/trust/BLEVersionExchangeResolver.java b/service/src/com/android/car/trust/BLEVersionExchangeResolver.java
index 4465b25..d5c7383 100644
--- a/service/src/com/android/car/trust/BLEVersionExchangeResolver.java
+++ b/service/src/com/android/car/trust/BLEVersionExchangeResolver.java
@@ -16,6 +16,12 @@
 
 package com.android.car.trust;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.os.Handler;
+import android.os.Looper;
 import android.util.Log;
 
 import com.android.car.BLEStreamProtos.VersionExchangeProto.BLEVersionExchange;
@@ -31,13 +37,23 @@
     private static final int SECURITY_VERSION = 1;
 
     /**
-     * Return whether or not the given version exchange proto has the a version that is currently
-     * supported by this device.
+     * Returns a message stream that can be used to send messages to the given
+     * {@link BluetoothDevice} based on the version exchange proto.
      *
      * @param versionExchange The version exchange proto to resolve
-     * @return {@code true} if there is a supported version.
+     * @param device The remote device to send messages to.
+     * @param readCharacteristic The characteristic the remote device will use to write messages to.
+     *                           This is the characteristic this IHU will read from.
+     * @param writeCharacteristic The characteristic on the remote device that this IHU can write
+     *                            messages to.
+     * @return A stream that can send message or {@code null} if resolution was not possible.
      */
-    static boolean hasSupportedVersion(BLEVersionExchange versionExchange) {
+    @Nullable
+    static BleMessageStream resolveToStream(
+            @NonNull BLEVersionExchange versionExchange,
+            @NonNull BluetoothDevice device, @NonNull BlePeripheralManager blePeripheralManager,
+            @NonNull BluetoothGattCharacteristic writeCharacteristic,
+            @NonNull BluetoothGattCharacteristic readCharacteristic) {
         int minMessagingVersion = versionExchange.getMinSupportedMessagingVersion();
         int minSecurityVersion = versionExchange.getMinSupportedSecurityVersion();
 
@@ -47,13 +63,23 @@
         }
 
         // Only one supported version, so ensure the minimum version matches.
-        return minMessagingVersion == MESSAGING_VERSION && minSecurityVersion == SECURITY_VERSION;
+        if (minMessagingVersion == MESSAGING_VERSION && minSecurityVersion == SECURITY_VERSION) {
+            return new BleMessageStreamV1(
+                    new Handler(Looper.getMainLooper()),
+                    blePeripheralManager,
+                    device,
+                    writeCharacteristic,
+                    readCharacteristic);
+        }
+
+        return null;
     }
 
     /**
      * Returns a version exchange proto with the maximum and minimum protocol and security versions
      * this device currently supports.
      */
+    @NonNull
     static BLEVersionExchange makeVersionExchange() {
         return BLEVersionExchange.newBuilder()
                 .setMinSupportedMessagingVersion(MESSAGING_VERSION)
diff --git a/service/src/com/android/car/trust/BleManager.java b/service/src/com/android/car/trust/BleManager.java
deleted file mode 100644
index 28a8fee..0000000
--- a/service/src/com/android/car/trust/BleManager.java
+++ /dev/null
@@ -1,437 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.car.trust;
-
-import static android.bluetooth.BluetoothProfile.GATT_SERVER;
-
-import android.bluetooth.BluetoothAdapter;
-import android.bluetooth.BluetoothDevice;
-import android.bluetooth.BluetoothGatt;
-import android.bluetooth.BluetoothGattCallback;
-import android.bluetooth.BluetoothGattCharacteristic;
-import android.bluetooth.BluetoothGattDescriptor;
-import android.bluetooth.BluetoothGattServer;
-import android.bluetooth.BluetoothGattServerCallback;
-import android.bluetooth.BluetoothGattService;
-import android.bluetooth.BluetoothManager;
-import android.bluetooth.BluetoothProfile;
-import android.bluetooth.le.AdvertiseCallback;
-import android.bluetooth.le.AdvertiseData;
-import android.bluetooth.le.AdvertiseSettings;
-import android.bluetooth.le.BluetoothLeAdvertiser;
-import android.content.Context;
-import android.content.pm.PackageManager;
-import android.os.Handler;
-import android.util.Log;
-
-import androidx.annotation.Nullable;
-
-import com.android.car.Utils;
-
-import java.util.UUID;
-
-/**
- * A generic class that manages BLE operations like start/stop advertising, notifying connects/
- * disconnects and reading/writing values to GATT characteristics.
- *
- * TODO(b/123248433) This could move to a separate comms library.
- */
-public abstract class BleManager {
-    private static final String TAG = BleManager.class.getSimpleName();
-
-    private static final int BLE_RETRY_LIMIT = 5;
-    private static final int BLE_RETRY_INTERVAL_MS = 1000;
-
-    private static final int GATT_SERVER_RETRY_LIMIT = 20;
-    private static final int GATT_SERVER_RETRY_DELAY_MS = 200;
-
-    // https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth
-    // .service.generic_access.xml
-    private static final UUID GENERIC_ACCESS_PROFILE_UUID =
-            UUID.fromString("00001800-0000-1000-8000-00805f9b34fb");
-    //https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth
-    // .characteristic.gap.device_name.xml
-    private static final UUID DEVICE_NAME_UUID =
-            UUID.fromString("00002a00-0000-1000-8000-00805f9b34fb");
-
-    private final Handler mHandler = new Handler();
-
-    private final Context mContext;
-    private BluetoothManager mBluetoothManager;
-    private BluetoothLeAdvertiser mAdvertiser;
-    private BluetoothGattServer mGattServer;
-    private BluetoothGatt mBluetoothGatt;
-    private int mAdvertiserStartCount;
-    private int mGattServerRetryStartCount;
-    private BluetoothGattService mBluetoothGattService;
-    private AdvertiseCallback mAdvertiseCallback;
-    private AdvertiseData mData;
-
-    BleManager(Context context) {
-        mContext = context;
-    }
-
-    /**
-     * Starts the GATT server with the given {@link BluetoothGattService} and begins
-     * advertising.
-     *
-     * <p>It is possible that BLE service is still in TURNING_ON state when this method is invoked.
-     * Therefore, several retries will be made to ensure advertising is started.
-     *
-     * @param service           {@link BluetoothGattService} that will be discovered by clients
-     * @param data              {@link AdvertiseData} data to advertise
-     * @param advertiseCallback {@link AdvertiseCallback} callback for advertiser
-     */
-    protected void startAdvertising(BluetoothGattService service, AdvertiseData data,
-            AdvertiseCallback advertiseCallback) {
-        if (Log.isLoggable(TAG, Log.DEBUG)) {
-            Log.d(TAG, "startAdvertising: " + service.getUuid().toString());
-        }
-        if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
-            Log.e(TAG, "System does not support BLE");
-            return;
-        }
-
-        mBluetoothGattService = service;
-        mAdvertiseCallback = advertiseCallback;
-        mData = data;
-        mGattServerRetryStartCount = 0;
-        mBluetoothManager = (BluetoothManager) mContext.getSystemService(
-            Context.BLUETOOTH_SERVICE);
-        openGattServer();
-    }
-
-    private void openGattServer() {
-        // Only open one Gatt server.
-        if (mGattServer != null) {
-            if (Log.isLoggable(TAG, Log.DEBUG)) {
-                Log.d(TAG, "Gatt Server created, retry count: " + mGattServerRetryStartCount);
-            }
-            mGattServer.clearServices();
-            mGattServer.addService(mBluetoothGattService);
-            AdvertiseSettings settings = new AdvertiseSettings.Builder()
-                .setAdvertiseMode(AdvertiseSettings.ADVERTISE_MODE_LOW_LATENCY)
-                .setTxPowerLevel(AdvertiseSettings.ADVERTISE_TX_POWER_HIGH)
-                .setConnectable(true)
-                .build();
-            mAdvertiserStartCount = 0;
-            startAdvertisingInternally(settings, mData, mAdvertiseCallback);
-            mGattServerRetryStartCount = 0;
-        } else if (mGattServerRetryStartCount < GATT_SERVER_RETRY_LIMIT) {
-            mGattServer = mBluetoothManager.openGattServer(mContext, mGattServerCallback);
-            mGattServerRetryStartCount++;
-            mHandler.postDelayed(() -> openGattServer(), GATT_SERVER_RETRY_DELAY_MS);
-        } else {
-            Log.e(TAG, "Gatt server not created - exceeded retry limit.");
-        }
-    }
-
-    private void startAdvertisingInternally(AdvertiseSettings settings, AdvertiseData data,
-            AdvertiseCallback advertiseCallback) {
-        if (BluetoothAdapter.getDefaultAdapter() != null) {
-            mAdvertiser = BluetoothAdapter.getDefaultAdapter().getBluetoothLeAdvertiser();
-        }
-
-        if (mAdvertiser != null) {
-            if (Log.isLoggable(TAG, Log.DEBUG)) {
-                Log.d(TAG, "Advertiser created, retry count: " + mAdvertiserStartCount);
-            }
-            mAdvertiser.startAdvertising(settings, data, advertiseCallback);
-            mAdvertiserStartCount = 0;
-        } else if (mAdvertiserStartCount < BLE_RETRY_LIMIT) {
-            mHandler.postDelayed(
-                    () -> startAdvertisingInternally(settings, data, advertiseCallback),
-                    BLE_RETRY_INTERVAL_MS);
-            mAdvertiserStartCount += 1;
-        } else {
-            Log.e(TAG, "Cannot start BLE Advertisement.  BT Adapter: "
-                    + BluetoothAdapter.getDefaultAdapter() + " Advertise Retry count: "
-                    + mAdvertiserStartCount);
-        }
-    }
-
-    protected void stopAdvertising(AdvertiseCallback advertiseCallback) {
-        if (mAdvertiser != null) {
-            if (Log.isLoggable(TAG, Log.DEBUG)) {
-                Log.d(TAG, "stopAdvertising: ");
-            }
-            mAdvertiser.stopAdvertising(advertiseCallback);
-        }
-    }
-
-    /**
-     * Notifies the characteristic change via {@link BluetoothGattServer}
-     */
-    protected void notifyCharacteristicChanged(BluetoothDevice device,
-            BluetoothGattCharacteristic characteristic, boolean confirm) {
-        if (mGattServer == null) {
-            return;
-        }
-
-        boolean result = mGattServer.notifyCharacteristicChanged(device, characteristic, confirm);
-
-        if (Log.isLoggable(TAG, Log.DEBUG)) {
-            Log.d(TAG, "notifyCharacteristicChanged succeeded: " + result);
-        }
-    }
-
-    /**
-     * Connect the Gatt server of the remote device to retrieve device name.
-     */
-    protected final void retrieveDeviceName(BluetoothDevice device) {
-        mBluetoothGatt = device.connectGatt(getContext(), false, mGattCallback);
-    }
-
-    protected Context getContext() {
-        return mContext;
-    }
-
-    /**
-     * Cleans up the BLE GATT server state.
-     */
-    void cleanup() {
-        // Stops the advertiser and GATT server. This needs to be done to avoid leaks
-        if (mAdvertiser != null) {
-            mAdvertiser.cleanup();
-        }
-
-        if (mGattServer != null) {
-            mGattServer.clearServices();
-            try {
-                for (BluetoothDevice d : mBluetoothManager.getConnectedDevices(GATT_SERVER)) {
-                    mGattServer.cancelConnection(d);
-                }
-            } catch (UnsupportedOperationException e) {
-                Log.e(TAG, "Error getting connected devices", e);
-            } finally {
-                stopGattServer();
-            }
-        }
-    }
-
-    /**
-     * Close the GATT Server
-     */
-    void stopGattServer() {
-        if (mGattServer == null) {
-            return;
-        }
-        if (Log.isLoggable(TAG, Log.DEBUG)) {
-            Log.d(TAG, "stopGattServer");
-        }
-        if (mBluetoothGatt != null) {
-            mBluetoothGatt.disconnect();
-        }
-        mGattServer.close();
-        mGattServer = null;
-    }
-
-    /**
-     * Triggered when the name of the remote device is retrieved.
-     *
-     * @param deviceName Name of the remote device.
-     */
-    protected void onDeviceNameRetrieved(@Nullable String deviceName) {
-    }
-
-    /**
-     * Triggered if a remote client has requested to change the MTU for a given connection.
-     *
-     * @param size The new MTU size.
-     */
-    protected void onMtuSizeChanged(int size) {
-    }
-
-    /**
-     * Triggered when a device (GATT client) connected.
-     *
-     * @param device Remote device that connected on BLE.
-     */
-    protected void onRemoteDeviceConnected(BluetoothDevice device) {
-    }
-
-    /**
-     * Triggered when a device (GATT client) disconnected.
-     *
-     * @param device Remote device that disconnected on BLE.
-     */
-    protected void onRemoteDeviceDisconnected(BluetoothDevice device) {
-    }
-
-    /**
-     * Triggered when this BleManager receives a write request from a remote
-     * device. Sub-classes should implement how to handle requests.
-     * <p>
-     *
-     * @see BluetoothGattServerCallback#onCharacteristicWriteRequest(BluetoothDevice, int,
-     * BluetoothGattCharacteristic, boolean, boolean, int, byte[])
-     */
-    protected abstract void onCharacteristicWrite(BluetoothDevice device, int requestId,
-            BluetoothGattCharacteristic characteristic, boolean preparedWrite, boolean
-            responseNeeded, int offset, byte[] value);
-
-    /**
-     * Triggered when this BleManager receives a read request from a remote device.
-     * <p>
-     *
-     * @see BluetoothGattServerCallback#onCharacteristicReadRequest(BluetoothDevice, int, int,
-     * BluetoothGattCharacteristic)
-     */
-    protected abstract void onCharacteristicRead(BluetoothDevice device,
-            int requestId, int offset, BluetoothGattCharacteristic characteristic);
-
-    private final BluetoothGattServerCallback mGattServerCallback =
-            new BluetoothGattServerCallback() {
-                @Override
-                public void onConnectionStateChange(BluetoothDevice device, int status,
-                        int newState) {
-                    if (Log.isLoggable(TAG, Log.DEBUG)) {
-                        Log.d(TAG, "BLE Connection State Change: " + newState);
-                    }
-                    switch (newState) {
-                        case BluetoothProfile.STATE_CONNECTED:
-                            onRemoteDeviceConnected(device);
-                            break;
-                        case BluetoothProfile.STATE_DISCONNECTED:
-                            onRemoteDeviceDisconnected(device);
-                            break;
-                        default:
-                            Log.w(TAG,
-                                    "Connection state not connecting or disconnecting; ignoring: "
-                                            + newState);
-                    }
-                }
-
-                @Override
-                public void onServiceAdded(int status, BluetoothGattService service) {
-                    if (Log.isLoggable(TAG, Log.DEBUG)) {
-                        Log.d(TAG,
-                                "Service added status: " + status + " uuid: " + service.getUuid());
-                    }
-                }
-
-                @Override
-                public void onCharacteristicReadRequest(BluetoothDevice device, int requestId,
-                        int offset, BluetoothGattCharacteristic characteristic) {
-                    if (Log.isLoggable(TAG, Log.DEBUG)) {
-                        Log.d(TAG, "Read request for characteristic: " + characteristic.getUuid());
-                    }
-
-                    mGattServer.sendResponse(device, requestId,
-                            BluetoothGatt.GATT_SUCCESS, offset, characteristic.getValue());
-                    onCharacteristicRead(device, requestId, offset, characteristic);
-                }
-
-                @Override
-                public void onCharacteristicWriteRequest(BluetoothDevice device, int requestId,
-                        BluetoothGattCharacteristic characteristic, boolean preparedWrite,
-                        boolean responseNeeded, int offset, byte[] value) {
-                    if (Log.isLoggable(TAG, Log.DEBUG)) {
-                        Log.d(TAG, "Write request for characteristic: " + characteristic.getUuid()
-                                + "value: " + Utils.byteArrayToHexString(value));
-                    }
-
-                    mGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS,
-                            offset, value);
-                    onCharacteristicWrite(device, requestId, characteristic,
-                            preparedWrite, responseNeeded, offset, value);
-                }
-
-                @Override
-                public void onDescriptorWriteRequest(BluetoothDevice device, int requestId,
-                        BluetoothGattDescriptor descriptor, boolean preparedWrite,
-                        boolean responseNeeded, int offset, byte[] value) {
-                    if (Log.isLoggable(TAG, Log.DEBUG)) {
-                        Log.d(TAG, "Write request for descriptor: " + descriptor.getUuid()
-                                + "; value: " + Utils.byteArrayToHexString(value));
-                    }
-
-                    mGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS,
-                            offset, value);
-                }
-
-                @Override
-                public void onMtuChanged(BluetoothDevice device, int mtu) {
-                    if (Log.isLoggable(TAG, Log.DEBUG)) {
-                        Log.d(TAG, "onMtuChanged: " + mtu + " for device " + device.getAddress());
-                    }
-                    onMtuSizeChanged(mtu);
-                }
-
-            };
-
-    private final BluetoothGattCallback mGattCallback = new BluetoothGattCallback() {
-        @Override
-        public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
-            if (Log.isLoggable(TAG, Log.DEBUG)) {
-                Log.d(TAG, "Gatt Connection State Change: " + newState);
-            }
-            switch (newState) {
-                case BluetoothProfile.STATE_CONNECTED:
-                    if (Log.isLoggable(TAG, Log.DEBUG)) {
-                        Log.d(TAG, "Gatt connected");
-                    }
-                    mBluetoothGatt.discoverServices();
-                    break;
-                case BluetoothProfile.STATE_DISCONNECTED:
-                    if (Log.isLoggable(TAG, Log.DEBUG)) {
-                        Log.d(TAG, "Gatt Disconnected");
-                    }
-                    break;
-                default:
-                    if (Log.isLoggable(TAG, Log.DEBUG)) {
-                        Log.d(TAG,
-                                "Connection state not connecting or disconnecting; ignoring: "
-                                        + newState);
-                    }
-            }
-        }
-
-        @Override
-        public void onServicesDiscovered(BluetoothGatt gatt, int status) {
-            if (Log.isLoggable(TAG, Log.DEBUG)) {
-                Log.d(TAG, "Gatt Services Discovered");
-            }
-            BluetoothGattService gapService = mBluetoothGatt.getService(
-                    GENERIC_ACCESS_PROFILE_UUID);
-            if (gapService == null) {
-                Log.e(TAG, "Generic Access Service is Null");
-                return;
-            }
-            BluetoothGattCharacteristic deviceNameCharacteristic = gapService.getCharacteristic(
-                    DEVICE_NAME_UUID);
-            if (deviceNameCharacteristic == null) {
-                Log.e(TAG, "Device Name Characteristic is Null");
-                return;
-            }
-            mBluetoothGatt.readCharacteristic(deviceNameCharacteristic);
-        }
-
-        @Override
-        public void onCharacteristicRead(BluetoothGatt gatt,
-                BluetoothGattCharacteristic characteristic, int status) {
-            if (status == BluetoothGatt.GATT_SUCCESS) {
-                String deviceName = characteristic.getStringValue(0);
-                if (Log.isLoggable(TAG, Log.DEBUG)) {
-                    Log.d(TAG, "BLE Device Name: " + deviceName);
-                }
-                onDeviceNameRetrieved(deviceName);
-            } else {
-                Log.e(TAG, "Reading GAP Failed: " + status);
-            }
-        }
-    };
-}
diff --git a/service/src/com/android/car/trust/BleMessageStream.java b/service/src/com/android/car/trust/BleMessageStream.java
new file mode 100644
index 0000000..19da2c2
--- /dev/null
+++ b/service/src/com/android/car/trust/BleMessageStream.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.trust;
+
+import android.annotation.NonNull;
+
+import com.android.car.BLEStreamProtos.BLEOperationProto.OperationType;
+
+/**
+ * Handles the streaming of BLE messages to a specific {@link android.bluetooth.BluetoothDevice}.
+ *
+ * <p>This stream will handle if messages to a particular peripheral need to be split into
+ * multiple messages or if the messages can be sent all at once. Internally, it will have its own
+ * protocol for how the split messages are structured.
+ */
+interface BleMessageStream {
+   /** Registers the given callback to be notified of various events within the stream. */
+    void registerCallback(@NonNull BleMessageStreamCallback callback);
+
+    /** Unregisters the given callback from being notified of stream events. */
+    void unregisterCallback(@NonNull BleMessageStreamCallback callback);
+
+    /** Sets the maximum size of a message that can be sent. */
+    void setMaxWriteSize(int maxWriteSize);
+
+    /** Returns the maximum size of a message that can be sent. */
+    int getMaxWriteSize();
+
+    /**
+     * Writes the given message to the write characteristic set on this stream to the
+     * {@code BleutoothDevice} associated with this stream.
+     *
+     * <p>The given message will adhere to the max write size set on this stream. If the message is
+     * larger than this size, then this stream should take the appropriate actions necessary to
+     * chunk the message to the device so that no parts of the message is dropped.
+     *
+     * <p>If there was an error, then this stream will notify the [callback] of this stream via a
+     * call to its {@code onWriteMessageError} method.
+     *
+     * @param message The message to send.
+     * @param operationType The {@link OperationType} of this message.
+     * @param isPayloadEncrypted {@code true} if the message to send has been encrypted.
+     */
+    void writeMessage(@NonNull byte[] message, @NonNull OperationType operationType,
+            boolean isPayloadEncrypted);
+}
diff --git a/service/src/com/android/car/trust/BleMessageStreamCallback.java b/service/src/com/android/car/trust/BleMessageStreamCallback.java
new file mode 100644
index 0000000..d497c85
--- /dev/null
+++ b/service/src/com/android/car/trust/BleMessageStreamCallback.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.trust;
+
+import android.annotation.NonNull;
+
+import java.util.UUID;
+
+/**
+ * The callback that will be notified of various actions that occur in a {@link BleMessageStream}.
+ */
+interface BleMessageStreamCallback {
+    /**
+     * Called if an error was encountered during a processing of a client message.
+     *
+     * @param uuid The {@link UUID} of the characteristic that the client message was retrieved
+     *             from.
+     */
+    void onMessageReceivedError(@NonNull UUID uuid);
+
+    /**
+     * Called when a complete message is received from the client.
+     *
+     * @param message The complete message.
+     * @param uuid The {@link UUID} of the characteristic that the client message was retrieved
+     *             from.
+     */
+    void onMessageReceived(@NonNull byte[] message, UUID uuid);
+
+    /**
+     * Called if there was an error during a write of a message to the stream.
+     */
+    void onWriteMessageError();
+}
diff --git a/service/src/com/android/car/trust/BleMessageStreamV1.java b/service/src/com/android/car/trust/BleMessageStreamV1.java
new file mode 100644
index 0000000..d3f68cf
--- /dev/null
+++ b/service/src/com/android/car/trust/BleMessageStreamV1.java
@@ -0,0 +1,304 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.trust;
+
+import android.annotation.NonNull;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.os.Handler;
+import android.util.Log;
+
+import com.android.car.BLEStreamProtos.BLEMessageProto.BLEMessage;
+import com.android.car.BLEStreamProtos.BLEOperationProto.OperationType;
+import com.android.car.protobuf.InvalidProtocolBufferException;
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.io.IOException;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Deque;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Version 1 of the message stream.
+ */
+class BleMessageStreamV1 implements BleMessageStream {
+    private static final String TAG = "BleMessageStreamV1";
+
+    @VisibleForTesting
+    static final int BLE_MESSAGE_RETRY_LIMIT = 5;
+
+    /**
+     * The delay in milliseconds before a failed message is retried for sending.
+     *
+     * <p>This delay is only present for a message has been chunked. Each part of this chunked
+     * message requires an ACK from the remote device before the next chunk is sent. If this ACK is
+     * not received, then a second message is sent after this delay.
+     *
+     * <p>The value of this delay is 2 seconds to allow for 1 second to notify the remote device of
+     * a new message and 1 second to wait for an ACK.
+     */
+    private static final long BLE_MESSAGE_RETRY_DELAY_MS = TimeUnit.SECONDS.toMillis(2);
+
+    private final Handler mHandler;
+    private final BlePeripheralManager mBlePeripheralManager;
+    private final BluetoothDevice mDevice;
+    private final BluetoothGattCharacteristic mWriteCharacteristic;
+    private final BluetoothGattCharacteristic mReadCharacteristic;
+
+    // Explicitly using an ArrayDequeue here for performance when used as a queue.
+    private final Deque<BLEMessage> mMessageQueue = new ArrayDeque<>();
+    private final BLEMessagePayloadStream mPayloadStream = new BLEMessagePayloadStream();
+
+    /** The number of times that a message to send has been retried. */
+    private int mRetryCount = 0;
+
+    /**
+     * The maximum write size for a single message.
+     *
+     * <p>By default, this value is 20 because the smaller possible write size over BLE is 23 bytes.
+     * However, 3 bytes need to be subtracted due to them being used by the header of the BLE
+     * packet. Thus, the final value is 20.
+     */
+    private int mMaxWriteSize = 20;
+
+    private final List<BleMessageStreamCallback> mCallbacks = new ArrayList<>();
+
+    BleMessageStreamV1(@NonNull Handler handler, @NonNull BlePeripheralManager blePeripheralManager,
+            @NonNull BluetoothDevice device,
+            @NonNull BluetoothGattCharacteristic writeCharacteristic,
+            @NonNull BluetoothGattCharacteristic readCharacteristic) {
+        mHandler = handler;
+        mBlePeripheralManager = blePeripheralManager;
+        mDevice = device;
+        mWriteCharacteristic = writeCharacteristic;
+        mReadCharacteristic = readCharacteristic;
+
+        mBlePeripheralManager.addOnCharacteristicWriteListener(this::onCharacteristicWrite);
+    }
+
+    /** Registers the given callback to be notified of various events within the stream. */
+    @Override
+    public void registerCallback(@NonNull BleMessageStreamCallback callback) {
+        mCallbacks.add(callback);
+    }
+
+    /** Unregisters the given callback from being notified of stream events. */
+    @Override
+    public void unregisterCallback(@NonNull BleMessageStreamCallback callback) {
+        mCallbacks.remove(callback);
+    }
+
+    /** Sets the maximum size of a message that can be sent. */
+    @Override
+    public void setMaxWriteSize(int maxWriteSize) {
+        mMaxWriteSize = maxWriteSize;
+    }
+
+    /** Returns the maximum size of a message that can be sent. */
+    @Override
+    public int getMaxWriteSize() {
+        return mMaxWriteSize;
+    }
+
+    /**
+     * Writes the given message to the write characteristic of this stream.
+     *
+     * <p>This method will handle the chunking of messages based on maximum write size assigned to
+     * this stream.. If there is an error during the send, any callbacks on this stream will be
+     * notified of the error.
+     *
+     * @param message The message to send.
+     * @param operationType The {@link OperationType} of this message.
+     * @param isPayloadEncrypted {@code true} if the message to send has been encrypted.
+     */
+    @Override
+    public void writeMessage(@NonNull byte[] message, @NonNull OperationType operationType,
+            boolean isPayloadEncrypted) {
+        if (Log.isLoggable(TAG, Log.DEBUG)) {
+            Log.d(TAG, "Writing message to device with name: " + mDevice.getName());
+        }
+
+        List<BLEMessage> bleMessages = BLEMessageV1Factory.makeBLEMessages(message, operationType,
+                mMaxWriteSize, isPayloadEncrypted);
+
+        if (Log.isLoggable(TAG, Log.DEBUG)) {
+            Log.d(TAG, "Number of messages to send to device: " + bleMessages.size());
+        }
+
+        // Each write will override previous messages.
+        if (!mMessageQueue.isEmpty()) {
+            mMessageQueue.clear();
+            Log.w(TAG, "Request to write a new message when there are still messages in the "
+                    + "queue.");
+        }
+
+        mMessageQueue.addAll(bleMessages);
+
+        writeNextMessageInQueue();
+    }
+
+    /**
+     * Processes a message from the client and notifies any callbacks of the success of this
+     * call.
+     */
+    @VisibleForTesting
+    void onCharacteristicWrite(@NonNull BluetoothDevice device,
+            @NonNull BluetoothGattCharacteristic characteristic, @NonNull byte[] value) {
+        if (!mDevice.equals(device)) {
+            Log.w(TAG, "Received a message from a device (" + device.getAddress() + ") that is not "
+                    + "the expected device (" + mDevice.getAddress() + ") registered to this "
+                    + "stream. Ignoring.");
+            return;
+        }
+
+        if (!characteristic.getUuid().equals(mReadCharacteristic.getUuid())) {
+            Log.w(TAG, "Received a write to a characteristic (" + characteristic.getUuid()
+                    + ") that is not the expected UUID (" + mReadCharacteristic.getUuid()
+                    + "). Ignoring.");
+            return;
+        }
+
+        BLEMessage bleMessage;
+        try {
+            bleMessage = BLEMessage.parseFrom(value);
+        } catch (InvalidProtocolBufferException e) {
+            Log.e(TAG, "Can not parse BLE message from client.", e);
+
+            for (BleMessageStreamCallback callback : mCallbacks) {
+                callback.onMessageReceivedError(characteristic.getUuid());
+            }
+            return;
+        }
+
+        if (bleMessage.getOperation() == OperationType.ACK) {
+            handleClientAckMessage();
+            return;
+        }
+
+        try {
+            mPayloadStream.write(bleMessage);
+        } catch (IOException e) {
+            Log.e(TAG, "Unable to parse the BLE message's payload from client.", e);
+
+            for (BleMessageStreamCallback callback : mCallbacks) {
+                callback.onMessageReceivedError(characteristic.getUuid());
+            }
+            return;
+        }
+
+        // If it's not complete, make sure the client knows that this message was received.
+        if (!mPayloadStream.isComplete()) {
+            sendAcknowledgmentMessage();
+            return;
+        }
+
+        for (BleMessageStreamCallback callback : mCallbacks) {
+            callback.onMessageReceived(mPayloadStream.toByteArray(), characteristic.getUuid());
+        }
+
+        mPayloadStream.reset();
+    }
+
+    /**
+     * Writes the next message in the message queue to the write characteristic.
+     *
+     * <p>If the message queue is empty, then this method will do nothing.
+     */
+    private void writeNextMessageInQueue() {
+        // This should not happen in practice since this method is private and should only be called
+        // for a non-empty queue.
+        if (mMessageQueue.isEmpty()) {
+            Log.e(TAG, "Call to write next message in queue, but the message queue is empty.");
+            return;
+        }
+
+        if (mMessageQueue.size() == 1) {
+            writeValueAndNotify(mMessageQueue.remove().toByteArray());
+            return;
+        }
+
+        mHandler.post(mSendMessageWithTimeoutRunnable);
+    }
+
+    private void handleClientAckMessage() {
+        if (Log.isLoggable(TAG, Log.DEBUG)) {
+            Log.d(TAG, "Received ACK from client. Attempting to write next message in queue.");
+        }
+
+        mHandler.removeCallbacks(mSendMessageWithTimeoutRunnable);
+        mRetryCount = 0;
+
+        if (mMessageQueue.isEmpty()) {
+            Log.e(TAG, "Received ACK, but the message queue is empty. Ignoring.");
+            return;
+        }
+
+        // Previous message has been sent successfully so we can start the next message.
+        mMessageQueue.remove();
+        writeNextMessageInQueue();
+    }
+
+    private void sendAcknowledgmentMessage() {
+        writeValueAndNotify(BLEMessageV1Factory.makeAcknowledgementMessage().toByteArray());
+    }
+
+    /**
+     * Convenience method to write the given message to the {@link #mWriteCharacteristic} of this
+     * class. After writing, this method will also send notifications to any listening devices that
+     * the write was made.
+     */
+    private void writeValueAndNotify(@NonNull byte[] message) {
+        mWriteCharacteristic.setValue(message);
+
+        mBlePeripheralManager.notifyCharacteristicChanged(mDevice, mWriteCharacteristic,
+                /* confirm= */ false);
+    }
+
+    /**
+     * A runnable that will write the message at the head of the {@link #mMessageQueue} and set up
+     * a timeout for receiving an ACK for that write.
+     *
+     * <p>If the timeout is reached before an ACK is received, the message write is retried.
+     */
+    private final Runnable mSendMessageWithTimeoutRunnable = new Runnable() {
+        @Override
+        public void run() {
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG, "Sending BLE message; retry count: " + mRetryCount);
+            }
+
+            if (mRetryCount < BLE_MESSAGE_RETRY_LIMIT) {
+                writeValueAndNotify(mMessageQueue.peek().toByteArray());
+                mRetryCount++;
+                mHandler.postDelayed(this, BLE_MESSAGE_RETRY_DELAY_MS);
+                return;
+            }
+
+            mHandler.removeCallbacks(this);
+            mRetryCount = 0;
+            mMessageQueue.clear();
+
+            Log.e(TAG, "Error during BLE message sending - exceeded retry limit.");
+
+            for (BleMessageStreamCallback callback : mCallbacks) {
+                callback.onWriteMessageError();
+            }
+        }
+    };
+}
diff --git a/service/src/com/android/car/trust/BlePeripheralManager.java b/service/src/com/android/car/trust/BlePeripheralManager.java
new file mode 100644
index 0000000..d582708
--- /dev/null
+++ b/service/src/com/android/car/trust/BlePeripheralManager.java
@@ -0,0 +1,559 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.car.trust;
+
+import static android.bluetooth.BluetoothProfile.GATT_SERVER;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattCallback;
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.bluetooth.BluetoothGattDescriptor;
+import android.bluetooth.BluetoothGattServer;
+import android.bluetooth.BluetoothGattServerCallback;
+import android.bluetooth.BluetoothGattService;
+import android.bluetooth.BluetoothManager;
+import android.bluetooth.BluetoothProfile;
+import android.bluetooth.le.AdvertiseCallback;
+import android.bluetooth.le.AdvertiseData;
+import android.bluetooth.le.AdvertiseSettings;
+import android.bluetooth.le.BluetoothLeAdvertiser;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.os.Handler;
+import android.util.Log;
+
+import com.android.car.Utils;
+
+import java.util.UUID;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+/**
+ * A generic class that manages BLE peripheral operations like start/stop advertising, notifying
+ * connects/disconnects and reading/writing values to GATT characteristics.
+ *
+ * @deprecated Enrolling a trusted device through car service is no longer a supported feature.
+ */
+@Deprecated
+public class BlePeripheralManager {
+    private static final String TAG = BlePeripheralManager.class.getSimpleName();
+
+    private static final int BLE_RETRY_LIMIT = 5;
+    private static final int BLE_RETRY_INTERVAL_MS = 1000;
+
+    private static final int GATT_SERVER_RETRY_LIMIT = 20;
+    private static final int GATT_SERVER_RETRY_DELAY_MS = 200;
+
+    // https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth
+    // .service.generic_access.xml
+    private static final UUID GENERIC_ACCESS_PROFILE_UUID =
+            UUID.fromString("00001800-0000-1000-8000-00805f9b34fb");
+    //https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth
+    // .characteristic.gap.device_name.xml
+    private static final UUID DEVICE_NAME_UUID =
+            UUID.fromString("00002a00-0000-1000-8000-00805f9b34fb");
+
+    private final Handler mHandler = new Handler();
+
+    private final Context mContext;
+    private final CopyOnWriteArrayList<Callback> mCallbacks = new CopyOnWriteArrayList<>();
+    private final CopyOnWriteArrayList<OnCharacteristicWriteListener> mWriteListeners =
+            new CopyOnWriteArrayList<>();
+    private final CopyOnWriteArrayList<OnCharacteristicReadListener> mReadListeners =
+            new CopyOnWriteArrayList<>();
+
+    private int mMtuSize = 20;
+
+    private BluetoothManager mBluetoothManager;
+    private BluetoothLeAdvertiser mAdvertiser;
+    private BluetoothGattServer mGattServer;
+    private BluetoothGatt mBluetoothGatt;
+    private int mAdvertiserStartCount;
+    private int mGattServerRetryStartCount;
+    private BluetoothGattService mBluetoothGattService;
+    private AdvertiseCallback mAdvertiseCallback;
+    private AdvertiseData mAdvertiseData;
+
+
+    BlePeripheralManager(Context context) {
+        mContext = context;
+    }
+
+    /**
+     * Registers the given callback to be notified of various events within the
+     * {@link BlePeripheralManager}.
+     *
+     * @param callback The callback to be notified.
+     */
+    void registerCallback(@NonNull Callback callback) {
+        mCallbacks.add(callback);
+    }
+
+    /**
+     * Unregisters a previously registered callback.
+     *
+     * @param callback The callback to unregister.
+     */
+    void unregisterCallback(@NonNull Callback callback) {
+        mCallbacks.remove(callback);
+    }
+
+    /**
+     * Adds a listener to be notified of a write to characteristics.
+     *
+     * @param listener The listener to notify.
+     */
+    void addOnCharacteristicWriteListener(@NonNull OnCharacteristicWriteListener listener) {
+        mWriteListeners.add(listener);
+    }
+
+    /**
+     * Removes the given listener from being notified of characteristic writes.
+     *
+     * @param listener The listener to remove.
+     */
+    void removeOnCharacteristicWriteListener(@NonNull OnCharacteristicWriteListener listener) {
+        mWriteListeners.remove(listener);
+    }
+
+    /**
+     * Adds a listener to be notified of reads to characteristics.
+     *
+     * @param listener The listener to notify.
+     */
+    void addOnCharacteristicReadListener(@NonNull OnCharacteristicReadListener listener) {
+        mReadListeners.add(listener);
+    }
+
+    /**
+     * Removes the given listener from being notified of characteristic reads.
+     *
+     * @param listener The listener to remove.
+     */
+    void removeOnCharacteristicReadistener(@NonNull OnCharacteristicReadListener listener) {
+        mReadListeners.remove(listener);
+    }
+
+    /**
+     * Returns the current MTU size.
+     *
+     * @return The size of the MTU in bytes.
+     */
+    int getMtuSize() {
+        return mMtuSize;
+    }
+
+    /**
+     * Starts the GATT server with the given {@link BluetoothGattService} and begins
+     * advertising.
+     *
+     * <p>It is possible that BLE service is still in TURNING_ON state when this method is invoked.
+     * Therefore, several retries will be made to ensure advertising is started.
+     *
+     * @param service           {@link BluetoothGattService} that will be discovered by clients
+     * @param data              {@link AdvertiseData} data to advertise
+     * @param advertiseCallback {@link AdvertiseCallback} callback for advertiser
+     */
+    void startAdvertising(BluetoothGattService service, AdvertiseData data,
+            AdvertiseCallback advertiseCallback) {
+        if (Log.isLoggable(TAG, Log.DEBUG)) {
+            Log.d(TAG, "startAdvertising: " + service.getUuid().toString());
+        }
+        if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
+            Log.e(TAG, "Attempted start advertising, but system does not support BLE. "
+                    + "Ignoring");
+            return;
+        }
+
+        mBluetoothGattService = service;
+        mAdvertiseCallback = advertiseCallback;
+        mAdvertiseData = data;
+        mGattServerRetryStartCount = 0;
+        mBluetoothManager = (BluetoothManager) mContext.getSystemService(
+                Context.BLUETOOTH_SERVICE);
+        openGattServer();
+    }
+
+    /**
+     * Stops the GATT server from advertising.
+     *
+     * @param advertiseCallback The callback that is associated with the advertisement.
+     */
+    void stopAdvertising(AdvertiseCallback advertiseCallback) {
+        if (mAdvertiser != null) {
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG, "stopAdvertising: ");
+            }
+            mAdvertiser.stopAdvertising(advertiseCallback);
+        }
+    }
+
+    /**
+     * Notifies the characteristic change via {@link BluetoothGattServer}
+     */
+    void notifyCharacteristicChanged(@NonNull BluetoothDevice device,
+            @NonNull BluetoothGattCharacteristic characteristic, boolean confirm) {
+        if (mGattServer == null) {
+            return;
+        }
+
+        boolean result = mGattServer.notifyCharacteristicChanged(device, characteristic, confirm);
+
+        if (Log.isLoggable(TAG, Log.DEBUG)) {
+            Log.d(TAG, "notifyCharacteristicChanged succeeded: " + result);
+        }
+    }
+
+    /**
+     * Connect the Gatt server of the remote device to retrieve device name.
+     */
+    final void retrieveDeviceName(BluetoothDevice device) {
+        mBluetoothGatt = device.connectGatt(mContext, false, mGattCallback);
+    }
+
+    /**
+     * Returns the currently opened GATT server within this manager.
+     *
+     * @return An opened GATT server or {@code null} if none have been opened.
+     */
+    @Nullable
+    BluetoothGattServer getGattServer() {
+        return mGattServer;
+    }
+
+    /**
+     * Cleans up the BLE GATT server state.
+     */
+    void cleanup() {
+        // Stops the advertiser, scanner and GATT server. This needs to be done to avoid leaks.
+        if (mAdvertiser != null) {
+            mAdvertiser.cleanup();
+        }
+
+        if (mGattServer != null) {
+            mGattServer.clearServices();
+            try {
+                for (BluetoothDevice d : mBluetoothManager.getConnectedDevices(GATT_SERVER)) {
+                    mGattServer.cancelConnection(d);
+                }
+            } catch (UnsupportedOperationException e) {
+                Log.e(TAG, "Error getting connected devices", e);
+            } finally {
+                stopGattServer();
+            }
+        }
+    }
+
+    /**
+     * Close the GATT Server
+     */
+    void stopGattServer() {
+        if (mGattServer == null) {
+            return;
+        }
+        if (Log.isLoggable(TAG, Log.DEBUG)) {
+            Log.d(TAG, "stopGattServer");
+        }
+        if (mBluetoothGatt != null) {
+            mBluetoothGatt.disconnect();
+        }
+        mGattServer.close();
+        mGattServer = null;
+    }
+
+    private void openGattServer() {
+        // Only open one Gatt server.
+        if (mGattServer != null) {
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG, "Gatt Server created, retry count: " + mGattServerRetryStartCount);
+            }
+            mGattServer.clearServices();
+            mGattServer.addService(mBluetoothGattService);
+            AdvertiseSettings settings = new AdvertiseSettings.Builder()
+                    .setAdvertiseMode(AdvertiseSettings.ADVERTISE_MODE_LOW_LATENCY)
+                    .setTxPowerLevel(AdvertiseSettings.ADVERTISE_TX_POWER_HIGH)
+                    .setConnectable(true)
+                    .build();
+            mAdvertiserStartCount = 0;
+            startAdvertisingInternally(settings, mAdvertiseData, mAdvertiseCallback);
+            mGattServerRetryStartCount = 0;
+        } else if (mGattServerRetryStartCount < GATT_SERVER_RETRY_LIMIT) {
+            mGattServer = mBluetoothManager.openGattServer(mContext, mGattServerCallback);
+            mGattServerRetryStartCount++;
+            mHandler.postDelayed(() -> openGattServer(), GATT_SERVER_RETRY_DELAY_MS);
+        } else {
+            Log.e(TAG, "Gatt server not created - exceeded retry limit.");
+        }
+    }
+
+    private void startAdvertisingInternally(AdvertiseSettings settings, AdvertiseData data,
+            AdvertiseCallback advertiseCallback) {
+        if (BluetoothAdapter.getDefaultAdapter() != null) {
+            mAdvertiser = BluetoothAdapter.getDefaultAdapter().getBluetoothLeAdvertiser();
+        }
+
+        if (mAdvertiser != null) {
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG, "Advertiser created, retry count: " + mAdvertiserStartCount);
+            }
+            mAdvertiser.startAdvertising(settings, data, advertiseCallback);
+            mAdvertiserStartCount = 0;
+        } else if (mAdvertiserStartCount < BLE_RETRY_LIMIT) {
+            mHandler.postDelayed(
+                    () -> startAdvertisingInternally(settings, data, advertiseCallback),
+                    BLE_RETRY_INTERVAL_MS);
+            mAdvertiserStartCount += 1;
+        } else {
+            Log.e(TAG, "Cannot start BLE Advertisement.  BT Adapter: "
+                    + BluetoothAdapter.getDefaultAdapter() + " Advertise Retry count: "
+                    + mAdvertiserStartCount);
+        }
+    }
+
+    private final BluetoothGattServerCallback mGattServerCallback =
+            new BluetoothGattServerCallback() {
+                @Override
+                public void onConnectionStateChange(BluetoothDevice device, int status,
+                        int newState) {
+                    if (Log.isLoggable(TAG, Log.DEBUG)) {
+                        Log.d(TAG, "BLE Connection State Change: " + newState);
+                    }
+                    switch (newState) {
+                        case BluetoothProfile.STATE_CONNECTED:
+                            for (Callback callback : mCallbacks) {
+                                callback.onRemoteDeviceConnected(device);
+                            }
+                            break;
+                        case BluetoothProfile.STATE_DISCONNECTED:
+                            for (Callback callback : mCallbacks) {
+                                callback.onRemoteDeviceDisconnected(device);
+                            }
+                            break;
+                        default:
+                            Log.w(TAG,
+                                    "Connection state not connecting or disconnecting; ignoring: "
+                                            + newState);
+                    }
+                }
+
+                @Override
+                public void onServiceAdded(int status, BluetoothGattService service) {
+                    if (Log.isLoggable(TAG, Log.DEBUG)) {
+                        Log.d(TAG,
+                                "Service added status: " + status + " uuid: " + service.getUuid());
+                    }
+                }
+
+                @Override
+                public void onCharacteristicReadRequest(BluetoothDevice device, int requestId,
+                        int offset, BluetoothGattCharacteristic characteristic) {
+                    if (Log.isLoggable(TAG, Log.DEBUG)) {
+                        Log.d(TAG, "Read request for characteristic: " + characteristic.getUuid());
+                    }
+
+                    boolean isSuccessful = mGattServer.sendResponse(device, requestId,
+                            BluetoothGatt.GATT_SUCCESS, offset, characteristic.getValue());
+
+                    if (isSuccessful) {
+                        for (OnCharacteristicReadListener listener : mReadListeners) {
+                            listener.onCharacteristicRead(device, characteristic);
+                        }
+                    } else {
+                        stopGattServer();
+                        for (Callback callback : mCallbacks) {
+                            callback.onRemoteDeviceDisconnected(device);
+                        }
+                    }
+                }
+
+                @Override
+                public void onCharacteristicWriteRequest(BluetoothDevice device, int requestId,
+                        BluetoothGattCharacteristic characteristic, boolean preparedWrite,
+                        boolean responseNeeded, int offset, byte[] value) {
+                    if (Log.isLoggable(TAG, Log.DEBUG)) {
+                        Log.d(TAG, "Write request for characteristic: " + characteristic.getUuid()
+                                + "value: " + Utils.byteArrayToHexString(value));
+                    }
+
+                    mGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS,
+                            offset, value);
+
+                    for (OnCharacteristicWriteListener listener : mWriteListeners) {
+                        listener.onCharacteristicWrite(device, characteristic, value);
+                    }
+                }
+
+                @Override
+                public void onDescriptorWriteRequest(BluetoothDevice device, int requestId,
+                        BluetoothGattDescriptor descriptor, boolean preparedWrite,
+                        boolean responseNeeded, int offset, byte[] value) {
+                    if (Log.isLoggable(TAG, Log.DEBUG)) {
+                        Log.d(TAG, "Write request for descriptor: " + descriptor.getUuid()
+                                + "; value: " + Utils.byteArrayToHexString(value));
+                    }
+
+                    mGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS,
+                            offset, value);
+                }
+
+                @Override
+                public void onMtuChanged(BluetoothDevice device, int mtu) {
+                    if (Log.isLoggable(TAG, Log.DEBUG)) {
+                        Log.d(TAG, "onMtuChanged: " + mtu + " for device " + device.getAddress());
+                    }
+
+                    mMtuSize = mtu;
+
+                    for (Callback callback : mCallbacks) {
+                        callback.onMtuSizeChanged(mtu);
+                    }
+                }
+
+            };
+
+    private final BluetoothGattCallback mGattCallback = new BluetoothGattCallback() {
+        @Override
+        public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG, "Gatt Connection State Change: " + newState);
+            }
+            switch (newState) {
+                case BluetoothProfile.STATE_CONNECTED:
+                    if (Log.isLoggable(TAG, Log.DEBUG)) {
+                        Log.d(TAG, "Gatt connected");
+                    }
+                    mBluetoothGatt.discoverServices();
+                    break;
+                case BluetoothProfile.STATE_DISCONNECTED:
+                    if (Log.isLoggable(TAG, Log.DEBUG)) {
+                        Log.d(TAG, "Gatt Disconnected");
+                    }
+                    break;
+                default:
+                    if (Log.isLoggable(TAG, Log.DEBUG)) {
+                        Log.d(TAG,
+                                "Connection state not connecting or disconnecting; ignoring: "
+                                        + newState);
+                    }
+            }
+        }
+
+        @Override
+        public void onServicesDiscovered(BluetoothGatt gatt, int status) {
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG, "Gatt Services Discovered");
+            }
+            BluetoothGattService gapService = mBluetoothGatt.getService(
+                    GENERIC_ACCESS_PROFILE_UUID);
+            if (gapService == null) {
+                Log.e(TAG, "Generic Access Service is Null");
+                return;
+            }
+            BluetoothGattCharacteristic deviceNameCharacteristic = gapService.getCharacteristic(
+                    DEVICE_NAME_UUID);
+            if (deviceNameCharacteristic == null) {
+                Log.e(TAG, "Device Name Characteristic is Null");
+                return;
+            }
+            mBluetoothGatt.readCharacteristic(deviceNameCharacteristic);
+        }
+
+        @Override
+        public void onCharacteristicRead(BluetoothGatt gatt,
+                BluetoothGattCharacteristic characteristic, int status) {
+            if (status == BluetoothGatt.GATT_SUCCESS) {
+                String deviceName = characteristic.getStringValue(0);
+                if (Log.isLoggable(TAG, Log.DEBUG)) {
+                    Log.d(TAG, "BLE Device Name: " + deviceName);
+                }
+
+                for (Callback callback : mCallbacks) {
+                    callback.onDeviceNameRetrieved(deviceName);
+                }
+            } else {
+                Log.e(TAG, "Reading GAP Failed: " + status);
+            }
+        }
+    };
+
+    /**
+     * Interface to be notified of various events within the {@link BlePeripheralManager}.
+     */
+    interface Callback {
+         /**
+         * Triggered when the name of the remote device is retrieved.
+         *
+         * @param deviceName Name of the remote device.
+         */
+        void onDeviceNameRetrieved(@Nullable String deviceName);
+
+        /**
+         * Triggered if a remote client has requested to change the MTU for a given connection.
+         *
+         * @param size The new MTU size.
+         */
+        void onMtuSizeChanged(int size);
+
+        /**
+         * Triggered when a device (GATT client) connected.
+         *
+         * @param device Remote device that connected on BLE.
+         */
+        void onRemoteDeviceConnected(@NonNull BluetoothDevice device);
+
+        /**
+         * Triggered when a device (GATT client) disconnected.
+         *
+         * @param device Remote device that disconnected on BLE.
+         */
+        void onRemoteDeviceDisconnected(@NonNull BluetoothDevice device);
+    }
+
+    /**
+     * An interface for classes that wish to be notified of writes to a characteristic.
+     */
+    interface OnCharacteristicWriteListener {
+        /**
+         * Triggered when this BlePeripheralManager receives a write request from a remote device.
+         *
+         * @param device The bluetooth device that holds the characteristic.
+         * @param characteristic The characteristic that was written to.
+         * @param value The value that was written.
+         */
+        void onCharacteristicWrite(
+                @NonNull BluetoothDevice device,
+                @NonNull BluetoothGattCharacteristic characteristic,
+                @NonNull byte[] value);
+    }
+
+    /**
+     * An interface for classes that wish to be notified of reads on a characteristic.
+     */
+    interface OnCharacteristicReadListener {
+        /**
+         * Triggered when this BlePeripheralManager receives a read request from a remote device.
+         *
+         * @param device The bluetooth device that holds the characteristic.
+         * @param characteristic The characteristic that was read from.
+         */
+        void onCharacteristicRead(
+                @NonNull BluetoothDevice device,
+                @NonNull BluetoothGattCharacteristic characteristic);
+    }
+}
diff --git a/service/src/com/android/car/trust/CarBleTrustAgent.java b/service/src/com/android/car/trust/CarBleTrustAgent.java
index d3790ff..428327c 100644
--- a/service/src/com/android/car/trust/CarBleTrustAgent.java
+++ b/service/src/com/android/car/trust/CarBleTrustAgent.java
@@ -48,7 +48,10 @@
  * The system {@link com.android.server.trust.TrustManagerService} binds to this agent and uses
  * the data it receives from this agent to authorize a user in lieu of the PIN/Pattern/Password
  * credentials.
+ *
+ * @deprecated Enrolling a trusted device through car service is no longer supported.
  */
+@Deprecated
 public class CarBleTrustAgent extends TrustAgentService {
     private static final String TAG = CarBleTrustAgent.class.getSimpleName();
     private boolean mIsDeviceLocked;
@@ -234,7 +237,7 @@
 
     private void onBluetoothStateChanged(int state) {
         if (Log.isLoggable(TAG, Log.DEBUG)) {
-            Log.d(TAG, "onBluetoothStateChanged: " + state);
+            Log.d(TAG, "onBluetoothStateChanged: " + BluetoothAdapter.nameForState(state));
         }
         if (!mIsDeviceLocked) {
             return;
diff --git a/service/src/com/android/car/trust/CarCompanionDeviceStorage.java b/service/src/com/android/car/trust/CarCompanionDeviceStorage.java
new file mode 100644
index 0000000..47edebb
--- /dev/null
+++ b/service/src/com/android/car/trust/CarCompanionDeviceStorage.java
@@ -0,0 +1,296 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.trust;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.security.keystore.KeyGenParameterSpec;
+import android.security.keystore.KeyProperties;
+import android.util.Base64;
+import android.util.Log;
+
+import com.android.car.R;
+
+import java.io.IOException;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.Key;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.UnrecoverableKeyException;
+import java.security.cert.CertificateException;
+import java.util.UUID;
+
+import javax.crypto.BadPaddingException;
+import javax.crypto.Cipher;
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.KeyGenerator;
+import javax.crypto.NoSuchPaddingException;
+import javax.crypto.spec.GCMParameterSpec;
+
+/**
+ * Storage for Trusted Devices in a car.
+ */
+class CarCompanionDeviceStorage {
+    private static final String TAG = CarCompanionDeviceStorage.class.getSimpleName();
+
+    private static final String UNIQUE_ID_KEY = "CTABM_unique_id";
+    private static final String PREF_ENCRYPTION_KEY_PREFIX = "CTABM_encryption_key";
+    private static final String KEY_ALIAS = "Ukey2Key";
+    private static final String CIPHER_TRANSFORMATION = "AES/GCM/NoPadding";
+    private static final String KEYSTORE_PROVIDER = "AndroidKeyStore";
+    private static final String IV_SPEC_SEPARATOR = ";";
+
+    // The length of the authentication tag for a cipher in GCM mode. The GCM specification states
+    // that this length can only have the values {128, 120, 112, 104, 96}. Using the highest
+    // possible value.
+    private static final int GCM_AUTHENTICATION_TAG_LENGTH = 128;
+
+    private Context mContext;
+    private SharedPreferences mSharedPreferences;
+    private UUID mUniqueId;
+
+    CarCompanionDeviceStorage(@NonNull Context context) {
+        mContext = context;
+    }
+
+    /** Return the car TrustedAgent {@link SharedPreferences}. */
+    @NonNull
+    SharedPreferences getSharedPrefs() {
+        // This should be called only after user 0 is unlocked.
+        if (mSharedPreferences != null) {
+            return mSharedPreferences;
+        }
+        mSharedPreferences = mContext.getSharedPreferences(
+                mContext.getString(R.string.token_handle_shared_preferences), Context.MODE_PRIVATE);
+        return mSharedPreferences;
+    }
+
+    /**
+     * Returns User Id for the given token handle
+     *
+     * @param handle The handle corresponding to the escrow token
+     * @return User id corresponding to the handle
+     */
+    int getUserHandleByTokenHandle(long handle) {
+        return getSharedPrefs().getInt(String.valueOf(handle), -1);
+    }
+
+    /**
+     * Get communication encryption key for the given device
+     *
+     * @param deviceId id of trusted device
+     * @return encryption key, null if device id is not recognized
+     */
+    @Nullable
+    byte[] getEncryptionKey(@NonNull String deviceId) {
+        SharedPreferences prefs = getSharedPrefs();
+        String key = createSharedPrefKey(deviceId);
+        if (!prefs.contains(key)) {
+            return null;
+        }
+
+        // This value will not be "null" because we already checked via a call to contains().
+        String[] values = prefs.getString(key, null).split(IV_SPEC_SEPARATOR);
+
+        if (values.length != 2) {
+            return null;
+        }
+
+        byte[] encryptedKey = Base64.decode(values[0], Base64.DEFAULT);
+        byte[] ivSpec = Base64.decode(values[1], Base64.DEFAULT);
+        return decryptWithKeyStore(KEY_ALIAS, encryptedKey, ivSpec);
+    }
+
+    /**
+     * Save encryption key for the given device
+     *
+     * @param deviceId did of trusted device
+     * @param encryptionKey encryption key
+     * @return {@code true} if the operation succeeded
+     */
+    boolean saveEncryptionKey(@NonNull String deviceId, @NonNull byte[] encryptionKey) {
+        String encryptedKey = encryptWithKeyStore(KEY_ALIAS, encryptionKey);
+        if (encryptedKey == null) {
+            return false;
+        }
+        if (getSharedPrefs().contains(createSharedPrefKey(deviceId))) {
+            clearEncryptionKey(deviceId);
+        }
+
+        return getSharedPrefs()
+                .edit()
+                .putString(createSharedPrefKey(deviceId), encryptedKey)
+                .commit();
+    }
+
+    /**
+     * Clear the encryption key for the given device
+     *
+     * @param deviceId id of the peer device
+     */
+    void clearEncryptionKey(@Nullable String deviceId) {
+        if (deviceId == null) {
+            return;
+        }
+        getSharedPrefs()
+                .edit()
+                .remove(createSharedPrefKey(deviceId))
+                .commit();
+    }
+
+    /**
+     * Encrypt value with designated key
+     *
+     * <p>The encrypted value is of the form:
+     *
+     * <p>key + IV_SPEC_SEPARATOR + ivSpec
+     *
+     * <p>The {@code ivSpec} is needed to decrypt this key later on.
+     *
+     * @param keyAlias KeyStore alias for key to use
+     * @param value a value to encrypt
+     * @return encrypted value, null if unable to encrypt
+     */
+    @Nullable
+    String encryptWithKeyStore(@NonNull String keyAlias, @Nullable byte[] value) {
+        if (value == null) {
+            return null;
+        }
+
+        Key key = getKeyStoreKey(keyAlias);
+        try {
+            Cipher cipher = Cipher.getInstance(CIPHER_TRANSFORMATION);
+            cipher.init(Cipher.ENCRYPT_MODE, key);
+            return new StringBuffer(Base64.encodeToString(cipher.doFinal(value), Base64.DEFAULT))
+                    .append(IV_SPEC_SEPARATOR)
+                    .append(Base64.encodeToString(cipher.getIV(), Base64.DEFAULT))
+                    .toString();
+        } catch (IllegalBlockSizeException
+                | BadPaddingException
+                | NoSuchAlgorithmException
+                | NoSuchPaddingException
+                | IllegalStateException
+                | InvalidKeyException e) {
+            Log.e(TAG, "Unable to encrypt value with key " + keyAlias, e);
+            return null;
+        }
+    }
+
+    /**
+     * Decrypt value with designated key
+     *
+     * @param keyAlias KeyStore alias for key to use
+     * @param value encrypted value
+     * @return decrypted value, null if unable to decrypt
+     */
+    @Nullable
+    byte[] decryptWithKeyStore(@NonNull String keyAlias, @Nullable byte[] value,
+            @NonNull byte[] ivSpec) {
+        if (value == null) {
+            return null;
+        }
+
+        try {
+            Key key = getKeyStoreKey(keyAlias);
+            Cipher cipher = Cipher.getInstance(CIPHER_TRANSFORMATION);
+            cipher.init(Cipher.DECRYPT_MODE, key,
+                    new GCMParameterSpec(GCM_AUTHENTICATION_TAG_LENGTH, ivSpec));
+            return cipher.doFinal(value);
+        } catch (IllegalBlockSizeException
+                | BadPaddingException
+                | NoSuchAlgorithmException
+                | NoSuchPaddingException
+                | IllegalStateException
+                | InvalidKeyException
+                | InvalidAlgorithmParameterException e) {
+            Log.e(TAG, "Unable to decrypt value with key " + keyAlias, e);
+            return null;
+        }
+    }
+
+    private Key getKeyStoreKey(@NonNull String keyAlias) {
+        KeyStore keyStore;
+        try {
+            keyStore = KeyStore.getInstance(KEYSTORE_PROVIDER);
+            keyStore.load(null);
+            if (!keyStore.containsAlias(keyAlias)) {
+                KeyGenerator keyGenerator = KeyGenerator.getInstance(
+                        KeyProperties.KEY_ALGORITHM_AES, KEYSTORE_PROVIDER);
+                keyGenerator.init(
+                        new KeyGenParameterSpec.Builder(keyAlias,
+                                KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
+                                .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
+                                .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
+                                .build());
+                keyGenerator.generateKey();
+            }
+            return keyStore.getKey(keyAlias, null);
+
+        } catch (KeyStoreException
+                | NoSuchAlgorithmException
+                | UnrecoverableKeyException
+                | NoSuchProviderException
+                | CertificateException
+                | IOException
+                | InvalidAlgorithmParameterException e) {
+            Log.e(TAG, "Unable to retrieve key " + keyAlias + " from KeyStore.", e);
+            throw new IllegalStateException(e);
+        }
+    }
+
+    /**
+     * Get the unique id for head unit. Persists on device until factory reset.
+     * This should be called only after user 0 is unlocked.
+     *
+     * @return unique id, or null if unable to retrieve generated id (this should never happen)
+     */
+    @Nullable
+    UUID getUniqueId() {
+        if (mUniqueId != null) {
+            return mUniqueId;
+        }
+
+        SharedPreferences prefs = getSharedPrefs();
+        if (prefs.contains(UNIQUE_ID_KEY)) {
+            mUniqueId = UUID.fromString(
+                    prefs.getString(UNIQUE_ID_KEY, null));
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG, "Found existing trusted unique id: "
+                        + prefs.getString(UNIQUE_ID_KEY, ""));
+            }
+        } else {
+            mUniqueId = UUID.randomUUID();
+            if (!prefs.edit().putString(UNIQUE_ID_KEY, mUniqueId.toString()).commit()) {
+                mUniqueId = null;
+            } else if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG, "Generated new trusted unique id: "
+                        + prefs.getString(UNIQUE_ID_KEY, ""));
+            }
+        }
+
+        return mUniqueId;
+    }
+
+    private String createSharedPrefKey(@NonNull String deviceId) {
+        return PREF_ENCRYPTION_KEY_PREFIX + deviceId;
+    }
+}
diff --git a/service/src/com/android/car/trust/CarTrustAgentBleManager.java b/service/src/com/android/car/trust/CarTrustAgentBleManager.java
index 1e43300..b1fedd0 100644
--- a/service/src/com/android/car/trust/CarTrustAgentBleManager.java
+++ b/service/src/com/android/car/trust/CarTrustAgentBleManager.java
@@ -17,6 +17,7 @@
 package com.android.car.trust;
 
 import android.annotation.IntDef;
+import android.annotation.Nullable;
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothGattCharacteristic;
@@ -27,36 +28,33 @@
 import android.bluetooth.le.AdvertiseSettings;
 import android.content.Context;
 import android.os.Handler;
-import android.os.Looper;
 import android.os.ParcelUuid;
 import android.util.Log;
 
-import androidx.annotation.Nullable;
+import androidx.collection.SimpleArrayMap;
 
-import com.android.car.BLEStreamProtos.BLEMessageProto.BLEMessage;
 import com.android.car.BLEStreamProtos.BLEOperationProto.OperationType;
 import com.android.car.BLEStreamProtos.VersionExchangeProto.BLEVersionExchange;
-import com.android.car.CarLocalServices;
 import com.android.car.R;
 import com.android.car.Utils;
 import com.android.car.protobuf.InvalidProtocolBufferException;
+import com.android.internal.annotations.VisibleForTesting;
 
-import java.io.IOException;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
-import java.util.LinkedList;
+import java.util.ArrayList;
 import java.util.List;
-import java.util.Queue;
 import java.util.UUID;
 import java.util.concurrent.TimeUnit;
 
 /**
  * A BLE Service that is used for communicating with the trusted peer device. This extends from a
- * more generic {@link BleManager} and has more context on the BLE requirements for the Trusted
- * device feature. It has knowledge on the GATT services and characteristics that are specific to
- * the Trusted Device feature.
+ * more generic {@link BlePeripheralManager} and has more context on the BLE requirements for the
+ * Trusted device feature. It has knowledge on the GATT services and characteristics that are
+ * specific to the Trusted Device feature.
  */
-class CarTrustAgentBleManager extends BleManager {
+class CarTrustAgentBleManager implements BleMessageStreamCallback, BlePeripheralManager.Callback,
+        BlePeripheralManager.OnCharacteristicWriteListener {
     private static final String TAG = "CarTrustBLEManager";
 
     /**
@@ -91,17 +89,16 @@
     private static final int TRUSTED_DEVICE_OPERATION_NONE = 0;
     private static final int TRUSTED_DEVICE_OPERATION_ENROLLMENT = 1;
     private static final int TRUSTED_DEVICE_OPERATION_UNLOCK = 2;
-    private static final long BLE_MESSAGE_RETRY_DELAY_MS = TimeUnit.SECONDS.toMillis(2);
-    private static final int BLE_MESSAGE_RETRY_LIMIT = 20;
+    @VisibleForTesting
+    static final long BLE_MESSAGE_RETRY_DELAY_MS = TimeUnit.SECONDS.toMillis(2);
+
+    private final Context mContext;
+    private final BlePeripheralManager mBlePeripheralManager;
 
     @TrustedDeviceOperation
     private int mCurrentTrustedDeviceOperation = TRUSTED_DEVICE_OPERATION_NONE;
-    private CarTrustedDeviceService mCarTrustedDeviceService;
-    private CarTrustAgentEnrollmentService mCarTrustAgentEnrollmentService;
-    private CarTrustAgentUnlockService mCarTrustAgentUnlockService;
     private String mOriginalBluetoothName;
     private byte[] mUniqueId;
-    private String mEnrollmentDeviceName;
 
     /**
      * The maximum amount of bytes that can be written over BLE.
@@ -113,6 +110,13 @@
      */
     private int mMaxWriteSize = 20;
 
+    @VisibleForTesting
+    int mBleMessageRetryLimit = 20;
+
+    private final List<BleEventCallback> mBleEventCallbacks = new ArrayList<>();
+    private AdvertiseCallback mEnrollmentAdvertisingCallback;
+    private SendMessageCallback mSendMessageCallback;
+
     // Enrollment Service and Characteristic UUIDs
     private UUID mEnrollmentServiceUuid;
     private UUID mEnrollmentClientWriteUuid;
@@ -125,23 +129,40 @@
     private UUID mUnlockServerWriteUuid;
     private BluetoothGattService mUnlockGattService;
 
-    private Queue<BLEMessage> mMessageQueue = new LinkedList<>();
-    private BLEMessagePayloadStream mBleMessagePayloadStream = new BLEMessagePayloadStream();
+    @Nullable
+    private BleMessageStream mMessageStream;
+    private final Handler mHandler = new Handler();
 
-    // This is a boolean because there's only one supported version.
-    private boolean mIsVersionExchanged;
-    private int mBleMessageRetryStartCount;
-    private Handler mHandler = new Handler(Looper.getMainLooper());
-    private Runnable mSendRepeatedBleMessage;
+    // A map of enrollment/unlock client write uuid -> listener
+    private final SimpleArrayMap<UUID, DataReceivedListener> mDataReceivedListeners =
+            new SimpleArrayMap<>();
 
-    CarTrustAgentBleManager(Context context) {
-        super(context);
+    CarTrustAgentBleManager(Context context, BlePeripheralManager blePeripheralManager) {
+        mContext = context;
+        mBlePeripheralManager = blePeripheralManager;
+        mBlePeripheralManager.registerCallback(this);
+    }
+
+    /**
+     * This should be called before starting unlock advertising
+     */
+    void setUniqueId(UUID uniqueId) {
+        mUniqueId = Utils.uuidToBytes(uniqueId);
+    }
+
+    void cleanup() {
+        mBlePeripheralManager.cleanup();
+    }
+
+    void stopGattServer() {
+        mBlePeripheralManager.stopGattServer();
     }
 
     // Overriding some of the {@link BLEManager} methods to be specific for Trusted Device feature.
     @Override
     public void onRemoteDeviceConnected(BluetoothDevice device) {
-        if (getTrustedDeviceService() == null) {
+        if (mBleEventCallbacks.isEmpty()) {
+            Log.e(TAG, "No valid BleEventCallback for trust device.");
             return;
         }
 
@@ -149,45 +170,49 @@
         // stored in sharedPreference for further use.
         if (mCurrentTrustedDeviceOperation == TRUSTED_DEVICE_OPERATION_ENROLLMENT
                 && device.getName() == null) {
-            retrieveDeviceName(device);
+            mBlePeripheralManager.retrieveDeviceName(device);
         }
 
-        mMessageQueue.clear();
-        mIsVersionExchanged = false;
-        getTrustedDeviceService().onRemoteDeviceConnected(device);
-        if (mSendRepeatedBleMessage != null) {
-            mHandler.removeCallbacks(mSendRepeatedBleMessage);
-            mSendRepeatedBleMessage = null;
+        if (mMessageStream != null) {
+            mMessageStream.unregisterCallback(this);
+            mMessageStream = null;
         }
+
+        mSendMessageCallback = null;
+
+        mBlePeripheralManager.addOnCharacteristicWriteListener(this);
+        mBleEventCallbacks.forEach(bleEventCallback ->
+                bleEventCallback.onRemoteDeviceConnected(device));
     }
 
     @Override
     public void onRemoteDeviceDisconnected(BluetoothDevice device) {
-        if (getTrustedDeviceService() != null) {
-            getTrustedDeviceService().onRemoteDeviceDisconnected(device);
+        mBlePeripheralManager.removeOnCharacteristicWriteListener(this);
+        mBleEventCallbacks.forEach(bleEventCallback ->
+                bleEventCallback.onRemoteDeviceDisconnected(device));
+
+        if (mMessageStream != null) {
+            mMessageStream.unregisterCallback(this);
+            mMessageStream = null;
         }
 
-        mMessageQueue.clear();
-        mIsVersionExchanged = false;
-        mBleMessagePayloadStream.reset();
-
-        if (mSendRepeatedBleMessage != null) {
-            mHandler.removeCallbacks(mSendRepeatedBleMessage);
-        }
-        mSendRepeatedBleMessage = null;
+        mSendMessageCallback = null;
     }
 
     @Override
-    protected void onDeviceNameRetrieved(@Nullable String deviceName) {
-        if (getTrustedDeviceService() != null) {
-            getTrustedDeviceService().onDeviceNameRetrieved(deviceName);
-        }
+    public void onDeviceNameRetrieved(@Nullable String deviceName) {
+        mBleEventCallbacks.forEach(bleEventCallback ->
+                bleEventCallback.onClientDeviceNameRetrieved(deviceName));
     }
 
     @Override
-    protected void onMtuSizeChanged(int size) {
+    public void onMtuSizeChanged(int size) {
         mMaxWriteSize = size - ATT_PAYLOAD_RESERVED_BYTES;
 
+        if (mMessageStream != null) {
+            mMessageStream.setMaxWriteSize(mMaxWriteSize);
+        }
+
         if (Log.isLoggable(TAG, Log.DEBUG)) {
             Log.d(TAG, "MTU size changed to: " + size
                     + "; setting max payload size to: " + mMaxWriteSize);
@@ -195,136 +220,52 @@
     }
 
     @Override
-    public void onCharacteristicWrite(BluetoothDevice device, int requestId,
-            BluetoothGattCharacteristic characteristic, boolean preparedWrite,
-            boolean responseNeeded, int offset, byte[] value) {
+    public void onCharacteristicWrite(BluetoothDevice device,
+            BluetoothGattCharacteristic characteristic, byte[] value) {
         UUID uuid = characteristic.getUuid();
         if (Log.isLoggable(TAG, Log.DEBUG)) {
             Log.d(TAG, "onCharacteristicWrite received uuid: " + uuid);
         }
 
-        if (!mIsVersionExchanged) {
+        if (mMessageStream == null) {
             resolveBLEVersion(device, value, uuid);
             return;
         }
 
-        BLEMessage message;
-        try {
-            message = BLEMessage.parseFrom(value);
-        } catch (InvalidProtocolBufferException e) {
-            Log.e(TAG, "Can not parse BLE message", e);
-            return;
-        }
-
-        if (message.getOperation() == OperationType.ACK) {
-            handleClientAckMessage(device, uuid);
-            return;
-        }
-
-        // This write operation is not thread safe individually, but is guarded by the callback
-        // here.
-        try {
-            mBleMessagePayloadStream.write(message);
-        } catch (IOException e) {
-            Log.e(TAG, "Can write the BLE message's payload", e);
-            return;
-        }
-
-        if (!mBleMessagePayloadStream.isComplete()) {
-            // If it's not complete, make sure the client knows that this message was received.
-            sendAcknowledgmentMessage(device, uuid);
-            return;
-        }
-
-        if (uuid.equals(mEnrollmentClientWriteUuid)) {
-            if (getEnrollmentService() != null) {
-                getEnrollmentService().onEnrollmentDataReceived(
-                        mBleMessagePayloadStream.toByteArray());
-            }
-        } else if (uuid.equals(mUnlockClientWriteUuid)) {
-            if (getUnlockService() != null) {
-                getUnlockService().onUnlockDataReceived(mBleMessagePayloadStream.toByteArray());
-            }
-        }
-
-        mBleMessagePayloadStream.reset();
+        Log.e(TAG, "Received a message but message stream has already been created. "
+                + "Was this manager not unregistered as a listener for writes?");
     }
 
-    @Override
-    public void onCharacteristicRead(BluetoothDevice device, int requestId, int offset,
-            final BluetoothGattCharacteristic characteristic) {
-        // Ignored read requests.
-    }
-
-    @Nullable
-    private CarTrustedDeviceService getTrustedDeviceService() {
-        if (mCarTrustedDeviceService == null) {
-            mCarTrustedDeviceService = CarLocalServices.getService(CarTrustedDeviceService.class);
-        }
-        return mCarTrustedDeviceService;
-    }
-
-    @Nullable
-    private CarTrustAgentEnrollmentService getEnrollmentService() {
-        if (mCarTrustAgentEnrollmentService != null) {
-            return mCarTrustAgentEnrollmentService;
-        }
-
-        if (getTrustedDeviceService() != null) {
-            mCarTrustAgentEnrollmentService =
-                    getTrustedDeviceService().getCarTrustAgentEnrollmentService();
-        }
-        return mCarTrustAgentEnrollmentService;
-    }
-
-    @Nullable
-    private CarTrustAgentUnlockService getUnlockService() {
-        if (mCarTrustAgentUnlockService != null) {
-            return mCarTrustAgentUnlockService;
-        }
-
-        if (getTrustedDeviceService() != null) {
-            mCarTrustAgentUnlockService = getTrustedDeviceService().getCarTrustAgentUnlockService();
-        }
-        return mCarTrustAgentUnlockService;
-    }
-
-    @Nullable
-    private byte[] getUniqueId() {
-        if (mUniqueId != null) {
-            return mUniqueId;
-        }
-
-        if (getTrustedDeviceService() != null && getTrustedDeviceService().getUniqueId() != null) {
-            mUniqueId = Utils.uuidToBytes(getTrustedDeviceService().getUniqueId());
-        }
-        return mUniqueId;
-    }
-
-    @Nullable
-    private String getEnrollmentDeviceName() {
-        if (mEnrollmentDeviceName != null) {
-            return mEnrollmentDeviceName;
-        }
-
-        if (getTrustedDeviceService() != null) {
-            mEnrollmentDeviceName = getTrustedDeviceService().getEnrollmentDeviceName();
-        }
-        return mEnrollmentDeviceName;
+    @VisibleForTesting
+    void setBleMessageRetryLimit(int limit) {
+        mBleMessageRetryLimit = limit;
     }
 
     private void resolveBLEVersion(BluetoothDevice device, byte[] value,
             UUID clientCharacteristicUUID) {
-        BluetoothGattCharacteristic characteristic =
+        BluetoothGattCharacteristic writeCharacteristic =
                 getCharacteristicForWrite(clientCharacteristicUUID);
 
-        if (characteristic == null) {
-            Log.e(TAG, "Invalid UUID (" + clientCharacteristicUUID
+        if (writeCharacteristic == null) {
+            Log.e(TAG, "Invalid write UUID (" + clientCharacteristicUUID
                     + ") during version exchange; disconnecting from remote device.");
             disconnectRemoteDevice();
             return;
         }
 
+        BluetoothGattCharacteristic readCharacteristic =
+                clientCharacteristicUUID.equals(mEnrollmentClientWriteUuid)
+                        ? mEnrollmentGattService.getCharacteristic(clientCharacteristicUUID)
+                        : mUnlockGattService.getCharacteristic(clientCharacteristicUUID);
+
+        // If this occurs, then there is a bug in the retrieval code above.
+        if (readCharacteristic == null) {
+            Log.e(TAG, "No read characteristic corresponding to UUID ("
+                    + clientCharacteristicUUID + "). Cannot listen for messages. Disconnecting.");
+            disconnectRemoteDevice();
+            return;
+        }
+
         BLEVersionExchange deviceVersion;
         try {
             deviceVersion = BLEVersionExchange.parseFrom(value);
@@ -334,20 +275,32 @@
             return;
         }
 
-        if (!BLEVersionExchangeResolver.hasSupportedVersion(deviceVersion)) {
-            Log.e(TAG, "No supported version found during version exchange.");
+        mMessageStream = BLEVersionExchangeResolver.resolveToStream(
+                deviceVersion, device, mBlePeripheralManager, writeCharacteristic,
+                readCharacteristic);
+        mMessageStream.setMaxWriteSize(mMaxWriteSize);
+        mMessageStream.registerCallback(this);
+
+        if (mMessageStream == null) {
+            Log.e(TAG, "No supported version found during version exchange. "
+                    +  "Could not create message stream.");
             disconnectRemoteDevice();
             return;
         }
 
+        // No need for this manager to listen for any writes; the stream will handle that from now
+        // on.
+        mBlePeripheralManager.removeOnCharacteristicWriteListener(this);
+
+        // The message stream is not used to send the IHU's version, but will be used for
+        // any subsequent messages.
         BLEVersionExchange headunitVersion = BLEVersionExchangeResolver.makeVersionExchange();
-        setValueOnCharacteristicAndNotify(device, headunitVersion.toByteArray(), characteristic);
+        setValueOnCharacteristicAndNotify(device, headunitVersion.toByteArray(),
+                writeCharacteristic);
 
         if (Log.isLoggable(TAG, Log.DEBUG)) {
             Log.d(TAG, "Sent supported version to the phone.");
         }
-
-        mIsVersionExchanged = true;
     }
 
     /**
@@ -357,11 +310,11 @@
      */
     void setupEnrollmentBleServer() {
         mEnrollmentServiceUuid = UUID.fromString(
-                getContext().getString(R.string.enrollment_service_uuid));
+                mContext.getString(R.string.enrollment_service_uuid));
         mEnrollmentClientWriteUuid = UUID.fromString(
-                getContext().getString(R.string.enrollment_client_write_uuid));
+                mContext.getString(R.string.enrollment_client_write_uuid));
         mEnrollmentServerWriteUuid = UUID.fromString(
-                getContext().getString(R.string.enrollment_server_write_uuid));
+                mContext.getString(R.string.enrollment_server_write_uuid));
 
         mEnrollmentGattService = new BluetoothGattService(mEnrollmentServiceUuid,
                 BluetoothGattService.SERVICE_TYPE_PRIMARY);
@@ -391,11 +344,11 @@
      * from the phone to the head unit.
      */
     void setupUnlockBleServer() {
-        mUnlockServiceUuid = UUID.fromString(getContext().getString(R.string.unlock_service_uuid));
+        mUnlockServiceUuid = UUID.fromString(mContext.getString(R.string.unlock_service_uuid));
         mUnlockClientWriteUuid = UUID
-                .fromString(getContext().getString(R.string.unlock_client_write_uuid));
+                .fromString(mContext.getString(R.string.unlock_client_write_uuid));
         mUnlockServerWriteUuid = UUID
-                .fromString(getContext().getString(R.string.unlock_server_write_uuid));
+                .fromString(mContext.getString(R.string.unlock_server_write_uuid));
 
         mUnlockGattService = new BluetoothGattService(mUnlockServiceUuid,
                 BluetoothGattService.SERVICE_TYPE_PRIMARY);
@@ -419,6 +372,25 @@
         mUnlockGattService.addCharacteristic(serverCharacteristic);
     }
 
+    @Override
+    public void onMessageReceivedError(UUID uuid) {
+        Log.e(TAG, "Error parsing the message from the client on UUID: " + uuid);
+    }
+
+    @Override
+    public void onMessageReceived(byte[] message, UUID uuid) {
+        if (mDataReceivedListeners.containsKey(uuid)) {
+            mDataReceivedListeners.get(uuid).onDataReceived(message);
+        }
+    }
+
+    @Override
+    public void onWriteMessageError() {
+        if (mSendMessageCallback != null) {
+            mSendMessageCallback.onSendMessageFailure();
+        }
+    }
+
     private void addDescriptorToCharacteristic(BluetoothGattCharacteristic characteristic) {
         BluetoothGattDescriptor descriptor = new BluetoothGattDescriptor(
                 CLIENT_CHARACTERISTIC_CONFIG,
@@ -427,22 +399,52 @@
         characteristic.addDescriptor(descriptor);
     }
 
-    void startEnrollmentAdvertising() {
+    /**
+     * Begins advertising for enrollment
+     *
+     * @param deviceName device name to advertise
+     * @param enrollmentAdvertisingCallback callback for advertiser
+     */
+    void startEnrollmentAdvertising(@Nullable String deviceName,
+            AdvertiseCallback enrollmentAdvertisingCallback) {
+        if (enrollmentAdvertisingCallback == null) {
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG, "Enrollment Advertising not started: "
+                        + "enrollmentAdvertisingCallback is null");
+            }
+            return;
+        }
         mCurrentTrustedDeviceOperation = TRUSTED_DEVICE_OPERATION_ENROLLMENT;
+        mEnrollmentAdvertisingCallback = enrollmentAdvertisingCallback;
         // Replace name to ensure it is small enough to be advertised
-        String name = getEnrollmentDeviceName();
-        if (name != null) {
+        if (deviceName != null) {
             BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
             if (mOriginalBluetoothName == null) {
                 mOriginalBluetoothName = adapter.getName();
             }
-            adapter.setName(name);
+            adapter.setName(deviceName);
             if (Log.isLoggable(TAG, Log.DEBUG)) {
                 Log.d(TAG, "Changing bluetooth adapter name from "
-                        + mOriginalBluetoothName + " to " + name);
+                        + mOriginalBluetoothName + " to " + deviceName);
             }
         }
-        startAdvertising(mEnrollmentGattService,
+
+        attemptAdvertising();
+    }
+
+    private void attemptAdvertising() {
+        // Validate the adapter name change has happened. If not, try again after delay.
+        if (mOriginalBluetoothName != null
+                && BluetoothAdapter.getDefaultAdapter().getName().equals(mOriginalBluetoothName)) {
+            mHandler.postDelayed(this::attemptAdvertising, BLE_MESSAGE_RETRY_DELAY_MS);
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG, "Adapter name change has not taken affect prior to advertising attempt. "
+                        + "Trying again.");
+            }
+            return;
+        }
+
+        mBlePeripheralManager.startAdvertising(mEnrollmentGattService,
                 new AdvertiseData.Builder()
                         .setIncludeDeviceName(true)
                         .addServiceUuid(new ParcelUuid(mEnrollmentServiceUuid))
@@ -457,16 +459,23 @@
                         + mOriginalBluetoothName);
             }
             BluetoothAdapter.getDefaultAdapter().setName(mOriginalBluetoothName);
+            mOriginalBluetoothName = null;
         }
-        stopAdvertising(mEnrollmentAdvertisingCallback);
+        if (mEnrollmentAdvertisingCallback != null) {
+            mBlePeripheralManager.stopAdvertising(mEnrollmentAdvertisingCallback);
+        }
     }
 
     void startUnlockAdvertising() {
+        if (mUniqueId == null) {
+            Log.e(TAG, "unique id is null");
+            return;
+        }
         mCurrentTrustedDeviceOperation = TRUSTED_DEVICE_OPERATION_UNLOCK;
-        startAdvertising(mUnlockGattService,
+        mBlePeripheralManager.startAdvertising(mUnlockGattService,
                 new AdvertiseData.Builder()
                         .setIncludeDeviceName(false)
-                        .addServiceData(new ParcelUuid(mUnlockServiceUuid), getUniqueId())
+                        .addServiceData(new ParcelUuid(mUnlockServiceUuid), mUniqueId)
                         .addServiceUuid(new ParcelUuid(mUnlockServiceUuid))
                         .build(),
                 mUnlockAdvertisingCallback);
@@ -474,144 +483,23 @@
 
     void stopUnlockAdvertising() {
         mCurrentTrustedDeviceOperation = TRUSTED_DEVICE_OPERATION_NONE;
-        stopAdvertising(mUnlockAdvertisingCallback);
+        mBlePeripheralManager.stopAdvertising(mUnlockAdvertisingCallback);
     }
 
     void disconnectRemoteDevice() {
-        stopGattServer();
+        mBlePeripheralManager.stopGattServer();
     }
 
-    void sendUnlockMessage(BluetoothDevice device, byte[] message, OperationType operation,
-            boolean isPayloadEncrypted) {
-        BluetoothGattCharacteristic writeCharacteristic = mUnlockGattService
-                .getCharacteristic(mUnlockServerWriteUuid);
-
-        sendMessage(device, writeCharacteristic, message, operation, isPayloadEncrypted);
-    }
-
-    void sendEnrollmentMessage(BluetoothDevice device, byte[] message, OperationType operation,
-            boolean isPayloadEncrypted) {
-        BluetoothGattCharacteristic writeCharacteristic = mEnrollmentGattService
-                .getCharacteristic(mEnrollmentServerWriteUuid);
-
-        sendMessage(device, writeCharacteristic, message, operation, isPayloadEncrypted);
-    }
-
-    /**
-     * Handles an ACK from the client.
-     *
-     * <p>An ACK means that the client has successfully received a partial BLEMessage, meaning the
-     * next part of the message can be sent.
-     *
-     * @param device                   The client device.
-     * @param clientCharacteristicUUID The UUID of the characteristic on the device that the ACK
-     *                                 was written to.
-     */
-    private void handleClientAckMessage(BluetoothDevice device, UUID clientCharacteristicUUID) {
-        if (Log.isLoggable(TAG, Log.DEBUG)) {
-            Log.d(TAG, "Received ACK from client. Attempting to write next message in queue. "
-                    + "UUID: " + clientCharacteristicUUID);
-        }
-
-        BluetoothGattCharacteristic writeCharacteristic =
-                getCharacteristicForWrite(clientCharacteristicUUID);
-
-        if (writeCharacteristic == null) {
-            Log.e(TAG, "No corresponding write characteristic found for writing next message in"
-                    + " queue. UUID: " + clientCharacteristicUUID);
-            return;
-        }
-        if (mSendRepeatedBleMessage != null) {
-            mHandler.removeCallbacks(mSendRepeatedBleMessage);
-            mSendRepeatedBleMessage = null;
-        }
-        // Previous message has been sent successfully so we can start the next message.
-        mMessageQueue.remove();
-        writeNextMessageInQueue(device, writeCharacteristic);
-    }
-
-    /**
-     * Sends the given message to the specified device and characteristic.
-     * The message will be splited into multiple messages wrapped in BLEMessage proto.
-     *
-     * @param device             The device to send the message to.
-     * @param characteristic     The characteristic to write to.
-     * @param message            A message to send.
-     * @param operation          The type of operation this message represents.
-     * @param isPayloadEncrypted {@code true} if the message is encrypted.
-     */
-    private void sendMessage(BluetoothDevice device, BluetoothGattCharacteristic characteristic,
-            byte[] message, OperationType operation, boolean isPayloadEncrypted) {
-        if (Log.isLoggable(TAG, Log.DEBUG)) {
-            Log.d(TAG, "sendMessage to: " + device.getAddress() + "; and characteristic UUID: "
-                    + characteristic.getUuid());
-        }
-
-        List<BLEMessage> bleMessages = BLEMessageV1Factory.makeBLEMessages(message, operation,
-                mMaxWriteSize, isPayloadEncrypted);
-
-        if (Log.isLoggable(TAG, Log.DEBUG)) {
-            Log.d(TAG, "sending " + bleMessages.size() + " messages to device");
-        }
-
-        mMessageQueue.addAll(bleMessages);
-        writeNextMessageInQueue(device, characteristic);
-    }
-
-    /**
-     * Writes the next message in {@link #mMessageQueue} to the given characteristic.
-     *
-     * <p>If the message queue is empty, then this method will do nothing.
-     */
-    private void writeNextMessageInQueue(BluetoothDevice device,
-            BluetoothGattCharacteristic characteristic) {
-        if (mMessageQueue.isEmpty()) {
-            Log.e(TAG, "Call to write next message in queue, but the message queue is empty");
-            return;
-        }
-        // When there is only one message, no ACKs are sent, so we no need to retry based on ACKs.
-        if (mMessageQueue.size() == 1) {
-            setValueOnCharacteristicAndNotify(device, mMessageQueue.remove().toByteArray(),
-                    characteristic);
-            return;
-        }
-        mBleMessageRetryStartCount = 0;
-        mSendRepeatedBleMessage = new Runnable() {
-            @Override
-            public void run() {
-                if (Log.isLoggable(TAG, Log.DEBUG)) {
-                    Log.d(TAG, "BLE message sending... " + "retry count: "
-                            + mBleMessageRetryStartCount);
-                }
-                if (mBleMessageRetryStartCount < BLE_MESSAGE_RETRY_LIMIT) {
-                    setValueOnCharacteristicAndNotify(device, mMessageQueue.peek().toByteArray(),
-                            characteristic);
-                    mBleMessageRetryStartCount++;
-                    mHandler.postDelayed(this, BLE_MESSAGE_RETRY_DELAY_MS);
-                } else {
-                    Log.e(TAG, "Error during BLE message sending - exceeded retry limit.");
-                    mHandler.removeCallbacks(this);
-                    mCarTrustAgentEnrollmentService.terminateEnrollmentHandshake();
-                    mSendRepeatedBleMessage = null;
-                }
-            }
-        };
-        mHandler.post(mSendRepeatedBleMessage);
-    }
-
-    private void sendAcknowledgmentMessage(BluetoothDevice device, UUID clientCharacteristicUUID) {
-        BluetoothGattCharacteristic writeCharacteristic =
-                getCharacteristicForWrite(clientCharacteristicUUID);
-
-        if (writeCharacteristic == null) {
-            Log.e(TAG, "No corresponding write characteristic found for sending ACK. UUID: "
-                    + clientCharacteristicUUID);
+    void sendMessage(byte[] message, OperationType operation, boolean isPayloadEncrypted,
+            SendMessageCallback callback) {
+        if (mMessageStream == null) {
+            Log.e(TAG, "Request to send message, but no valid message stream.");
             return;
         }
 
-        setValueOnCharacteristicAndNotify(device,
-                BLEMessageV1Factory.makeAcknowledgementMessage().toByteArray(),
-                writeCharacteristic);
+        mSendMessageCallback = callback;
+
+        mMessageStream.writeMessage(message, operation, isPayloadEncrypted);
     }
 
     /**
@@ -627,7 +515,7 @@
     private void setValueOnCharacteristicAndNotify(BluetoothDevice device, byte[] message,
             BluetoothGattCharacteristic characteristic) {
         characteristic.setValue(message);
-        notifyCharacteristicChanged(device, characteristic, false);
+        mBlePeripheralManager.notifyCharacteristicChanged(device, characteristic, false);
     }
 
     /**
@@ -651,29 +539,6 @@
         return null;
     }
 
-    private final AdvertiseCallback mEnrollmentAdvertisingCallback = new AdvertiseCallback() {
-        @Override
-        public void onStartSuccess(AdvertiseSettings settingsInEffect) {
-            super.onStartSuccess(settingsInEffect);
-            if (getEnrollmentService() != null) {
-                getEnrollmentService().onEnrollmentAdvertiseStartSuccess();
-            }
-            if (Log.isLoggable(TAG, Log.DEBUG)) {
-                Log.d(TAG, "Successfully started advertising service");
-            }
-        }
-
-        @Override
-        public void onStartFailure(int errorCode) {
-            Log.e(TAG, "Failed to advertise, errorCode: " + errorCode);
-
-            super.onStartFailure(errorCode);
-            if (getEnrollmentService() != null) {
-                getEnrollmentService().onEnrollmentAdvertiseStartFailure();
-            }
-        }
-    };
-
     private final AdvertiseCallback mUnlockAdvertisingCallback = new AdvertiseCallback() {
         @Override
         public void onStartSuccess(AdvertiseSettings settingsInEffect) {
@@ -697,4 +562,93 @@
             startUnlockAdvertising();
         }
     };
+
+    /**
+     * Adds the given listener to be notified when the characteristic with the given uuid has
+     * received data.
+     */
+    void addDataReceivedListener(UUID uuid, DataReceivedListener listener) {
+        mDataReceivedListeners.put(uuid, listener);
+        if (Log.isLoggable(TAG, Log.DEBUG)) {
+            Log.d(TAG, "Register DataReceivedListener: " + listener + "for uuid "
+                    + uuid.toString());
+        }
+    }
+
+    void removeDataReceivedListener(UUID uuid) {
+        if (!mDataReceivedListeners.containsKey(uuid)) {
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG, "No DataReceivedListener for uuid " + uuid.toString()
+                        + " to unregister.");
+            }
+            return;
+        }
+        mDataReceivedListeners.remove(uuid);
+        if (Log.isLoggable(TAG, Log.DEBUG)) {
+            Log.d(TAG, "Unregister DataReceivedListener for uuid " + uuid.toString());
+        }
+    }
+
+    void addBleEventCallback(BleEventCallback callback) {
+        mBleEventCallbacks.add(callback);
+    }
+
+    void removeBleEventCallback(BleEventCallback callback) {
+        if (!mBleEventCallbacks.remove(callback)) {
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG, "Remove BleEventCallback that does not exist.");
+            }
+        }
+    }
+
+    /**
+     * Callback to be invoked for enrollment events
+     */
+    interface SendMessageCallback {
+
+        /**
+         * Called when enrollment handshake needs to be terminated
+         */
+        void onSendMessageFailure();
+    }
+
+    /**
+     * Callback to be invoked when BLE receives data from remote device.
+     */
+    interface DataReceivedListener {
+
+        /**
+         * Called when data has been received from a remote device.
+         *
+         * @param value received data
+         */
+        void onDataReceived(byte[] value);
+    }
+
+    /**
+     * The interface that device service has to implement to get notified when BLE events occur
+     */
+    interface BleEventCallback {
+
+        /**
+         * Called when a remote device is connected
+         *
+         * @param device the remote device
+         */
+        void onRemoteDeviceConnected(BluetoothDevice device);
+
+        /**
+         * Called when a remote device is disconnected
+         *
+         * @param device the remote device
+         */
+        void onRemoteDeviceDisconnected(BluetoothDevice device);
+
+        /**
+         * Called when the device name of the remote device is retrieved
+         *
+         * @param deviceName device name of the remote device
+         */
+        void onClientDeviceNameRetrieved(String deviceName);
+    }
 }
diff --git a/service/src/com/android/car/trust/CarTrustAgentEnrollmentService.java b/service/src/com/android/car/trust/CarTrustAgentEnrollmentService.java
index 7f2923d..412d3ea 100644
--- a/service/src/com/android/car/trust/CarTrustAgentEnrollmentService.java
+++ b/service/src/com/android/car/trust/CarTrustAgentEnrollmentService.java
@@ -37,6 +37,8 @@
 import android.annotation.RequiresPermission;
 import android.app.ActivityManager;
 import android.bluetooth.BluetoothDevice;
+import android.bluetooth.le.AdvertiseCallback;
+import android.bluetooth.le.AdvertiseSettings;
 import android.car.encryptionrunner.EncryptionRunner;
 import android.car.encryptionrunner.EncryptionRunnerFactory;
 import android.car.encryptionrunner.HandshakeException;
@@ -51,12 +53,15 @@
 import android.content.SharedPreferences;
 import android.os.IBinder;
 import android.os.RemoteException;
+import android.sysprop.CarProperties;
 import android.util.Log;
 
 import com.android.car.BLEStreamProtos.BLEOperationProto.OperationType;
 import com.android.car.ICarImpl;
 import com.android.car.R;
 import com.android.car.Utils;
+import com.android.car.trust.CarTrustAgentBleManager.DataReceivedListener;
+import com.android.car.trust.CarTrustAgentBleManager.SendMessageCallback;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 
@@ -80,8 +85,12 @@
  * phone to enroll as a trusted device.  The enrolled phone can then be used for authenticating a
  * user on the HU.  This implements the {@link android.car.trust.CarTrustAgentEnrollmentManager}
  * APIs that an app like Car Settings can call to conduct an enrollment.
+ *
+ * @deprecated Enrollment of a trusted device is no longer a supported feature of car service.
  */
-public class CarTrustAgentEnrollmentService extends ICarTrustAgentEnrollment.Stub {
+@Deprecated
+public class CarTrustAgentEnrollmentService extends ICarTrustAgentEnrollment.Stub implements
+        DataReceivedListener {
     private static final String TAG = "CarTrustAgentEnroll";
     private static final String TRUSTED_DEVICE_ENROLLMENT_ENABLED_KEY =
             "trusted_device_enrollment_enabled";
@@ -94,7 +103,21 @@
     // TrustedDeviceInfo and this delimiter will be removed.
     private static final char DEVICE_INFO_DELIMITER = '#';
 
+    // Device name length is limited by available bytes in BLE advertisement data packet.
+    //
+    // BLE advertisement limits data packet length to 31
+    // Currently we send:
+    // - 18 bytes for 16 chars UUID: 16 bytes + 2 bytes for header;
+    // - 3 bytes for advertisement being connectable;
+    // which leaves 10 bytes.
+    // Subtracting 2 bytes used by header, we have 8 bytes for device name.
+    private static final int DEVICE_NAME_LENGTH_LIMIT = 8;
+    // Limit prefix to 4 chars and fill the rest with randomly generated name. Use random name
+    // to improve uniqueness in paired device name.
+    private static final int DEVICE_NAME_PREFIX_LIMIT = 4;
+
     private final CarTrustedDeviceService mTrustedDeviceService;
+    private final CarCompanionDeviceStorage mCarCompanionDeviceStorage;
     // List of clients listening to Enrollment state change events.
     private final List<EnrollmentStateClient> mEnrollmentStateClients = new ArrayList<>();
     // List of clients listening to BLE state changes events during enrollment.
@@ -110,7 +133,10 @@
     private final Map<Long, Boolean> mTokenActiveStateMap = new HashMap<>();
     private String mClientDeviceName;
     private String mClientDeviceId;
+    private final UUID mEnrollmentClientWriteUuid;
     private final Context mContext;
+    private String mEnrollmentDeviceName;
+    private SendMessageCallback mSendMessageCallback = this::terminateEnrollmentHandshake;
 
     private EncryptionRunner mEncryptionRunner = EncryptionRunnerFactory.newRunner();
     private HandshakeMessage mHandshakeMessage;
@@ -143,10 +169,15 @@
         mContext = context;
         mTrustedDeviceService = service;
         mCarTrustAgentBleManager = bleService;
+        mEnrollmentClientWriteUuid = UUID.fromString(context
+                .getString(R.string.enrollment_client_write_uuid));
+        mCarCompanionDeviceStorage = new CarCompanionDeviceStorage(context);
     }
 
     public synchronized void init() {
         mCarTrustAgentBleManager.setupEnrollmentBleServer();
+        mCarTrustAgentBleManager.addDataReceivedListener(mEnrollmentClientWriteUuid,
+                this);
     }
 
     /**
@@ -177,7 +208,7 @@
     @RequiresPermission(PERMISSION_CAR_ENROLL_TRUST)
     public void startEnrollmentAdvertising() {
         ICarImpl.assertTrustAgentEnrollmentPermission(mContext);
-        if (!mTrustedDeviceService.getSharedPrefs()
+        if (!mCarCompanionDeviceStorage.getSharedPrefs()
                 .getBoolean(TRUSTED_DEVICE_ENROLLMENT_ENABLED_KEY, true)) {
             Log.e(TAG, "Trusted Device Enrollment disabled");
             dispatchEnrollmentFailure(ENROLLMENT_NOT_ALLOWED);
@@ -189,7 +220,24 @@
 
         logEnrollmentEvent(START_ENROLLMENT_ADVERTISING);
         addEnrollmentServiceLog("startEnrollmentAdvertising");
-        mCarTrustAgentBleManager.startEnrollmentAdvertising();
+        mCarTrustAgentBleManager.startEnrollmentAdvertising(getEnrollmentDeviceName(),
+                new AdvertiseCallback() {
+                    @Override
+                    public void onStartSuccess(AdvertiseSettings settingsInEffect) {
+                        super.onStartSuccess(settingsInEffect);
+                        onEnrollmentAdvertiseStartSuccess();
+                        if (Log.isLoggable(TAG, Log.DEBUG)) {
+                            Log.d(TAG, "Successfully started advertising service");
+                        }
+                    }
+
+                    @Override
+                    public void onStartFailure(int errorCode) {
+                        super.onStartFailure(errorCode);
+                        Log.e(TAG, "Failed to advertise, errorCode: " + errorCode);
+                        onEnrollmentAdvertiseStartFailure();
+                    }
+            });
         mEnrollmentState = ENROLLMENT_STATE_NONE;
     }
 
@@ -224,8 +272,9 @@
             mCarTrustAgentBleManager.disconnectRemoteDevice();
             return;
         }
-        mCarTrustAgentBleManager.sendEnrollmentMessage(mRemoteEnrollmentDevice, CONFIRMATION_SIGNAL,
-                OperationType.ENCRYPTION_HANDSHAKE, /* isPayloadEncrypted= */ false);
+        mCarTrustAgentBleManager.sendMessage(CONFIRMATION_SIGNAL,
+                OperationType.ENCRYPTION_HANDSHAKE, /* isPayloadEncrypted= */ false,
+                mSendMessageCallback);
         setEnrollmentHandshakeAccepted();
     }
 
@@ -248,7 +297,8 @@
             boolean isHandleActive = pair.getValue();
             if (!isHandleActive) {
                 long handle = pair.getKey();
-                int uid = mTrustedDeviceService.getSharedPrefs().getInt(String.valueOf(handle), -1);
+                int uid = mCarCompanionDeviceStorage.getSharedPrefs().getInt(String.valueOf(handle),
+                        -1);
                 removeEscrowToken(handle, uid);
                 it.remove();
             }
@@ -311,7 +361,7 @@
     @RequiresPermission(PERMISSION_CAR_ENROLL_TRUST)
     public void setTrustedDeviceEnrollmentEnabled(boolean isEnabled) {
         ICarImpl.assertTrustAgentEnrollmentPermission(mContext);
-        SharedPreferences.Editor editor = mTrustedDeviceService.getSharedPrefs().edit();
+        SharedPreferences.Editor editor = mCarCompanionDeviceStorage.getSharedPrefs().edit();
         editor.putBoolean(TRUSTED_DEVICE_ENROLLMENT_ENABLED_KEY, isEnabled);
         if (!editor.commit()) {
             Log.wtf(TAG,
@@ -346,7 +396,7 @@
     @RequiresPermission(PERMISSION_CAR_ENROLL_TRUST)
     public List<TrustedDeviceInfo> getEnrolledDeviceInfosForUser(int uid) {
         ICarImpl.assertTrustAgentEnrollmentPermission(mContext);
-        Set<String> enrolledDeviceInfos = mTrustedDeviceService.getSharedPrefs().getStringSet(
+        Set<String> enrolledDeviceInfos = mCarCompanionDeviceStorage.getSharedPrefs().getStringSet(
                 String.valueOf(uid), new HashSet<>());
         List<TrustedDeviceInfo> trustedDeviceInfos = new ArrayList<>(enrolledDeviceInfos.size());
         for (String deviceInfoWithId : enrolledDeviceInfos) {
@@ -429,7 +479,7 @@
                 Log.e(TAG, "onEscrowTokenRemoved dispatch failed", e);
             }
         }
-        SharedPreferences sharedPrefs = mTrustedDeviceService.getSharedPrefs();
+        SharedPreferences sharedPrefs = mCarCompanionDeviceStorage.getSharedPrefs();
         SharedPreferences.Editor editor = sharedPrefs.edit();
         editor.remove(String.valueOf(handle));
         Set<String> deviceInfos = sharedPrefs.getStringSet(String.valueOf(uid), new HashSet<>());
@@ -479,7 +529,7 @@
 
         // Avoid storing duplicate info for same device by checking if there is already device info
         // and deleting it.
-        SharedPreferences sharedPrefs = mTrustedDeviceService.getSharedPrefs();
+        SharedPreferences sharedPrefs = mCarCompanionDeviceStorage.getSharedPrefs();
         if (sharedPrefs.contains(mClientDeviceId)) {
             removeEscrowToken(sharedPrefs.getLong(mClientDeviceId, -1), uid);
         }
@@ -535,9 +585,10 @@
             Log.d(TAG, "Sending handle: " + handle);
         }
         mHandle = handle;
-        mCarTrustAgentBleManager.sendEnrollmentMessage(mRemoteEnrollmentDevice,
+        mCarTrustAgentBleManager.sendMessage(
                 mEncryptionKey.encryptData(Utils.longToBytes(handle)),
-                OperationType.CLIENT_MESSAGE, /* isPayloadEncrypted= */ true);
+                OperationType.CLIENT_MESSAGE, /* isPayloadEncrypted= */ true,
+                mSendMessageCallback);
     }
 
     void onEnrollmentAdvertiseStartSuccess() {
@@ -610,7 +661,8 @@
      *
      * @param value received data
      */
-    void onEnrollmentDataReceived(byte[] value) {
+    @Override
+    public void onDataReceived(byte[] value) {
         if (mEnrollmentDelegate == null) {
             if (Log.isLoggable(TAG, Log.DEBUG)) {
                 Log.d(TAG, "Enrollment Delegate not set");
@@ -647,7 +699,7 @@
         }
     }
 
-    void onDeviceNameRetrieved(String deviceName) {
+    void onClientDeviceNameRetrieved(String deviceName) {
         mClientDeviceName = deviceName;
     }
 
@@ -661,7 +713,7 @@
         if (Log.isLoggable(TAG, Log.DEBUG)) {
             Log.d(TAG, "Received device id: " + mClientDeviceId);
         }
-        UUID uniqueId = mTrustedDeviceService.getUniqueId();
+        UUID uniqueId = mCarCompanionDeviceStorage.getUniqueId();
         if (uniqueId == null) {
             Log.e(TAG, "Cannot get Unique ID for the IHU");
             resetEnrollmentStateOnFailure();
@@ -671,9 +723,10 @@
         if (Log.isLoggable(TAG, Log.DEBUG)) {
             Log.d(TAG, "Sending device id: " + uniqueId.toString());
         }
-        mCarTrustAgentBleManager.sendEnrollmentMessage(mRemoteEnrollmentDevice,
+        mCarTrustAgentBleManager.sendMessage(
                 Utils.uuidToBytes(uniqueId), OperationType.CLIENT_MESSAGE,
-                /* isPayloadEncrypted= */ false);
+                /* isPayloadEncrypted= */ false,
+                mSendMessageCallback);
         mEnrollmentState++;
     }
 
@@ -709,9 +762,10 @@
 
                 mHandshakeMessage = mEncryptionRunner.respondToInitRequest(message);
                 mEncryptionState = mHandshakeMessage.getHandshakeState();
-                mCarTrustAgentBleManager.sendEnrollmentMessage(
-                        mRemoteEnrollmentDevice, mHandshakeMessage.getNextMessage(),
-                        OperationType.ENCRYPTION_HANDSHAKE, /* isPayloadEncrypted= */ false);
+                mCarTrustAgentBleManager.sendMessage(
+                        mHandshakeMessage.getNextMessage(),
+                        OperationType.ENCRYPTION_HANDSHAKE, /* isPayloadEncrypted= */ false,
+                        mSendMessageCallback);
 
                 logEnrollmentEvent(ENROLLMENT_ENCRYPTION_STATE, mEncryptionState);
                 break;
@@ -734,9 +788,9 @@
                     showVerificationCode();
                     return;
                 }
-                mCarTrustAgentBleManager.sendEnrollmentMessage(mRemoteEnrollmentDevice,
+                mCarTrustAgentBleManager.sendMessage(
                         mHandshakeMessage.getNextMessage(), OperationType.ENCRYPTION_HANDSHAKE,
-                        /* isPayloadEncrypted= */ false);
+                        /* isPayloadEncrypted= */ false, mSendMessageCallback);
                 break;
             case HandshakeState.VERIFICATION_NEEDED:
                 Log.w(TAG, "Encountered VERIFICATION_NEEDED state when it should have been "
@@ -825,7 +879,8 @@
 
         mEncryptionState = HandshakeState.FINISHED;
         mEncryptionKey = message.getKey();
-        if (!mTrustedDeviceService.saveEncryptionKey(mClientDeviceId, mEncryptionKey.asBytes())) {
+        if (!mCarCompanionDeviceStorage.saveEncryptionKey(mClientDeviceId,
+                mEncryptionKey.asBytes())) {
             resetEnrollmentStateOnFailure();
             dispatchEnrollmentFailure(ENROLLMENT_HANDSHAKE_FAILURE);
             return;
@@ -1134,4 +1189,22 @@
             }
         }
     }
+
+    /**
+     * Returns the name that should be used for the device during enrollment of a trusted device.
+     *
+     * <p>The returned name will be a combination of a prefix sysprop and randomized digits.
+     */
+    private String getEnrollmentDeviceName() {
+        if (mEnrollmentDeviceName == null) {
+            String deviceNamePrefix = CarProperties.trusted_device_device_name_prefix().orElse("");
+            deviceNamePrefix = deviceNamePrefix.substring(
+                0, Math.min(deviceNamePrefix.length(), DEVICE_NAME_PREFIX_LIMIT));
+
+            int randomNameLength = DEVICE_NAME_LENGTH_LIMIT - deviceNamePrefix.length();
+            String randomName = Utils.generateRandomNumberString(randomNameLength);
+            mEnrollmentDeviceName = deviceNamePrefix + randomName;
+        }
+        return mEnrollmentDeviceName;
+    }
 }
diff --git a/service/src/com/android/car/trust/CarTrustAgentUnlockService.java b/service/src/com/android/car/trust/CarTrustAgentUnlockService.java
index 2bc7aee..e3129bd 100644
--- a/service/src/com/android/car/trust/CarTrustAgentUnlockService.java
+++ b/service/src/com/android/car/trust/CarTrustAgentUnlockService.java
@@ -35,31 +35,26 @@
 import android.car.encryptionrunner.HandshakeException;
 import android.car.encryptionrunner.HandshakeMessage;
 import android.car.encryptionrunner.Key;
+import android.content.Context;
 import android.content.SharedPreferences;
 import android.util.Log;
 
 import com.android.car.BLEStreamProtos.BLEOperationProto.OperationType;
 import com.android.car.PhoneAuthProtos.PhoneAuthProto.PhoneCredentials;
+import com.android.car.R;
 import com.android.car.Utils;
 import com.android.car.protobuf.InvalidProtocolBufferException;
+import com.android.car.trust.CarTrustAgentBleManager.SendMessageCallback;
 import com.android.internal.annotations.GuardedBy;
 
-import com.google.security.cryptauth.lib.securegcm.D2DConnectionContext;
-import com.google.security.cryptauth.lib.securemessage.CryptoOps;
-
 import java.io.PrintWriter;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
-import java.security.InvalidKeyException;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
 import java.security.SignatureException;
 import java.util.LinkedList;
 import java.util.Queue;
 import java.util.UUID;
 
-import javax.crypto.spec.SecretKeySpec;
-
 /**
  * A service that interacts with the Trust Agent {@link CarBleTrustAgent} and a comms (BLE) service
  * {@link CarTrustAgentBleManager} to receive the necessary credentials to authenticate
@@ -92,17 +87,16 @@
  * <li>Phone sends the encrypted escrow token and handle to the IHU.
  * <li>IHU retrieves the user id and authenticates the user.
  * </ol>
+ *
+ * @deprecated Unlocking of a trusted device is no longer a supported feature of car service.
  */
+@Deprecated
 public class CarTrustAgentUnlockService {
     private static final String TAG = "CarTrustAgentUnlock";
     private static final String TRUSTED_DEVICE_UNLOCK_ENABLED_KEY = "trusted_device_unlock_enabled";
 
     // Arbitrary log size
     private static final int MAX_LOG_SIZE = 20;
-    private static final byte[] RESUME = "RESUME".getBytes();
-    private static final byte[] SERVER = "SERVER".getBytes();
-    private static final byte[] CLIENT = "CLIENT".getBytes();
-    private static final int RESUME_HMAC_LENGTH = 32;
 
     private static final byte[] ACKNOWLEDGEMENT_MESSAGE = "ACK".getBytes();
 
@@ -110,15 +104,14 @@
     // State increments to the next state on successful completion.
     private static final int UNLOCK_STATE_WAITING_FOR_UNIQUE_ID = 0;
     private static final int UNLOCK_STATE_KEY_EXCHANGE_IN_PROGRESS = 1;
-    private static final int UNLOCK_STATE_WAITING_FOR_CLIENT_AUTH = 2;
-    private static final int UNLOCK_STATE_MUTUAL_AUTH_ESTABLISHED = 3;
-    private static final int UNLOCK_STATE_PHONE_CREDENTIALS_RECEIVED = 4;
+    private static final int UNLOCK_STATE_MUTUAL_AUTH_ESTABLISHED = 2;
+    private static final int UNLOCK_STATE_PHONE_CREDENTIALS_RECEIVED = 3;
 
     /** @hide */
     @Retention(RetentionPolicy.SOURCE)
-    @IntDef(prefix = {"UNLOCK_STATE_"}, value = {UNLOCK_STATE_WAITING_FOR_UNIQUE_ID,
-            UNLOCK_STATE_KEY_EXCHANGE_IN_PROGRESS, UNLOCK_STATE_WAITING_FOR_CLIENT_AUTH,
-            UNLOCK_STATE_MUTUAL_AUTH_ESTABLISHED, UNLOCK_STATE_PHONE_CREDENTIALS_RECEIVED})
+    @IntDef(prefix = {"UNLOCK_STATE_"},
+            value = {UNLOCK_STATE_WAITING_FOR_UNIQUE_ID, UNLOCK_STATE_KEY_EXCHANGE_IN_PROGRESS,
+                    UNLOCK_STATE_MUTUAL_AUTH_ESTABLISHED, UNLOCK_STATE_PHONE_CREDENTIALS_RECEIVED})
     @interface UnlockState {
     }
 
@@ -126,10 +119,13 @@
     private int mCurrentUnlockState = UNLOCK_STATE_WAITING_FOR_UNIQUE_ID;
 
     private final CarTrustedDeviceService mTrustedDeviceService;
+    private final CarCompanionDeviceStorage mCarCompanionDeviceStorage;
     private final CarTrustAgentBleManager mCarTrustAgentBleManager;
     private CarTrustAgentUnlockDelegate mUnlockDelegate;
     private String mClientDeviceId;
     private final Queue<String> mLogQueue = new LinkedList<>();
+    private final UUID mUnlockClientWriteUuid;
+    private SendMessageCallback mSendMessageCallback;
 
     // Locks
     private final Object mDeviceLock = new Object();
@@ -138,18 +134,19 @@
     private BluetoothDevice mRemoteUnlockDevice;
 
     private EncryptionRunner mEncryptionRunner = EncryptionRunnerFactory.newRunner();
-    private HandshakeMessage mHandshakeMessage;
     private Key mEncryptionKey;
     @HandshakeMessage.HandshakeState
     private int mEncryptionState = HandshakeMessage.HandshakeState.UNKNOWN;
 
-    private D2DConnectionContext mPrevContext;
-    private D2DConnectionContext mCurrentContext;
-
-    CarTrustAgentUnlockService(CarTrustedDeviceService service,
+    CarTrustAgentUnlockService(Context context, CarTrustedDeviceService service,
             CarTrustAgentBleManager bleService) {
         mTrustedDeviceService = service;
         mCarTrustAgentBleManager = bleService;
+        mUnlockClientWriteUuid = UUID.fromString(context
+                .getString(R.string.unlock_client_write_uuid));
+        mEncryptionRunner.setIsReconnect(true);
+        mSendMessageCallback = () -> mCarTrustAgentBleManager.disconnectRemoteDevice();
+        mCarCompanionDeviceStorage = new CarCompanionDeviceStorage(context);
     }
 
     /**
@@ -175,7 +172,7 @@
      *                  back.
      */
     public void setTrustedDeviceUnlockEnabled(boolean isEnabled) {
-        SharedPreferences.Editor editor = mTrustedDeviceService.getSharedPrefs().edit();
+        SharedPreferences.Editor editor = mCarCompanionDeviceStorage.getSharedPrefs().edit();
         editor.putBoolean(TRUSTED_DEVICE_UNLOCK_ENABLED_KEY, isEnabled);
         if (!editor.commit()) {
             Log.wtf(TAG, "Unlock Enable Failed. Enable? " + isEnabled);
@@ -195,8 +192,8 @@
      * Start Unlock Advertising
      */
     void startUnlockAdvertising() {
-        if (!mTrustedDeviceService.getSharedPrefs().getBoolean(TRUSTED_DEVICE_UNLOCK_ENABLED_KEY,
-                true)) {
+        if (!mCarCompanionDeviceStorage.getSharedPrefs().getBoolean(
+                TRUSTED_DEVICE_UNLOCK_ENABLED_KEY, true)) {
             Log.e(TAG, "Trusted Device Unlock is disabled");
             return;
         }
@@ -205,6 +202,7 @@
 
         logUnlockEvent(START_UNLOCK_ADVERTISING);
         queueMessageForLog("startUnlockAdvertising");
+        mCarTrustAgentBleManager.setUniqueId(mCarCompanionDeviceStorage.getUniqueId());
         mCarTrustAgentBleManager.startUnlockAdvertising();
     }
 
@@ -225,14 +223,14 @@
     void init() {
         logUnlockEvent(UNLOCK_SERVICE_INIT);
         mCarTrustAgentBleManager.setupUnlockBleServer();
+        mCarTrustAgentBleManager.addDataReceivedListener(mUnlockClientWriteUuid,
+                this::onUnlockDataReceived);
     }
 
     void release() {
         synchronized (mDeviceLock) {
             mRemoteUnlockDevice = null;
         }
-        mPrevContext = null;
-        mCurrentContext = null;
     }
 
     void onRemoteDeviceConnected(BluetoothDevice device) {
@@ -291,33 +289,19 @@
                     resetUnlockStateOnFailure();
                 }
                 break;
-            case UNLOCK_STATE_WAITING_FOR_CLIENT_AUTH:
-                if (!authenticateClient(value)) {
-                    if (Log.isLoggable(TAG, Log.DEBUG)) {
-                        Log.d(TAG, "HMAC from the phone is not correct. Cannot resume session. Need"
-                                + " to re-enroll");
-                    }
-                    mTrustedDeviceService.clearEncryptionKey(mClientDeviceId);
-                    resetUnlockStateOnFailure();
-                    return;
-                }
-
-                logUnlockEvent(CLIENT_AUTHENTICATED);
-                sendServerAuthToClient();
-                mCurrentUnlockState = UNLOCK_STATE_MUTUAL_AUTH_ESTABLISHED;
-                break;
             case UNLOCK_STATE_MUTUAL_AUTH_ESTABLISHED:
                 if (mEncryptionKey == null) {
                     Log.e(TAG, "Current session key null. Unexpected at this stage: "
                             + mCurrentUnlockState);
                     // Clear the previous session key.  Need to re-enroll the trusted device.
-                    mTrustedDeviceService.clearEncryptionKey(mClientDeviceId);
+                    mCarCompanionDeviceStorage.clearEncryptionKey(mClientDeviceId);
                     resetUnlockStateOnFailure();
                     return;
                 }
 
                 // Save the current session to be used for authenticating the next session
-                mTrustedDeviceService.saveEncryptionKey(mClientDeviceId, mEncryptionKey.asBytes());
+                mCarCompanionDeviceStorage.saveEncryptionKey(mClientDeviceId,
+                        mEncryptionKey.asBytes());
 
                 byte[] decryptedCredentials;
                 try {
@@ -348,8 +332,9 @@
         // Let the phone know that the handle was received.
         byte[] ack = isEncrypted ? mEncryptionKey.encryptData(ACKNOWLEDGEMENT_MESSAGE)
                 : ACKNOWLEDGEMENT_MESSAGE;
-        mCarTrustAgentBleManager.sendUnlockMessage(mRemoteUnlockDevice, ack,
-                OperationType.CLIENT_MESSAGE, /* isPayloadEncrypted= */ isEncrypted);
+        mCarTrustAgentBleManager.sendMessage(ack,
+                OperationType.CLIENT_MESSAGE, /* isPayloadEncrypted= */ isEncrypted,
+                mSendMessageCallback);
     }
 
     @Nullable
@@ -357,7 +342,7 @@
         // Validate if the id exists i.e., if the phone is enrolled already
         UUID deviceId = Utils.bytesToUUID(id);
         if (deviceId == null
-                || mTrustedDeviceService.getEncryptionKey(deviceId.toString()) == null) {
+                || mCarCompanionDeviceStorage.getEncryptionKey(deviceId.toString()) == null) {
             if (deviceId != null) {
                 Log.e(TAG, "Unknown phone connected: " + deviceId.toString());
             }
@@ -368,18 +353,19 @@
     }
 
     private void processKeyExchangeHandshakeMessage(byte[] message) throws HandshakeException {
+        HandshakeMessage handshakeMessage;
         switch (mEncryptionState) {
             case HandshakeMessage.HandshakeState.UNKNOWN:
                 if (Log.isLoggable(TAG, Log.DEBUG)) {
                     Log.d(TAG, "Responding to handshake init request.");
                 }
 
-                mHandshakeMessage = mEncryptionRunner.respondToInitRequest(message);
-                mEncryptionState = mHandshakeMessage.getHandshakeState();
-                mCarTrustAgentBleManager.sendUnlockMessage(mRemoteUnlockDevice,
-                        mHandshakeMessage.getNextMessage(),
+                handshakeMessage = mEncryptionRunner.respondToInitRequest(message);
+                mEncryptionState = handshakeMessage.getHandshakeState();
+                mCarTrustAgentBleManager.sendMessage(
+                        handshakeMessage.getNextMessage(),
                         OperationType.ENCRYPTION_HANDSHAKE,
-                        /* isPayloadEncrypted= */ false);
+                        /* isPayloadEncrypted= */ false, mSendMessageCallback);
                 logUnlockEvent(UNLOCK_ENCRYPTION_STATE, mEncryptionState);
                 break;
 
@@ -388,124 +374,58 @@
                     Log.d(TAG, "Continuing handshake.");
                 }
 
-                mHandshakeMessage = mEncryptionRunner.continueHandshake(message);
-                mEncryptionState = mHandshakeMessage.getHandshakeState();
+                handshakeMessage = mEncryptionRunner.continueHandshake(message);
+                mEncryptionState = handshakeMessage.getHandshakeState();
 
-                if (Log.isLoggable(TAG, Log.DEBUG)) {
-                    Log.d(TAG, "Updated encryption state: " + mEncryptionState);
-                }
+                logUnlockEvent(UNLOCK_ENCRYPTION_STATE, mEncryptionState);
 
-                // The state is updated after a call to continueHandshake(). Thus, need to check
-                // if we're in the next stage.
-                if (mEncryptionState == HandshakeMessage.HandshakeState.VERIFICATION_NEEDED) {
-                    logUnlockEvent(UNLOCK_ENCRYPTION_STATE, mEncryptionState);
-                    showVerificationCode();
+                if (mEncryptionState != HandshakeMessage.HandshakeState.RESUMING_SESSION) {
+                    Log.e(TAG,
+                            "Handshake did not went to resume session after calling verify PIN. "
+                                    + "Instead got state: " + mEncryptionState);
+                    resetUnlockStateOnFailure();
                     return;
                 }
-
-                // control shouldn't get here with Ukey2
-                mCarTrustAgentBleManager.sendUnlockMessage(mRemoteUnlockDevice,
-                        mHandshakeMessage.getNextMessage(),
-                        OperationType.ENCRYPTION_HANDSHAKE, /*isPayloadEncrypted= */false);
+                logUnlockEvent(WAITING_FOR_CLIENT_AUTH);
+                break;
+            case HandshakeMessage.HandshakeState.RESUMING_SESSION:
+                if (Log.isLoggable(TAG, Log.DEBUG)) {
+                    Log.d(TAG, "Start reconnection authentication.");
+                }
+                if (mClientDeviceId == null) {
+                    resetUnlockStateOnFailure();
+                    return;
+                }
+                handshakeMessage = mEncryptionRunner.authenticateReconnection(
+                        message, mCarCompanionDeviceStorage.getEncryptionKey(mClientDeviceId));
+                mEncryptionKey = handshakeMessage.getKey();
+                mEncryptionState = handshakeMessage.getHandshakeState();
+                logUnlockEvent(UNLOCK_ENCRYPTION_STATE, mEncryptionState);
+                if (mEncryptionState != HandshakeMessage.HandshakeState.FINISHED) {
+                    resetUnlockStateOnFailure();
+                    return;
+                }
+                mCurrentUnlockState = UNLOCK_STATE_MUTUAL_AUTH_ESTABLISHED;
+                sendServerAuthToClient(handshakeMessage.getNextMessage());
+                logUnlockEvent(CLIENT_AUTHENTICATED);
                 break;
             case HandshakeMessage.HandshakeState.VERIFICATION_NEEDED:
             case HandshakeMessage.HandshakeState.FINISHED:
                 // Should never reach this case since this state should occur after a verification
                 // code has been accepted. But it should mean handshake is done and the message
-                // is one for the escrow token. Start Mutual Auth from server - compute MACs and
-                // send it over
-                showVerificationCode();
-                break;
-
+                // is one for the escrow token. Waiting Mutual Auth from client, authenticate,
+                // compute MACs and send it over
             default:
                 Log.w(TAG, "Encountered invalid handshake state: " + mEncryptionState);
                 break;
         }
     }
 
-    /**
-     * Verify the handshake.
-     * TODO(b/134073741) combine this with the method in CarTrustAgentEnrollmentService and
-     * have this take a boolean to blindly confirm the numeric code.
-     */
-    private void showVerificationCode() {
-        HandshakeMessage handshakeMessage;
-
-        // Blindly accept the verification code.
-        try {
-            handshakeMessage = mEncryptionRunner.verifyPin();
-        } catch (HandshakeException e) {
-            Log.e(TAG, "Verify pin failed for new keys - Unexpected");
-            resetUnlockStateOnFailure();
-            return;
-        }
-
-        if (handshakeMessage.getHandshakeState() != HandshakeMessage.HandshakeState.FINISHED) {
-            Log.e(TAG, "Handshake not finished after calling verify PIN. Instead got state: "
-                    + handshakeMessage.getHandshakeState());
-            resetUnlockStateOnFailure();
-            return;
-        }
-
-        mEncryptionState = HandshakeMessage.HandshakeState.FINISHED;
-        mEncryptionKey = handshakeMessage.getKey();
-        mCurrentContext = D2DConnectionContext.fromSavedSession(mEncryptionKey.asBytes());
-
-        if (mClientDeviceId == null) {
-            resetUnlockStateOnFailure();
-            return;
-        }
-        byte[] oldSessionKeyBytes = mTrustedDeviceService.getEncryptionKey(mClientDeviceId);
-        if (oldSessionKeyBytes == null) {
-            Log.e(TAG,
-                    "Could not retrieve previous session keys! Have to re-enroll trusted device");
-            resetUnlockStateOnFailure();
-            return;
-        }
-
-        mPrevContext = D2DConnectionContext.fromSavedSession(oldSessionKeyBytes);
-        if (mPrevContext == null) {
-            resetUnlockStateOnFailure();
-            return;
-        }
-
-        // Now wait for the phone to send its MAC.
-        mCurrentUnlockState = UNLOCK_STATE_WAITING_FOR_CLIENT_AUTH;
-        logUnlockEvent(WAITING_FOR_CLIENT_AUTH);
-    }
-
-    private void sendServerAuthToClient() {
-        byte[] resumeBytes = computeMAC(mPrevContext, mCurrentContext, SERVER);
-        if (resumeBytes == null) {
-            return;
-        }
+    private void sendServerAuthToClient(byte[] resumeBytes) {
         // send to client
-        mCarTrustAgentBleManager.sendUnlockMessage(mRemoteUnlockDevice, resumeBytes,
-                OperationType.CLIENT_MESSAGE, /* isPayloadEncrypted= */false);
-    }
-
-    @Nullable
-    private byte[] computeMAC(D2DConnectionContext previous, D2DConnectionContext next,
-            byte[] info) {
-        try {
-            SecretKeySpec inputKeyMaterial = new SecretKeySpec(
-                    Utils.concatByteArrays(previous.getSessionUnique(), next.getSessionUnique()),
-                    "" /* key type is just plain raw bytes */);
-            return CryptoOps.hkdf(inputKeyMaterial, RESUME, info);
-        } catch (NoSuchAlgorithmException | InvalidKeyException e) {
-            // Does not happen in practice
-            Log.e(TAG, "Compute MAC failed");
-            return null;
-        }
-    }
-
-    private boolean authenticateClient(byte[] message) {
-        if (message.length != RESUME_HMAC_LENGTH) {
-            Log.e(TAG, "failing because message.length is " + message.length);
-            return false;
-        }
-        return MessageDigest.isEqual(message,
-                computeMAC(mPrevContext, mCurrentContext, CLIENT));
+        mCarTrustAgentBleManager.sendMessage(resumeBytes,
+                OperationType.CLIENT_MESSAGE, /* isPayloadEncrypted= */false,
+                mSendMessageCallback);
     }
 
     void processCredentials(byte[] credentials) {
@@ -529,7 +449,7 @@
         byte[] handle = phoneCredentials.getHandle().toByteArray();
 
         mUnlockDelegate.onUnlockDataReceived(
-                mTrustedDeviceService.getUserHandleByTokenHandle(Utils.bytesToLong(handle)),
+                mCarCompanionDeviceStorage.getUserHandleByTokenHandle(Utils.bytesToLong(handle)),
                 phoneCredentials.getEscrowToken().toByteArray(),
                 Utils.bytesToLong(handle));
     }
@@ -553,16 +473,12 @@
      */
     private void resetEncryptionState() {
         mEncryptionRunner = EncryptionRunnerFactory.newRunner();
-        mHandshakeMessage = null;
+        // It should always be a reconnection for unlock because only enrolled device can unlock
+        // the IHU.
+        mEncryptionRunner.setIsReconnect(true);
         mEncryptionKey = null;
         mEncryptionState = HandshakeMessage.HandshakeState.UNKNOWN;
         mCurrentUnlockState = UNLOCK_STATE_WAITING_FOR_UNIQUE_ID;
-        if (mCurrentContext != null) {
-            mCurrentContext = null;
-        }
-        if (mPrevContext != null) {
-            mPrevContext = null;
-        }
     }
 
     void dump(PrintWriter writer) {
diff --git a/service/src/com/android/car/trust/CarTrustedDeviceService.java b/service/src/com/android/car/trust/CarTrustedDeviceService.java
index 58869a4..b339179 100644
--- a/service/src/com/android/car/trust/CarTrustedDeviceService.java
+++ b/service/src/com/android/car/trust/CarTrustedDeviceService.java
@@ -16,42 +16,17 @@
 
 package com.android.car.trust;
 
-import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.bluetooth.BluetoothDevice;
 import android.car.trust.TrustedDeviceInfo;
 import android.content.Context;
-import android.content.SharedPreferences;
-import android.security.keystore.KeyGenParameterSpec;
-import android.security.keystore.KeyProperties;
-import android.sysprop.CarProperties;
-import android.util.Base64;
 import android.util.Log;
 
 import com.android.car.CarServiceBase;
-import com.android.car.R;
-import com.android.car.Utils;
+import com.android.car.trust.CarTrustAgentBleManager.BleEventCallback;
 
-import java.io.IOException;
 import java.io.PrintWriter;
-import java.security.InvalidAlgorithmParameterException;
-import java.security.InvalidKeyException;
-import java.security.Key;
-import java.security.KeyStore;
-import java.security.KeyStoreException;
-import java.security.NoSuchAlgorithmException;
-import java.security.NoSuchProviderException;
-import java.security.UnrecoverableKeyException;
-import java.security.cert.CertificateException;
 import java.util.List;
-import java.util.UUID;
-
-import javax.crypto.BadPaddingException;
-import javax.crypto.Cipher;
-import javax.crypto.IllegalBlockSizeException;
-import javax.crypto.KeyGenerator;
-import javax.crypto.NoSuchPaddingException;
-import javax.crypto.spec.GCMParameterSpec;
 
 /**
  * The part of the Car service that enables the Trusted device feature.  Trusted Device is a feature
@@ -61,53 +36,24 @@
  * It is comprised of the {@link CarTrustAgentEnrollmentService} for handling enrollment and
  * {@link CarTrustAgentUnlockService} for handling unlock/auth.
  *
+ * @deprecated Enrolling a trusted device is no longer a supported feature of car service.
  */
-public class CarTrustedDeviceService implements CarServiceBase {
+@Deprecated
+public class CarTrustedDeviceService implements CarServiceBase, BleEventCallback {
     private static final String TAG = CarTrustedDeviceService.class.getSimpleName();
 
-    private static final String UNIQUE_ID_KEY = "CTABM_unique_id";
-    private static final String PREF_ENCRYPTION_KEY_PREFIX = "CTABM_encryption_key";
-    private static final String KEY_ALIAS = "Ukey2Key";
-    private static final String CIPHER_TRANSFORMATION = "AES/GCM/NoPadding";
-    private static final String KEYSTORE_PROVIDER = "AndroidKeyStore";
-    private static final String IV_SPEC_SEPARATOR = ";";
-
-    // Device name length is limited by available bytes in BLE advertisement data packet.
-    //
-    // BLE advertisement limits data packet length to 31
-    // Currently we send:
-    // - 18 bytes for 16 chars UUID: 16 bytes + 2 bytes for header;
-    // - 3 bytes for advertisement being connectable;
-    // which leaves 10 bytes.
-    // Subtracting 2 bytes used by header, we have 8 bytes for device name.
-    private static final int DEVICE_NAME_LENGTH_LIMIT = 8;
-    // Limit prefix to 4 chars and fill the rest with randomly generated name. Use random name
-    // to improve uniqueness in paired device name.
-    private static final int DEVICE_NAME_PREFIX_LIMIT = 4;
-
-    // The length of the authentication tag for a cipher in GCM mode. The GCM specification states
-    // that this length can only have the values {128, 120, 112, 104, 96}. Using the highest
-    // possible value.
-    private static final int GCM_AUTHENTICATION_TAG_LENGTH = 128;
-
     private final Context mContext;
     private CarTrustAgentEnrollmentService mCarTrustAgentEnrollmentService;
     private CarTrustAgentUnlockService mCarTrustAgentUnlockService;
     private CarTrustAgentBleManager mCarTrustAgentBleManager;
-    private SharedPreferences mTrustAgentTokenPreferences;
-    private UUID mUniqueId;
-    private String mEnrollmentDeviceName;
 
     public CarTrustedDeviceService(Context context) {
         mContext = context;
-
-        // TODO(b/134695083): Decouple these classes. The services should instead register as
-        // listeners on CarTrustAgentBleManager. CarTrustAgentBleManager should not know about
-        // the services and just dispatch BLE events.
-        mCarTrustAgentBleManager = new CarTrustAgentBleManager(context);
+        BlePeripheralManager blePeripheralManager = new BlePeripheralManager(context);
+        mCarTrustAgentBleManager = new CarTrustAgentBleManager(context, blePeripheralManager);
         mCarTrustAgentEnrollmentService = new CarTrustAgentEnrollmentService(mContext, this,
                 mCarTrustAgentBleManager);
-        mCarTrustAgentUnlockService = new CarTrustAgentUnlockService(this,
+        mCarTrustAgentUnlockService = new CarTrustAgentUnlockService(mContext, this,
                 mCarTrustAgentBleManager);
     }
 
@@ -115,6 +61,7 @@
     public synchronized void init() {
         mCarTrustAgentEnrollmentService.init();
         mCarTrustAgentUnlockService.init();
+        mCarTrustAgentBleManager.addBleEventCallback(this);
     }
 
     @Override
@@ -139,27 +86,25 @@
     }
 
     /**
-     * Returns User Id for the given token handle
+     * Called when a remote device is connected
      *
-     * @param handle The handle corresponding to the escrow token
-     * @return User id corresponding to the handle
+     * @param device the remote device
      */
-    int getUserHandleByTokenHandle(long handle) {
-        return getSharedPrefs().getInt(String.valueOf(handle), -1);
-    }
-
-    void onRemoteDeviceConnected(BluetoothDevice device) {
+    @Override
+    public void onRemoteDeviceConnected(BluetoothDevice device) {
         mCarTrustAgentEnrollmentService.onRemoteDeviceConnected(device);
         mCarTrustAgentUnlockService.onRemoteDeviceConnected(device);
     }
 
-    void onRemoteDeviceDisconnected(BluetoothDevice device) {
+    @Override
+    public void onRemoteDeviceDisconnected(BluetoothDevice device) {
         mCarTrustAgentEnrollmentService.onRemoteDeviceDisconnected(device);
         mCarTrustAgentUnlockService.onRemoteDeviceDisconnected(device);
     }
 
-    void onDeviceNameRetrieved(String deviceName) {
-        mCarTrustAgentEnrollmentService.onDeviceNameRetrieved(deviceName);
+    @Override
+    public void onClientDeviceNameRetrieved(String deviceName) {
+        mCarTrustAgentEnrollmentService.onClientDeviceNameRetrieved(deviceName);
     }
 
     void cleanupBleService() {
@@ -171,15 +116,6 @@
         mCarTrustAgentBleManager.stopUnlockAdvertising();
     }
 
-    SharedPreferences getSharedPrefs() {
-        if (mTrustAgentTokenPreferences != null) {
-            return mTrustAgentTokenPreferences;
-        }
-        mTrustAgentTokenPreferences = mContext.getSharedPreferences(
-                mContext.getString(R.string.token_handle_shared_preferences), Context.MODE_PRIVATE);
-        return mTrustAgentTokenPreferences;
-    }
-
     @Override
     public void dump(PrintWriter writer) {
         writer.println("*CarTrustedDeviceService*");
@@ -206,216 +142,4 @@
         return sb.toString();
     }
 
-    /**
-     * Get the unique id for head unit. Persists on device until factory reset.
-     *
-     * @return unique id, or null if unable to retrieve generated id (this should never happen)
-     */
-    @Nullable
-    UUID getUniqueId() {
-        if (mUniqueId != null) {
-            return mUniqueId;
-        }
-
-        SharedPreferences prefs = getSharedPrefs();
-        if (prefs.contains(UNIQUE_ID_KEY)) {
-            mUniqueId = UUID.fromString(
-                    prefs.getString(UNIQUE_ID_KEY, null));
-            if (Log.isLoggable(TAG, Log.DEBUG)) {
-                Log.d(TAG, "Found existing trusted unique id: "
-                        + prefs.getString(UNIQUE_ID_KEY, ""));
-            }
-        } else {
-            mUniqueId = UUID.randomUUID();
-            if (!prefs.edit().putString(UNIQUE_ID_KEY, mUniqueId.toString()).commit()) {
-                mUniqueId = null;
-            } else if (Log.isLoggable(TAG, Log.DEBUG)) {
-                Log.d(TAG, "Generated new trusted unique id: "
-                        + prefs.getString(UNIQUE_ID_KEY, ""));
-            }
-        }
-
-        return mUniqueId;
-    }
-
-    /**
-     * Get communication encryption key for the given device
-     *
-     * @param deviceId id of trusted device
-     * @return encryption key, null if device id is not recognized
-     */
-    @Nullable
-    byte[] getEncryptionKey(String deviceId) {
-        SharedPreferences prefs = getSharedPrefs();
-        String key = PREF_ENCRYPTION_KEY_PREFIX + deviceId;
-        if (!prefs.contains(key)) {
-            return null;
-        }
-
-        // This value will not be "null" because we already checked via a call to contains().
-        String[] values = prefs.getString(key, null).split(IV_SPEC_SEPARATOR);
-
-        if (values.length != 2) {
-            return null;
-        }
-
-        byte[] encryptedKey = Base64.decode(values[0], Base64.DEFAULT);
-        byte[] ivSpec = Base64.decode(values[1], Base64.DEFAULT);
-        return decryptWithKeyStore(KEY_ALIAS, encryptedKey, ivSpec);
-    }
-
-    /**
-     * Save encryption key for the given device
-     *
-     * @param deviceId did of trusted device
-     * @param encryptionKey encryption key
-     * @return {@code true} if the operation succeeded
-     */
-    boolean saveEncryptionKey(@Nullable String deviceId, @Nullable byte[] encryptionKey) {
-        if (encryptionKey == null || deviceId == null) {
-            return false;
-        }
-        String encryptedKey = encryptWithKeyStore(KEY_ALIAS, encryptionKey);
-        if (encryptedKey == null) {
-            return false;
-        }
-        if (getSharedPrefs().contains(deviceId)) {
-            clearEncryptionKey(deviceId);
-        }
-
-        return getSharedPrefs()
-                .edit()
-                .putString(PREF_ENCRYPTION_KEY_PREFIX + deviceId, encryptedKey)
-                .commit();
-    }
-
-    /**
-     * Clear the encryption key for the given device
-     *
-     * @param deviceId id of the peer device
-     */
-    void clearEncryptionKey(@Nullable String deviceId) {
-        if (deviceId == null) {
-            return;
-        }
-        getSharedPrefs().edit().remove(deviceId);
-    }
-
-    /**
-     * Returns the name that should be used for the device during enrollment of a trusted device.
-     *
-     * <p>The returned name will be a combination of a prefix sysprop and randomized digits.
-     */
-    String getEnrollmentDeviceName() {
-        if (mEnrollmentDeviceName == null) {
-            String deviceNamePrefix =
-                    CarProperties.trusted_device_device_name_prefix().orElse("");
-            deviceNamePrefix = deviceNamePrefix.substring(
-                    0, Math.min(deviceNamePrefix.length(), DEVICE_NAME_PREFIX_LIMIT));
-
-            int randomNameLength = DEVICE_NAME_LENGTH_LIMIT - deviceNamePrefix.length();
-            String randomName = Utils.generateRandomNumberString(randomNameLength);
-            mEnrollmentDeviceName = deviceNamePrefix + randomName;
-        }
-        return mEnrollmentDeviceName;
-    }
-
-    /**
-     * Encrypt value with designated key
-     *
-     * <p>The encrypted value is of the form:
-     *
-     * <p>key + IV_SPEC_SEPARATOR + ivSpec
-     *
-     * <p>The {@code ivSpec} is needed to decrypt this key later on.
-     *
-     * @param keyAlias KeyStore alias for key to use
-     * @param value a value to encrypt
-     * @return encrypted value, null if unable to encrypt
-     */
-    @Nullable
-    String encryptWithKeyStore(String keyAlias, byte[] value) {
-        if (value == null) {
-            return null;
-        }
-
-        Key key = getKeyStoreKey(keyAlias);
-        try {
-            Cipher cipher = Cipher.getInstance(CIPHER_TRANSFORMATION);
-            cipher.init(Cipher.ENCRYPT_MODE, key);
-            return new StringBuffer(Base64.encodeToString(cipher.doFinal(value), Base64.DEFAULT))
-                .append(IV_SPEC_SEPARATOR)
-                .append(Base64.encodeToString(cipher.getIV(), Base64.DEFAULT))
-                .toString();
-        } catch (IllegalBlockSizeException
-                | BadPaddingException
-                | NoSuchAlgorithmException
-                | NoSuchPaddingException
-                | IllegalStateException
-                | InvalidKeyException e) {
-            Log.e(TAG, "Unable to encrypt value with key " + keyAlias, e);
-            return null;
-        }
-    }
-
-    /**
-     * Decrypt value with designated key
-     *
-     * @param keyAlias KeyStore alias for key to use
-     * @param value encrypted value
-     * @return decrypted value, null if unable to decrypt
-     */
-    @Nullable
-    byte[] decryptWithKeyStore(String keyAlias, byte[] value, byte[] ivSpec) {
-        if (value == null) {
-            return null;
-        }
-
-        try {
-            Key key = getKeyStoreKey(keyAlias);
-            Cipher cipher = Cipher.getInstance(CIPHER_TRANSFORMATION);
-            cipher.init(Cipher.DECRYPT_MODE, key,
-                    new GCMParameterSpec(GCM_AUTHENTICATION_TAG_LENGTH, ivSpec));
-            return cipher.doFinal(value);
-        } catch (IllegalBlockSizeException
-                | BadPaddingException
-                | NoSuchAlgorithmException
-                | NoSuchPaddingException
-                | IllegalStateException
-                | InvalidKeyException
-                | InvalidAlgorithmParameterException e) {
-            Log.e(TAG, "Unable to decrypt value with key " + keyAlias, e);
-            return null;
-        }
-    }
-
-    private Key getKeyStoreKey(String keyAlias) {
-        KeyStore keyStore;
-        try {
-            keyStore = KeyStore.getInstance(KEYSTORE_PROVIDER);
-            keyStore.load(null);
-            if (!keyStore.containsAlias(keyAlias)) {
-                KeyGenerator keyGenerator = KeyGenerator.getInstance(
-                        KeyProperties.KEY_ALGORITHM_AES, KEYSTORE_PROVIDER);
-                keyGenerator.init(
-                        new KeyGenParameterSpec.Builder(keyAlias,
-                                KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
-                                .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
-                                .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
-                                .build());
-                keyGenerator.generateKey();
-            }
-            return keyStore.getKey(keyAlias, null);
-
-        } catch (KeyStoreException
-                | NoSuchAlgorithmException
-                | UnrecoverableKeyException
-                | NoSuchProviderException
-                | CertificateException
-                | IOException
-                | InvalidAlgorithmParameterException e) {
-            Log.e(TAG, "Unable to retrieve key " + keyAlias + " from KeyStore.", e);
-            throw new IllegalStateException(e);
-        }
-    }
 }
diff --git a/service/src/com/android/car/user/CarUserNoticeService.java b/service/src/com/android/car/user/CarUserNoticeService.java
index 045d960..94caeaf 100644
--- a/service/src/com/android/car/user/CarUserNoticeService.java
+++ b/service/src/com/android/car/user/CarUserNoticeService.java
@@ -48,6 +48,8 @@
 import android.view.IWindowManager;
 import android.view.WindowManagerGlobal;
 
+import androidx.annotation.VisibleForTesting;
+
 import com.android.car.CarLocalServices;
 import com.android.car.CarServiceBase;
 import com.android.car.R;
@@ -78,7 +80,7 @@
     @Nullable
     private final Intent mServiceIntent;
 
-    private final Handler mMainHandler = new Handler(Looper.getMainLooper());
+    private final Handler mMainHandler;
 
     private final Object mLock = new Object();
 
@@ -167,6 +169,7 @@
     };
 
     private final ServiceConnection mUiServiceConnection = new ServiceConnection() {
+        @Override
         public void onServiceConnected(ComponentName name, IBinder service) {
             synchronized (mLock) {
                 if (!mServiceBound) {
@@ -187,6 +190,7 @@
             }
         }
 
+        @Override
         public void onServiceDisconnected(ComponentName name) {
             // UI crashed. Stop it so that it does not come again.
             stopUi(/* clearUiShown= */ true);
@@ -205,6 +209,12 @@
     };
 
     public CarUserNoticeService(Context context) {
+        this(context, new Handler(Looper.getMainLooper()));
+    }
+
+    @VisibleForTesting
+    CarUserNoticeService(Context context, Handler handler) {
+        mMainHandler = handler;
         Resources res = context.getResources();
         String componentName = res.getString(R.string.config_userNoticeUiService);
         if (componentName.isEmpty()) {
diff --git a/service/src/com/android/car/user/CarUserService.java b/service/src/com/android/car/user/CarUserService.java
index 90b5f1d..706f6e5 100644
--- a/service/src/com/android/car/user/CarUserService.java
+++ b/service/src/com/android/car/user/CarUserService.java
@@ -16,182 +16,613 @@
 
 package com.android.car.user;
 
+import static com.android.car.CarLog.TAG_USER;
+
+import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.UserIdInt;
 import android.app.ActivityManager;
+import android.app.ActivityManager.StackInfo;
 import android.app.IActivityManager;
+import android.car.CarOccupantZoneManager;
+import android.car.CarOccupantZoneManager.OccupantTypeEnum;
+import android.car.CarOccupantZoneManager.OccupantZoneInfo;
+import android.car.ICarUserService;
 import android.car.settings.CarSettings;
+import android.car.user.CarUserManager;
 import android.car.userlib.CarUserManagerHelper;
-import android.content.BroadcastReceiver;
 import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
+import android.content.pm.UserInfo;
+import android.graphics.Bitmap;
+import android.hardware.automotive.vehicle.V2_0.InitialUserInfoResponseAction;
+import android.hardware.automotive.vehicle.V2_0.UsersInfo;
 import android.location.LocationManager;
+import android.os.Binder;
+import android.os.Bundle;
 import android.os.RemoteException;
+import android.os.Trace;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.provider.Settings;
 import android.util.Log;
+import android.util.SparseArray;
+import android.util.TimingsTraceLog;
 
 import com.android.car.CarServiceBase;
+import com.android.car.R;
+import com.android.car.hal.UserHalHelper;
+import com.android.car.hal.UserHalService;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.os.IResultReceiver;
+import com.android.internal.util.UserIcons;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Objects;
 import java.util.concurrent.CopyOnWriteArrayList;
 
 /**
  * User service for cars. Manages users at boot time. Including:
  *
  * <ol>
+ *   <li> Creates a user used as driver.
+ *   <li> Creates a user used as passenger.
  *   <li> Creates a secondary admin user on first run.
- *   <li> Log in to the last active user.
+ *   <li> Switch drivers.
  * <ol/>
  */
-public class CarUserService extends BroadcastReceiver implements CarServiceBase {
-    private static final String TAG = "CarUserService";
+public final class CarUserService extends ICarUserService.Stub implements CarServiceBase {
+
+    /** {@code int} extra used to represent a user id in a {@link IResultReceiver} response. */
+    public static final String BUNDLE_USER_ID = "user.id";
+    /** {@code int} extra used to represent user flags in a {@link IResultReceiver} response. */
+    public static final String BUNDLE_USER_FLAGS = "user.flags";
+    /** {@code String} extra used to represent a user name in a {@link IResultReceiver} response. */
+    public static final String BUNDLE_USER_NAME = "user.name";
+    /** {@code int} extra used to represent the info action {@link IResultReceiver} response. */
+    public static final String BUNDLE_INITIAL_INFO_ACTION = "initial_info.action";
+
     private final Context mContext;
     private final CarUserManagerHelper mCarUserManagerHelper;
     private final IActivityManager mAm;
+    private final UserManager mUserManager;
+    private final int mMaxRunningUsers;
+    private final boolean mEnablePassengerSupport;
 
-    private final Object mLock = new Object();
-    @GuardedBy("mLock")
+    private final Object mLockUser = new Object();
+    @GuardedBy("mLockUser")
     private boolean mUser0Unlocked;
-    @GuardedBy("mLock")
+    @GuardedBy("mLockUser")
     private final ArrayList<Runnable> mUser0UnlockTasks = new ArrayList<>();
+    // Only one passenger is supported.
+    @GuardedBy("mLockUser")
+    private @UserIdInt int mLastPassengerId;
     /**
      * Background users that will be restarted in garage mode. This list can include the
-     * current foreground user bit the current foreground user should not be restarted.
+     * current foreground user but the current foreground user should not be restarted.
      */
-    @GuardedBy("mLock")
+    @GuardedBy("mLockUser")
     private final ArrayList<Integer> mBackgroundUsersToRestart = new ArrayList<>();
     /**
      * Keep the list of background users started here. This is wholly for debugging purpose.
      */
-    @GuardedBy("mLock")
+    @GuardedBy("mLockUser")
     private final ArrayList<Integer> mBackgroundUsersRestartedHere = new ArrayList<>();
 
-    private final int mMaxRunningUsers;
-
-    private final UserManager mUserManager;
-
-
+    // TODO(b/144120654): mege then
     private final CopyOnWriteArrayList<UserCallback> mUserCallbacks = new CopyOnWriteArrayList<>();
 
+    private final UserHalService mHal;
+
+    /**
+     * List of lifecycle listeners by uid.
+     */
+    @GuardedBy("mLockUser")
+    private final SparseArray<IResultReceiver> mLifecycleListeners = new SparseArray<>();
+
+    // TODO(b/144120654): replace by CarUserManager listener
     /** Interface for callbacks related to user activities. */
     public interface UserCallback {
         /** Gets called when user lock status has been changed. */
-        void onUserLockChanged(int userId, boolean unlocked);
+        void onUserLockChanged(@UserIdInt int userId, boolean unlocked);
         /** Called when new foreground user started to boot. */
-        void onSwitchUser(int userId);
+        void onSwitchUser(@UserIdInt int userId);
     }
 
-    public CarUserService(
-                @Nullable Context context, @Nullable CarUserManagerHelper carUserManagerHelper,
-                IActivityManager am, int maxRunningUsers) {
-        if (Log.isLoggable(TAG, Log.DEBUG)) {
-            Log.d(TAG, "constructed");
+    private final CopyOnWriteArrayList<PassengerCallback> mPassengerCallbacks =
+            new CopyOnWriteArrayList<>();
+
+    /** Interface for callbaks related to passenger activities. */
+    public interface PassengerCallback {
+        /** Called when passenger is started at a certain zone. */
+        void onPassengerStarted(@UserIdInt int passengerId, int zoneId);
+        /** Called when passenger is stopped. */
+        void onPassengerStopped(@UserIdInt int passengerId);
+    }
+
+    /** Interface for delegating zone-related implementation to CarOccupantZoneService. */
+    public interface ZoneUserBindingHelper {
+        /** Gets occupant zones corresponding to the occupant type. */
+        @NonNull
+        List<OccupantZoneInfo> getOccupantZones(@OccupantTypeEnum int occupantType);
+        /** Assigns the user to the occupant zone. */
+        boolean assignUserToOccupantZone(@UserIdInt int userId, int zoneId);
+        /** Makes the occupant zone unoccupied. */
+        boolean unassignUserFromOccupantZone(@UserIdInt int userId);
+        /** Returns whether there is a passenger display. */
+        boolean isPassengerDisplayAvailable();
+    }
+
+    private final Object mLockHelper = new Object();
+    @GuardedBy("mLockHelper")
+    private ZoneUserBindingHelper mZoneUserBindingHelper;
+
+    public CarUserService(@NonNull Context context, @NonNull UserHalService hal,
+            @NonNull CarUserManagerHelper carUserManagerHelper,
+            @NonNull UserManager userManager, @NonNull IActivityManager am, int maxRunningUsers) {
+        if (Log.isLoggable(TAG_USER, Log.DEBUG)) {
+            Log.d(TAG_USER, "constructed");
         }
         mContext = context;
+        mHal = hal;
         mCarUserManagerHelper = carUserManagerHelper;
         mAm = am;
         mMaxRunningUsers = maxRunningUsers;
-        mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
+        mUserManager = userManager;
+        mLastPassengerId = UserHandle.USER_NULL;
+        mEnablePassengerSupport = context.getResources().getBoolean(R.bool.enablePassengerSupport);
     }
 
     @Override
     public void init() {
-        if (Log.isLoggable(TAG, Log.DEBUG)) {
-            Log.d(TAG, "init");
+        if (Log.isLoggable(TAG_USER, Log.DEBUG)) {
+            Log.d(TAG_USER, "init");
         }
-        IntentFilter filter = new IntentFilter();
-        filter.addAction(Intent.ACTION_USER_SWITCHED);
-
-        mContext.registerReceiver(this, filter);
     }
 
     @Override
     public void release() {
-        if (Log.isLoggable(TAG, Log.DEBUG)) {
-            Log.d(TAG, "release");
+        if (Log.isLoggable(TAG_USER, Log.DEBUG)) {
+            Log.d(TAG_USER, "release");
         }
-        mContext.unregisterReceiver(this);
     }
 
     @Override
-    public void dump(PrintWriter writer) {
-        writer.println(TAG);
-        boolean user0Unlocked;
-        ArrayList<Integer> backgroundUsersToRestart;
-        ArrayList<Integer> backgroundUsersRestarted;
-        synchronized (mLock) {
-            user0Unlocked = mUser0Unlocked;
-            backgroundUsersToRestart = new ArrayList<>(mBackgroundUsersToRestart);
-            backgroundUsersRestarted = new ArrayList<>(mBackgroundUsersRestartedHere);
-
+    public void dump(@NonNull PrintWriter writer) {
+        checkAtLeastOnePermission("dump()", android.Manifest.permission.DUMP);
+        writer.println("*CarUserService*");
+        String indent = "  ";
+        synchronized (mLockUser) {
+            int numberListeners = mLifecycleListeners.size();
+            if (numberListeners == 0) {
+                writer.println("No lifecycle listeners");
+            } else {
+                writer.printf("%d lifecycle listeners\n", numberListeners);
+                for (int i = 0; i < numberListeners; i++) {
+                    int uid = mLifecycleListeners.keyAt(i);
+                    IResultReceiver listener = mLifecycleListeners.valueAt(i);
+                    writer.printf("%suid: %d Listener %s\n", indent, uid, listener);
+                }
+            }
+            writer.println("User0Unlocked: " + mUser0Unlocked);
+            writer.println("MaxRunningUsers: " + mMaxRunningUsers);
+            writer.println("BackgroundUsersToRestart: " + mBackgroundUsersToRestart);
+            writer.println("BackgroundUsersRestarted: " + mBackgroundUsersRestartedHere);
+            List<UserInfo> allDrivers = getAllDrivers();
+            int driversSize = allDrivers.size();
+            writer.println("NumberOfDrivers: " + driversSize);
+            for (int i = 0; i < driversSize; i++) {
+                int driverId = allDrivers.get(i).id;
+                writer.print(indent + "#" + i + ": id=" + driverId);
+                List<UserInfo> passengers = getPassengers(driverId);
+                int passengersSize = passengers.size();
+                writer.print(" NumberPassengers: " + passengersSize);
+                if (passengersSize > 0) {
+                    writer.print(" [");
+                    for (int j = 0; j < passengersSize; j++) {
+                        writer.print(passengers.get(j).id);
+                        if (j < passengersSize - 1) {
+                            writer.print(" ");
+                        }
+                    }
+                    writer.print("]");
+                }
+                writer.println();
+            }
+            writer.println("EnablePassengerSupport: " + mEnablePassengerSupport);
         }
-        writer.println("User0Unlocked: " + user0Unlocked);
-        writer.println("maxRunningUsers:" + mMaxRunningUsers);
-        writer.println("BackgroundUsersToRestart:" + backgroundUsersToRestart);
-        writer.println("BackgroundUsersRestarted:" + backgroundUsersRestarted);
+    }
+
+    /**
+     * Creates a driver who is a regular user and is allowed to login to the driving occupant zone.
+     *
+     * @param name The name of the driver to be created.
+     * @param admin Whether the created driver will be an admin.
+     * @return {@link UserInfo} object of the created driver, or {@code null} if the driver could
+     *         not be created.
+     */
+    @Override
+    @Nullable
+    public UserInfo createDriver(@NonNull String name, boolean admin) {
+        checkManageUsersPermission("createDriver");
+        Objects.requireNonNull(name, "name cannot be null");
+        if (admin) {
+            return createNewAdminUser(name);
+        }
+        return mCarUserManagerHelper.createNewNonAdminUser(name);
+    }
+
+    /**
+     * Creates a passenger who is a profile of the given driver.
+     *
+     * @param name The name of the passenger to be created.
+     * @param driverId User id of the driver under whom a passenger is created.
+     * @return {@link UserInfo} object of the created passenger, or {@code null} if the passenger
+     *         could not be created.
+     */
+    @Override
+    @Nullable
+    public UserInfo createPassenger(@NonNull String name, @UserIdInt int driverId) {
+        checkManageUsersPermission("createPassenger");
+        Objects.requireNonNull(name, "name cannot be null");
+        UserInfo driver = mUserManager.getUserInfo(driverId);
+        if (driver == null) {
+            Log.w(TAG_USER, "the driver is invalid");
+            return null;
+        }
+        if (driver.isGuest()) {
+            Log.w(TAG_USER, "a guest driver cannot create a passenger");
+            return null;
+        }
+        UserInfo user = mUserManager.createProfileForUser(name,
+                UserManager.USER_TYPE_PROFILE_MANAGED, /* flags */ 0, driverId);
+        if (user == null) {
+            // Couldn't create user, most likely because there are too many.
+            Log.w(TAG_USER, "can't create a profile for user" + driverId);
+            return null;
+        }
+        // Passenger user should be a non-admin user.
+        mCarUserManagerHelper.setDefaultNonAdminRestrictions(user, /* enable= */ true);
+        assignDefaultIcon(user);
+        return user;
+    }
+
+    /**
+     * @see CarUserManager.switchDriver
+     */
+    @Override
+    public boolean switchDriver(@UserIdInt int driverId) {
+        checkManageUsersPermission("switchDriver");
+        if (driverId == UserHandle.USER_SYSTEM && UserManager.isHeadlessSystemUserMode()) {
+            // System user doesn't associate with real person, can not be switched to.
+            Log.w(TAG_USER, "switching to system user in headless system user mode is not allowed");
+            return false;
+        }
+        int userSwitchable = mUserManager.getUserSwitchability();
+        if (userSwitchable != UserManager.SWITCHABILITY_STATUS_OK) {
+            Log.w(TAG_USER, "current process is not allowed to switch user");
+            return false;
+        }
+        if (driverId == ActivityManager.getCurrentUser()) {
+            // The current user is already the given user.
+            return true;
+        }
+        try {
+            return mAm.switchUser(driverId);
+        } catch (RemoteException e) {
+            // ignore
+            Log.w(TAG_USER, "error while switching user", e);
+        }
+        return false;
+    }
+
+    /**
+     * Returns all drivers who can occupy the driving zone. Guest users are included in the list.
+     *
+     * @return the list of {@link UserInfo} who can be a driver on the device.
+     */
+    @Override
+    @NonNull
+    public List<UserInfo> getAllDrivers() {
+        checkManageUsersOrDumpPermission("getAllDrivers");
+        return getUsers((user) -> {
+            return !isSystemUser(user.id) && user.isEnabled() && !user.isManagedProfile()
+                    && !user.isEphemeral();
+        });
+    }
+
+    /**
+     * Returns all passengers under the given driver.
+     *
+     * @param driverId User id of a driver.
+     * @return the list of {@link UserInfo} who is a passenger under the given driver.
+     */
+    @Override
+    @NonNull
+    public List<UserInfo> getPassengers(@UserIdInt int driverId) {
+        checkManageUsersOrDumpPermission("getPassengers");
+        return getUsers((user) -> {
+            return !isSystemUser(user.id) && user.isEnabled() && user.isManagedProfile()
+                    && user.profileGroupId == driverId;
+        });
+    }
+
+    /**
+     * @see CarUserManager.startPassenger
+     */
+    @Override
+    public boolean startPassenger(@UserIdInt int passengerId, int zoneId) {
+        checkManageUsersPermission("startPassenger");
+        synchronized (mLockUser) {
+            try {
+                if (!mAm.startUserInBackgroundWithListener(passengerId, null)) {
+                    Log.w(TAG_USER, "could not start passenger");
+                    return false;
+                }
+            } catch (RemoteException e) {
+                // ignore
+                Log.w(TAG_USER, "error while starting passenger", e);
+                return false;
+            }
+            if (!assignUserToOccupantZone(passengerId, zoneId)) {
+                Log.w(TAG_USER, "could not assign passenger to zone");
+                return false;
+            }
+            mLastPassengerId = passengerId;
+        }
+        for (PassengerCallback callback : mPassengerCallbacks) {
+            callback.onPassengerStarted(passengerId, zoneId);
+        }
+        return true;
+    }
+
+    /**
+     * @see CarUserManager.stopPassenger
+     */
+    @Override
+    public boolean stopPassenger(@UserIdInt int passengerId) {
+        checkManageUsersPermission("stopPassenger");
+        return stopPassengerInternal(passengerId, true);
+    }
+
+    private boolean stopPassengerInternal(@UserIdInt int passengerId, boolean checkCurrentDriver) {
+        synchronized (mLockUser) {
+            UserInfo passenger = mUserManager.getUserInfo(passengerId);
+            if (passenger == null) {
+                Log.w(TAG_USER, "passenger " + passengerId + " doesn't exist");
+                return false;
+            }
+            if (mLastPassengerId != passengerId) {
+                Log.w(TAG_USER, "passenger " + passengerId + " hasn't been started");
+                return true;
+            }
+            if (checkCurrentDriver) {
+                int currentUser = ActivityManager.getCurrentUser();
+                if (passenger.profileGroupId != currentUser) {
+                    Log.w(TAG_USER, "passenger " + passengerId
+                            + " is not a profile of the current user");
+                    return false;
+                }
+            }
+            // Passenger is a profile, so cannot be stopped through activity manager.
+            // Instead, activities started by the passenger are stopped and the passenger is
+            // unassigned from the zone.
+            stopAllTasks(passengerId);
+            if (!unassignUserFromOccupantZone(passengerId)) {
+                Log.w(TAG_USER, "could not unassign user from occupant zone");
+                return false;
+            }
+            mLastPassengerId = UserHandle.USER_NULL;
+        }
+        for (PassengerCallback callback : mPassengerCallbacks) {
+            callback.onPassengerStopped(passengerId);
+        }
+        return true;
+    }
+
+    private void stopAllTasks(@UserIdInt int userId) {
+        try {
+            for (StackInfo info : mAm.getAllStackInfos()) {
+                for (int i = 0; i < info.taskIds.length; i++) {
+                    if (info.taskUserIds[i] == userId) {
+                        int taskId = info.taskIds[i];
+                        if (!mAm.removeTask(taskId)) {
+                            Log.w(TAG_USER, "could not remove task " + taskId);
+                        }
+                    }
+                }
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG_USER, "could not get stack info", e);
+        }
+    }
+
+    @Override
+    public void setLifecycleListenerForUid(IResultReceiver listener) {
+        int uid = Binder.getCallingUid();
+        checkInteractAcrossUsersPermission("setLifecycleListenerForUid" + uid);
+
+        try {
+            listener.asBinder().linkToDeath(() -> onListenerDeath(uid), 0);
+        } catch (RemoteException e) {
+            Log.wtf(TAG_USER, "Cannot listen to death of " + uid);
+        }
+        synchronized (mLockUser) {
+            mLifecycleListeners.append(uid, listener);
+        }
+    }
+
+    private void onListenerDeath(int uid) {
+        Log.i(TAG_USER, "Removing listeners for uid " + uid + " on binder death");
+        synchronized (mLockUser) {
+            removeLifecycleListenerLocked(uid);
+        }
+    }
+
+    @Override
+    public void resetLifecycleListenerForUid() {
+        int uid = Binder.getCallingUid();
+        checkInteractAcrossUsersPermission("resetLifecycleListenerForUid-" + uid);
+
+        synchronized (mLockUser) {
+            removeLifecycleListenerLocked(uid);
+        }
+    }
+
+    private void removeLifecycleListenerLocked(int uid) {
+        mLifecycleListeners.remove(uid);
+    }
+
+    @Override
+    public void getInitialUserInfo(int requestType, int timeoutMs,
+            @NonNull IResultReceiver receiver) {
+        UsersInfo usersInfo = getUsersInfo();
+        mHal.getInitialUserInfo(requestType, timeoutMs, usersInfo, (status, resp) -> {
+            try {
+                Bundle resultData = null;
+                if (resp != null) {
+                    switch (resp.action) {
+                        case InitialUserInfoResponseAction.SWITCH:
+                            resultData = new Bundle();
+                            resultData.putInt(BUNDLE_INITIAL_INFO_ACTION, resp.action);
+                            resultData.putInt(BUNDLE_USER_ID, resp.userToSwitchOrCreate.userId);
+                            break;
+                        case InitialUserInfoResponseAction.CREATE:
+                            resultData = new Bundle();
+                            resultData.putInt(BUNDLE_INITIAL_INFO_ACTION, resp.action);
+                            resultData.putInt(BUNDLE_USER_FLAGS, resp.userToSwitchOrCreate.flags);
+                            resultData.putString(BUNDLE_USER_NAME, resp.userNameToCreate);
+                            break;
+                        case InitialUserInfoResponseAction.DEFAULT:
+                            // do nothing
+                            break;
+                        default:
+                            // That's ok, it will be the same as DEFAULT...
+                            Log.w(TAG_USER, "invalid response action on " + resp);
+                    }
+                }
+                receiver.send(status, resultData);
+            } catch (RemoteException e) {
+                Log.w(TAG_USER, "Could not send result back to receiver", e);
+            }
+        });
+    }
+
+    // TODO(b/144120654): use helper to generate UsersInfo
+    private UsersInfo getUsersInfo() {
+        UserInfo currentUser;
+        try {
+            currentUser = mAm.getCurrentUser();
+        } catch (RemoteException e) {
+            // shouldn't happen
+            throw new IllegalStateException("Could not get current user: ", e);
+        }
+        List<UserInfo> existingUsers = mUserManager.getUsers();
+        int size = existingUsers.size();
+
+        UsersInfo usersInfo = new UsersInfo();
+        usersInfo.numberUsers = size;
+        usersInfo.currentUser.userId = currentUser.id;
+        usersInfo.currentUser.flags = UserHalHelper.convertFlags(currentUser);
+
+        for (int i = 0; i < size; i++) {
+            UserInfo androidUser = existingUsers.get(i);
+            android.hardware.automotive.vehicle.V2_0.UserInfo halUser =
+                    new android.hardware.automotive.vehicle.V2_0.UserInfo();
+            halUser.userId = androidUser.id;
+            halUser.flags = UserHalHelper.convertFlags(androidUser);
+            usersInfo.existingUsers.add(halUser);
+        }
+
+        return usersInfo;
+    }
+
+    /** Returns whether the given user is a system user. */
+    private static boolean isSystemUser(@UserIdInt int userId) {
+        return userId == UserHandle.USER_SYSTEM;
     }
 
     private void updateDefaultUserRestriction() {
         // We want to set restrictions on system and guest users only once. These are persisted
         // onto disk, so it's sufficient to do it once + we minimize the number of disk writes.
         if (Settings.Global.getInt(mContext.getContentResolver(),
-                CarSettings.Global.DEFAULT_USER_RESTRICTIONS_SET, /* default= */ 0) == 0) {
-            // Only apply the system user restrictions if the system user is headless.
-            if (mCarUserManagerHelper.isHeadlessSystemUser()) {
-                setSystemUserRestrictions();
-            }
-            mCarUserManagerHelper.initDefaultGuestRestrictions();
-            Settings.Global.putInt(mContext.getContentResolver(),
-                    CarSettings.Global.DEFAULT_USER_RESTRICTIONS_SET, 1);
+                CarSettings.Global.DEFAULT_USER_RESTRICTIONS_SET, /* default= */ 0) != 0) {
+            return;
         }
+        // Only apply the system user restrictions if the system user is headless.
+        if (UserManager.isHeadlessSystemUserMode()) {
+            setSystemUserRestrictions();
+        }
+        Settings.Global.putInt(mContext.getContentResolver(),
+                CarSettings.Global.DEFAULT_USER_RESTRICTIONS_SET, 1);
     }
 
-    @Override
-    public void onReceive(Context context, Intent intent) {
-        if (Log.isLoggable(TAG, Log.DEBUG)) {
-            Log.d(TAG, "onReceive " + intent);
-        }
-
-        if (Intent.ACTION_USER_SWITCHED.equals(intent.getAction())) {
-            // Update last active user if the switched-to user is a persistent, non-system user.
-            final int currentUser = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
-            if (currentUser > UserHandle.USER_SYSTEM
-                        && mCarUserManagerHelper.isPersistentUser(currentUser)) {
-                mCarUserManagerHelper.setLastActiveUser(currentUser);
-            }
-        }
+    private boolean isPersistentUser(@UserIdInt int userId) {
+        return !mUserManager.getUserInfo(userId).isEphemeral();
     }
 
-    /** Add callback to listen to user activity events. */
-    public void addUserCallback(UserCallback callback) {
+    /** Adds callback to listen to user activity events. */
+    public void addUserCallback(@NonNull UserCallback callback) {
+        Objects.requireNonNull(callback, "callback cannot be null");
         mUserCallbacks.add(callback);
     }
 
     /** Removes previously added callback to listen user events. */
-    public void removeUserCallback(UserCallback callback) {
+    public void removeUserCallback(@NonNull UserCallback callback) {
+        Objects.requireNonNull(callback, "callback cannot be null");
         mUserCallbacks.remove(callback);
     }
 
-    /**
-     * Set user lock / unlocking status. This is coming from system server through ICar binder call.
-     * @param userHandle Handle of user
-     * @param unlocked unlocked (=true) or locked (=false)
-     */
-    public void setUserLockStatus(int userHandle, boolean unlocked) {
-        for (UserCallback callback : mUserCallbacks) {
-            callback.onUserLockChanged(userHandle, unlocked);
+    /** Adds callback to listen to passenger activity events. */
+    public void addPassengerCallback(@NonNull PassengerCallback callback) {
+        Objects.requireNonNull(callback, "callback cannot be null");
+        mPassengerCallbacks.add(callback);
+    }
+
+    /** Removes previously added callback to listen passenger events. */
+    public void removePassengerCallback(@NonNull PassengerCallback callback) {
+        Objects.requireNonNull(callback, "callback cannot be null");
+        mPassengerCallbacks.remove(callback);
+    }
+
+    /** Sets the implementation of ZoneUserBindingHelper. */
+    public void setZoneUserBindingHelper(@NonNull ZoneUserBindingHelper helper) {
+        synchronized (mLockHelper) {
+            mZoneUserBindingHelper = helper;
         }
+    }
+
+    /**
+     * Sets user lock/unlocking status. This is coming from system server through ICar binder call.
+     *
+     * @param userId User id whoes lock status is changed.
+     * @param unlocked Unlocked (={@code true}) or locked (={@code false}).
+     */
+    public void setUserLockStatus(@UserIdInt int userId, boolean unlocked) {
+        TimingsTraceLog t = new TimingsTraceLog(TAG_USER,
+                Trace.TRACE_TAG_SYSTEM_SERVER);
+        t.traceBegin("onUserLockChanged-" + userId
+                + (unlocked ? "-unlocked" : "-locked"));
+        for (UserCallback callback : mUserCallbacks) {
+            t.traceBegin("onUserLockChanged-"
+                    + callback.getClass().getSimpleName());
+            callback.onUserLockChanged(userId, unlocked);
+            t.traceEnd();
+        }
+        t.traceEnd();
+
         if (!unlocked) { // nothing else to do when it is locked back.
             return;
         }
+
+        t.traceBegin("setUserLockStatus-UnlockTasks-" + userId);
         ArrayList<Runnable> tasks = null;
-        synchronized (mLock) {
-            if (userHandle == UserHandle.USER_SYSTEM) {
+        synchronized (mLockUser) {
+            if (userId == UserHandle.USER_SYSTEM) {
                 if (!mUser0Unlocked) { // user 0, unlocked, do this only once
                     updateDefaultUserRestriction();
                     tasks = new ArrayList<>(mUser0UnlockTasks);
@@ -199,18 +630,18 @@
                     mUser0Unlocked = unlocked;
                 }
             } else { // none user0
-                Integer user = userHandle;
-                if (mCarUserManagerHelper.isPersistentUser(userHandle)) {
+                Integer user = userId;
+                if (isPersistentUser(userId)) {
                     // current foreground user should stay in top priority.
-                    if (userHandle == mCarUserManagerHelper.getCurrentForegroundUserId()) {
+                    if (userId == ActivityManager.getCurrentUser()) {
                         mBackgroundUsersToRestart.remove(user);
                         mBackgroundUsersToRestart.add(0, user);
                     }
                     // -1 for user 0
                     if (mBackgroundUsersToRestart.size() > (mMaxRunningUsers - 1)) {
-                        final int userToDrop = mBackgroundUsersToRestart.get(
+                        int userToDrop = mBackgroundUsersToRestart.get(
                                 mBackgroundUsersToRestart.size() - 1);
-                        Log.i(TAG, "New user unlocked:" + userHandle
+                        Log.i(TAG_USER, "New user unlocked:" + userId
                                 + ", dropping least recently user from restart list:" + userToDrop);
                         // Drop the least recently used user.
                         mBackgroundUsersToRestart.remove(mBackgroundUsersToRestart.size() - 1);
@@ -219,27 +650,30 @@
             }
         }
         if (tasks != null && tasks.size() > 0) {
-            Log.d(TAG, "User0 unlocked, run queued tasks:" + tasks.size());
+            Log.d(TAG_USER, "User0 unlocked, run queued tasks:" + tasks.size());
             for (Runnable r : tasks) {
                 r.run();
             }
         }
+        t.traceEnd();
     }
 
     /**
-     * Start all background users that were active in system.
+     * Starts all background users that were active in system.
+     *
      * @return list of background users started successfully.
      */
+    @NonNull
     public ArrayList<Integer> startAllBackgroundUsers() {
         ArrayList<Integer> users;
-        synchronized (mLock) {
+        synchronized (mLockUser) {
             users = new ArrayList<>(mBackgroundUsersToRestart);
             mBackgroundUsersRestartedHere.clear();
             mBackgroundUsersRestartedHere.addAll(mBackgroundUsersToRestart);
         }
         ArrayList<Integer> startedUsers = new ArrayList<>();
         for (Integer user : users) {
-            if (user == mCarUserManagerHelper.getCurrentForegroundUserId()) {
+            if (user == ActivityManager.getCurrentUser()) {
                 continue;
             }
             try {
@@ -250,7 +684,7 @@
                     } else if (mAm.unlockUser(user, null, null, null)) {
                         startedUsers.add(user);
                     } else { // started but cannot unlock
-                        Log.w(TAG, "Background user started but cannot be unlocked:" + user);
+                        Log.w(TAG_USER, "Background user started but cannot be unlocked:" + user);
                         if (mUserManager.isUserRunning(user)) {
                             // add to started list so that it can be stopped later.
                             startedUsers.add(user);
@@ -259,10 +693,11 @@
                 }
             } catch (RemoteException e) {
                 // ignore
+                Log.w(TAG_USER, "error while starting user in background", e);
             }
         }
         // Keep only users that were re-started in mBackgroundUsersRestartedHere
-        synchronized (mLock) {
+        synchronized (mLockUser) {
             ArrayList<Integer> usersToRemove = new ArrayList<>();
             for (Integer user : mBackgroundUsersToRestart) {
                 if (!startedUsers.contains(user)) {
@@ -275,32 +710,34 @@
     }
 
     /**
-     * Stop all background users that were active in system.
-     * @return true if stopping succeeds.
+     * Stops all background users that were active in system.
+     *
+     * @return whether stopping succeeds.
      */
-    public boolean stopBackgroundUser(int userId) {
+    public boolean stopBackgroundUser(@UserIdInt int userId) {
         if (userId == UserHandle.USER_SYSTEM) {
             return false;
         }
-        if (userId == mCarUserManagerHelper.getCurrentForegroundUserId()) {
-            Log.i(TAG, "stopBackgroundUser, already a fg user:" + userId);
+        if (userId == ActivityManager.getCurrentUser()) {
+            Log.i(TAG_USER, "stopBackgroundUser, already a FG user:" + userId);
             return false;
         }
         try {
-            int r = mAm.stopUser(userId, true, null);
+            int r = mAm.stopUserWithDelayedLocking(userId, true, null);
             if (r == ActivityManager.USER_OP_SUCCESS) {
-                synchronized (mLock) {
+                synchronized (mLockUser) {
                     Integer user = userId;
                     mBackgroundUsersRestartedHere.remove(user);
                 }
             } else if (r == ActivityManager.USER_OP_IS_CURRENT) {
                 return false;
             } else {
-                Log.i(TAG, "stopBackgroundUser failed, user:" + userId + " err:" + r);
+                Log.i(TAG_USER, "stopBackgroundUser failed, user:" + userId + " err:" + r);
                 return false;
             }
         } catch (RemoteException e) {
             // ignore
+            Log.w(TAG_USER, "error while stopping user", e);
         }
         return true;
     }
@@ -308,22 +745,81 @@
     /**
      * Called when new foreground user started to boot.
      *
-     * @param userHandle user handle of new user
+     * @param userId User id of new user.
      */
-    public void onSwitchUser(int userHandle) {
-        for (UserCallback callback : mUserCallbacks) {
-            callback.onSwitchUser(userHandle);
+    public void onSwitchUser(@UserIdInt int userId) {
+        Log.i(TAG_USER, "onSwitchUser() callback for user " + userId);
+        TimingsTraceLog t = new TimingsTraceLog(TAG_USER, Trace.TRACE_TAG_SYSTEM_SERVER);
+        t.traceBegin("onSwitchUser-" + userId);
+
+        if (!isSystemUser(userId)) {
+            mCarUserManagerHelper.setLastActiveUser(userId);
         }
+        if (mLastPassengerId != UserHandle.USER_NULL) {
+            stopPassengerInternal(mLastPassengerId, false);
+        }
+        if (mEnablePassengerSupport && isPassengerDisplayAvailable()) {
+            setupPassengerUser();
+            startFirstPassenger(userId);
+        }
+
+        // TODO(b/144120654): right now just the app listeners are running in the background so the
+        // CTS tests pass (as otherwise they might fail if a car service callback takes too long),
+        // but once we refactor the car service callback into lifecycle listeners, we should use a
+        // proper thread management (like a Threadpool / executor);
+
+        int listenersSize = mLifecycleListeners.size();
+        if (listenersSize == 0) {
+            Log.i(TAG_USER, "Not notifying app listeners");
+        } else {
+            new Thread(() -> {
+                // Must use a different TimingsTraceLog because it's another thread
+                TimingsTraceLog t2 = new TimingsTraceLog(TAG_USER, Trace.TRACE_TAG_SYSTEM_SERVER);
+                Log.i(TAG_USER, "Notifying " + listenersSize + " listeners");
+                for (int i = 0; i < listenersSize; i++) {
+                    int uid = mLifecycleListeners.keyAt(i);
+                    IResultReceiver listener = mLifecycleListeners.valueAt(i);
+                    t2.traceBegin("notify-listener-" + uid);
+                    Bundle data = new Bundle();
+                    data.putInt(CarUserManager.BUNDLE_PARAM_ACTION,
+                            CarUserManager.USER_LIFECYCLE_EVENT_TYPE_SWITCHING);
+                    // TODO(b/144120654): should pass currentId from CarServiceHelperService so it
+                    // can set BUNDLE_PARAM_PREVIOUS_USER_HANDLE
+                    if (Log.isLoggable(TAG_USER, Log.DEBUG)) {
+                        Log.d(TAG_USER, "Notifying listener for uid " + uid);
+                    }
+                    try {
+                        listener.send(userId, data);
+                    } catch (RemoteException e) {
+                        Log.e(TAG_USER, "Error calling lifecycle listener", e);
+                    } finally {
+                        t2.traceEnd();
+                    }
+                }
+
+            }, "SwitchUser-" + userId + "-Listeners").start();
+        }
+
+        Log.i(TAG_USER, "Notifying " + mUserCallbacks.size() + " callbacks");
+        for (UserCallback callback : mUserCallbacks) {
+            t.traceBegin("onSwitchUser-" + callback.getClass().getName());
+            callback.onSwitchUser(userId);
+            t.traceEnd();
+        }
+        t.traceEnd(); // onSwitchUser
+
     }
 
     /**
-     * Run give runnable when user 0 is unlocked. If user 0 is already unlocked, it is
+     * Runs the given runnable when user 0 is unlocked. If user 0 is already unlocked, it is
      * run inside this call.
+     *
      * @param r Runnable to run.
      */
-    public void runOnUser0Unlock(Runnable r) {
+    public void runOnUser0Unlock(@NonNull Runnable r) {
+        Objects.requireNonNull(r, "runnable cannot be null");
         boolean runNow = false;
-        synchronized (mLock) {
+        synchronized (mLockUser) {
             if (mUser0Unlocked) {
                 runNow = true;
             } else {
@@ -336,23 +832,252 @@
     }
 
     @VisibleForTesting
-    protected ArrayList<Integer> getBackgroundUsersToRestart() {
-        ArrayList<Integer> backgroundUsersToRestart;
-        synchronized (mLock) {
+    @NonNull
+    ArrayList<Integer> getBackgroundUsersToRestart() {
+        ArrayList<Integer> backgroundUsersToRestart = null;
+        synchronized (mLockUser) {
             backgroundUsersToRestart = new ArrayList<>(mBackgroundUsersToRestart);
         }
         return backgroundUsersToRestart;
     }
 
     private void setSystemUserRestrictions() {
-        // Disable adding accounts for system user.
-        mCarUserManagerHelper.setUserRestriction(mCarUserManagerHelper.getSystemUserInfo(),
-                UserManager.DISALLOW_MODIFY_ACCOUNTS, /* enable= */ true);
-
         // Disable Location service for system user.
         LocationManager locationManager =
                 (LocationManager) mContext.getSystemService(Context.LOCATION_SERVICE);
         locationManager.setLocationEnabledForUser(
                 /* enabled= */ false, UserHandle.of(UserHandle.USER_SYSTEM));
     }
+
+    /**
+     * Creates a new user on the system, the created user would be granted admin role.
+     *
+     * @param name Name to be given to the newly created user.
+     * @return newly created admin user, {@code null} if it fails to create a user.
+     */
+    @Nullable
+    private UserInfo createNewAdminUser(String name) {
+        if (!(mUserManager.isAdminUser() || mUserManager.isSystemUser())) {
+            // Only admins or system user can create other privileged users.
+            Log.e(TAG_USER, "Only admin users and system user can create other admins.");
+            return null;
+        }
+
+        UserInfo user = mUserManager.createUser(name, UserInfo.FLAG_ADMIN);
+        if (user == null) {
+            // Couldn't create user, most likely because there are too many.
+            Log.w(TAG_USER, "can't create admin user.");
+            return null;
+        }
+        assignDefaultIcon(user);
+
+        return user;
+    }
+
+    /**
+     * Assigns a default icon to a user according to the user's id.
+     *
+     * @param userInfo User whose avatar is set to default icon.
+     * @return Bitmap of the user icon.
+     */
+    private Bitmap assignDefaultIcon(UserInfo userInfo) {
+        int idForIcon = userInfo.isGuest() ? UserHandle.USER_NULL : userInfo.id;
+        Bitmap bitmap = UserIcons.convertToBitmap(
+                UserIcons.getDefaultUserIcon(mContext.getResources(), idForIcon, false));
+        mUserManager.setUserIcon(userInfo.id, bitmap);
+        return bitmap;
+    }
+
+    private interface UserFilter {
+        boolean isEligibleUser(UserInfo user);
+    }
+
+    /** Returns all users who are matched by the given filter. */
+    private List<UserInfo> getUsers(UserFilter filter) {
+        List<UserInfo> users = mUserManager.getUsers(/* excludeDying= */ true);
+
+        for (Iterator<UserInfo> iterator = users.iterator(); iterator.hasNext(); ) {
+            UserInfo user = iterator.next();
+            if (!filter.isEligibleUser(user)) {
+                iterator.remove();
+            }
+        }
+        return users;
+    }
+
+    /**
+     * Enforces that apps which have the
+     * {@link android.Manifest.permission#MANAGE_USERS MANAGE_USERS}
+     * can make certain calls to the CarUserManager.
+     *
+     * @param message used as message if SecurityException is thrown.
+     * @throws SecurityException if the caller is not system or root.
+     */
+    private static void checkManageUsersPermission(String message) {
+        checkAtLeastOnePermission(message, android.Manifest.permission.MANAGE_USERS);
+    }
+
+    private static void checkManageUsersOrDumpPermission(String message) {
+        checkAtLeastOnePermission(message,
+                android.Manifest.permission.MANAGE_USERS,
+                android.Manifest.permission.DUMP);
+    }
+
+    private void checkInteractAcrossUsersPermission(String message) {
+        checkAtLeastOnePermission(message, android.Manifest.permission.INTERACT_ACROSS_USERS,
+                android.Manifest.permission.INTERACT_ACROSS_USERS_FULL);
+    }
+
+    private static void checkAtLeastOnePermission(String message, String...permissions) {
+        int callingUid = Binder.getCallingUid();
+        if (!hasAtLeastOnePermissionGranted(callingUid, permissions)) {
+            throw new SecurityException("You need one of " + Arrays.toString(permissions)
+                    + " to: " + message);
+        }
+    }
+
+    private static boolean hasAtLeastOnePermissionGranted(int uid, String... permissions) {
+        for (String permission : permissions) {
+            if (ActivityManager.checkComponentPermission(permission, uid, /* owningUid = */-1,
+                    /* exported = */ true)
+                    == android.content.pm.PackageManager.PERMISSION_GRANTED) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private int getNumberOfManagedProfiles(@UserIdInt int userId) {
+        List<UserInfo> users = mUserManager.getUsers(/* excludeDying= */true);
+        // Count all users that are managed profiles of the given user.
+        int managedProfilesCount = 0;
+        for (UserInfo user : users) {
+            if (user.isManagedProfile() && user.profileGroupId == userId) {
+                managedProfilesCount++;
+            }
+        }
+        return managedProfilesCount;
+    }
+
+    /**
+     * Starts the first passenger of the given driver and assigns the passenger to the front
+     * passenger zone.
+     *
+     * @param driverId User id of the driver.
+     * @return whether it succeeds.
+     */
+    private boolean startFirstPassenger(@UserIdInt int driverId) {
+        int zoneId = getAvailablePassengerZone();
+        if (zoneId == OccupantZoneInfo.INVALID_ZONE_ID) {
+            Log.w(TAG_USER, "passenger occupant zone is not found");
+            return false;
+        }
+        List<UserInfo> passengers = getPassengers(driverId);
+        if (passengers.size() < 1) {
+            Log.w(TAG_USER, "passenger is not found");
+            return false;
+        }
+        // Only one passenger is supported. If there are two or more passengers, the first passenger
+        // is chosen.
+        int passengerId = passengers.get(0).id;
+        if (!startPassenger(passengerId, zoneId)) {
+            Log.w(TAG_USER, "cannot start passenger " + passengerId);
+            return false;
+        }
+        return true;
+    }
+
+    private int getAvailablePassengerZone() {
+        int[] occupantTypes = new int[] {CarOccupantZoneManager.OCCUPANT_TYPE_FRONT_PASSENGER,
+                CarOccupantZoneManager.OCCUPANT_TYPE_REAR_PASSENGER};
+        for (int occupantType : occupantTypes) {
+            int zoneId = getZoneId(occupantType);
+            if (zoneId != OccupantZoneInfo.INVALID_ZONE_ID) {
+                return zoneId;
+            }
+        }
+        return OccupantZoneInfo.INVALID_ZONE_ID;
+    }
+
+    /**
+     * Creates a new passenger user when there is no passenger user.
+     */
+    private void setupPassengerUser() {
+        int currentUser = ActivityManager.getCurrentUser();
+        int profileCount = getNumberOfManagedProfiles(currentUser);
+        if (profileCount > 0) {
+            Log.w(TAG_USER, "max profile of user" + currentUser
+                    + " is exceeded: current profile count is " + profileCount);
+            return;
+        }
+        // TODO(b/140311342): Use resource string for the default passenger name.
+        UserInfo passenger = createPassenger("Passenger", currentUser);
+        if (passenger == null) {
+            // Couldn't create user, most likely because there are too many.
+            Log.w(TAG_USER, "cannot create a passenger user");
+            return;
+        }
+    }
+
+    @NonNull
+    private List<OccupantZoneInfo> getOccupantZones(@OccupantTypeEnum int occupantType) {
+        ZoneUserBindingHelper helper = null;
+        synchronized (mLockHelper) {
+            if (mZoneUserBindingHelper == null) {
+                Log.w(TAG_USER, "implementation is not delegated");
+                return new ArrayList<OccupantZoneInfo>();
+            }
+            helper = mZoneUserBindingHelper;
+        }
+        return helper.getOccupantZones(occupantType);
+    }
+
+    private boolean assignUserToOccupantZone(@UserIdInt int userId, int zoneId) {
+        ZoneUserBindingHelper helper = null;
+        synchronized (mLockHelper) {
+            if (mZoneUserBindingHelper == null) {
+                Log.w(TAG_USER, "implementation is not delegated");
+                return false;
+            }
+            helper = mZoneUserBindingHelper;
+        }
+        return helper.assignUserToOccupantZone(userId, zoneId);
+    }
+
+    private boolean unassignUserFromOccupantZone(@UserIdInt int userId) {
+        ZoneUserBindingHelper helper = null;
+        synchronized (mLockHelper) {
+            if (mZoneUserBindingHelper == null) {
+                Log.w(TAG_USER, "implementation is not delegated");
+                return false;
+            }
+            helper = mZoneUserBindingHelper;
+        }
+        return helper.unassignUserFromOccupantZone(userId);
+    }
+
+    private boolean isPassengerDisplayAvailable() {
+        ZoneUserBindingHelper helper = null;
+        synchronized (mLockHelper) {
+            if (mZoneUserBindingHelper == null) {
+                Log.w(TAG_USER, "implementation is not delegated");
+                return false;
+            }
+            helper = mZoneUserBindingHelper;
+        }
+        return helper.isPassengerDisplayAvailable();
+    }
+
+    /**
+     * Gets the zone id of the given occupant type. If there are two or more zones, the first found
+     * zone is returned.
+     *
+     * @param occupantType The type of an occupant.
+     * @return The zone id of the given occupant type. {@link OccupantZoneInfo.INVALID_ZONE_ID},
+     *         if not found.
+     */
+    private int getZoneId(@OccupantTypeEnum int occupantType) {
+        List<OccupantZoneInfo> zoneInfos = getOccupantZones(occupantType);
+        return (zoneInfos.size() > 0) ? zoneInfos.get(0).zoneId : OccupantZoneInfo.INVALID_ZONE_ID;
+    }
 }
diff --git a/service/src/com/android/car/user/UserMetrics.java b/service/src/com/android/car/user/UserMetrics.java
new file mode 100644
index 0000000..6c94f28
--- /dev/null
+++ b/service/src/com/android/car/user/UserMetrics.java
@@ -0,0 +1,355 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.user;
+
+import static android.car.user.CarUserManager.USER_LIFECYCLE_EVENT_TYPE_STARTING;
+import static android.car.user.CarUserManager.USER_LIFECYCLE_EVENT_TYPE_STOPPED;
+import static android.car.user.CarUserManager.USER_LIFECYCLE_EVENT_TYPE_STOPPING;
+import static android.car.user.CarUserManager.USER_LIFECYCLE_EVENT_TYPE_SWITCHING;
+import static android.car.user.CarUserManager.USER_LIFECYCLE_EVENT_TYPE_UNLOCKED;
+import static android.car.user.CarUserManager.USER_LIFECYCLE_EVENT_TYPE_UNLOCKING;
+import static android.car.user.CarUserManager.lifecycleEventTypeToString;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.car.user.CarUserManager.UserLifecycleEventType;
+import android.util.LocalLog;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.util.SparseLongArray;
+import android.util.TimeUtils;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+
+/**
+ * Metrics for user switches.
+ *
+ * <p>It stores 2 types of metrics:
+ *
+ * <ol>
+ *   <li>Time to start a user (from start to unlock)
+ *   <li>Time to stop a user (from stop to shutdown)
+ * </ol>
+ *
+ * <p>It keeps track of the users being started and stopped, then logs the last
+ * {{@link #INITIAL_CAPACITY}} occurrences of each when the operation finished (so it can be dumped
+ * later).
+ */
+public final class UserMetrics {
+
+    private static final String TAG = UserMetrics.class.getSimpleName();
+
+    /**
+     * Initial capacity for the current operations.
+     */
+    // Typically there are at most 2 users (system and 1st full), although it could be higher on
+    // garage mode
+    private static final int INITIAL_CAPACITY = 2;
+
+    // TODO(b/144120654): read from resources
+    private static final int LOG_SIZE = 10;
+
+    private final Object mLock = new Object();
+
+    @GuardedBy("mLock")
+    private SparseArray<UserStartingMetric> mUserStartingMetrics;
+    @GuardedBy("mLock")
+    private SparseArray<UserStoppingMetric> mUserStoppingMetrics;
+
+    @GuardedBy("mLock")
+    private final LocalLog mUserStartedLogs = new LocalLog(LOG_SIZE);
+    @GuardedBy("mLock")
+    private final LocalLog mUserStoppedLogs = new LocalLog(LOG_SIZE);
+
+    @GuardedBy("mLock")
+    private final SparseLongArray mFirstUserUnlockDuration = new SparseLongArray(1);
+
+    /**
+     * Logs a user lifecycle event.
+     */
+    public void onEvent(@UserLifecycleEventType int eventType, long timestampMs,
+            @UserIdInt int fromUserId, @UserIdInt int toUserId) {
+        synchronized (mLock) {
+            switch(eventType) {
+                case USER_LIFECYCLE_EVENT_TYPE_STARTING:
+                    onUserStartingEventLocked(timestampMs, toUserId);
+                    return;
+                case USER_LIFECYCLE_EVENT_TYPE_SWITCHING:
+                    onUserSwitchingEventLocked(timestampMs, fromUserId, toUserId);
+                    return;
+                case USER_LIFECYCLE_EVENT_TYPE_UNLOCKING:
+                    onUserUnlockingEventLocked(timestampMs, toUserId);
+                    return;
+                case USER_LIFECYCLE_EVENT_TYPE_UNLOCKED:
+                    onUserUnlockedEventLocked(timestampMs, toUserId);
+                    return;
+                case USER_LIFECYCLE_EVENT_TYPE_STOPPING:
+                    onUserStoppingEventLocked(timestampMs, toUserId);
+                    return;
+                case USER_LIFECYCLE_EVENT_TYPE_STOPPED:
+                    onUserStoppedEventLocked(timestampMs, toUserId);
+                    return;
+                default:
+                    Slog.w(TAG, "Invalid event: " + lifecycleEventTypeToString(eventType));
+            }
+        }
+    }
+
+    /**
+     * Logs when the first user was unlocked.
+     */
+    public void logFirstUnlockedUser(int userId, long timestampMs, long duration) {
+        synchronized (mLock) {
+            mFirstUserUnlockDuration.put(userId, duration);
+            onUserUnlockedEventLocked(timestampMs, userId);
+        }
+    }
+
+    private void onUserStartingEventLocked(long timestampMs, @UserIdInt int userId) {
+        if (mUserStartingMetrics == null) {
+            mUserStartingMetrics = new SparseArray<>(INITIAL_CAPACITY);
+        }
+
+        UserStartingMetric existingMetrics = mUserStartingMetrics.get(userId);
+        if (existingMetrics != null) {
+            Slog.w(TAG, "user re-started: " + existingMetrics);
+            finishUserStartingLocked(existingMetrics);
+        }
+
+        mUserStartingMetrics.put(userId, new UserStartingMetric(userId, timestampMs));
+    }
+
+    private void onUserSwitchingEventLocked(long timestampMs, @UserIdInt int fromUserId,
+            @UserIdInt int toUserId) {
+        UserStartingMetric metrics = getExistingMetricsLocked(mUserStartingMetrics, toUserId);
+        if (metrics == null) return;
+
+        metrics.switchFromUserId = fromUserId;
+        metrics.switchTime = timestampMs;
+    }
+
+    private void onUserUnlockingEventLocked(long timestampMs, @UserIdInt int userId) {
+        UserStartingMetric metrics = getExistingMetricsLocked(mUserStartingMetrics, userId);
+        if (metrics == null) return;
+
+        metrics.unlockingTime = timestampMs;
+    }
+
+    private void onUserUnlockedEventLocked(long timestampMs, @UserIdInt int userId) {
+        UserStartingMetric metrics = getExistingMetricsLocked(mUserStartingMetrics, userId);
+        if (metrics == null) return;
+
+        metrics.unlockedTime = timestampMs;
+
+        finishUserStartingLocked(metrics);
+    }
+
+    private void onUserStoppingEventLocked(long timestampMs, @UserIdInt int userId) {
+        if (mUserStoppingMetrics == null) {
+            mUserStoppingMetrics = new SparseArray<>(INITIAL_CAPACITY);
+        }
+        UserStoppingMetric existingMetrics = mUserStoppingMetrics.get(userId);
+        if (existingMetrics != null) {
+            Slog.w(TAG, "user re-stopped: " + existingMetrics);
+            finishUserStoppingLocked(existingMetrics);
+        }
+        mUserStoppingMetrics.put(userId, new UserStoppingMetric(userId, timestampMs));
+    }
+
+    private void onUserStoppedEventLocked(long timestampMs, @UserIdInt int userId) {
+        UserStoppingMetric metrics = getExistingMetricsLocked(mUserStoppingMetrics, userId);
+        if (metrics == null) return;
+
+        metrics.shutdownTime = timestampMs;
+        finishUserStoppingLocked(metrics);
+    }
+
+    @Nullable
+    private <T extends BaseUserMetric> T getExistingMetricsLocked(
+            @NonNull SparseArray<? extends BaseUserMetric> metrics, @UserIdInt int userId) {
+        @SuppressWarnings("unchecked")
+        T metric = (T) metrics.get(userId);
+        if (metric == null) {
+            String name = metrics == mUserStartingMetrics ? "starting" : "stopping";
+            Slog.w(TAG, "no " + name + " metrics for user " + userId);
+        }
+        return metric;
+    }
+
+    private void removeExistingMetricsLogged(@NonNull SparseArray<? extends BaseUserMetric> metrics,
+            @UserIdInt int userId) {
+        metrics.remove(userId);
+        if (metrics.size() != 0) return;
+
+        if (metrics == mUserStartingMetrics) {
+            mUserStartingMetrics = null;
+        } else {
+            mUserStoppingMetrics = null;
+        }
+    }
+
+    private void finishUserStartingLocked(@NonNull UserStartingMetric metrics) {
+        mUserStartedLogs.log(metrics.toString());
+        removeExistingMetricsLogged(mUserStartingMetrics, metrics.userId);
+    }
+
+    private void finishUserStoppingLocked(@NonNull UserStoppingMetric metrics) {
+        mUserStoppedLogs.log(metrics.toString());
+        removeExistingMetricsLogged(mUserStoppingMetrics, metrics.userId);
+    }
+
+    /**
+     * Dumps its contents.
+     */
+    public void dump(@NonNull PrintWriter pw) {
+        pw.println("* User Metrics *");
+        synchronized (mLock) {
+
+            if (mFirstUserUnlockDuration.size() == 0) {
+                pw.println("First user not unlocked yet");
+            } else {
+                pw.printf("First user (%d) unlocked in ", mFirstUserUnlockDuration.keyAt(0));
+                TimeUtils.formatDuration(mFirstUserUnlockDuration.valueAt(0), pw);
+                pw.println();
+            }
+
+            dump(pw, "starting", mUserStartingMetrics);
+            dump(pw, "stopping", mUserStoppingMetrics);
+
+            pw.printf("Last %d started users\n", LOG_SIZE);
+            mUserStartedLogs.dump("  ", pw);
+
+            pw.printf("Last %d stopped users\n", LOG_SIZE);
+            mUserStoppedLogs.dump("  ", pw);
+        }
+    }
+
+    /**
+     * Dumps only how long it took to unlock the first user (or {@code -1} if not available).
+     */
+    public void dumpFirstUserUnlockDuration(@NonNull PrintWriter pw) {
+        synchronized (mLock) {
+            if (mFirstUserUnlockDuration.size() == 0) {
+                pw.println(-1);
+                return;
+            }
+            pw.println(mFirstUserUnlockDuration.valueAt(0));
+        }
+    }
+
+    private void dump(@NonNull PrintWriter pw, @NonNull String message,
+            @NonNull SparseArray<? extends BaseUserMetric> metrics) {
+        String indent = "  ";
+        if (metrics == null) {
+            pw.printf("%sno users %s\n", indent, message);
+            return;
+        }
+        int size = metrics.size();
+        pw.printf("%d users %s\n", size, message);
+        for (int i = 0; i < size; i++) {
+            BaseUserMetric metric = metrics.valueAt(i);
+            pw.printf("%s%d: ", indent, i);
+            metric.dump(pw);
+            pw.println();
+        }
+    }
+
+    private abstract class BaseUserMetric {
+        public final @UserIdInt int userId;
+
+        protected BaseUserMetric(@UserIdInt int userId) {
+            this.userId = userId;
+        }
+
+        @Override
+        public String toString() {
+            StringWriter sw = new StringWriter();
+            PrintWriter pw = new PrintWriter(sw);
+            dump(pw);
+            pw.flush();
+            return sw.toString();
+        }
+
+        abstract void dump(@NonNull PrintWriter pw);
+    }
+
+    private final class UserStartingMetric extends BaseUserMetric {
+        public final long startTime;
+        public long switchTime;
+        public long unlockingTime;
+        public long unlockedTime;
+        public @UserIdInt int switchFromUserId;
+
+        UserStartingMetric(@UserIdInt int userId, long startTime) {
+            super(userId);
+            this.startTime = startTime;
+        }
+
+        @Override
+        public void dump(@NonNull PrintWriter pw) {
+            pw.printf("user=%d start=", userId);
+            TimeUtils.dumpTime(pw, startTime);
+
+            if (switchTime > 0) {
+                long delta = switchTime - startTime;
+                pw.print(" switch");
+                if (switchFromUserId != 0) {
+                    pw.printf("(from %d)", switchFromUserId);
+                }
+                pw.print('=');
+                TimeUtils.formatDuration(delta, pw);
+            }
+
+            if (unlockingTime > 0) {
+                long delta = unlockingTime - startTime;
+                pw.print(" unlocking=");
+                TimeUtils.formatDuration(delta, pw);
+            }
+            if (unlockedTime > 0) {
+                long delta = unlockedTime - startTime;
+                pw.print(" unlocked=");
+                TimeUtils.formatDuration(delta, pw);
+            }
+        }
+    }
+
+    private final class UserStoppingMetric extends BaseUserMetric {
+        public final long stopTime;
+        public long shutdownTime;
+
+        UserStoppingMetric(@UserIdInt int userId, long stopTime) {
+            super(userId);
+            this.stopTime = stopTime;
+        }
+
+        @Override
+        public void dump(@NonNull PrintWriter pw) {
+            pw.printf("user=%d stop=", userId);
+            TimeUtils.dumpTime(pw, stopTime);
+
+            if (shutdownTime > 0) {
+                long delta = shutdownTime - stopTime;
+                pw.print(" shutdown=");
+                TimeUtils.formatDuration(delta, pw);
+            }
+        }
+    }
+}
diff --git a/service/src/com/android/car/vms/VmsBrokerService.java b/service/src/com/android/car/vms/VmsBrokerService.java
deleted file mode 100644
index ad0bfad..0000000
--- a/service/src/com/android/car/vms/VmsBrokerService.java
+++ /dev/null
@@ -1,375 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.car.vms;
-
-import android.car.vms.IVmsSubscriberClient;
-import android.car.vms.VmsAvailableLayers;
-import android.car.vms.VmsLayer;
-import android.car.vms.VmsLayersOffering;
-import android.car.vms.VmsOperationRecorder;
-import android.car.vms.VmsSubscriptionState;
-import android.os.IBinder;
-import android.util.Log;
-
-import com.android.car.VmsLayersAvailability;
-import com.android.car.VmsPublishersInfo;
-import com.android.car.VmsRouting;
-import com.android.internal.annotations.GuardedBy;
-
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.CopyOnWriteArrayList;
-
-/**
- * Broker service facilitating subscription handling and message passing between
- * VmsPublisherService, VmsSubscriberService, and VmsHalService.
- */
-public class VmsBrokerService {
-    private static final boolean DBG = false;
-    private static final String TAG = "VmsBrokerService";
-
-    private CopyOnWriteArrayList<PublisherListener> mPublisherListeners =
-            new CopyOnWriteArrayList<>();
-    private CopyOnWriteArrayList<SubscriberListener> mSubscriberListeners =
-            new CopyOnWriteArrayList<>();
-
-    private final Object mLock = new Object();
-    @GuardedBy("mLock")
-    private final VmsRouting mRouting = new VmsRouting();
-    @GuardedBy("mLock")
-    private final Map<IBinder, Map<Integer, VmsLayersOffering>> mOfferings = new HashMap<>();
-    @GuardedBy("mLock")
-    private final VmsLayersAvailability mAvailableLayers = new VmsLayersAvailability();
-    @GuardedBy("mLock")
-    private final VmsPublishersInfo mPublishersInfo = new VmsPublishersInfo();
-
-    /**
-     * The VMS publisher service implements this interface to receive publisher callbacks.
-     */
-    public interface PublisherListener {
-        /**
-         * Callback triggered when publisher subscription state changes.
-         *
-         * @param subscriptionState Current subscription state.
-         */
-        void onSubscriptionChange(VmsSubscriptionState subscriptionState);
-    }
-
-    /**
-     * The VMS subscriber service implements this interface to receive subscriber callbacks.
-     */
-    public interface SubscriberListener {
-        /**
-         * Callback triggered when the layers available for subscription changes.
-         *
-         * @param availableLayers Current layer availability
-         */
-        void onLayersAvailabilityChange(VmsAvailableLayers availableLayers);
-    }
-
-    /**
-     * Adds a listener for publisher callbacks.
-     *
-     * @param listener Publisher callback listener
-     */
-    public void addPublisherListener(PublisherListener listener) {
-        mPublisherListeners.add(listener);
-    }
-
-    /**
-     * Adds a listener for subscriber callbacks.
-     *
-     * @param listener Subscriber callback listener
-     */
-    public void addSubscriberListener(SubscriberListener listener) {
-        mSubscriberListeners.add(listener);
-    }
-
-    /**
-     * Removes a listener for publisher callbacks.
-     *
-     * @param listener Publisher callback listener
-     */
-    public void removePublisherListener(PublisherListener listener) {
-        mPublisherListeners.remove(listener);
-    }
-
-    /**
-     * Removes a listener for subscriber callbacks.
-     *
-     * @param listener Subscriber callback listener
-     */
-    public void removeSubscriberListener(SubscriberListener listener) {
-        mSubscriberListeners.remove(listener);
-    }
-
-    /**
-     * Adds a subscription to all layers.
-     *
-     * @param subscriber Subscriber client to send layer data
-     */
-    public void addSubscription(IVmsSubscriberClient subscriber) {
-        synchronized (mLock) {
-            mRouting.addSubscription(subscriber);
-        }
-    }
-
-    /**
-     * Removes a subscription to all layers.
-     *
-     * @param subscriber Subscriber client to remove subscription for
-     */
-    public void removeSubscription(IVmsSubscriberClient subscriber) {
-        synchronized (mLock) {
-            mRouting.removeSubscription(subscriber);
-        }
-    }
-
-    /**
-     * Adds a layer subscription.
-     *
-     * @param subscriber Subscriber client to send layer data
-     * @param layer      Layer to send
-     */
-    public void addSubscription(IVmsSubscriberClient subscriber, VmsLayer layer) {
-        boolean firstSubscriptionForLayer;
-        if (DBG) Log.d(TAG, "Checking for first subscription. Layer: " + layer);
-        synchronized (mLock) {
-            // Check if publishers need to be notified about this change in subscriptions.
-            firstSubscriptionForLayer = !mRouting.hasLayerSubscriptions(layer);
-
-            // Add the listeners subscription to the layer
-            mRouting.addSubscription(subscriber, layer);
-        }
-        if (firstSubscriptionForLayer) {
-            notifyOfSubscriptionChange();
-        }
-    }
-
-    /**
-     * Removes a layer subscription.
-     *
-     * @param subscriber Subscriber client to remove subscription for
-     * @param layer      Layer to remove
-     */
-    public void removeSubscription(IVmsSubscriberClient subscriber, VmsLayer layer) {
-        boolean layerHasSubscribers;
-        synchronized (mLock) {
-            if (!mRouting.hasLayerSubscriptions(layer)) {
-                if (DBG) Log.d(TAG, "Trying to remove a layer with no subscription: " + layer);
-                return;
-            }
-
-            // Remove the listeners subscription to the layer
-            mRouting.removeSubscription(subscriber, layer);
-
-            // Check if publishers need to be notified about this change in subscriptions.
-            layerHasSubscribers = mRouting.hasLayerSubscriptions(layer);
-        }
-        if (!layerHasSubscribers) {
-            notifyOfSubscriptionChange();
-        }
-    }
-
-    /**
-     * Adds a publisher-specific layer subscription.
-     *
-     * @param subscriber  Subscriber client to send layer data
-     * @param layer       Layer to send
-     * @param publisherId Publisher of layer
-     */
-    public void addSubscription(IVmsSubscriberClient subscriber, VmsLayer layer, int publisherId) {
-        boolean firstSubscriptionForLayer;
-        synchronized (mLock) {
-            // Check if publishers need to be notified about this change in subscriptions.
-            firstSubscriptionForLayer = !(mRouting.hasLayerSubscriptions(layer)
-                    || mRouting.hasLayerFromPublisherSubscriptions(layer, publisherId));
-
-            // Add the listeners subscription to the layer
-            mRouting.addSubscription(subscriber, layer, publisherId);
-        }
-        if (firstSubscriptionForLayer) {
-            notifyOfSubscriptionChange();
-        }
-    }
-
-    /**
-     * Removes a publisher-specific layer subscription.
-     *
-     * @param subscriber  Subscriber client to remove subscription for
-     * @param layer       Layer to remove
-     * @param publisherId Publisher of layer
-     */
-    public void removeSubscription(IVmsSubscriberClient subscriber, VmsLayer layer,
-            int publisherId) {
-        boolean layerHasSubscribers;
-        synchronized (mLock) {
-            if (!mRouting.hasLayerFromPublisherSubscriptions(layer, publisherId)) {
-                if (DBG) {
-                    Log.d(TAG, "Trying to remove a layer with no subscription: "
-                        + layer + ", publisher ID:" + publisherId);
-                }
-                return;
-            }
-
-            // Remove the listeners subscription to the layer
-            mRouting.removeSubscription(subscriber, layer, publisherId);
-
-            // Check if publishers need to be notified about this change in subscriptions.
-            layerHasSubscribers = mRouting.hasLayerSubscriptions(layer)
-                    || mRouting.hasLayerFromPublisherSubscriptions(layer, publisherId);
-        }
-        if (!layerHasSubscribers) {
-            notifyOfSubscriptionChange();
-        }
-    }
-
-    /**
-     * Removes a disconnected subscriber's subscriptions
-     *
-     * @param subscriber Subscriber that was disconnected
-     */
-    public void removeDeadSubscriber(IVmsSubscriberClient subscriber) {
-        boolean subscriptionStateChanged;
-        synchronized (mLock) {
-            subscriptionStateChanged = mRouting.removeDeadSubscriber(subscriber);
-        }
-        if (subscriptionStateChanged) {
-            notifyOfSubscriptionChange();
-        }
-    }
-
-    /**
-     * Gets all subscribers for a specific layer/publisher combination.
-     *
-     * @param layer       Layer to query
-     * @param publisherId Publisher of layer
-     */
-    public Set<IVmsSubscriberClient> getSubscribersForLayerFromPublisher(VmsLayer layer,
-            int publisherId) {
-        synchronized (mLock) {
-            return mRouting.getSubscribersForLayerFromPublisher(layer, publisherId);
-        }
-    }
-
-    /**
-     * Gets the state of all layer subscriptions.
-     */
-    public VmsSubscriptionState getSubscriptionState() {
-        synchronized (mLock) {
-            return mRouting.getSubscriptionState();
-        }
-    }
-
-    /**
-     * Assigns an idempotent ID for publisherInfo and stores it. The idempotency in this case means
-     * that the same publisherInfo will always, within a trip of the vehicle, return the same ID.
-     * The publisherInfo should be static for a binary and should only change as part of a software
-     * update. The publisherInfo is a serialized proto message which VMS clients can interpret.
-     */
-    public int getPublisherId(byte[] publisherInfo) {
-        if (DBG) Log.i(TAG, "Getting publisher static ID");
-        synchronized (mLock) {
-            return mPublishersInfo.getIdForInfo(publisherInfo);
-        }
-    }
-
-    /**
-     * Gets the publisher information data registered in {@link #getPublisherId(byte[])}
-     *
-     * @param publisherId Publisher ID to query
-     * @return Publisher information
-     */
-    public byte[] getPublisherInfo(int publisherId) {
-        if (DBG) Log.i(TAG, "Getting information for publisher ID: " + publisherId);
-        synchronized (mLock) {
-            return mPublishersInfo.getPublisherInfo(publisherId);
-        }
-    }
-
-    /**
-     * Sets the layers offered by the publisher with the given publisher token.
-     *
-     * @param publisherToken Identifier token of publisher
-     * @param offering       Layers offered by publisher
-     */
-    public void setPublisherLayersOffering(IBinder publisherToken, VmsLayersOffering offering) {
-        synchronized (mLock) {
-            Map<Integer, VmsLayersOffering> publisherOfferings = mOfferings.computeIfAbsent(
-                    publisherToken, k -> new HashMap<>());
-            publisherOfferings.put(offering.getPublisherId(), offering);
-            updateLayerAvailability();
-        }
-        VmsOperationRecorder.get().setPublisherLayersOffering(offering);
-        notifyOfAvailabilityChange();
-    }
-
-    /**
-     * Removes a disconnected publisher's offerings
-     *
-     * @param publisherToken Identifier token of publisher to be removed
-     */
-    public void removeDeadPublisher(IBinder publisherToken) {
-        synchronized (mLock) {
-            mOfferings.remove(publisherToken);
-            updateLayerAvailability();
-        }
-        notifyOfAvailabilityChange();
-    }
-
-    /**
-     * Gets all layers available for subscription.
-     *
-     * @return All available layers
-     */
-    public VmsAvailableLayers getAvailableLayers() {
-        synchronized (mLock) {
-            return mAvailableLayers.getAvailableLayers();
-        }
-    }
-
-    private void updateLayerAvailability() {
-        Set<VmsLayersOffering> allPublisherOfferings = new HashSet<>();
-        synchronized (mLock) {
-            for (Map<Integer, VmsLayersOffering> offerings : mOfferings.values()) {
-                allPublisherOfferings.addAll(offerings.values());
-            }
-            if (DBG) Log.d(TAG, "New layer availability: " + allPublisherOfferings);
-            mAvailableLayers.setPublishersOffering(allPublisherOfferings);
-        }
-    }
-
-    private void notifyOfSubscriptionChange() {
-        VmsSubscriptionState subscriptionState = getSubscriptionState();
-        Log.i(TAG, "Notifying publishers of subscriptions: " + subscriptionState);
-        // Notify the App publishers
-        for (PublisherListener listener : mPublisherListeners) {
-            listener.onSubscriptionChange(subscriptionState);
-        }
-    }
-
-    private void notifyOfAvailabilityChange() {
-        VmsAvailableLayers availableLayers = getAvailableLayers();
-        Log.i(TAG, "Notifying subscribers of layers availability: " + availableLayers);
-        // Notify the App subscribers
-        for (SubscriberListener listener : mSubscriberListeners) {
-            listener.onLayersAvailabilityChange(availableLayers);
-        }
-    }
-}
diff --git a/service/src/com/android/car/vms/VmsClientInfo.java b/service/src/com/android/car/vms/VmsClientInfo.java
new file mode 100644
index 0000000..05a11dd
--- /dev/null
+++ b/service/src/com/android/car/vms/VmsClientInfo.java
@@ -0,0 +1,180 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.vms;
+
+import android.car.vms.IVmsClientCallback;
+import android.car.vms.VmsAssociatedLayer;
+import android.car.vms.VmsLayer;
+import android.car.vms.VmsLayerDependency;
+import android.car.vms.VmsLayersOffering;
+import android.util.ArraySet;
+import android.util.SparseArray;
+import android.util.SparseBooleanArray;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+/**
+ * Class for tracking Vehicle Map Service client information, offerings, and subscriptions.
+ */
+final class VmsClientInfo {
+    private final int mUid;
+    private final String mPackageName;
+    private final IVmsClientCallback mCallback;
+    private final boolean mLegacyClient;
+
+    private final Object mLock = new Object();
+    @GuardedBy("mLock")
+    private final SparseBooleanArray mProviderIds = new SparseBooleanArray();
+    @GuardedBy("mLock")
+    private final SparseArray<Set<VmsLayerDependency>> mOfferings = new SparseArray<>();
+    @GuardedBy("mLock")
+    private final SparseArray<Set<VmsLayer>> mPotentialOfferings = new SparseArray<>();
+    @GuardedBy("mLock")
+    private Set<VmsLayer> mLayerSubscriptions = Collections.emptySet();
+    @GuardedBy("mLock")
+    private Map<VmsLayer, Set<Integer>> mLayerAndProviderSubscriptions = Collections.emptyMap();
+    @GuardedBy("mLock")
+    private boolean mMonitoringEnabled;
+
+    VmsClientInfo(int uid, String packageName, IVmsClientCallback callback, boolean legacyClient) {
+        mUid = uid;
+        mPackageName = packageName;
+        mCallback = callback;
+        mLegacyClient = legacyClient;
+    }
+
+    int getUid() {
+        return mUid;
+    }
+
+    String getPackageName() {
+        return mPackageName;
+    }
+
+    IVmsClientCallback getCallback() {
+        return mCallback;
+    }
+
+    void addProviderId(int providerId) {
+        synchronized (mLock) {
+            mProviderIds.put(providerId, true);
+        }
+    }
+
+    boolean hasProviderId(int providerId) {
+        synchronized (mLock) {
+            return mProviderIds.get(providerId);
+        }
+    }
+
+    boolean setProviderOfferings(int providerId, Collection<VmsLayerDependency> offerings) {
+        synchronized (mLock) {
+            Set<VmsLayerDependency> providerOfferings = mOfferings.get(providerId);
+
+            // If the offerings are unchanged, do nothing
+            if (providerOfferings != null
+                    && providerOfferings.size() == offerings.size()
+                    && providerOfferings.containsAll(offerings)) {
+                return false;
+            }
+
+            // Otherwise, update the offerings and return true
+            mOfferings.put(providerId, new ArraySet<>(offerings));
+            mPotentialOfferings.put(providerId, offerings.stream()
+                    .map(VmsLayerDependency::getLayer)
+                    .collect(Collectors.toSet()));
+            return true;
+        }
+    }
+
+    Collection<VmsLayersOffering> getAllOfferings() {
+        List<VmsLayersOffering> result = new ArrayList<>(mOfferings.size());
+        synchronized (mLock) {
+            for (int i = 0; i < mOfferings.size(); i++) {
+                int providerId = mOfferings.keyAt(i);
+                Set<VmsLayerDependency> providerOfferings = mOfferings.valueAt(i);
+                result.add(new VmsLayersOffering(new ArraySet<>(providerOfferings), providerId));
+            }
+        }
+        return result;
+    }
+
+    boolean hasOffering(int providerId, VmsLayer layer) {
+        synchronized (mLock) {
+            return mPotentialOfferings.get(providerId, Collections.emptySet()).contains(layer);
+        }
+    }
+
+    void setSubscriptions(List<VmsAssociatedLayer> layers) {
+        synchronized (mLock) {
+            mLayerSubscriptions = layers.stream()
+                    .filter(associatedLayer -> associatedLayer.getProviderIds().isEmpty())
+                    .map(VmsAssociatedLayer::getVmsLayer)
+                    .collect(Collectors.toSet());
+            mLayerAndProviderSubscriptions = layers.stream()
+                    .filter(associatedLayer -> !associatedLayer.getProviderIds().isEmpty())
+                    .collect(Collectors.toMap(
+                            VmsAssociatedLayer::getVmsLayer,
+                            associatedLayer -> new ArraySet<>(associatedLayer.getProviderIds())));
+        }
+    }
+
+    Set<VmsLayer> getLayerSubscriptions() {
+        synchronized (mLock) {
+            return new ArraySet<>(mLayerSubscriptions);
+        }
+    }
+
+    Map<VmsLayer, Set<Integer>> getLayerAndProviderSubscriptions() {
+        synchronized (mLock) {
+            return deepCopy(mLayerAndProviderSubscriptions);
+        }
+    }
+
+    void setMonitoringEnabled(boolean enabled) {
+        synchronized (mLock) {
+            mMonitoringEnabled = enabled;
+        }
+    }
+
+    boolean isSubscribed(int providerId, VmsLayer layer) {
+        synchronized (mLock) {
+            return mMonitoringEnabled
+                    || mLayerSubscriptions.contains(layer)
+                    || mLayerAndProviderSubscriptions.getOrDefault(layer, Collections.emptySet())
+                            .contains(providerId);
+        }
+    }
+
+    boolean isLegacyClient() {
+        return mLegacyClient;
+    }
+
+    private static <K, V> Map<K, Set<V>> deepCopy(Map<K, Set<V>> original) {
+        return original.entrySet().stream().collect(Collectors.toMap(
+                Map.Entry::getKey,
+                entry -> new ArraySet<>(entry.getValue())));
+    }
+}
diff --git a/service/src/com/android/car/vms/VmsClientManager.java b/service/src/com/android/car/vms/VmsClientManager.java
deleted file mode 100644
index f2c4813..0000000
--- a/service/src/com/android/car/vms/VmsClientManager.java
+++ /dev/null
@@ -1,614 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.car.vms;
-
-import android.car.Car;
-import android.car.vms.IVmsPublisherClient;
-import android.car.vms.IVmsSubscriberClient;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.ServiceConnection;
-import android.content.pm.PackageManager;
-import android.content.pm.ServiceInfo;
-import android.os.Binder;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.Looper;
-import android.os.Process;
-import android.os.RemoteException;
-import android.os.UserHandle;
-import android.os.UserManager;
-import android.util.ArrayMap;
-import android.util.Log;
-
-import com.android.car.CarServiceBase;
-import com.android.car.R;
-import com.android.car.VmsPublisherService;
-import com.android.car.hal.VmsHalService;
-import com.android.car.stats.CarStatsService;
-import com.android.car.stats.VmsClientLogger;
-import com.android.car.stats.VmsClientLogger.ConnectionState;
-import com.android.car.user.CarUserService;
-import com.android.internal.annotations.GuardedBy;
-import com.android.internal.annotations.VisibleForTesting;
-
-import java.io.PrintWriter;
-import java.util.Collection;
-import java.util.Map;
-import java.util.NoSuchElementException;
-import java.util.function.IntSupplier;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
-
-/**
- * Manages service connections lifecycle for VMS publisher clients.
- *
- * Binds to system-level clients at boot and creates/destroys bindings for userspace clients
- * according to the Android user lifecycle.
- */
-public class VmsClientManager implements CarServiceBase {
-    private static final boolean DBG = false;
-    private static final String TAG = "VmsClientManager";
-    private static final String HAL_CLIENT_NAME = "HalClient";
-    private static final String UNKNOWN_PACKAGE = "UnknownPackage";
-
-    private final Context mContext;
-    private final PackageManager mPackageManager;
-    private final UserManager mUserManager;
-    private final CarUserService mUserService;
-    private final CarStatsService mStatsService;
-    private final Handler mHandler;
-    private final IntSupplier mGetCallingUid;
-    private final int mMillisBeforeRebind;
-
-    private final Object mLock = new Object();
-
-    @GuardedBy("mLock")
-    private final VmsBrokerService mBrokerService;
-    @GuardedBy("mLock")
-    private VmsPublisherService mPublisherService;
-
-    @GuardedBy("mLock")
-    private final Map<String, PublisherConnection> mSystemClients = new ArrayMap<>();
-    @GuardedBy("mLock")
-    private IVmsPublisherClient mHalClient;
-    @GuardedBy("mLock")
-    private boolean mSystemUserUnlocked;
-
-    @GuardedBy("mLock")
-    private final Map<String, PublisherConnection> mCurrentUserClients = new ArrayMap<>();
-    @GuardedBy("mLock")
-    private int mCurrentUser;
-
-    @GuardedBy("mLock")
-    private final Map<IBinder, SubscriberConnection> mSubscribers = new ArrayMap<>();
-
-    @VisibleForTesting
-    final Runnable mSystemUserUnlockedListener = () -> {
-        synchronized (mLock) {
-            mSystemUserUnlocked = true;
-        }
-        bindToSystemClients();
-    };
-
-    @VisibleForTesting
-    public final CarUserService.UserCallback mUserCallback = new CarUserService.UserCallback() {
-        @Override
-        public void onSwitchUser(int userId) {
-            synchronized (mLock) {
-                if (mCurrentUser != userId) {
-                    mCurrentUser = userId;
-                    terminate(mCurrentUserClients);
-                    terminate(mSubscribers.values().stream()
-                            .filter(subscriber -> subscriber.mUserId != mCurrentUser)
-                            .filter(subscriber -> subscriber.mUserId != UserHandle.USER_SYSTEM));
-                }
-            }
-            bindToUserClients();
-        }
-
-        @Override
-        public void onUserLockChanged(int userId, boolean unlocked) {
-            synchronized (mLock) {
-                if (mCurrentUser == userId && unlocked) {
-                    bindToUserClients();
-                }
-            }
-        }
-    };
-
-    /**
-     * Constructor for client manager.
-     *
-     * @param context           Context to use for registering receivers and binding services.
-     * @param statsService      Service for logging client metrics.
-     * @param userService       User service for registering system unlock listener.
-     * @param brokerService     Service managing the VMS publisher/subscriber state.
-     * @param halService        Service providing the HAL client interface
-     */
-    public VmsClientManager(Context context, CarStatsService statsService,
-            CarUserService userService, VmsBrokerService brokerService,
-            VmsHalService halService) {
-        this(context, statsService, userService, brokerService, halService,
-                new Handler(Looper.getMainLooper()), Binder::getCallingUid);
-    }
-
-    @VisibleForTesting
-    VmsClientManager(Context context, CarStatsService statsService,
-            CarUserService userService, VmsBrokerService brokerService,
-            VmsHalService halService, Handler handler, IntSupplier getCallingUid) {
-        mContext = context;
-        mPackageManager = context.getPackageManager();
-        mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
-        mStatsService = statsService;
-        mUserService = userService;
-        mCurrentUser = UserHandle.USER_NULL;
-        mBrokerService = brokerService;
-        mHandler = handler;
-        mGetCallingUid = getCallingUid;
-        mMillisBeforeRebind = context.getResources().getInteger(
-                com.android.car.R.integer.millisecondsBeforeRebindToVmsPublisher);
-
-        halService.setClientManager(this);
-    }
-
-    /**
-     * Registers the publisher service for connection callbacks.
-     *
-     * @param publisherService Publisher service to register.
-     */
-    public void setPublisherService(VmsPublisherService publisherService) {
-        synchronized (mLock) {
-            mPublisherService = publisherService;
-        }
-    }
-
-    @Override
-    public void init() {
-        mUserService.runOnUser0Unlock(mSystemUserUnlockedListener);
-        mUserService.addUserCallback(mUserCallback);
-    }
-
-    @Override
-    public void release() {
-        mUserService.removeUserCallback(mUserCallback);
-        synchronized (mLock) {
-            if (mHalClient != null) {
-                mPublisherService.onClientDisconnected(HAL_CLIENT_NAME);
-            }
-            terminate(mSystemClients);
-            terminate(mCurrentUserClients);
-            terminate(mSubscribers.values().stream());
-        }
-    }
-
-    @Override
-    public void dump(PrintWriter writer) {
-        writer.println("*" + getClass().getSimpleName() + "*");
-        synchronized (mLock) {
-            writer.println("mCurrentUser:" + mCurrentUser);
-            writer.println("mHalClient: " + (mHalClient != null ? "connected" : "disconnected"));
-            writer.println("mSystemClients:");
-            dumpConnections(writer, mSystemClients);
-
-            writer.println("mCurrentUserClients:");
-            dumpConnections(writer, mCurrentUserClients);
-
-            writer.println("mSubscribers:");
-            for (SubscriberConnection subscriber : mSubscribers.values()) {
-                writer.printf("\t%s\n", subscriber);
-            }
-        }
-    }
-
-
-    /**
-     * Adds a subscriber for connection tracking.
-     *
-     * @param subscriberClient Subscriber client to track.
-     */
-    public void addSubscriber(IVmsSubscriberClient subscriberClient) {
-        if (subscriberClient == null) {
-            Log.e(TAG, "Trying to add a null subscriber: "
-                    + getCallingPackage(mGetCallingUid.getAsInt()));
-            throw new IllegalArgumentException("subscriber cannot be null.");
-        }
-
-        synchronized (mLock) {
-            IBinder subscriberBinder = subscriberClient.asBinder();
-            if (mSubscribers.containsKey(subscriberBinder)) {
-                // Already registered
-                return;
-            }
-
-            int callingUid = mGetCallingUid.getAsInt();
-            int subscriberUserId = UserHandle.getUserId(callingUid);
-            if (subscriberUserId != mCurrentUser && subscriberUserId != UserHandle.USER_SYSTEM) {
-                throw new SecurityException("Caller must be foreground user or system");
-            }
-
-            SubscriberConnection subscriber = new SubscriberConnection(
-                    subscriberClient, callingUid, getCallingPackage(callingUid), subscriberUserId);
-            if (DBG) Log.d(TAG, "Registering subscriber: " + subscriber);
-            try {
-                subscriberBinder.linkToDeath(subscriber, 0);
-            } catch (RemoteException e) {
-                throw new IllegalStateException("Subscriber already dead: " + subscriber, e);
-            }
-            mSubscribers.put(subscriberBinder, subscriber);
-        }
-    }
-
-    /**
-     * Removes a subscriber for connection tracking and expires its subscriptions.
-     *
-     * @param subscriberClient Subscriber client to remove.
-     */
-    public void removeSubscriber(IVmsSubscriberClient subscriberClient) {
-        synchronized (mLock) {
-            SubscriberConnection subscriber = mSubscribers.get(subscriberClient.asBinder());
-            if (subscriber != null) {
-                subscriber.terminate();
-            }
-        }
-    }
-
-    /**
-     * Returns all active subscriber clients.
-     */
-    public Collection<IVmsSubscriberClient> getAllSubscribers() {
-        synchronized (mLock) {
-            return mSubscribers.values().stream()
-                    .map(subscriber -> subscriber.mClient)
-                    .collect(Collectors.toList());
-        }
-    }
-
-    /**
-     * Gets the application UID associated with a subscriber client.
-     */
-    public int getSubscriberUid(IVmsSubscriberClient subscriberClient) {
-        synchronized (mLock) {
-            SubscriberConnection subscriber = mSubscribers.get(subscriberClient.asBinder());
-            return subscriber != null ? subscriber.mUid : Process.INVALID_UID;
-        }
-    }
-
-    /**
-     * Gets the package name for a given subscriber client.
-     */
-    public String getPackageName(IVmsSubscriberClient subscriberClient) {
-        synchronized (mLock) {
-            SubscriberConnection subscriber = mSubscribers.get(subscriberClient.asBinder());
-            return subscriber != null ? subscriber.mPackageName : UNKNOWN_PACKAGE;
-        }
-    }
-
-    /**
-     * Registers the HAL client connections.
-     */
-    public void onHalConnected(IVmsPublisherClient publisherClient,
-            IVmsSubscriberClient subscriberClient) {
-        synchronized (mLock) {
-            mHalClient = publisherClient;
-            mPublisherService.onClientConnected(HAL_CLIENT_NAME, mHalClient);
-            mSubscribers.put(subscriberClient.asBinder(),
-                    new SubscriberConnection(subscriberClient, Process.myUid(), HAL_CLIENT_NAME,
-                            UserHandle.USER_SYSTEM));
-        }
-        mStatsService.getVmsClientLogger(Process.myUid())
-                .logConnectionState(ConnectionState.CONNECTED);
-    }
-
-    /**
-     *
-     */
-    public void onHalDisconnected() {
-        synchronized (mLock) {
-            if (mHalClient != null) {
-                mPublisherService.onClientDisconnected(HAL_CLIENT_NAME);
-                mStatsService.getVmsClientLogger(Process.myUid())
-                        .logConnectionState(ConnectionState.DISCONNECTED);
-            }
-            mHalClient = null;
-            terminate(mSubscribers.values().stream()
-                    .filter(subscriber -> HAL_CLIENT_NAME.equals(subscriber.mPackageName)));
-        }
-    }
-
-    private void dumpConnections(PrintWriter writer,
-            Map<String, PublisherConnection> connectionMap) {
-        for (PublisherConnection connection : connectionMap.values()) {
-            writer.printf("\t%s: %s\n",
-                    connection.mName.getPackageName(),
-                    connection.mIsBound ? "connected" : "disconnected");
-        }
-    }
-
-    private void bindToSystemClients() {
-        String[] clientNames = mContext.getResources().getStringArray(
-                R.array.vmsPublisherSystemClients);
-        synchronized (mLock) {
-            if (!mSystemUserUnlocked) {
-                return;
-            }
-            Log.i(TAG, "Attempting to bind " + clientNames.length + " system client(s)");
-            for (String clientName : clientNames) {
-                bind(mSystemClients, clientName, UserHandle.SYSTEM);
-            }
-        }
-    }
-
-    private void bindToUserClients() {
-        bindToSystemClients(); // Bind system clients on user switch, if they are not already bound.
-        synchronized (mLock) {
-            if (mCurrentUser == UserHandle.USER_NULL) {
-                Log.e(TAG, "Unknown user in foreground.");
-                return;
-            }
-            // To avoid the risk of double-binding, clients running as the system user must only
-            // ever be bound in bindToSystemClients().
-            if (mCurrentUser == UserHandle.USER_SYSTEM) {
-                Log.e(TAG, "System user in foreground. Userspace clients will not be bound.");
-                return;
-            }
-
-            if (!mUserManager.isUserUnlockingOrUnlocked(mCurrentUser)) {
-                Log.i(TAG, "Waiting for foreground user " + mCurrentUser + " to be unlocked.");
-                return;
-            }
-
-            String[] clientNames = mContext.getResources().getStringArray(
-                    R.array.vmsPublisherUserClients);
-            Log.i(TAG, "Attempting to bind " + clientNames.length + " user client(s)");
-            UserHandle currentUserHandle = UserHandle.of(mCurrentUser);
-            for (String clientName : clientNames) {
-                bind(mCurrentUserClients, clientName, currentUserHandle);
-            }
-        }
-    }
-
-    private void bind(Map<String, PublisherConnection> connectionMap, String clientName,
-            UserHandle userHandle) {
-        if (connectionMap.containsKey(clientName)) {
-            Log.i(TAG, "Already bound: " + clientName);
-            return;
-        }
-
-        ComponentName name = ComponentName.unflattenFromString(clientName);
-        if (name == null) {
-            Log.e(TAG, "Invalid client name: " + clientName);
-            return;
-        }
-
-        ServiceInfo serviceInfo;
-        try {
-            serviceInfo = mContext.getPackageManager().getServiceInfo(name,
-                    PackageManager.MATCH_DIRECT_BOOT_AUTO);
-        } catch (PackageManager.NameNotFoundException e) {
-            Log.w(TAG, "Client not installed: " + clientName);
-            return;
-        }
-
-        VmsClientLogger statsLog = mStatsService.getVmsClientLogger(
-                UserHandle.getUid(userHandle.getIdentifier(), serviceInfo.applicationInfo.uid));
-
-        if (!Car.PERMISSION_BIND_VMS_CLIENT.equals(serviceInfo.permission)) {
-            Log.e(TAG, "Client service: " + clientName
-                    + " does not require " + Car.PERMISSION_BIND_VMS_CLIENT + " permission");
-            statsLog.logConnectionState(ConnectionState.CONNECTION_ERROR);
-            return;
-        }
-
-        PublisherConnection connection = new PublisherConnection(name, userHandle, statsLog);
-        if (connection.bind()) {
-            Log.i(TAG, "Client bound: " + connection);
-            connectionMap.put(clientName, connection);
-        } else {
-            Log.e(TAG, "Binding failed: " + connection);
-        }
-    }
-
-    private void terminate(Map<String, PublisherConnection> connectionMap) {
-        connectionMap.values().forEach(PublisherConnection::terminate);
-        connectionMap.clear();
-    }
-
-    class PublisherConnection implements ServiceConnection {
-        private final ComponentName mName;
-        private final UserHandle mUser;
-        private final String mFullName;
-        private final VmsClientLogger mStatsLog;
-        private boolean mIsBound = false;
-        private boolean mIsTerminated = false;
-        private boolean mRebindScheduled = false;
-        private IVmsPublisherClient mClientService;
-
-        PublisherConnection(ComponentName name, UserHandle user, VmsClientLogger statsLog) {
-            mName = name;
-            mUser = user;
-            mFullName = mName.flattenToString() + " U=" + mUser.getIdentifier();
-            mStatsLog = statsLog;
-        }
-
-        synchronized boolean bind() {
-            if (mIsBound) {
-                return true;
-            }
-            if (mIsTerminated) {
-                return false;
-            }
-            mStatsLog.logConnectionState(ConnectionState.CONNECTING);
-
-            if (DBG) Log.d(TAG, "binding: " + mFullName);
-            Intent intent = new Intent();
-            intent.setComponent(mName);
-            try {
-                mIsBound = mContext.bindServiceAsUser(intent, this, Context.BIND_AUTO_CREATE,
-                        mHandler, mUser);
-            } catch (SecurityException e) {
-                Log.e(TAG, "While binding " + mFullName, e);
-            }
-
-            if (!mIsBound) {
-                mStatsLog.logConnectionState(ConnectionState.CONNECTION_ERROR);
-            }
-
-            return mIsBound;
-        }
-
-        synchronized void unbind() {
-            if (!mIsBound) {
-                return;
-            }
-
-            if (DBG) Log.d(TAG, "unbinding: " + mFullName);
-            try {
-                mContext.unbindService(this);
-            } catch (Throwable t) {
-                Log.e(TAG, "While unbinding " + mFullName, t);
-            }
-            mIsBound = false;
-        }
-
-        synchronized void scheduleRebind() {
-            if (mRebindScheduled) {
-                return;
-            }
-
-            if (DBG) {
-                Log.d(TAG,
-                        String.format("rebinding %s after %dms", mFullName, mMillisBeforeRebind));
-            }
-            mHandler.postDelayed(this::doRebind, mMillisBeforeRebind);
-            mRebindScheduled = true;
-        }
-
-        synchronized void doRebind() {
-            mRebindScheduled = false;
-            // Do not rebind if the connection has been terminated, or the client service has
-            // reconnected on its own.
-            if (mIsTerminated || mClientService != null) {
-                return;
-            }
-
-            Log.i(TAG, "Rebinding: " + mFullName);
-            // Ensure that the client is not bound before attempting to rebind.
-            // If the client is not currently bound, unbind() will have no effect.
-            unbind();
-            bind();
-        }
-
-        synchronized void terminate() {
-            if (DBG) Log.d(TAG, "terminating: " + mFullName);
-            mIsTerminated = true;
-            notifyOnDisconnect(ConnectionState.TERMINATED);
-            unbind();
-        }
-
-        synchronized void notifyOnDisconnect(int connectionState) {
-            if (mClientService != null) {
-                mPublisherService.onClientDisconnected(mFullName);
-                mClientService = null;
-                mStatsLog.logConnectionState(connectionState);
-            }
-        }
-
-        @Override
-        public void onServiceConnected(ComponentName name, IBinder service) {
-            if (DBG) Log.d(TAG, "onServiceConnected: " + mFullName);
-            mClientService = IVmsPublisherClient.Stub.asInterface(service);
-            mPublisherService.onClientConnected(mFullName, mClientService);
-            mStatsLog.logConnectionState(ConnectionState.CONNECTED);
-        }
-
-        @Override
-        public void onServiceDisconnected(ComponentName name) {
-            if (DBG) Log.d(TAG, "onServiceDisconnected: " + mFullName);
-            notifyOnDisconnect(ConnectionState.DISCONNECTED);
-            scheduleRebind();
-        }
-
-        @Override
-        public void onBindingDied(ComponentName name) {
-            if (DBG) Log.d(TAG, "onBindingDied: " + mFullName);
-            notifyOnDisconnect(ConnectionState.DISCONNECTED);
-            scheduleRebind();
-        }
-
-        @Override
-        public String toString() {
-            return mFullName;
-        }
-    }
-
-    private void terminate(Stream<SubscriberConnection> subscribers) {
-        // Make a copy of the stream, so that terminate() doesn't cause a concurrent modification
-        subscribers.collect(Collectors.toList()).forEach(SubscriberConnection::terminate);
-    }
-
-    // If we're in a binder call, returns back the package name of the caller of the binder call.
-    private String getCallingPackage(int uid) {
-        String packageName = mPackageManager.getNameForUid(uid);
-        if (packageName == null) {
-            return UNKNOWN_PACKAGE;
-        } else {
-            return packageName;
-        }
-    }
-
-    private class SubscriberConnection implements IBinder.DeathRecipient {
-        private final IVmsSubscriberClient mClient;
-        private final int mUid;
-        private final String mPackageName;
-        private final int mUserId;
-
-        SubscriberConnection(IVmsSubscriberClient subscriberClient, int uid, String packageName,
-                int userId) {
-            mClient = subscriberClient;
-            mUid = uid;
-            mPackageName = packageName;
-            mUserId = userId;
-        }
-
-        @Override
-        public void binderDied() {
-            if (DBG) Log.d(TAG, "Subscriber died: " + this);
-            terminate();
-        }
-
-        @Override
-        public String toString() {
-            return mPackageName + " U=" + mUserId;
-        }
-
-        void terminate() {
-            if (DBG) Log.d(TAG, "Terminating subscriber: " + this);
-            synchronized (mLock) {
-                mBrokerService.removeDeadSubscriber(mClient);
-                IBinder subscriberBinder = mClient.asBinder();
-                try {
-                    subscriberBinder.unlinkToDeath(this, 0);
-                } catch (NoSuchElementException e) {
-                    if (DBG) Log.d(TAG, "While unlinking subscriber binder for " + this, e);
-                }
-                mSubscribers.remove(subscriberBinder);
-            }
-        }
-    }
-}
diff --git a/service/src/com/android/car/vms/VmsNewBrokerService.java b/service/src/com/android/car/vms/VmsNewBrokerService.java
new file mode 100644
index 0000000..8cbc33d
--- /dev/null
+++ b/service/src/com/android/car/vms/VmsNewBrokerService.java
@@ -0,0 +1,351 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.vms;
+
+import static com.android.car.ICarImpl.assertAnyVmsPermission;
+import static com.android.car.ICarImpl.assertVmsPublisherPermission;
+import static com.android.car.ICarImpl.assertVmsSubscriberPermission;
+
+import android.car.vms.IVmsBrokerService;
+import android.car.vms.IVmsClientCallback;
+import android.car.vms.VmsAssociatedLayer;
+import android.car.vms.VmsAvailableLayers;
+import android.car.vms.VmsLayer;
+import android.car.vms.VmsLayerDependency;
+import android.car.vms.VmsLayersOffering;
+import android.car.vms.VmsProviderInfo;
+import android.car.vms.VmsRegistrationInfo;
+import android.car.vms.VmsSubscriptionState;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.Log;
+
+import com.android.car.CarServiceBase;
+import com.android.car.VmsLayersAvailability;
+import com.android.car.VmsPublishersInfo;
+import com.android.car.stats.CarStatsService;
+import com.android.car.stats.VmsClientLogger;
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.IntSupplier;
+import java.util.stream.Collectors;
+
+/**
+ * Message broker service for routing Vehicle Map Service messages between clients.
+ *
+ * This service is also responsible for tracking VMS client connections and broadcasting
+ * notifications to clients about layer offering or subscription state changes.
+ */
+public class VmsNewBrokerService extends IVmsBrokerService.Stub implements CarServiceBase {
+    private static final boolean DBG = false;
+    private static final String TAG = VmsNewBrokerService.class.getSimpleName();
+
+    private final Context mContext;
+    private final PackageManager mPackageManager;
+    private final CarStatsService mStatsService;
+    private final IntSupplier mGetCallingUid;
+
+    private final VmsPublishersInfo mPublishersInfo = new VmsPublishersInfo();
+    private final VmsLayersAvailability mAvailableLayers = new VmsLayersAvailability();
+
+    private final Object mLock = new Object();
+    @GuardedBy("mLock")
+    private final Map<IBinder /* clientToken */, VmsClientInfo> mClientMap = new ArrayMap<>();
+    @GuardedBy("mLock")
+    private Set<VmsLayersOffering> mAllOfferings = Collections.emptySet();
+    @GuardedBy("mLock")
+    private VmsSubscriptionState mSubscriptionState = new VmsSubscriptionState(0,
+            Collections.emptySet(), Collections.emptySet());
+
+    public VmsNewBrokerService(Context context, CarStatsService statsService) {
+        this(context, statsService, Binder::getCallingUid);
+    }
+
+    @VisibleForTesting
+    VmsNewBrokerService(
+            Context context,
+            CarStatsService statsService,
+            IntSupplier getCallingUid) {
+        mContext = context;
+        mPackageManager = context.getPackageManager();
+        mStatsService = statsService;
+        mGetCallingUid = getCallingUid;
+    }
+
+    @Override
+    public void init() {
+    }
+
+    @Override
+    public void release() {
+    }
+
+    @Override
+    public void dump(PrintWriter writer) {
+        // TODO(b/149125079): Implement dumpsys
+    }
+
+    @Override
+    public VmsRegistrationInfo registerClient(IBinder clientToken, IVmsClientCallback callback,
+            boolean legacyClient) {
+        assertAnyVmsPermission(mContext);
+        int clientUid = mGetCallingUid.getAsInt();
+        String clientPackage = mPackageManager.getNameForUid(clientUid);
+        if (DBG) Log.d(TAG, "registerClient uid: " + clientUid + " package: " + clientPackage);
+
+        mStatsService.getVmsClientLogger(clientUid)
+                .logConnectionState(VmsClientLogger.ConnectionState.CONNECTED);
+
+        synchronized (mLock) {
+            try {
+                callback.asBinder().linkToDeath(
+                        () -> unregisterClient(clientToken,
+                                VmsClientLogger.ConnectionState.DISCONNECTED), 0);
+                mClientMap.put(clientToken, new VmsClientInfo(clientUid, clientPackage, callback,
+                        legacyClient));
+            } catch (RemoteException e) {
+                Log.w(TAG, "Client process is already dead", e);
+                mStatsService.getVmsClientLogger(clientUid)
+                        .logConnectionState(VmsClientLogger.ConnectionState.DISCONNECTED);
+            }
+            return new VmsRegistrationInfo(
+                    mAvailableLayers.getAvailableLayers(),
+                    mSubscriptionState);
+        }
+    }
+
+    @Override
+    public void unregisterClient(IBinder clientToken) {
+        assertAnyVmsPermission(mContext);
+        unregisterClient(clientToken, VmsClientLogger.ConnectionState.TERMINATED);
+    }
+
+    @Override
+    public VmsProviderInfo getProviderInfo(IBinder clientToken, int providerId) {
+        assertAnyVmsPermission(mContext);
+        getClient(clientToken); // Assert that the client is registered
+        return new VmsProviderInfo(mPublishersInfo.getPublisherInfoOrNull(providerId));
+    }
+
+    @Override
+    public void setSubscriptions(IBinder clientToken, List<VmsAssociatedLayer> layers) {
+        assertVmsSubscriberPermission(mContext);
+        getClient(clientToken).setSubscriptions(layers);
+        updateSubscriptionState();
+    }
+
+    @Override
+    public void setMonitoringEnabled(IBinder clientToken, boolean enabled) {
+        assertVmsSubscriberPermission(mContext);
+        getClient(clientToken).setMonitoringEnabled(enabled);
+    }
+
+    @Override
+    public int registerProvider(IBinder clientToken, VmsProviderInfo providerInfo) {
+        assertVmsPublisherPermission(mContext);
+        synchronized (mLock) {
+            VmsClientInfo client = getClient(clientToken);
+            int publisherId = mPublishersInfo.getIdForInfo(providerInfo.getDescription());
+            client.addProviderId(publisherId);
+            return publisherId;
+        }
+    }
+
+    @Override
+    public void setProviderOfferings(IBinder clientToken, int providerId,
+            List<VmsLayerDependency> offerings) {
+        assertVmsPublisherPermission(mContext);
+        VmsClientInfo client = getClient(clientToken);
+        if (!client.hasProviderId(providerId) && !client.isLegacyClient()) {
+            throw new IllegalArgumentException("Client not registered to offer layers as "
+                    + providerId);
+        }
+        if (client.setProviderOfferings(providerId, offerings)) {
+            updateAvailableLayers();
+        }
+    }
+
+    @Override
+    public void publishPacket(IBinder clientToken, int providerId, VmsLayer layer, byte[] packet) {
+        assertVmsPublisherPermission(mContext);
+        VmsClientInfo client = getClient(clientToken);
+        if (!client.hasOffering(providerId, layer) && !client.isLegacyClient()) {
+            throw new IllegalArgumentException("Client does not offer " + layer + " as "
+                    + providerId);
+        }
+
+        int packetLength = packet != null ? packet.length : 0;
+        mStatsService.getVmsClientLogger(client.getUid())
+                .logPacketSent(layer, packetLength);
+
+        Collection<VmsClientInfo> subscribers;
+        synchronized (mLock) {
+            subscribers = mClientMap.values().stream()
+                    .filter(subscriber -> subscriber.isSubscribed(providerId, layer))
+                    .collect(Collectors.toList());
+        }
+
+        if (DBG) Log.d(TAG, String.format("Number of subscribers: %d", subscribers.size()));
+
+        if (subscribers.isEmpty()) {
+            // A negative UID signals that the packet had zero subscribers
+            mStatsService.getVmsClientLogger(-1).logPacketDropped(layer, packetLength);
+            return;
+        }
+
+        for (VmsClientInfo subscriber : subscribers) {
+            try {
+                subscriber.getCallback().onPacketReceived(providerId, layer, packet);
+                mStatsService.getVmsClientLogger(subscriber.getUid())
+                        .logPacketReceived(layer, packetLength);
+            } catch (RemoteException ex) {
+                mStatsService.getVmsClientLogger(subscriber.getUid())
+                        .logPacketDropped(layer, packetLength);
+                Log.e(TAG, String.format("Unable to publish to listener: %s",
+                        subscriber.getPackageName()));
+            }
+        }
+    }
+
+    private void unregisterClient(IBinder clientToken, int connectionState) {
+        synchronized (mLock) {
+            VmsClientInfo client = mClientMap.remove(clientToken);
+            if (client != null) {
+                mStatsService.getVmsClientLogger(client.getUid())
+                        .logConnectionState(connectionState);
+            }
+        }
+        updateAvailableLayers();
+        updateSubscriptionState();
+    }
+
+    private VmsClientInfo getClient(IBinder clientToken) {
+        synchronized (mLock) {
+            VmsClientInfo client = mClientMap.get(clientToken);
+            if (client == null) {
+                throw new IllegalStateException("Unknown client token");
+            }
+            return client;
+        }
+    }
+
+    private Collection<VmsClientInfo> getActiveClients() {
+        synchronized (mLock) {
+            return new ArrayList<>(mClientMap.values());
+        }
+    }
+
+    private void updateAvailableLayers() {
+        synchronized (mLock) {
+            // Fuse layer offerings
+            Set<VmsLayersOffering> allOfferings = mClientMap.values().stream()
+                    .map(VmsClientInfo::getAllOfferings)
+                    .flatMap(Collection::stream)
+                    .collect(Collectors.toCollection(ArraySet::new));
+
+            // Ignore update if offerings are unchanged
+            if (mAllOfferings.equals(allOfferings)) {
+                return;
+            }
+
+            // Update offerings and compute available layers
+            mAllOfferings = allOfferings;
+            mAvailableLayers.setPublishersOffering(allOfferings);
+        }
+        notifyOfAvailabilityChange(mAvailableLayers.getAvailableLayers());
+    }
+
+    private void notifyOfAvailabilityChange(VmsAvailableLayers availableLayers) {
+        Log.i(TAG, "Notifying clients of layer availability change: " + availableLayers);
+        for (VmsClientInfo client : getActiveClients()) {
+            try {
+                client.getCallback().onLayerAvailabilityChanged(availableLayers);
+            } catch (RemoteException e) {
+                Log.w(TAG, "onLayersAvailabilityChanged failed: " + client.getPackageName(),
+                        e);
+            }
+        }
+    }
+
+    private void updateSubscriptionState() {
+        VmsSubscriptionState subscriptionState;
+        synchronized (mLock) {
+            Set<VmsLayer> layerSubscriptions = new ArraySet<>();
+            Map<VmsLayer, Set<Integer>> layerAndProviderSubscriptions = new ArrayMap<>();
+            // Fuse subscriptions
+            for (VmsClientInfo client : mClientMap.values()) {
+                layerSubscriptions.addAll(client.getLayerSubscriptions());
+                client.getLayerAndProviderSubscriptions().forEach((layer, providerIds) -> {
+                    Set<Integer> providerSubscriptions =
+                            layerAndProviderSubscriptions.computeIfAbsent(
+                                    layer,
+                                    ignored -> new ArraySet<>());
+                    providerSubscriptions.addAll(providerIds);
+                });
+            }
+
+            // Remove global layer subscriptions from provider-specific subscription state
+            layerSubscriptions.forEach(layerAndProviderSubscriptions::remove);
+
+            // Transform provider-specific subscriptions into VmsAssociatedLayers
+            Set<VmsAssociatedLayer> associatedLayers =
+                    layerAndProviderSubscriptions.entrySet().stream()
+                            .map(entry -> new VmsAssociatedLayer(entry.getKey(), entry.getValue()))
+                            .collect(Collectors.toCollection(ArraySet::new));
+
+            // Ignore update if subscriptions are unchanged
+            if (mSubscriptionState.getLayers().equals(layerSubscriptions)
+                    && mSubscriptionState.getAssociatedLayers().equals(associatedLayers)) {
+                return;
+            }
+
+            // Update subscription state
+            subscriptionState = new VmsSubscriptionState(
+                    mSubscriptionState.getSequenceNumber() + 1,
+                    layerSubscriptions,
+                    associatedLayers);
+            mSubscriptionState = subscriptionState;
+        }
+        // Notify clients of update
+        notifyOfSubscriptionChange(subscriptionState);
+    }
+
+    private void notifyOfSubscriptionChange(VmsSubscriptionState subscriptionState) {
+        Log.i(TAG, "Notifying clients of subscription state change: " + subscriptionState);
+        for (VmsClientInfo client : getActiveClients()) {
+            try {
+                client.getCallback().onSubscriptionStateChanged(subscriptionState);
+            } catch (RemoteException e) {
+                Log.w(TAG, "onSubscriptionStateChanged failed: " + client.getPackageName(),
+                        e);
+            }
+        }
+    }
+}
diff --git a/service/src/com/android/car/watchdog/CarWatchdogService.java b/service/src/com/android/car/watchdog/CarWatchdogService.java
new file mode 100644
index 0000000..ae92168
--- /dev/null
+++ b/service/src/com/android/car/watchdog/CarWatchdogService.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.watchdog;
+
+import android.automotive.watchdog.ICarWatchdogClient;
+import android.car.watchdog.ICarWatchdogService;
+
+import com.android.car.CarServiceBase;
+
+import java.io.PrintWriter;
+import java.lang.ref.WeakReference;
+
+/**
+ * Service to implement CarWatchdogManager API.
+ *
+ * <p>CarWatchdogService runs as car watchdog mediator, which checks clients' health status and
+ * reports the result to car watchdog server.
+ */
+public final class CarWatchdogService extends ICarWatchdogService.Stub implements CarServiceBase {
+
+    private final ICarWatchdogClientImpl mWatchogClient;
+
+    CarWatchdogService() {
+        mWatchogClient = new ICarWatchdogClientImpl(this);
+    }
+
+    @Override
+    public void init() {
+        // TODO(b/145556670): implement body.
+    }
+
+    @Override
+    public void release() {
+        // TODO(b/145556670): implement body.
+    }
+
+    @Override
+    public void dump(PrintWriter writer) {
+        // TODO(b/145556670): implement body.
+    }
+
+    @Override
+    public void registerClient(ICarWatchdogClient client, int timeout) {
+        // TODO(b/145556670): implement body.
+    }
+
+    @Override
+    public void unregisterClient(ICarWatchdogClient client) {
+        // TODO(b/145556670): implement body.
+    }
+
+    @Override
+    public void tellClientAlive(ICarWatchdogClient client, int sessionId) {
+        // TODO(b/145556670): implement body.
+    }
+
+    private static final class ICarWatchdogClientImpl extends ICarWatchdogClient.Stub {
+        private final WeakReference<CarWatchdogService> mService;
+
+        private ICarWatchdogClientImpl(CarWatchdogService service) {
+            mService = new WeakReference<>(service);
+        }
+
+        @Override
+        public void checkIfAlive(int sessionId, int timeout) {
+            // TODO(b/145556670): implement body.
+        }
+    }
+}
diff --git a/surround_view/OWNERS b/surround_view/OWNERS
new file mode 100644
index 0000000..2c5c8a7
--- /dev/null
+++ b/surround_view/OWNERS
@@ -0,0 +1,3 @@
+haoxiangl@google.com
+tanmayp@google.com
+swan@google.com
diff --git a/tests/BugReportApp/Android.bp b/tests/BugReportApp/Android.bp
new file mode 100644
index 0000000..5034ebf
--- /dev/null
+++ b/tests/BugReportApp/Android.bp
@@ -0,0 +1,64 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+android_app {
+    name: "BugReportApp",
+
+    srcs: ["src/**/*.java"],
+
+    resource_dirs: ["res"],
+
+    platform_apis: true,
+
+    aaptflags: ["--auto-add-overlay"],
+
+    optimize: {
+        enabled: false,
+    },
+
+    certificate: "platform",
+
+    privileged: true,
+
+    dex_preopt: {
+        enabled: false,
+    },
+
+    libs: [
+        "android.car",
+    ],
+
+    static_libs: [
+        "androidx.recyclerview_recyclerview",
+        "car-br-auto-value-jar",
+        "car-br-google-api-client-android-jar",
+        "car-br-google-api-java-client-jar",
+        "car-br-google-http-client-android-jar",
+        "car-br-google-http-client-jackson2-jar",
+        "car-br-google-http-client-jar",
+        "car-br-google-oauth-client-jar",
+        "car-br-google-storage-services-jar",
+        "car-br-jackson-core-jar",
+        "car-br-grpc-context-jar",
+        "car-br-opencensus-api-jar",
+        "car-br-opencensus-contrib-http-util-jar",
+        "guava",
+        "jsr305",
+    ],
+
+    required: ["privapp_whitelist_com.android.car.bugreport"],
+
+    // Explicitly define annotation processors even if javac can find them from static_libs.
+    plugins: ["car-br-auto-value"],
+}
diff --git a/tests/BugReportApp/Android.mk b/tests/BugReportApp/Android.mk
deleted file mode 100644
index 186fe4b..0000000
--- a/tests/BugReportApp/Android.mk
+++ /dev/null
@@ -1,109 +0,0 @@
-# Copyright (C) 2019 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-LOCAL_PATH:= $(call my-dir)
-
-include $(CLEAR_VARS)
-
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-
-LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
-
-LOCAL_PACKAGE_NAME := BugReportApp
-LOCAL_PRIVATE_PLATFORM_APIS := true
-
-LOCAL_AAPT_FLAGS := --auto-add-overlay
-
-LOCAL_MODULE_TAGS := optional
-
-LOCAL_PROGUARD_ENABLED := disabled
-
-LOCAL_CERTIFICATE := platform
-
-LOCAL_PRIVILEGED_MODULE := true
-
-LOCAL_DEX_PREOPT := false
-
-LOCAL_JAVA_LIBRARIES += \
-    android.car \
-    br_google_auto_value_target
-
-LOCAL_STATIC_JAVA_LIBRARIES := \
-    androidx.recyclerview_recyclerview \
-    br_google-api-java-client \
-    br_google-api-client-android \
-    br_google-oauth-client \
-    br_google-http-client \
-    br_google-http-client-android \
-    br_google-http-client-jackson2 \
-    br_jackson-core \
-    br_gson-2.1 \
-    br_google-storage-services \
-    br_apache_commons \
-    guava \
-    jsr305
-
-LOCAL_REQUIRED_MODULES := privapp_whitelist_com.google.android.car.bugreport
-
-# Explicitly define annotation processors even if javac can find them from
-# LOCAL_STATIC_JAVA_LIBRARIES.
-LOCAL_ANNOTATION_PROCESSORS := br_google_auto_value
-LOCAL_ANNOTATION_PROCESSOR_CLASSES := com.google.auto.value.processor.AutoValueProcessor
-
-include $(BUILD_PACKAGE)
-
-# ====  prebuilt library  ========================
-include $(CLEAR_VARS)
-
-COMMON_LIBS_PATH := ../../../../../prebuilts/tools/common/m2/repository
-
-LOCAL_PREBUILT_STATIC_JAVA_LIBRARIES := \
-    br_google-api-java-client:libs/google-api-client-1.25.0.jar \
-    br_google-api-client-android:libs/google-api-client-android-1.25.0.jar \
-    br_google-oauth-client:libs/google-oauth-client-1.25.0.jar \
-    br_google-http-client:libs/google-http-client-1.25.0.jar \
-    br_google-http-client-android:libs/google-http-client-android-1.25.0.jar \
-    br_jackson-core:libs/jackson-core-2.9.6.jar \
-    br_gson-2.1:libs/gson-2.1.jar \
-    br_google-http-client-jackson2:libs/google-http-client-jackson2-1.25.0.jar \
-    br_google-storage-services:libs/google-api-services-storage-v1-rev136-1.25.0.jar \
-    br_apache_commons:$(COMMON_LIBS_PATH)/org/eclipse/tycho/tycho-bundles-external/0.18.1/eclipse/plugins/org.apache.commons.codec_1.4.0.v201209201156.jar
-
-include $(BUILD_MULTI_PREBUILT)
-
-# Following shenanigans are needed for LOCAL_ANNOTATION_PROCESSORS.
-
-# ====  prebuilt host libraries  ========================
-include $(CLEAR_VARS)
-
-LOCAL_PREBUILT_STATIC_JAVA_LIBRARIES := \
-    br_google_auto_value:../../../../../prebuilts/tools/common/m2/repository/com/google/auto/value/auto-value/1.5.2/auto-value-1.5.2.jar
-
-include $(BUILD_HOST_PREBUILT)
-
-include $(CLEAR_VARS)
-
-LOCAL_MODULE_CLASS := JAVA_LIBRARIES
-LOCAL_MODULE := br_google_auto_value_target
-LOCAL_SDK_VERSION := current
-LOCAL_SRC_FILES := ../../../../../prebuilts/tools/common/m2/repository/com/google/auto/value/auto-value/1.5.2/auto-value-1.5.2.jar
-LOCAL_UNINSTALLABLE_MODULE := true
-
-include $(BUILD_PREBUILT)
-
-include $(CLEAR_VARS)
-
-# Use the following include to make our test apk.
-include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/tests/BugReportApp/AndroidManifest.xml b/tests/BugReportApp/AndroidManifest.xml
index dcccb83..7389260 100644
--- a/tests/BugReportApp/AndroidManifest.xml
+++ b/tests/BugReportApp/AndroidManifest.xml
@@ -15,7 +15,7 @@
      limitations under the License.
 -->
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="com.google.android.car.bugreport"
+          package="com.android.car.bugreport"
           android:versionCode="13"
           android:versionName="1.7.2">
 
@@ -57,18 +57,18 @@
                   android:excludeFromRecents="true">
             <meta-data android:name="distractionOptimized" android:value="true"/>
             <intent-filter>
-                <action android:name="com.google.android.car.bugreport.action.START_SILENT"/>
+                <action android:name="com.android.car.bugreport.action.START_SILENT"/>
             </intent-filter>
         </activity>
 
         <service android:name=".BugReportService"
                  android:exported="true">
             <intent-filter>
-                <action android:name="com.google.android.car.bugreport.action.START_SILENT"/>
+                <action android:name="com.android.car.bugreport.action.START_SILENT"/>
             </intent-filter>
         </service>
 
-        <service android:name="com.google.android.car.bugreport.UploadJob"
+        <service android:name="com.android.car.bugreport.UploadJob"
                  android:permission="android.permission.BIND_JOB_SERVICE"
                  android:exported="true"/>
 
@@ -79,8 +79,8 @@
             </intent-filter>
         </receiver>
 
-        <provider android:name="com.google.android.car.bugreport.BugStorageProvider"
-                  android:authorities="com.google.android.car.bugreport"
+        <provider android:name="com.android.car.bugreport.BugStorageProvider"
+                  android:authorities="com.android.car.bugreport"
                   android:exported="false"
                   android:singleUser="true"
                   android:multiprocess="false">
diff --git a/tests/BugReportApp/README.md b/tests/BugReportApp/README.md
index 476efe4..ce952ad 100644
--- a/tests/BugReportApp/README.md
+++ b/tests/BugReportApp/README.md
@@ -30,7 +30,7 @@
 
 To allow AAE BugReport app to access the API, you need to overlay
 `config_car_bugreport_application` in `packages/services/Car/service/res/values/config.xml`
-with value `com.google.android.car.bugreport`.
+with value `com.android.car.bugreport`.
 
 ## App Configuration
 
@@ -55,10 +55,10 @@
 
 The app supports following intents:
 
-1. `adb shell am start com.google.android.car.bugreport/.BugReportActivity`
+1. `adb shell am start com.android.car.bugreport/.BugReportActivity`
     - generates `MetaBugReport.Type.INTERACTIVE` bug report, shows audio message dialog before
     collecting bugreport.
-2. `adb shell am start-foreground-service -a com.google.android.car.bugreport.action.START_SILENT com.google.android.car.bugreport/.BugReportService`
+2. `adb shell am start-foreground-service -a com.android.car.bugreport.action.START_SILENT com.android.car.bugreport/.BugReportService`
     - generates `MetaBugReport.Type.SILENT` bug report, without audio message. It shows audio dialog
     after collecting bugreport.
 
@@ -82,7 +82,7 @@
      task scheduling rules.
    * You should see progress bar in notification shade.
 5. Pull collected zip files from the device:
-   * `adb pull /data/user/0/com.google.android.car.bugreport/bug_reports_pending/`
+   * `adb pull /data/user/0/com.android.car.bugreport/bug_reports_pending/`
 6. Please manually verify bug report contents.
    * Images - the should contain screenshots of all the physical displays.
    * Audio files - they should contain the audio message you recorded.
diff --git a/tests/BugReportApp/libs/Android.bp b/tests/BugReportApp/libs/Android.bp
new file mode 100644
index 0000000..8f36053
--- /dev/null
+++ b/tests/BugReportApp/libs/Android.bp
@@ -0,0 +1,95 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+java_import {
+    name: "car-br-auto-value-jar",
+    host_supported: true,
+    sdk_version: "current",
+    jars: ["auto-value-1.5.2.jar"],
+}
+
+java_plugin {
+    name: "car-br-auto-value",
+    static_libs: [
+        "car-br-auto-value-jar",
+        "guava",
+    ],
+    processor_class: "com.google.auto.value.processor.AutoValueProcessor",
+}
+
+java_import {
+    name: "car-br-google-api-java-client-jar",
+    jars: ["google-api-client-1.30.2.jar"],
+    sdk_version: "current",
+}
+
+java_import {
+    name: "car-br-google-api-client-android-jar",
+    jars: ["google-api-client-android-1.30.2.jar"],
+    sdk_version: "current",
+}
+
+java_import {
+    name: "car-br-google-storage-services-jar",
+    jars: ["google-api-services-storage-v1-rev158-1.25.0.jar"],
+    sdk_version: "current",
+}
+
+java_import {
+    name: "car-br-google-oauth-client-jar",
+    jars: ["google-oauth-client-1.30.1.jar"],
+    sdk_version: "current",
+}
+
+java_import {
+    name: "car-br-google-http-client-jar",
+    jars: ["google-http-client-1.30.1.jar"],
+    sdk_version: "current",
+}
+
+java_import {
+    name: "car-br-google-http-client-android-jar",
+    jars: ["google-http-client-android-1.30.1.jar"],
+    sdk_version: "current",
+}
+
+java_import {
+    name: "car-br-google-http-client-jackson2-jar",
+    jars: ["google-http-client-jackson2-1.30.1.jar"],
+    sdk_version: "current",
+}
+
+java_import {
+    name: "car-br-grpc-context-jar",
+    jars: ["grpc-context-1.19.0.jar"],
+    sdk_version: "current",
+}
+
+java_import {
+    name: "car-br-jackson-core-jar",
+    jars: ["jackson-core-2.9.9.jar"],
+    sdk_version: "current",
+}
+
+java_import {
+    name: "car-br-opencensus-api-jar",
+    jars: ["opencensus-api-0.21.0.jar"],
+    sdk_version: "current",
+}
+
+java_import {
+    name: "car-br-opencensus-contrib-http-util-jar",
+    jars: ["opencensus-contrib-http-util-0.21.0.jar"],
+    sdk_version: "current",
+}
diff --git a/tests/BugReportApp/libs/auto-value-1.5.2.jar b/tests/BugReportApp/libs/auto-value-1.5.2.jar
new file mode 100644
index 0000000..8ac0567
--- /dev/null
+++ b/tests/BugReportApp/libs/auto-value-1.5.2.jar
Binary files differ
diff --git a/tests/BugReportApp/libs/google-api-client-1.25.0.jar b/tests/BugReportApp/libs/google-api-client-1.25.0.jar
deleted file mode 100644
index f1e65b2..0000000
--- a/tests/BugReportApp/libs/google-api-client-1.25.0.jar
+++ /dev/null
Binary files differ
diff --git a/tests/BugReportApp/libs/google-api-client-1.30.2.jar b/tests/BugReportApp/libs/google-api-client-1.30.2.jar
new file mode 100644
index 0000000..b2330d8
--- /dev/null
+++ b/tests/BugReportApp/libs/google-api-client-1.30.2.jar
Binary files differ
diff --git a/tests/BugReportApp/libs/google-api-client-android-1.25.0.jar b/tests/BugReportApp/libs/google-api-client-android-1.25.0.jar
deleted file mode 100644
index 2e62e7a..0000000
--- a/tests/BugReportApp/libs/google-api-client-android-1.25.0.jar
+++ /dev/null
Binary files differ
diff --git a/tests/BugReportApp/libs/google-api-client-android-1.30.2.jar b/tests/BugReportApp/libs/google-api-client-android-1.30.2.jar
new file mode 100644
index 0000000..fb04d53
--- /dev/null
+++ b/tests/BugReportApp/libs/google-api-client-android-1.30.2.jar
Binary files differ
diff --git a/tests/BugReportApp/libs/google-api-client-gson-1.25.0.jar b/tests/BugReportApp/libs/google-api-client-gson-1.25.0.jar
deleted file mode 100644
index 734491d..0000000
--- a/tests/BugReportApp/libs/google-api-client-gson-1.25.0.jar
+++ /dev/null
Binary files differ
diff --git a/tests/BugReportApp/libs/google-api-client-jackson2-1.25.0.jar b/tests/BugReportApp/libs/google-api-client-jackson2-1.25.0.jar
deleted file mode 100644
index da08ebe..0000000
--- a/tests/BugReportApp/libs/google-api-client-jackson2-1.25.0.jar
+++ /dev/null
Binary files differ
diff --git a/tests/BugReportApp/libs/google-api-services-storage-v1-rev136-1.25.0.jar b/tests/BugReportApp/libs/google-api-services-storage-v1-rev136-1.25.0.jar
deleted file mode 100644
index 2f265eb..0000000
--- a/tests/BugReportApp/libs/google-api-services-storage-v1-rev136-1.25.0.jar
+++ /dev/null
Binary files differ
diff --git a/tests/BugReportApp/libs/google-api-services-storage-v1-rev158-1.25.0.jar b/tests/BugReportApp/libs/google-api-services-storage-v1-rev158-1.25.0.jar
new file mode 100644
index 0000000..a8e58a6
--- /dev/null
+++ b/tests/BugReportApp/libs/google-api-services-storage-v1-rev158-1.25.0.jar
Binary files differ
diff --git a/tests/BugReportApp/libs/google-http-client-1.25.0.jar b/tests/BugReportApp/libs/google-http-client-1.25.0.jar
deleted file mode 100644
index 84c8ce3..0000000
--- a/tests/BugReportApp/libs/google-http-client-1.25.0.jar
+++ /dev/null
Binary files differ
diff --git a/tests/BugReportApp/libs/google-http-client-1.30.1.jar b/tests/BugReportApp/libs/google-http-client-1.30.1.jar
new file mode 100644
index 0000000..aa2ea71
--- /dev/null
+++ b/tests/BugReportApp/libs/google-http-client-1.30.1.jar
Binary files differ
diff --git a/tests/BugReportApp/libs/google-http-client-android-1.25.0.jar b/tests/BugReportApp/libs/google-http-client-android-1.25.0.jar
deleted file mode 100644
index f2bb0d4..0000000
--- a/tests/BugReportApp/libs/google-http-client-android-1.25.0.jar
+++ /dev/null
Binary files differ
diff --git a/tests/BugReportApp/libs/google-http-client-android-1.30.1.jar b/tests/BugReportApp/libs/google-http-client-android-1.30.1.jar
new file mode 100644
index 0000000..31c88a8
--- /dev/null
+++ b/tests/BugReportApp/libs/google-http-client-android-1.30.1.jar
Binary files differ
diff --git a/tests/BugReportApp/libs/google-http-client-gson-1.25.0.jar b/tests/BugReportApp/libs/google-http-client-gson-1.25.0.jar
deleted file mode 100644
index 158c0c8..0000000
--- a/tests/BugReportApp/libs/google-http-client-gson-1.25.0.jar
+++ /dev/null
Binary files differ
diff --git a/tests/BugReportApp/libs/google-http-client-jackson2-1.25.0.jar b/tests/BugReportApp/libs/google-http-client-jackson2-1.25.0.jar
deleted file mode 100644
index 34f5646..0000000
--- a/tests/BugReportApp/libs/google-http-client-jackson2-1.25.0.jar
+++ /dev/null
Binary files differ
diff --git a/tests/BugReportApp/libs/google-http-client-jackson2-1.30.1.jar b/tests/BugReportApp/libs/google-http-client-jackson2-1.30.1.jar
new file mode 100644
index 0000000..021b6b7
--- /dev/null
+++ b/tests/BugReportApp/libs/google-http-client-jackson2-1.30.1.jar
Binary files differ
diff --git a/tests/BugReportApp/libs/google-http-client-jdo-1.25.0.jar b/tests/BugReportApp/libs/google-http-client-jdo-1.25.0.jar
deleted file mode 100644
index dcb7461..0000000
--- a/tests/BugReportApp/libs/google-http-client-jdo-1.25.0.jar
+++ /dev/null
Binary files differ
diff --git a/tests/BugReportApp/libs/google-oauth-client-1.25.0.jar b/tests/BugReportApp/libs/google-oauth-client-1.25.0.jar
deleted file mode 100644
index 1304c30..0000000
--- a/tests/BugReportApp/libs/google-oauth-client-1.25.0.jar
+++ /dev/null
Binary files differ
diff --git a/tests/BugReportApp/libs/google-oauth-client-1.30.1.jar b/tests/BugReportApp/libs/google-oauth-client-1.30.1.jar
new file mode 100644
index 0000000..2489729
--- /dev/null
+++ b/tests/BugReportApp/libs/google-oauth-client-1.30.1.jar
Binary files differ
diff --git a/tests/BugReportApp/libs/grpc-context-1.19.0.jar b/tests/BugReportApp/libs/grpc-context-1.19.0.jar
new file mode 100644
index 0000000..94b2e38
--- /dev/null
+++ b/tests/BugReportApp/libs/grpc-context-1.19.0.jar
Binary files differ
diff --git a/tests/BugReportApp/libs/gson-2.1.jar b/tests/BugReportApp/libs/gson-2.1.jar
deleted file mode 100644
index 83c5c99..0000000
--- a/tests/BugReportApp/libs/gson-2.1.jar
+++ /dev/null
Binary files differ
diff --git a/tests/BugReportApp/libs/httpclient-4.5.5.jar b/tests/BugReportApp/libs/httpclient-4.5.5.jar
deleted file mode 100644
index 7796b0e..0000000
--- a/tests/BugReportApp/libs/httpclient-4.5.5.jar
+++ /dev/null
Binary files differ
diff --git a/tests/BugReportApp/libs/jackson-core-2.9.6.jar b/tests/BugReportApp/libs/jackson-core-2.9.6.jar
deleted file mode 100644
index 09e7dd2..0000000
--- a/tests/BugReportApp/libs/jackson-core-2.9.6.jar
+++ /dev/null
Binary files differ
diff --git a/tests/BugReportApp/libs/jackson-core-2.9.9.jar b/tests/BugReportApp/libs/jackson-core-2.9.9.jar
new file mode 100644
index 0000000..02bd446
--- /dev/null
+++ b/tests/BugReportApp/libs/jackson-core-2.9.9.jar
Binary files differ
diff --git a/tests/BugReportApp/libs/jdo2-api-2.3-eb.jar b/tests/BugReportApp/libs/jdo2-api-2.3-eb.jar
deleted file mode 100644
index a039ae7..0000000
--- a/tests/BugReportApp/libs/jdo2-api-2.3-eb.jar
+++ /dev/null
Binary files differ
diff --git a/tests/BugReportApp/libs/opencensus-api-0.21.0.jar b/tests/BugReportApp/libs/opencensus-api-0.21.0.jar
new file mode 100644
index 0000000..ad0646e
--- /dev/null
+++ b/tests/BugReportApp/libs/opencensus-api-0.21.0.jar
Binary files differ
diff --git a/tests/BugReportApp/libs/opencensus-contrib-http-util-0.21.0.jar b/tests/BugReportApp/libs/opencensus-contrib-http-util-0.21.0.jar
new file mode 100644
index 0000000..32f0e51
--- /dev/null
+++ b/tests/BugReportApp/libs/opencensus-contrib-http-util-0.21.0.jar
Binary files differ
diff --git a/tests/BugReportApp/res/layout/bug_report_activity.xml b/tests/BugReportApp/res/layout/bug_report_activity.xml
index 888c628..1b1907d 100644
--- a/tests/BugReportApp/res/layout/bug_report_activity.xml
+++ b/tests/BugReportApp/res/layout/bug_report_activity.xml
@@ -45,7 +45,7 @@
             android:gravity="center"
             android:visibility="gone"
             android:text="@string/bugreport_dialog_add_audio_to_existing"/>
-        <com.google.android.car.bugreport.VoiceRecordingView
+        <com.android.car.bugreport.VoiceRecordingView
             android:id="@+id/voice_recording_view"
             android:layout_marginTop="@dimen/bug_report_voice_recording_margin_top"
             android:layout_height="@dimen/bug_report_voice_recording_height"
diff --git a/tests/BugReportApp/res/values-it/strings.xml b/tests/BugReportApp/res/values-it/strings.xml
index 2e283ad..3fdff3d 100644
--- a/tests/BugReportApp/res/values-it/strings.xml
+++ b/tests/BugReportApp/res/values-it/strings.xml
@@ -17,7 +17,7 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="2596316479611335185">"BugReport"</string>
+    <string name="app_name" msgid="2596316479611335185">"Segnalazione di bug"</string>
     <string name="bugreport_info_quit" msgid="5590138890181142473">"Chiudi"</string>
     <string name="bugreport_info_start" msgid="667324824650830832">"Avvia BugReport"</string>
     <string name="bugreport_info_status" msgid="7211044508792815451">"Stato:"</string>
diff --git a/tests/BugReportApp/src/com/android/car/bugreport/BugInfoAdapter.java b/tests/BugReportApp/src/com/android/car/bugreport/BugInfoAdapter.java
new file mode 100644
index 0000000..7d508b1
--- /dev/null
+++ b/tests/BugReportApp/src/com/android/car/bugreport/BugInfoAdapter.java
@@ -0,0 +1,184 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.car.bugreport;
+
+import android.os.Build;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.TextView;
+
+import androidx.recyclerview.widget.RecyclerView;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Shows bugreport title, status, status message and user action buttons. "Upload to Google" button
+ * is enabled when the status is {@link Status#STATUS_PENDING_USER_ACTION}, "Move to USB" button is
+ * enabled only when status is  {@link Status#STATUS_PENDING_USER_ACTION} and USB device is plugged
+ * in.
+ */
+public class BugInfoAdapter extends RecyclerView.Adapter<BugInfoAdapter.BugInfoViewHolder> {
+    static final int BUTTON_TYPE_UPLOAD = 0;
+    static final int BUTTON_TYPE_MOVE = 1;
+    static final int BUTTON_TYPE_ADD_AUDIO = 2;
+
+    /** Provides a handler for click events*/
+    interface ItemClickedListener {
+        /**
+         * Handles click events differently depending on provided buttonType and
+         * uses additional information provided in metaBugReport.
+         *
+         * @param buttonType One of {@link #BUTTON_TYPE_UPLOAD}, {@link #BUTTON_TYPE_MOVE} or
+         *                   {@link #BUTTON_TYPE_ADD_AUDIO}.
+         * @param metaBugReport Selected bugreport.
+         * @param holder ViewHolder of the clicked item.
+         */
+        void onItemClicked(int buttonType, MetaBugReport metaBugReport, BugInfoViewHolder holder);
+    }
+
+    /**
+     * Reference to each bug report info views.
+     */
+    static class BugInfoViewHolder extends RecyclerView.ViewHolder {
+        /** Title view */
+        TextView mTitleView;
+
+        /** Status View */
+        TextView mStatusView;
+
+        /** Message View */
+        TextView mMessageView;
+
+        /** Move Button */
+        Button mMoveButton;
+
+        /** Upload Button */
+        Button mUploadButton;
+
+        /** Add Audio Button */
+        Button mAddAudioButton;
+
+        BugInfoViewHolder(View v) {
+            super(v);
+            mTitleView = itemView.findViewById(R.id.bug_info_row_title);
+            mStatusView = itemView.findViewById(R.id.bug_info_row_status);
+            mMessageView = itemView.findViewById(R.id.bug_info_row_message);
+            mMoveButton = itemView.findViewById(R.id.bug_info_move_button);
+            mUploadButton = itemView.findViewById(R.id.bug_info_upload_button);
+            mAddAudioButton = itemView.findViewById(R.id.bug_info_add_audio_button);
+        }
+    }
+
+    private List<MetaBugReport> mDataset;
+    private final ItemClickedListener mItemClickedListener;
+    private final Config mConfig;
+
+    BugInfoAdapter(ItemClickedListener itemClickedListener, Config config) {
+        mItemClickedListener = itemClickedListener;
+        mDataset = new ArrayList<>();
+        mConfig = config;
+        // Allow RecyclerView to efficiently update UI; getItemId() is implemented below.
+        setHasStableIds(true);
+    }
+
+    @Override
+    public BugInfoViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+        // create a new view
+        View v = LayoutInflater.from(parent.getContext())
+                .inflate(R.layout.bug_info_view, parent, false);
+        return new BugInfoViewHolder(v);
+    }
+
+    @Override
+    public void onBindViewHolder(BugInfoViewHolder holder, int position) {
+        MetaBugReport bugreport = mDataset.get(position);
+        holder.mTitleView.setText(bugreport.getTitle());
+        holder.mStatusView.setText(Status.toString(bugreport.getStatus()));
+        holder.mMessageView.setText(bugreport.getStatusMessage());
+        if (bugreport.getStatusMessage().isEmpty()) {
+            holder.mMessageView.setVisibility(View.GONE);
+        } else {
+            holder.mMessageView.setVisibility(View.VISIBLE);
+        }
+        boolean enableUserActionButtons =
+                bugreport.getStatus() == Status.STATUS_PENDING_USER_ACTION.getValue()
+                        || bugreport.getStatus() == Status.STATUS_MOVE_FAILED.getValue()
+                        || bugreport.getStatus() == Status.STATUS_UPLOAD_FAILED.getValue();
+        if (enableUserActionButtons) {
+            holder.mMoveButton.setEnabled(true);
+            holder.mMoveButton.setVisibility(View.VISIBLE);
+            holder.mMoveButton.setOnClickListener(
+                    view -> mItemClickedListener.onItemClicked(BUTTON_TYPE_MOVE, bugreport,
+                            holder));
+        } else {
+            holder.mMoveButton.setEnabled(false);
+            holder.mMoveButton.setVisibility(View.GONE);
+        }
+        // Enable the upload button only for userdebug/eng builds.
+        if (enableUserActionButtons && Build.IS_DEBUGGABLE) {
+            holder.mUploadButton.setText(R.string.bugreport_upload_gcs_button_text);
+            holder.mUploadButton.setEnabled(true);
+            holder.mUploadButton.setVisibility(View.VISIBLE);
+            holder.mUploadButton.setOnClickListener(
+                    view -> mItemClickedListener.onItemClicked(BUTTON_TYPE_UPLOAD, bugreport,
+                            holder));
+        } else {
+            holder.mUploadButton.setVisibility(View.GONE);
+            holder.mUploadButton.setEnabled(false);
+        }
+        if (bugreport.getStatus() == Status.STATUS_AUDIO_PENDING.getValue()) {
+            if (mConfig.getAutoUpload()) {
+                holder.mAddAudioButton.setText(R.string.bugreport_add_audio_upload_button_text);
+            } else {
+                holder.mAddAudioButton.setText(R.string.bugreport_add_audio_button_text);
+            }
+            holder.mAddAudioButton.setEnabled(true);
+            holder.mAddAudioButton.setVisibility(View.VISIBLE);
+            holder.mAddAudioButton.setOnClickListener(view ->
+                    mItemClickedListener.onItemClicked(BUTTON_TYPE_ADD_AUDIO, bugreport, holder));
+        } else {
+            holder.mAddAudioButton.setEnabled(false);
+            holder.mAddAudioButton.setVisibility(View.GONE);
+        }
+    }
+
+    /** Sets dataSet; it copies the list, because it modifies it in this adapter. */
+    void setDataset(List<MetaBugReport> bugReports) {
+        mDataset = new ArrayList<>(bugReports);
+        notifyDataSetChanged();
+    }
+
+    /** Update a bug report in the data set. */
+    void updateBugReportInDataSet(MetaBugReport bugReport, int position) {
+        if (position != RecyclerView.NO_POSITION) {
+            mDataset.set(position, bugReport);
+            notifyItemChanged(position);
+        }
+    }
+
+    @Override
+    public long getItemId(int position) {
+        return mDataset.get(position).getId();
+    }
+
+    @Override
+    public int getItemCount() {
+        return mDataset.size();
+    }
+}
diff --git a/tests/BugReportApp/src/com/android/car/bugreport/BugReportActivity.java b/tests/BugReportApp/src/com/android/car/bugreport/BugReportActivity.java
new file mode 100644
index 0000000..c87ec77
--- /dev/null
+++ b/tests/BugReportApp/src/com/android/car/bugreport/BugReportActivity.java
@@ -0,0 +1,713 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.car.bugreport;
+
+import static com.android.car.bugreport.BugReportService.MAX_PROGRESS_VALUE;
+
+import android.Manifest;
+import android.app.Activity;
+import android.car.Car;
+import android.car.CarNotConnectedException;
+import android.car.drivingstate.CarDrivingStateEvent;
+import android.car.drivingstate.CarDrivingStateManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.content.pm.PackageManager;
+import android.media.AudioAttributes;
+import android.media.AudioFocusRequest;
+import android.media.AudioManager;
+import android.media.MediaRecorder;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.UserManager;
+import android.util.Log;
+import android.view.View;
+import android.view.Window;
+import android.widget.Button;
+import android.widget.ProgressBar;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import com.google.common.base.Preconditions;
+import com.google.common.io.ByteStreams;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.Random;
+
+/**
+ * Activity that shows two types of dialogs: starting a new bug report and current status of already
+ * in progress bug report.
+ *
+ * <p>If there is no in-progress bug report, it starts recording voice message. After clicking
+ * submit button it initiates {@link BugReportService}.
+ *
+ * <p>If bug report is in-progress, it shows a progress bar.
+ */
+public class BugReportActivity extends Activity {
+    private static final String TAG = BugReportActivity.class.getSimpleName();
+
+    /** Starts silent (no audio message recording) bugreporting. */
+    private static final String ACTION_START_SILENT =
+            "com.android.car.bugreport.action.START_SILENT";
+
+    /** This is deprecated action. Please start SILENT bugreport using {@link BugReportService}. */
+    private static final String ACTION_ADD_AUDIO =
+            "com.android.car.bugreport.action.ADD_AUDIO";
+
+    private static final int VOICE_MESSAGE_MAX_DURATION_MILLIS = 60 * 1000;
+    private static final int AUDIO_PERMISSIONS_REQUEST_ID = 1;
+
+    private static final String EXTRA_BUGREPORT_ID = "bugreport-id";
+
+    /**
+     * NOTE: mRecorder related messages are cleared when the activity finishes.
+     */
+    private final Handler mHandler = new Handler(Looper.getMainLooper());
+
+    /** Look up string length, e.g. [ABCDEF]. */
+    static final int LOOKUP_STRING_LENGTH = 6;
+
+    private TextView mInProgressTitleText;
+    private ProgressBar mProgressBar;
+    private TextView mProgressText;
+    private TextView mAddAudioText;
+    private VoiceRecordingView mVoiceRecordingView;
+    private View mVoiceRecordingFinishedView;
+    private View mSubmitBugReportLayout;
+    private View mInProgressLayout;
+    private View mShowBugReportsButton;
+    private Button mSubmitButton;
+
+    private boolean mBound;
+    /** Audio message recording process started (including waiting for permission). */
+    private boolean mAudioRecordingStarted;
+    /** Audio recording using MIC is running (permission given). */
+    private boolean mAudioRecordingIsRunning;
+    private boolean mIsNewBugReport;
+    private boolean mIsOnActivityStartedWithBugReportServiceBoundCalled;
+    private boolean mIsSubmitButtonClicked;
+    private BugReportService mService;
+    private MediaRecorder mRecorder;
+    private MetaBugReport mMetaBugReport;
+    private File mAudioFile;
+    private Car mCar;
+    private CarDrivingStateManager mDrivingStateManager;
+    private AudioManager mAudioManager;
+    private AudioFocusRequest mLastAudioFocusRequest;
+    private Config mConfig;
+
+    /** Defines callbacks for service binding, passed to bindService() */
+    private ServiceConnection mConnection = new ServiceConnection() {
+        @Override
+        public void onServiceConnected(ComponentName className, IBinder service) {
+            BugReportService.ServiceBinder binder = (BugReportService.ServiceBinder) service;
+            mService = binder.getService();
+            mBound = true;
+            onActivityStartedWithBugReportServiceBound();
+        }
+
+        @Override
+        public void onServiceDisconnected(ComponentName arg0) {
+            // called when service connection breaks unexpectedly.
+            mBound = false;
+        }
+    };
+
+    /**
+     * Builds an intent that starts {@link BugReportActivity} to add audio message to the existing
+     * bug report.
+     */
+    static Intent buildAddAudioIntent(Context context, MetaBugReport bug) {
+        Intent addAudioIntent = new Intent(context, BugReportActivity.class);
+        addAudioIntent.setAction(ACTION_ADD_AUDIO);
+        addAudioIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+        addAudioIntent.putExtra(EXTRA_BUGREPORT_ID, bug.getId());
+        return addAudioIntent;
+    }
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        Preconditions.checkState(Config.isBugReportEnabled(), "BugReport is disabled.");
+
+        super.onCreate(savedInstanceState);
+        requestWindowFeature(Window.FEATURE_NO_TITLE);
+
+        // Bind to BugReportService.
+        Intent intent = new Intent(this, BugReportService.class);
+        bindService(intent, mConnection, BIND_AUTO_CREATE);
+    }
+
+    @Override
+    protected void onStart() {
+        super.onStart();
+
+        if (mBound) {
+            onActivityStartedWithBugReportServiceBound();
+        }
+    }
+
+    @Override
+    protected void onStop() {
+        super.onStop();
+        // If SUBMIT button is clicked, cancelling audio has been taken care of.
+        if (!mIsSubmitButtonClicked) {
+            cancelAudioMessageRecording();
+        }
+        if (mBound) {
+            mService.removeBugReportProgressListener();
+        }
+        // Reset variables for the next onStart().
+        mAudioRecordingStarted = false;
+        mAudioRecordingIsRunning = false;
+        mIsSubmitButtonClicked = false;
+        mIsOnActivityStartedWithBugReportServiceBoundCalled = false;
+        mMetaBugReport = null;
+        mAudioFile = null;
+    }
+
+    @Override
+    public void onDestroy() {
+        super.onDestroy();
+
+        if (mRecorder != null) {
+            mHandler.removeCallbacksAndMessages(/* token= */ mRecorder);
+        }
+        if (mBound) {
+            unbindService(mConnection);
+            mBound = false;
+        }
+        if (mCar != null && mCar.isConnected()) {
+            mCar.disconnect();
+            mCar = null;
+        }
+    }
+
+    private void onCarDrivingStateChanged(CarDrivingStateEvent event) {
+        if (mShowBugReportsButton == null) {
+            Log.w(TAG, "Cannot handle driving state change, UI is not ready");
+            return;
+        }
+        // When adding audio message to the existing bugreport, do not show "Show Bug Reports"
+        // button, users either should explicitly Submit or Cancel.
+        if (mAudioRecordingStarted && !mIsNewBugReport) {
+            mShowBugReportsButton.setVisibility(View.GONE);
+            return;
+        }
+        if (event.eventValue == CarDrivingStateEvent.DRIVING_STATE_PARKED
+                || event.eventValue == CarDrivingStateEvent.DRIVING_STATE_IDLING) {
+            mShowBugReportsButton.setVisibility(View.VISIBLE);
+        } else {
+            mShowBugReportsButton.setVisibility(View.GONE);
+        }
+    }
+
+    private void onProgressChanged(float progress) {
+        int progressValue = (int) progress;
+        mProgressBar.setProgress(progressValue);
+        mProgressText.setText(progressValue + "%");
+        if (progressValue == MAX_PROGRESS_VALUE) {
+            mInProgressTitleText.setText(R.string.bugreport_dialog_in_progress_title_finished);
+        }
+    }
+
+    private void prepareUi() {
+        if (mSubmitBugReportLayout != null) {
+            return;
+        }
+        setContentView(R.layout.bug_report_activity);
+
+        // Connect to the services here, because they are used only when showing the dialog.
+        // We need to minimize system state change when performing SILENT bug report.
+        mConfig = new Config();
+        mConfig.start();
+        mCar = Car.createCar(this, /* handler= */ null,
+                Car.CAR_WAIT_TIMEOUT_DO_NOT_WAIT, this::onCarLifecycleChanged);
+
+        mInProgressTitleText = findViewById(R.id.in_progress_title_text);
+        mProgressBar = findViewById(R.id.progress_bar);
+        mProgressText = findViewById(R.id.progress_text);
+        mAddAudioText = findViewById(R.id.bug_report_add_audio_to_existing);
+        mVoiceRecordingView = findViewById(R.id.voice_recording_view);
+        mVoiceRecordingFinishedView = findViewById(R.id.voice_recording_finished_text_view);
+        mSubmitBugReportLayout = findViewById(R.id.submit_bug_report_layout);
+        mInProgressLayout = findViewById(R.id.in_progress_layout);
+        mShowBugReportsButton = findViewById(R.id.button_show_bugreports);
+        mSubmitButton = findViewById(R.id.button_submit);
+
+        mShowBugReportsButton.setOnClickListener(this::buttonShowBugReportsClick);
+        mSubmitButton.setOnClickListener(this::buttonSubmitClick);
+        findViewById(R.id.button_cancel).setOnClickListener(this::buttonCancelClick);
+        findViewById(R.id.button_close).setOnClickListener(this::buttonCancelClick);
+
+        if (mIsNewBugReport) {
+            mSubmitButton.setText(R.string.bugreport_dialog_submit);
+        } else {
+            mSubmitButton.setText(mConfig.getAutoUpload()
+                    ? R.string.bugreport_dialog_upload : R.string.bugreport_dialog_save);
+        }
+    }
+
+    private void onCarLifecycleChanged(Car car, boolean ready) {
+        if (!ready) {
+            mDrivingStateManager = null;
+            mCar = null;
+            Log.d(TAG, "Car service is not ready, ignoring");
+            // If car service is not ready for this activity, just ignore it - as it's only
+            // used to control UX restrictions.
+            return;
+        }
+        try {
+            mDrivingStateManager = (CarDrivingStateManager) car.getCarManager(
+                    Car.CAR_DRIVING_STATE_SERVICE);
+            mDrivingStateManager.registerListener(
+                    BugReportActivity.this::onCarDrivingStateChanged);
+            // Call onCarDrivingStateChanged(), because it's not called when Car is connected.
+            onCarDrivingStateChanged(mDrivingStateManager.getCurrentCarDrivingState());
+        } catch (CarNotConnectedException e) {
+            Log.w(TAG, "Failed to get CarDrivingStateManager", e);
+        }
+    }
+
+    private void showInProgressUi() {
+        mSubmitBugReportLayout.setVisibility(View.GONE);
+        mInProgressLayout.setVisibility(View.VISIBLE);
+        mInProgressTitleText.setText(R.string.bugreport_dialog_in_progress_title);
+        onProgressChanged(mService.getBugReportProgress());
+    }
+
+    private void showSubmitBugReportUi(boolean isRecording) {
+        mSubmitBugReportLayout.setVisibility(View.VISIBLE);
+        mInProgressLayout.setVisibility(View.GONE);
+        if (isRecording) {
+            mVoiceRecordingFinishedView.setVisibility(View.GONE);
+            mVoiceRecordingView.setVisibility(View.VISIBLE);
+        } else {
+            mVoiceRecordingFinishedView.setVisibility(View.VISIBLE);
+            mVoiceRecordingView.setVisibility(View.GONE);
+        }
+        // NOTE: mShowBugReportsButton visibility is also handled in #onCarDrivingStateChanged().
+        mShowBugReportsButton.setVisibility(View.GONE);
+        if (mDrivingStateManager != null) {
+            try {
+                onCarDrivingStateChanged(mDrivingStateManager.getCurrentCarDrivingState());
+            } catch (CarNotConnectedException e) {
+                Log.e(TAG, "Failed to get current driving state.", e);
+            }
+        }
+    }
+
+    /**
+     * Initializes MetaBugReport in a local DB and starts audio recording.
+     *
+     * <p>This method expected to be called when the activity is started and bound to the service.
+     */
+    private void onActivityStartedWithBugReportServiceBound() {
+        if (mIsOnActivityStartedWithBugReportServiceBoundCalled) {
+            return;
+        }
+        mIsOnActivityStartedWithBugReportServiceBoundCalled = true;
+
+        if (mService.isCollectingBugReport()) {
+            Log.i(TAG, "Bug report is already being collected.");
+            mService.setBugReportProgressListener(this::onProgressChanged);
+            prepareUi();
+            showInProgressUi();
+            return;
+        }
+
+        if (ACTION_START_SILENT.equals(getIntent().getAction())) {
+            Log.i(TAG, "Starting a silent bugreport.");
+            MetaBugReport bugReport = createBugReport(this, MetaBugReport.TYPE_SILENT);
+            startBugReportCollection(bugReport);
+            finish();
+            return;
+        }
+
+        // Close the notification shade and other dialogs when showing BugReportActivity dialog.
+        sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS));
+
+        if (ACTION_ADD_AUDIO.equals(getIntent().getAction())) {
+            addAudioToExistingBugReport(
+                    getIntent().getIntExtra(EXTRA_BUGREPORT_ID, /* defaultValue= */ -1));
+            return;
+        }
+
+        Log.i(TAG, "Starting an interactive bugreport.");
+        createNewBugReportWithAudioMessage();
+    }
+
+    private void addAudioToExistingBugReport(int bugreportId) {
+        MetaBugReport bug = BugStorageUtils.findBugReport(this, bugreportId).orElseThrow(
+                () -> new RuntimeException("Failed to find bug report with id " + bugreportId));
+        Log.i(TAG, "Adding audio to the existing bugreport " + bug.getTimestamp());
+        if (bug.getStatus() != Status.STATUS_AUDIO_PENDING.getValue()) {
+            Log.e(TAG, "Failed to add audio, bad status, expected "
+                    + Status.STATUS_AUDIO_PENDING.getValue() + ", got " + bug.getStatus());
+            finish();
+        }
+        File audioFile;
+        try {
+            audioFile = File.createTempFile("audio", "mp3", getCacheDir());
+        } catch (IOException e) {
+            throw new RuntimeException("failed to create temp audio file");
+        }
+        startAudioMessageRecording(/* isNewBugReport= */ false, bug, audioFile);
+    }
+
+    private void createNewBugReportWithAudioMessage() {
+        MetaBugReport bug = createBugReport(this, MetaBugReport.TYPE_INTERACTIVE);
+        startAudioMessageRecording(
+                /* isNewBugReport= */ true,
+                bug,
+                FileUtils.getFileWithSuffix(this, bug.getTimestamp(), "-message.3gp"));
+    }
+
+    /** Shows a dialog UI and starts recording audio message. */
+    private void startAudioMessageRecording(
+            boolean isNewBugReport, MetaBugReport bug, File audioFile) {
+        if (mAudioRecordingStarted) {
+            Log.i(TAG, "Audio message recording is already started.");
+            return;
+        }
+        mAudioRecordingStarted = true;
+        mAudioManager = getSystemService(AudioManager.class);
+        mIsNewBugReport = isNewBugReport;
+        mMetaBugReport = bug;
+        mAudioFile = audioFile;
+        prepareUi();
+        showSubmitBugReportUi(/* isRecording= */ true);
+        if (isNewBugReport) {
+            mAddAudioText.setVisibility(View.GONE);
+        } else {
+            mAddAudioText.setVisibility(View.VISIBLE);
+            mAddAudioText.setText(String.format(
+                    getString(R.string.bugreport_dialog_add_audio_to_existing),
+                    mMetaBugReport.getTimestamp()));
+        }
+
+        if (!hasRecordPermissions()) {
+            requestRecordPermissions();
+        } else {
+            startRecordingWithPermission();
+        }
+    }
+
+    /**
+     * Cancels bugreporting by stopping audio recording and deleting temp files.
+     */
+    private void cancelAudioMessageRecording() {
+        // If audio recording is not running, most likely there were permission issues,
+        // so leave the bugreport as is without cancelling it.
+        if (!mAudioRecordingIsRunning) {
+            Log.w(TAG, "Cannot cancel, audio recording is not running.");
+            return;
+        }
+        stopAudioRecording();
+        if (mIsNewBugReport) {
+            // The app creates a temp dir only for new INTERACTIVE bugreports.
+            File tempDir = FileUtils.getTempDir(this, mMetaBugReport.getTimestamp());
+            new DeleteFilesAndDirectoriesAsyncTask().execute(tempDir);
+        } else {
+            BugStorageUtils.deleteBugReportFiles(this, mMetaBugReport.getId());
+            new DeleteFilesAndDirectoriesAsyncTask().execute(mAudioFile);
+        }
+        BugStorageUtils.setBugReportStatus(
+                this, mMetaBugReport, Status.STATUS_USER_CANCELLED, "");
+        Log.i(TAG, "Bug report " + mMetaBugReport.getTimestamp() + " is cancelled");
+        mAudioRecordingStarted = false;
+        mAudioRecordingIsRunning = false;
+    }
+
+    private void buttonCancelClick(View view) {
+        finish();
+    }
+
+    private void buttonSubmitClick(View view) {
+        stopAudioRecording();
+        mIsSubmitButtonClicked = true;
+        if (mIsNewBugReport) {
+            Log.i(TAG, "Starting bugreport service.");
+            startBugReportCollection(mMetaBugReport);
+        } else {
+            Log.i(TAG, "Adding audio file to the bugreport " + mMetaBugReport.getTimestamp());
+            new AddAudioToBugReportAsyncTask(this, mConfig, mMetaBugReport, mAudioFile).execute();
+        }
+        setResult(Activity.RESULT_OK);
+        finish();
+    }
+
+    /** Starts the {@link BugReportService} to collect bug report. */
+    private void startBugReportCollection(MetaBugReport bug) {
+        Bundle bundle = new Bundle();
+        bundle.putParcelable(BugReportService.EXTRA_META_BUG_REPORT, bug);
+        Intent intent = new Intent(this, BugReportService.class);
+        intent.putExtras(bundle);
+        startForegroundService(intent);
+    }
+
+    /**
+     * Starts {@link BugReportInfoActivity} and finishes current activity, so it won't be running
+     * in the background and closing {@link BugReportInfoActivity} will not open the current
+     * activity again.
+     */
+    private void buttonShowBugReportsClick(View view) {
+        // First cancel the audio recording, then delete the bug report from database.
+        cancelAudioMessageRecording();
+        // Delete the bugreport from database, otherwise pressing "Show Bugreports" button will
+        // create unnecessary cancelled bugreports.
+        if (mMetaBugReport != null) {
+            BugStorageUtils.completeDeleteBugReport(this, mMetaBugReport.getId());
+        }
+        Intent intent = new Intent(this, BugReportInfoActivity.class);
+        startActivity(intent);
+        finish();
+    }
+
+    private void requestRecordPermissions() {
+        requestPermissions(
+                new String[]{Manifest.permission.RECORD_AUDIO}, AUDIO_PERMISSIONS_REQUEST_ID);
+    }
+
+    private boolean hasRecordPermissions() {
+        return checkSelfPermission(Manifest.permission.RECORD_AUDIO)
+                == PackageManager.PERMISSION_GRANTED;
+    }
+
+    @Override
+    public void onRequestPermissionsResult(
+            int requestCode, String[] permissions, int[] grantResults) {
+        if (requestCode != AUDIO_PERMISSIONS_REQUEST_ID) {
+            return;
+        }
+        for (int i = 0; i < grantResults.length; i++) {
+            if (Manifest.permission.RECORD_AUDIO.equals(permissions[i])
+                    && grantResults[i] == PackageManager.PERMISSION_GRANTED) {
+                // Start recording from UI thread, otherwise when MediaRecord#start() fails,
+                // stack trace gets confusing.
+                mHandler.post(this::startRecordingWithPermission);
+                return;
+            }
+        }
+        handleNoPermission(permissions);
+    }
+
+    private void handleNoPermission(String[] permissions) {
+        String text = this.getText(R.string.toast_permissions_denied) + " : "
+                + Arrays.toString(permissions);
+        Log.w(TAG, text);
+        Toast.makeText(this, text, Toast.LENGTH_LONG).show();
+        if (mIsNewBugReport) {
+            BugStorageUtils.setBugReportStatus(this, mMetaBugReport,
+                    Status.STATUS_USER_CANCELLED, text);
+        } else {
+            BugStorageUtils.setBugReportStatus(this, mMetaBugReport,
+                    Status.STATUS_AUDIO_PENDING, text);
+        }
+        finish();
+    }
+
+    private void startRecordingWithPermission() {
+        Log.i(TAG, "Started voice recording, and saving audio to " + mAudioFile);
+
+        mLastAudioFocusRequest = new AudioFocusRequest.Builder(
+                        AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE)
+                .setOnAudioFocusChangeListener(focusChange ->
+                        Log.d(TAG, "AudioManager focus change " + focusChange))
+                .setAudioAttributes(new AudioAttributes.Builder()
+                        .setUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION)
+                        .setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
+                        .build())
+                .setAcceptsDelayedFocusGain(true)
+                .build();
+        int focusGranted = mAudioManager.requestAudioFocus(mLastAudioFocusRequest);
+        // NOTE: We will record even if the audio focus was not granted.
+        Log.d(TAG,
+                "AudioFocus granted " + (focusGranted == AudioManager.AUDIOFOCUS_REQUEST_GRANTED));
+
+        mRecorder = new MediaRecorder();
+        mRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
+        mRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
+        mRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
+        mRecorder.setOnInfoListener((MediaRecorder recorder, int what, int extra) ->
+                Log.i(TAG, "OnMediaRecorderInfo: what=" + what + ", extra=" + extra));
+        mRecorder.setOnErrorListener((MediaRecorder recorder, int what, int extra) ->
+                Log.i(TAG, "OnMediaRecorderError: what=" + what + ", extra=" + extra));
+        mRecorder.setOutputFile(mAudioFile);
+
+        try {
+            mRecorder.prepare();
+        } catch (IOException e) {
+            Log.e(TAG, "Failed on MediaRecorder#prepare(), filename: " + mAudioFile, e);
+            finish();
+            return;
+        }
+
+        mRecorder.start();
+        mVoiceRecordingView.setRecorder(mRecorder);
+        mAudioRecordingIsRunning = true;
+
+        // Messages with token mRecorder are cleared when the activity finishes or recording stops.
+        mHandler.postDelayed(() -> {
+            Log.i(TAG, "Timed out while recording voice message, cancelling.");
+            stopAudioRecording();
+            showSubmitBugReportUi(/* isRecording= */ false);
+        }, /* token= */ mRecorder, VOICE_MESSAGE_MAX_DURATION_MILLIS);
+    }
+
+    private void stopAudioRecording() {
+        if (mRecorder != null) {
+            Log.i(TAG, "Recording ended, stopping the MediaRecorder.");
+            mHandler.removeCallbacksAndMessages(/* token= */ mRecorder);
+            try {
+                mRecorder.stop();
+            } catch (RuntimeException e) {
+                // Sometimes MediaRecorder doesn't start and stopping it throws an error.
+                // We just log these cases, no need to crash the app.
+                Log.w(TAG, "Couldn't stop media recorder", e);
+            }
+            mRecorder.release();
+            mRecorder = null;
+        }
+        if (mLastAudioFocusRequest != null) {
+            int focusAbandoned = mAudioManager.abandonAudioFocusRequest(mLastAudioFocusRequest);
+            Log.d(TAG, "Audio focus abandoned "
+                    + (focusAbandoned == AudioManager.AUDIOFOCUS_REQUEST_GRANTED));
+            mLastAudioFocusRequest = null;
+        }
+        mVoiceRecordingView.setRecorder(null);
+    }
+
+    private static String getCurrentUserName(Context context) {
+        UserManager um = UserManager.get(context);
+        return um.getUserName();
+    }
+
+    /**
+     * Creates a {@link MetaBugReport} and saves it in a local sqlite database.
+     *
+     * @param context an Android context.
+     * @param type bug report type, {@link MetaBugReport.BugReportType}.
+     */
+    static MetaBugReport createBugReport(Context context, int type) {
+        String timestamp = MetaBugReport.toBugReportTimestamp(new Date());
+        String username = getCurrentUserName(context);
+        String title = BugReportTitleGenerator.generateBugReportTitle(timestamp, username);
+        return BugStorageUtils.createBugReport(context, title, timestamp, username, type);
+    }
+
+    /** A helper class to generate bugreport title. */
+    private static final class BugReportTitleGenerator {
+        /** Contains easily readable characters. */
+        private static final char[] CHARS_FOR_RANDOM_GENERATOR =
+                new char[]{'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'J', 'K', 'L', 'M', 'N', 'P',
+                        'R', 'S', 'T', 'U', 'W', 'X', 'Y', 'Z'};
+
+        /**
+         * Generates a bugreport title from given timestamp and username.
+         *
+         * <p>Example: "[A45E8] Feedback from user Driver at 2019-09-21_12:00:00"
+         */
+        static String generateBugReportTitle(String timestamp, String username) {
+            // Lookup string is used to search a bug in Buganizer (see b/130915969).
+            String lookupString = generateRandomString(LOOKUP_STRING_LENGTH);
+            return "[" + lookupString + "] Feedback from user " + username + " at " + timestamp;
+        }
+
+        private static String generateRandomString(int length) {
+            Random random = new Random();
+            StringBuilder builder = new StringBuilder();
+            for (int i = 0; i < length; i++) {
+                int randomIndex = random.nextInt(CHARS_FOR_RANDOM_GENERATOR.length);
+                builder.append(CHARS_FOR_RANDOM_GENERATOR[randomIndex]);
+            }
+            return builder.toString();
+        }
+    }
+
+    /** AsyncTask that recursively deletes files and directories. */
+    private static class DeleteFilesAndDirectoriesAsyncTask extends AsyncTask<File, Void, Void> {
+        @Override
+        protected Void doInBackground(File... files) {
+            for (File file : files) {
+                Log.i(TAG, "Deleting " + file.getAbsolutePath());
+                if (file.isFile()) {
+                    file.delete();
+                } else {
+                    FileUtils.deleteDirectory(file);
+                }
+            }
+            return null;
+        }
+    }
+
+    /**
+     * AsyncTask that moves audio file to the system user's {@link FileUtils#getPendingDir} and
+     * sets status to either STATUS_UPLOAD_PENDING or STATUS_PENDING_USER_ACTION.
+     */
+    private static class AddAudioToBugReportAsyncTask extends AsyncTask<Void, Void, Void> {
+        private final Context mContext;
+        private final Config mConfig;
+        private final File mAudioFile;
+        private final MetaBugReport mOriginalBug;
+
+        AddAudioToBugReportAsyncTask(
+                Context context, Config config, MetaBugReport bug, File audioFile) {
+            mContext = context;
+            mConfig = config;
+            mOriginalBug = bug;
+            mAudioFile = audioFile;
+        }
+
+        @Override
+        protected Void doInBackground(Void... voids) {
+            String audioFileName = FileUtils.getAudioFileName(
+                    MetaBugReport.toBugReportTimestamp(new Date()), mOriginalBug);
+            MetaBugReport bug = BugStorageUtils.update(mContext,
+                    mOriginalBug.toBuilder().setAudioFileName(audioFileName).build());
+            try (OutputStream out = BugStorageUtils.openAudioMessageFileToWrite(mContext, bug);
+                 InputStream input = new FileInputStream(mAudioFile)) {
+                ByteStreams.copy(input, out);
+            } catch (IOException e) {
+                BugStorageUtils.setBugReportStatus(mContext, bug,
+                        com.android.car.bugreport.Status.STATUS_WRITE_FAILED,
+                        "Failed to write audio to bug report");
+                Log.e(TAG, "Failed to write audio to bug report", e);
+                return null;
+            }
+            if (mConfig.getAutoUpload()) {
+                BugStorageUtils.setBugReportStatus(mContext, bug,
+                        com.android.car.bugreport.Status.STATUS_UPLOAD_PENDING, "");
+            } else {
+                BugStorageUtils.setBugReportStatus(mContext, bug,
+                        com.android.car.bugreport.Status.STATUS_PENDING_USER_ACTION, "");
+                BugReportService.showBugReportFinishedNotification(mContext, bug);
+            }
+            mAudioFile.delete();
+            return null;
+        }
+    }
+}
diff --git a/tests/BugReportApp/src/com/android/car/bugreport/BugReportInfoActivity.java b/tests/BugReportApp/src/com/android/car/bugreport/BugReportInfoActivity.java
new file mode 100644
index 0000000..71c3aa8
--- /dev/null
+++ b/tests/BugReportApp/src/com/android/car/bugreport/BugReportInfoActivity.java
@@ -0,0 +1,389 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.car.bugreport;
+
+import static com.android.car.bugreport.PackageUtils.getPackageVersion;
+
+import android.app.Activity;
+import android.app.NotificationManager;
+import android.content.ContentResolver;
+import android.content.Intent;
+import android.content.res.AssetFileDescriptor;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.UserHandle;
+import android.provider.DocumentsContract;
+import android.util.Log;
+import android.view.View;
+import android.widget.TextView;
+
+import androidx.recyclerview.widget.DividerItemDecoration;
+import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.google.common.base.Preconditions;
+import com.google.common.base.Strings;
+import com.google.common.io.ByteStreams;
+
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
+import java.util.zip.ZipOutputStream;
+
+/**
+ * Provides an activity that provides information on the bugreports that are filed.
+ */
+public class BugReportInfoActivity extends Activity {
+    public static final String TAG = BugReportInfoActivity.class.getSimpleName();
+
+    /** Used for moving bug reports to a new location (e.g. USB drive). */
+    private static final int SELECT_DIRECTORY_REQUEST_CODE = 1;
+
+    /** Used to start {@link BugReportActivity} to add audio message. */
+    private static final int ADD_AUDIO_MESSAGE_REQUEST_CODE = 2;
+
+    private RecyclerView mRecyclerView;
+    private BugInfoAdapter mBugInfoAdapter;
+    private RecyclerView.LayoutManager mLayoutManager;
+    private NotificationManager mNotificationManager;
+    private MetaBugReport mLastSelectedBugReport;
+    private BugInfoAdapter.BugInfoViewHolder mLastSelectedBugInfoViewHolder;
+    private BugStorageObserver mBugStorageObserver;
+    private Config mConfig;
+    private boolean mAudioRecordingStarted;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        Preconditions.checkState(Config.isBugReportEnabled(), "BugReport is disabled.");
+
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.bug_report_info_activity);
+
+        mNotificationManager = getSystemService(NotificationManager.class);
+
+        mRecyclerView = findViewById(R.id.rv_bug_report_info);
+        mRecyclerView.setHasFixedSize(true);
+        // use a linear layout manager
+        mLayoutManager = new LinearLayoutManager(this);
+        mRecyclerView.setLayoutManager(mLayoutManager);
+        mRecyclerView.addItemDecoration(new DividerItemDecoration(mRecyclerView.getContext(),
+                DividerItemDecoration.VERTICAL));
+
+        mConfig = new Config();
+        mConfig.start();
+
+        mBugInfoAdapter = new BugInfoAdapter(this::onBugReportItemClicked, mConfig);
+        mRecyclerView.setAdapter(mBugInfoAdapter);
+
+        mBugStorageObserver = new BugStorageObserver(this, new Handler());
+
+        findViewById(R.id.quit_button).setOnClickListener(this::onQuitButtonClick);
+        findViewById(R.id.start_bug_report_button).setOnClickListener(
+                this::onStartBugReportButtonClick);
+        ((TextView) findViewById(R.id.version_text_view)).setText(
+                String.format("v%s", getPackageVersion(this)));
+
+        cancelBugReportFinishedNotification();
+    }
+
+    @Override
+    protected void onStart() {
+        super.onStart();
+        new BugReportsLoaderAsyncTask(this).execute();
+        // As BugStorageProvider is running under user0, we register using USER_ALL.
+        getContentResolver().registerContentObserver(BugStorageProvider.BUGREPORT_CONTENT_URI, true,
+                mBugStorageObserver, UserHandle.USER_ALL);
+    }
+
+    @Override
+    protected void onStop() {
+        super.onStop();
+        getContentResolver().unregisterContentObserver(mBugStorageObserver);
+    }
+
+    /**
+     * Dismisses {@link BugReportService#BUGREPORT_FINISHED_NOTIF_ID}, otherwise the notification
+     * will stay there forever if this activity opened through the App Launcher.
+     */
+    private void cancelBugReportFinishedNotification() {
+        mNotificationManager.cancel(BugReportService.BUGREPORT_FINISHED_NOTIF_ID);
+    }
+
+    private void onBugReportItemClicked(
+            int buttonType, MetaBugReport bugReport, BugInfoAdapter.BugInfoViewHolder holder) {
+        if (buttonType == BugInfoAdapter.BUTTON_TYPE_UPLOAD) {
+            Log.i(TAG, "Uploading " + bugReport.getTimestamp());
+            BugStorageUtils.setBugReportStatus(this, bugReport, Status.STATUS_UPLOAD_PENDING, "");
+            // Refresh the UI to reflect the new status.
+            new BugReportsLoaderAsyncTask(this).execute();
+        } else if (buttonType == BugInfoAdapter.BUTTON_TYPE_MOVE) {
+            Log.i(TAG, "Moving " + bugReport.getTimestamp());
+            mLastSelectedBugReport = bugReport;
+            mLastSelectedBugInfoViewHolder = holder;
+            startActivityForResult(new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE),
+                    SELECT_DIRECTORY_REQUEST_CODE);
+        } else if (buttonType == BugInfoAdapter.BUTTON_TYPE_ADD_AUDIO) {
+            // Check mAudioRecordingStarted to prevent double click to BUTTON_TYPE_ADD_AUDIO.
+            if (!mAudioRecordingStarted) {
+                mAudioRecordingStarted = true;
+                startActivityForResult(BugReportActivity.buildAddAudioIntent(this, bugReport),
+                        ADD_AUDIO_MESSAGE_REQUEST_CODE);
+            }
+        } else {
+            throw new IllegalStateException("unreachable");
+        }
+    }
+
+    @Override
+    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+        super.onActivityResult(requestCode, resultCode, data);
+        if (requestCode == SELECT_DIRECTORY_REQUEST_CODE && resultCode == RESULT_OK) {
+            int takeFlags =
+                    data.getFlags() & (Intent.FLAG_GRANT_READ_URI_PERMISSION
+                            | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+            Uri destDirUri = data.getData();
+            getContentResolver().takePersistableUriPermission(destDirUri, takeFlags);
+            if (mLastSelectedBugReport == null || mLastSelectedBugInfoViewHolder == null) {
+                Log.w(TAG, "No bug report is selected.");
+                return;
+            }
+            MetaBugReport updatedBugReport = BugStorageUtils.setBugReportStatus(this,
+                    mLastSelectedBugReport, Status.STATUS_MOVE_IN_PROGRESS, "");
+            mBugInfoAdapter.updateBugReportInDataSet(
+                    updatedBugReport, mLastSelectedBugInfoViewHolder.getAdapterPosition());
+            new AsyncMoveFilesTask(
+                this,
+                    mBugInfoAdapter,
+                    updatedBugReport,
+                    mLastSelectedBugInfoViewHolder,
+                    destDirUri).execute();
+        }
+    }
+
+    private void onQuitButtonClick(View view) {
+        finish();
+    }
+
+    private void onStartBugReportButtonClick(View view) {
+        Intent intent = new Intent(this, BugReportActivity.class);
+        // Clear top is needed, otherwise multiple BugReportActivity-ies get opened and
+        // MediaRecorder crashes.
+        intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
+        startActivity(intent);
+    }
+
+    /**
+     * Print the Provider's state into the given stream. This gets invoked if
+     * you run "adb shell dumpsys activity BugReportInfoActivity".
+     *
+     * @param prefix Desired prefix to prepend at each line of output.
+     * @param fd The raw file descriptor that the dump is being sent to.
+     * @param writer The PrintWriter to which you should dump your state.  This will be
+     * closed for you after you return.
+     * @param args additional arguments to the dump request.
+     */
+    public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
+        super.dump(prefix, fd, writer, args);
+        mConfig.dump(prefix, writer);
+    }
+
+    /**
+     * Moves bugreport zip to USB drive and updates RecyclerView.
+     *
+     * <p>It merges bugreport zip file and audio file into one final zip file and moves it.
+     */
+    private static final class AsyncMoveFilesTask extends AsyncTask<Void, Void, MetaBugReport> {
+        private final BugReportInfoActivity mActivity;
+        private final MetaBugReport mBugReport;
+        private final Uri mDestinationDirUri;
+        /** RecyclerView.Adapter that contains all the bug reports. */
+        private final BugInfoAdapter mBugInfoAdapter;
+        /** ViewHolder for {@link #mBugReport}. */
+        private final BugInfoAdapter.BugInfoViewHolder mBugViewHolder;
+        private final ContentResolver mResolver;
+
+        AsyncMoveFilesTask(BugReportInfoActivity activity, BugInfoAdapter bugInfoAdapter,
+                MetaBugReport bugReport, BugInfoAdapter.BugInfoViewHolder holder,
+                Uri destinationDir) {
+            mActivity = activity;
+            mBugInfoAdapter = bugInfoAdapter;
+            mBugReport = bugReport;
+            mBugViewHolder = holder;
+            mDestinationDirUri = destinationDir;
+            mResolver = mActivity.getContentResolver();
+        }
+
+        /** Moves the bugreport to the USB drive and returns the updated {@link MetaBugReport}. */
+        @Override
+        protected MetaBugReport doInBackground(Void... params) {
+            try {
+                return copyFilesToUsb();
+            } catch (IOException e) {
+                Log.e(TAG, "Failed to copy bugreport "
+                        + mBugReport.getTimestamp() + " to USB", e);
+                return BugStorageUtils.setBugReportStatus(
+                    mActivity, mBugReport,
+                    com.android.car.bugreport.Status.STATUS_MOVE_FAILED, e);
+            }
+        }
+
+        private MetaBugReport copyFilesToUsb() throws IOException {
+            String documentId = DocumentsContract.getTreeDocumentId(mDestinationDirUri);
+            Uri parentDocumentUri =
+                    DocumentsContract.buildDocumentUriUsingTree(mDestinationDirUri, documentId);
+            if (!Strings.isNullOrEmpty(mBugReport.getFilePath())) {
+                // There are still old bugreports with deprecated filePath.
+                Uri sourceUri = BugStorageProvider.buildUriWithSegment(
+                        mBugReport.getId(), BugStorageProvider.URL_SEGMENT_OPEN_FILE);
+                copyFileToUsb(
+                        new File(mBugReport.getFilePath()).getName(), sourceUri, parentDocumentUri);
+            } else {
+                mergeFilesAndCopyToUsb(parentDocumentUri);
+            }
+            Log.d(TAG, "Deleting local bug report files.");
+            BugStorageUtils.deleteBugReportFiles(mActivity, mBugReport.getId());
+            return BugStorageUtils.setBugReportStatus(mActivity, mBugReport,
+                    com.android.car.bugreport.Status.STATUS_MOVE_SUCCESSFUL,
+                    "Moved to: " + mDestinationDirUri.getPath());
+        }
+
+        private void mergeFilesAndCopyToUsb(Uri parentDocumentUri) throws IOException {
+            Uri sourceBugReport = BugStorageProvider.buildUriWithSegment(
+                    mBugReport.getId(), BugStorageProvider.URL_SEGMENT_OPEN_BUGREPORT_FILE);
+            Uri sourceAudio = BugStorageProvider.buildUriWithSegment(
+                    mBugReport.getId(), BugStorageProvider.URL_SEGMENT_OPEN_AUDIO_FILE);
+            String mimeType = mResolver.getType(sourceBugReport); // It's a zip file.
+            Uri newFileUri = DocumentsContract.createDocument(
+                    mResolver, parentDocumentUri, mimeType, mBugReport.getBugReportFileName());
+            if (newFileUri == null) {
+                throw new IOException(
+                        "Unable to create a file " + mBugReport.getBugReportFileName() + " in USB");
+            }
+            try (InputStream bugReportInput = mResolver.openInputStream(sourceBugReport);
+                 AssetFileDescriptor fd = mResolver.openAssetFileDescriptor(newFileUri, "w");
+                 OutputStream outputStream = fd.createOutputStream();
+                 ZipOutputStream zipOutStream =
+                         new ZipOutputStream(new BufferedOutputStream(outputStream))) {
+                // Extract bugreport zip file to the final zip file in USB drive.
+                ZipInputStream zipInStream = new ZipInputStream(bugReportInput);
+                ZipEntry entry;
+                while ((entry = zipInStream.getNextEntry()) != null) {
+                    ZipUtils.writeInputStreamToZipStream(
+                            entry.getName(), zipInStream, zipOutStream);
+                }
+                // Add audio file to the final zip file.
+                if (!Strings.isNullOrEmpty(mBugReport.getAudioFileName())) {
+                    try (InputStream audioInput = mResolver.openInputStream(sourceAudio)) {
+                        ZipUtils.writeInputStreamToZipStream(
+                                mBugReport.getAudioFileName(), audioInput, zipOutStream);
+                    }
+                }
+            }
+            try (AssetFileDescriptor fd = mResolver.openAssetFileDescriptor(newFileUri, "w")) {
+                // Force sync the written data from memory to the disk.
+                fd.getFileDescriptor().sync();
+            }
+            Log.d(TAG, "Writing to " + newFileUri + " finished");
+        }
+
+        private void copyFileToUsb(String filename, Uri sourceUri, Uri parentDocumentUri)
+                throws IOException {
+            String mimeType = mResolver.getType(sourceUri);
+            Uri newFileUri = DocumentsContract.createDocument(
+                    mResolver, parentDocumentUri, mimeType, filename);
+            if (newFileUri == null) {
+                throw new IOException("Unable to create a file " + filename + " in USB");
+            }
+            try (InputStream input = mResolver.openInputStream(sourceUri);
+                 AssetFileDescriptor fd = mResolver.openAssetFileDescriptor(newFileUri, "w")) {
+                OutputStream output = fd.createOutputStream();
+                ByteStreams.copy(input, output);
+                // Force sync the written data from memory to the disk.
+                fd.getFileDescriptor().sync();
+            }
+        }
+
+        @Override
+        protected void onPostExecute(MetaBugReport updatedBugReport) {
+            // Refresh the UI to reflect the new status.
+            mBugInfoAdapter.updateBugReportInDataSet(
+                    updatedBugReport, mBugViewHolder.getAdapterPosition());
+        }
+    }
+
+    /** Asynchronously loads bugreports from {@link BugStorageProvider}. */
+    private static final class BugReportsLoaderAsyncTask extends
+            AsyncTask<Void, Void, List<MetaBugReport>> {
+        private final WeakReference<BugReportInfoActivity> mBugReportInfoActivityWeakReference;
+
+        BugReportsLoaderAsyncTask(BugReportInfoActivity activity) {
+            mBugReportInfoActivityWeakReference = new WeakReference<>(activity);
+        }
+
+        @Override
+        protected List<MetaBugReport> doInBackground(Void... voids) {
+            BugReportInfoActivity activity = mBugReportInfoActivityWeakReference.get();
+            if (activity == null) {
+                Log.w(TAG, "Activity is gone, cancelling BugReportsLoaderAsyncTask.");
+                return new ArrayList<>();
+            }
+            return BugStorageUtils.getAllBugReportsDescending(activity);
+        }
+
+        @Override
+        protected void onPostExecute(List<MetaBugReport> result) {
+            BugReportInfoActivity activity = mBugReportInfoActivityWeakReference.get();
+            if (activity == null) {
+                Log.w(TAG, "Activity is gone, cancelling onPostExecute.");
+                return;
+            }
+            activity.mBugInfoAdapter.setDataset(result);
+        }
+    }
+
+    /** Observer for {@link BugStorageProvider}. */
+    private static class BugStorageObserver extends ContentObserver {
+        private final BugReportInfoActivity mInfoActivity;
+
+        /**
+         * Creates a content observer.
+         *
+         * @param activity A {@link BugReportInfoActivity} instance.
+         * @param handler The handler to run {@link #onChange} on, or null if none.
+         */
+        BugStorageObserver(BugReportInfoActivity activity, Handler handler) {
+            super(handler);
+            mInfoActivity = activity;
+        }
+
+        @Override
+        public void onChange(boolean selfChange) {
+            new BugReportsLoaderAsyncTask(mInfoActivity).execute();
+        }
+    }
+}
diff --git a/tests/BugReportApp/src/com/android/car/bugreport/BugReportService.java b/tests/BugReportApp/src/com/android/car/bugreport/BugReportService.java
new file mode 100644
index 0000000..6fe6a14
--- /dev/null
+++ b/tests/BugReportApp/src/com/android/car/bugreport/BugReportService.java
@@ -0,0 +1,601 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.car.bugreport;
+
+import static android.car.CarBugreportManager.CarBugreportManagerCallback.CAR_BUGREPORT_DUMPSTATE_CONNECTION_FAILED;
+import static android.car.CarBugreportManager.CarBugreportManagerCallback.CAR_BUGREPORT_DUMPSTATE_FAILED;
+import static android.car.CarBugreportManager.CarBugreportManagerCallback.CAR_BUGREPORT_SERVICE_NOT_AVAILABLE;
+
+import static com.android.car.bugreport.PackageUtils.getPackageVersion;
+
+import android.annotation.FloatRange;
+import android.annotation.StringRes;
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.app.Service;
+import android.car.Car;
+import android.car.CarBugreportManager;
+import android.car.CarNotConnectedException;
+import android.content.Context;
+import android.content.Intent;
+import android.media.AudioManager;
+import android.media.Ringtone;
+import android.media.RingtoneManager;
+import android.net.Uri;
+import android.os.Binder;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.ParcelFileDescriptor;
+import android.util.Log;
+import android.widget.Toast;
+
+import com.google.common.base.Preconditions;
+import com.google.common.io.ByteStreams;
+import com.google.common.util.concurrent.AtomicDouble;
+
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.zip.ZipOutputStream;
+
+/**
+ * Service that captures screenshot and bug report using dumpstate and bluetooth snoop logs.
+ *
+ * <p>After collecting all the logs it sets the {@link MetaBugReport} status to
+ * {@link Status#STATUS_AUDIO_PENDING} or {@link Status#STATUS_PENDING_USER_ACTION} depending
+ * on {@link MetaBugReport#getType}.
+ *
+ * <p>If the service is started with action {@link #ACTION_START_SILENT}, it will start
+ * bugreporting without showing dialog and recording audio message, see
+ * {@link MetaBugReport#TYPE_SILENT}.
+ */
+public class BugReportService extends Service {
+    private static final String TAG = BugReportService.class.getSimpleName();
+
+    /**
+     * Extra data from intent - current bug report.
+     */
+    static final String EXTRA_META_BUG_REPORT = "meta_bug_report";
+
+    /** Starts silent (no audio message recording) bugreporting. */
+    private static final String ACTION_START_SILENT =
+            "com.android.car.bugreport.action.START_SILENT";
+
+    // Wait a short time before starting to capture the bugreport and the screen, so that
+    // bugreport activity can detach from the view tree.
+    // It is ugly to have a timeout, but it is ok here because such a delay should not really
+    // cause bugreport to be tainted with so many other events. If in the future we want to change
+    // this, the best option is probably to wait for onDetach events from view tree.
+    private static final int ACTIVITY_FINISH_DELAY_MILLIS = 1000;
+
+    /** Stop the service only after some delay, to allow toasts to show on the screen. */
+    private static final int STOP_SERVICE_DELAY_MILLIS = 1000;
+
+    /**
+     * Wait a short time before showing "bugreport started" toast message, because the service
+     * will take a screenshot of the screen.
+     */
+    private static final int BUGREPORT_STARTED_TOAST_DELAY_MILLIS = 2000;
+
+    private static final String BT_SNOOP_LOG_LOCATION = "/data/misc/bluetooth/logs/btsnoop_hci.log";
+    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+    /** Notifications on this channel will silently appear in notification bar. */
+    private static final String PROGRESS_CHANNEL_ID = "BUGREPORT_PROGRESS_CHANNEL";
+
+    /** Notifications on this channel will pop-up. */
+    private static final String STATUS_CHANNEL_ID = "BUGREPORT_STATUS_CHANNEL";
+
+    /** Persistent notification is shown when bugreport is in progress or waiting for audio. */
+    private static final int BUGREPORT_IN_PROGRESS_NOTIF_ID = 1;
+
+    /** Dismissible notification is shown when bugreport is collected. */
+    static final int BUGREPORT_FINISHED_NOTIF_ID = 2;
+
+    private static final String OUTPUT_ZIP_FILE = "output_file.zip";
+    private static final String EXTRA_OUTPUT_ZIP_FILE = "extra_output_file.zip";
+
+    private static final String MESSAGE_FAILURE_DUMPSTATE = "Failed to grab dumpstate";
+    private static final String MESSAGE_FAILURE_ZIP = "Failed to zip files";
+
+    private static final int PROGRESS_HANDLER_EVENT_PROGRESS = 1;
+    private static final String PROGRESS_HANDLER_DATA_PROGRESS = "progress";
+
+    static final float MAX_PROGRESS_VALUE = 100f;
+
+    /** Binder given to clients. */
+    private final IBinder mBinder = new ServiceBinder();
+
+    /** True if {@link BugReportService} is already collecting bugreport, including zipping. */
+    private final AtomicBoolean mIsCollectingBugReport = new AtomicBoolean(false);
+    private final AtomicDouble mBugReportProgress = new AtomicDouble(0);
+
+    private MetaBugReport mMetaBugReport;
+    private NotificationManager mNotificationManager;
+    private ScheduledExecutorService mSingleThreadExecutor;
+    private BugReportProgressListener mBugReportProgressListener;
+    private Car mCar;
+    private CarBugreportManager mBugreportManager;
+    private CarBugreportManager.CarBugreportManagerCallback mCallback;
+    private Config mConfig;
+
+    /** A handler on the main thread. */
+    private Handler mHandler;
+    /**
+     * A handler to the main thread to show toast messages, it will be cleared when the service
+     * finishes. We need to clear it otherwise when bugreport fails, it will show "bugreport start"
+     * toast, which will confuse users.
+     */
+    private Handler mHandlerStartedToast;
+
+    /** A listener that's notified when bugreport progress changes. */
+    interface BugReportProgressListener {
+        /**
+         * Called when bug report progress changes.
+         *
+         * @param progress - a bug report progress in [0.0, 100.0].
+         */
+        void onProgress(float progress);
+    }
+
+    /** Client binder. */
+    public class ServiceBinder extends Binder {
+        BugReportService getService() {
+            // Return this instance of LocalService so clients can call public methods
+            return BugReportService.this;
+        }
+    }
+
+    /** A handler on the main thread. */
+    private class BugReportHandler extends Handler {
+        @Override
+        public void handleMessage(Message message) {
+            switch (message.what) {
+                case PROGRESS_HANDLER_EVENT_PROGRESS:
+                    if (mBugReportProgressListener != null) {
+                        float progress = message.getData().getFloat(PROGRESS_HANDLER_DATA_PROGRESS);
+                        mBugReportProgressListener.onProgress(progress);
+                    }
+                    showProgressNotification();
+                    break;
+                default:
+                    Log.d(TAG, "Unknown event " + message.what + ", ignoring.");
+            }
+        }
+    }
+
+    @Override
+    public void onCreate() {
+        Preconditions.checkState(Config.isBugReportEnabled(), "BugReport is disabled.");
+
+        mNotificationManager = getSystemService(NotificationManager.class);
+        mNotificationManager.createNotificationChannel(new NotificationChannel(
+                PROGRESS_CHANNEL_ID,
+                getString(R.string.notification_bugreport_channel_name),
+                NotificationManager.IMPORTANCE_DEFAULT));
+        mNotificationManager.createNotificationChannel(new NotificationChannel(
+                STATUS_CHANNEL_ID,
+                getString(R.string.notification_bugreport_channel_name),
+                NotificationManager.IMPORTANCE_HIGH));
+        mSingleThreadExecutor = Executors.newSingleThreadScheduledExecutor();
+        mHandler = new BugReportHandler();
+        mHandlerStartedToast = new Handler();
+        mConfig = new Config();
+        mConfig.start();
+    }
+
+    @Override
+    public void onDestroy() {
+        if (DEBUG) {
+            Log.d(TAG, "Service destroyed");
+        }
+        disconnectFromCarService();
+    }
+
+    @Override
+    public int onStartCommand(final Intent intent, int flags, int startId) {
+        if (mIsCollectingBugReport.getAndSet(true)) {
+            Log.w(TAG, "bug report is already being collected, ignoring");
+            Toast.makeText(this, R.string.toast_bug_report_in_progress, Toast.LENGTH_SHORT).show();
+            return START_NOT_STICKY;
+        }
+
+        Log.i(TAG, String.format("Will start collecting bug report, version=%s",
+                getPackageVersion(this)));
+
+        if (ACTION_START_SILENT.equals(intent.getAction())) {
+            Log.i(TAG, "Starting a silent bugreport.");
+            mMetaBugReport = BugReportActivity.createBugReport(this, MetaBugReport.TYPE_SILENT);
+        } else {
+            Bundle extras = intent.getExtras();
+            mMetaBugReport = extras.getParcelable(EXTRA_META_BUG_REPORT);
+        }
+
+        mBugReportProgress.set(0);
+
+        startForeground(BUGREPORT_IN_PROGRESS_NOTIF_ID, buildProgressNotification());
+        showProgressNotification();
+
+        collectBugReport();
+
+        // Show a short lived "bugreport started" toast message after a short delay.
+        mHandlerStartedToast.postDelayed(() -> {
+            Toast.makeText(this,
+                    getText(R.string.toast_bug_report_started), Toast.LENGTH_LONG).show();
+        }, BUGREPORT_STARTED_TOAST_DELAY_MILLIS);
+
+        // If the service process gets killed due to heavy memory pressure, do not restart.
+        return START_NOT_STICKY;
+    }
+
+    private void onCarLifecycleChanged(Car car, boolean ready) {
+        // not ready - car service is crashed or is restarting.
+        if (!ready) {
+            mBugreportManager = null;
+            mCar = null;
+
+            // NOTE: dumpstate still might be running, but we can't kill it or reconnect to it
+            //       so we ignore it.
+            handleBugReportManagerError(CAR_BUGREPORT_SERVICE_NOT_AVAILABLE);
+            return;
+        }
+        try {
+            mBugreportManager = (CarBugreportManager) car.getCarManager(Car.CAR_BUGREPORT_SERVICE);
+        } catch (CarNotConnectedException | NoClassDefFoundError e) {
+            throw new IllegalStateException("Failed to get CarBugreportManager.", e);
+        }
+    }
+
+    /** Shows an updated progress notification. */
+    private void showProgressNotification() {
+        if (isCollectingBugReport()) {
+            mNotificationManager.notify(
+                    BUGREPORT_IN_PROGRESS_NOTIF_ID, buildProgressNotification());
+        }
+    }
+
+    private Notification buildProgressNotification() {
+        Intent intent = new Intent(getApplicationContext(), BugReportInfoActivity.class);
+        PendingIntent startBugReportInfoActivity =
+                PendingIntent.getActivity(getApplicationContext(), 0, intent, 0);
+        return new Notification.Builder(this, PROGRESS_CHANNEL_ID)
+                .setContentTitle(getText(R.string.notification_bugreport_in_progress))
+                .setContentText(mMetaBugReport.getTitle())
+                .setSubText(String.format("%.1f%%", mBugReportProgress.get()))
+                .setSmallIcon(R.drawable.download_animation)
+                .setCategory(Notification.CATEGORY_STATUS)
+                .setOngoing(true)
+                .setProgress((int) MAX_PROGRESS_VALUE, (int) mBugReportProgress.get(), false)
+                .setContentIntent(startBugReportInfoActivity)
+                .build();
+    }
+
+    /** Returns true if bugreporting is in progress. */
+    public boolean isCollectingBugReport() {
+        return mIsCollectingBugReport.get();
+    }
+
+    /** Returns current bugreport progress. */
+    public float getBugReportProgress() {
+        return (float) mBugReportProgress.get();
+    }
+
+    /** Sets a bugreport progress listener. The listener is called on a main thread. */
+    public void setBugReportProgressListener(BugReportProgressListener listener) {
+        mBugReportProgressListener = listener;
+    }
+
+    /** Removes the bugreport progress listener. */
+    public void removeBugReportProgressListener() {
+        mBugReportProgressListener = null;
+    }
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return mBinder;
+    }
+
+    private void showToast(@StringRes int resId) {
+        // run on ui thread.
+        mHandler.post(() -> Toast.makeText(this, getText(resId), Toast.LENGTH_LONG).show());
+    }
+
+    private void disconnectFromCarService() {
+        if (mCar != null) {
+            mCar.disconnect();
+            mCar = null;
+        }
+        mBugreportManager = null;
+    }
+
+    private void connectToCarServiceSync() {
+        if (mCar == null || !(mCar.isConnected() || mCar.isConnecting())) {
+            mCar = Car.createCar(this, /* handler= */ null,
+                    Car.CAR_WAIT_TIMEOUT_WAIT_FOREVER, this::onCarLifecycleChanged);
+        }
+    }
+
+    private void collectBugReport() {
+        // Connect to the car service before collecting bugreport, because when car service crashes,
+        // BugReportService doesn't automatically reconnect to it.
+        connectToCarServiceSync();
+
+        if (Build.IS_USERDEBUG || Build.IS_ENG) {
+            mSingleThreadExecutor.schedule(
+                    this::grabBtSnoopLog, ACTIVITY_FINISH_DELAY_MILLIS, TimeUnit.MILLISECONDS);
+        }
+        mSingleThreadExecutor.schedule(
+                this::saveBugReport, ACTIVITY_FINISH_DELAY_MILLIS, TimeUnit.MILLISECONDS);
+    }
+
+    private void grabBtSnoopLog() {
+        Log.i(TAG, "Grabbing bt snoop log");
+        File result = FileUtils.getFileWithSuffix(this, mMetaBugReport.getTimestamp(),
+                "-btsnoop.bin.log");
+        File snoopFile = new File(BT_SNOOP_LOG_LOCATION);
+        if (!snoopFile.exists()) {
+            Log.w(TAG, BT_SNOOP_LOG_LOCATION + " not found, skipping");
+            return;
+        }
+        try (FileInputStream input = new FileInputStream(snoopFile);
+             FileOutputStream output = new FileOutputStream(result)) {
+            ByteStreams.copy(input, output);
+        } catch (IOException e) {
+            // this regularly happens when snooplog is not enabled so do not log as an error
+            Log.i(TAG, "Failed to grab bt snooplog, continuing to take bug report.", e);
+        }
+    }
+
+    private void saveBugReport() {
+        Log.i(TAG, "Dumpstate to file");
+        File outputFile = FileUtils.getFile(this, mMetaBugReport.getTimestamp(), OUTPUT_ZIP_FILE);
+        File extraOutputFile = FileUtils.getFile(this, mMetaBugReport.getTimestamp(),
+                EXTRA_OUTPUT_ZIP_FILE);
+        try (ParcelFileDescriptor outFd = ParcelFileDescriptor.open(outputFile,
+                ParcelFileDescriptor.MODE_CREATE | ParcelFileDescriptor.MODE_READ_WRITE);
+             ParcelFileDescriptor extraOutFd = ParcelFileDescriptor.open(extraOutputFile,
+                ParcelFileDescriptor.MODE_CREATE | ParcelFileDescriptor.MODE_READ_WRITE)) {
+            requestBugReport(outFd, extraOutFd);
+        } catch (IOException | RuntimeException e) {
+            Log.e(TAG, "Failed to grab dump state", e);
+            BugStorageUtils.setBugReportStatus(this, mMetaBugReport, Status.STATUS_WRITE_FAILED,
+                    MESSAGE_FAILURE_DUMPSTATE);
+            showToast(R.string.toast_status_dump_state_failed);
+            disconnectFromCarService();
+            mIsCollectingBugReport.set(false);
+        }
+    }
+
+    private void sendProgressEventToHandler(float progress) {
+        Message message = new Message();
+        message.what = PROGRESS_HANDLER_EVENT_PROGRESS;
+        message.getData().putFloat(PROGRESS_HANDLER_DATA_PROGRESS, progress);
+        mHandler.sendMessage(message);
+    }
+
+    private void requestBugReport(ParcelFileDescriptor outFd, ParcelFileDescriptor extraOutFd) {
+        if (DEBUG) {
+            Log.d(TAG, "Requesting a bug report from CarBugReportManager.");
+        }
+        mCallback = new CarBugreportManager.CarBugreportManagerCallback() {
+            @Override
+            public void onError(@CarBugreportErrorCode int errorCode) {
+                Log.e(TAG, "CarBugreportManager failed: " + errorCode);
+                disconnectFromCarService();
+                handleBugReportManagerError(errorCode);
+            }
+
+            @Override
+            public void onProgress(@FloatRange(from = 0f, to = MAX_PROGRESS_VALUE) float progress) {
+                mBugReportProgress.set(progress);
+                sendProgressEventToHandler(progress);
+            }
+
+            @Override
+            public void onFinished() {
+                Log.d(TAG, "CarBugreportManager finished");
+                disconnectFromCarService();
+                mBugReportProgress.set(MAX_PROGRESS_VALUE);
+                sendProgressEventToHandler(MAX_PROGRESS_VALUE);
+                mSingleThreadExecutor.submit(BugReportService.this::zipDirectoryAndUpdateStatus);
+            }
+        };
+        if (mBugreportManager == null) {
+            mHandler.post(() -> Toast.makeText(this,
+                    "Car service is not ready", Toast.LENGTH_LONG).show());
+            Log.e(TAG, "CarBugReportManager is not ready");
+            return;
+        }
+        mBugreportManager.requestBugreport(outFd, extraOutFd, mCallback);
+    }
+
+    private void handleBugReportManagerError(
+            @CarBugreportManager.CarBugreportManagerCallback.CarBugreportErrorCode int errorCode) {
+        if (mMetaBugReport == null) {
+            Log.w(TAG, "No bugreport is running");
+            mIsCollectingBugReport.set(false);
+            return;
+        }
+        // We let the UI know that bug reporting is finished, because the next step is to
+        // zip everything and upload.
+        mBugReportProgress.set(MAX_PROGRESS_VALUE);
+        sendProgressEventToHandler(MAX_PROGRESS_VALUE);
+        showToast(R.string.toast_status_failed);
+        BugStorageUtils.setBugReportStatus(
+                BugReportService.this, mMetaBugReport,
+                Status.STATUS_WRITE_FAILED, getBugReportFailureStatusMessage(errorCode));
+        mHandler.postDelayed(() -> {
+            mNotificationManager.cancel(BUGREPORT_IN_PROGRESS_NOTIF_ID);
+            stopForeground(true);
+        }, STOP_SERVICE_DELAY_MILLIS);
+        mHandlerStartedToast.removeCallbacksAndMessages(null);
+        mMetaBugReport = null;
+        mIsCollectingBugReport.set(false);
+    }
+
+    private static String getBugReportFailureStatusMessage(
+            @CarBugreportManager.CarBugreportManagerCallback.CarBugreportErrorCode int errorCode) {
+        switch (errorCode) {
+            case CAR_BUGREPORT_DUMPSTATE_CONNECTION_FAILED:
+            case CAR_BUGREPORT_DUMPSTATE_FAILED:
+                return "Failed to connect to dumpstate. Retry again after a minute.";
+            case CAR_BUGREPORT_SERVICE_NOT_AVAILABLE:
+                return "Car service is not available. Retry again.";
+            default:
+                return "Car service bugreport collection failed: " + errorCode;
+        }
+    }
+
+    /**
+     * Shows a clickable bugreport finished notification. When clicked it opens
+     * {@link BugReportInfoActivity}.
+     */
+    static void showBugReportFinishedNotification(Context context, MetaBugReport bug) {
+        Intent intent = new Intent(context, BugReportInfoActivity.class);
+        PendingIntent startBugReportInfoActivity =
+                PendingIntent.getActivity(context, 0, intent, 0);
+        Notification notification = new Notification
+                .Builder(context, STATUS_CHANNEL_ID)
+                .setContentTitle(context.getText(R.string.notification_bugreport_finished_title))
+                .setContentText(bug.getTitle())
+                .setCategory(Notification.CATEGORY_STATUS)
+                .setSmallIcon(R.drawable.ic_upload)
+                .setContentIntent(startBugReportInfoActivity)
+                .build();
+        context.getSystemService(NotificationManager.class)
+                .notify(BUGREPORT_FINISHED_NOTIF_ID, notification);
+    }
+
+    /**
+     * Zips the temp directory, writes to the system user's {@link FileUtils#getPendingDir} and
+     * updates the bug report status.
+     *
+     * <p>For {@link MetaBugReport#TYPE_INTERACTIVE}: Sets status to either STATUS_UPLOAD_PENDING or
+     * STATUS_PENDING_USER_ACTION and shows a regular notification.
+     *
+     * <p>For {@link MetaBugReport#TYPE_SILENT}: Sets status to STATUS_AUDIO_PENDING and shows
+     * a dialog to record audio message.
+     */
+    private void zipDirectoryAndUpdateStatus() {
+        try {
+            // All the generated zip files, images and audio messages are located in this dir.
+            // This is located under the current user.
+            String bugreportFileName = FileUtils.getZipFileName(mMetaBugReport);
+            Log.d(TAG, "Zipping bugreport into " + bugreportFileName);
+            mMetaBugReport = BugStorageUtils.update(this,
+                    mMetaBugReport.toBuilder().setBugReportFileName(bugreportFileName).build());
+            File bugReportTempDir = FileUtils.createTempDir(this, mMetaBugReport.getTimestamp());
+            zipDirectoryToOutputStream(bugReportTempDir,
+                    BugStorageUtils.openBugReportFileToWrite(this, mMetaBugReport));
+        } catch (IOException e) {
+            Log.e(TAG, "Failed to zip files", e);
+            BugStorageUtils.setBugReportStatus(this, mMetaBugReport, Status.STATUS_WRITE_FAILED,
+                    MESSAGE_FAILURE_ZIP);
+            showToast(R.string.toast_status_failed);
+            return;
+        }
+        if (mMetaBugReport.getType() == MetaBugReport.TYPE_SILENT) {
+            BugStorageUtils.setBugReportStatus(BugReportService.this,
+                    mMetaBugReport, Status.STATUS_AUDIO_PENDING, /* message= */ "");
+            playNotificationSound();
+            startActivity(BugReportActivity.buildAddAudioIntent(this, mMetaBugReport));
+        } else {
+            // NOTE: If bugreport type is INTERACTIVE, it will already contain an audio message.
+            Status status = mConfig.getAutoUpload()
+                    ? Status.STATUS_UPLOAD_PENDING : Status.STATUS_PENDING_USER_ACTION;
+            BugStorageUtils.setBugReportStatus(BugReportService.this,
+                    mMetaBugReport, status, /* message= */ "");
+            showBugReportFinishedNotification(this, mMetaBugReport);
+        }
+        mHandler.post(() -> {
+            mNotificationManager.cancel(BUGREPORT_IN_PROGRESS_NOTIF_ID);
+            stopForeground(true);
+        });
+        mHandlerStartedToast.removeCallbacksAndMessages(null);
+        mMetaBugReport = null;
+        mIsCollectingBugReport.set(false);
+    }
+
+    private void playNotificationSound() {
+        Uri notification = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
+        Ringtone ringtone = RingtoneManager.getRingtone(getApplicationContext(), notification);
+        if (ringtone == null) {
+            Log.w(TAG, "No notification ringtone found.");
+            return;
+        }
+        float volume = ringtone.getVolume();
+        // Use volume from audio manager, otherwise default ringtone volume can be too loud.
+        AudioManager audioManager = getSystemService(AudioManager.class);
+        if (audioManager != null) {
+            int currentVolume = audioManager.getStreamVolume(AudioManager.STREAM_NOTIFICATION);
+            int maxVolume = audioManager.getStreamMaxVolume(AudioManager.STREAM_NOTIFICATION);
+            volume = (currentVolume + 0.0f) / maxVolume;
+        }
+        Log.v(TAG, "Using volume " + volume);
+        ringtone.setVolume(volume);
+        ringtone.play();
+    }
+
+    /**
+     * Compresses a directory into a zip file. The method is not recursive. Any sub-directory
+     * contained in the main directory and any files contained in the sub-directories will be
+     * skipped.
+     *
+     * @param dirToZip  The path of the directory to zip
+     * @param outStream The output stream to write the zip file to
+     * @throws IOException if the directory does not exist, its files cannot be read, or the output
+     *                     zip file cannot be written.
+     */
+    private void zipDirectoryToOutputStream(File dirToZip, OutputStream outStream)
+            throws IOException {
+        if (!dirToZip.isDirectory()) {
+            throw new IOException("zip directory does not exist");
+        }
+        Log.v(TAG, "zipping directory " + dirToZip.getAbsolutePath());
+
+        File[] listFiles = dirToZip.listFiles();
+        try (ZipOutputStream zipStream = new ZipOutputStream(new BufferedOutputStream(outStream))) {
+            for (File file : listFiles) {
+                if (file.isDirectory()) {
+                    continue;
+                }
+                String filename = file.getName();
+                // only for the zipped output file, we add individual entries to zip file.
+                if (filename.equals(OUTPUT_ZIP_FILE) || filename.equals(EXTRA_OUTPUT_ZIP_FILE)) {
+                    ZipUtils.extractZippedFileToZipStream(file, zipStream);
+                } else {
+                    ZipUtils.addFileToZipStream(file, zipStream);
+                }
+            }
+        } finally {
+            outStream.close();
+        }
+        // Zipping successful, now cleanup the temp dir.
+        FileUtils.deleteDirectory(dirToZip);
+    }
+}
diff --git a/tests/BugReportApp/src/com/android/car/bugreport/BugStorageProvider.java b/tests/BugReportApp/src/com/android/car/bugreport/BugStorageProvider.java
new file mode 100644
index 0000000..1abbdb2
--- /dev/null
+++ b/tests/BugReportApp/src/com/android/car/bugreport/BugStorageProvider.java
@@ -0,0 +1,435 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.car.bugreport;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.StringDef;
+import android.content.ContentProvider;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.UriMatcher;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.net.Uri;
+import android.os.CancellationSignal;
+import android.os.ParcelFileDescriptor;
+import android.util.Log;
+
+import com.google.common.base.Preconditions;
+import com.google.common.base.Strings;
+
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileNotFoundException;
+import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.function.Function;
+
+
+/**
+ * Provides a bug storage interface to save and upload bugreports filed from all users.
+ * In Android Automotive user 0 runs as the system and all the time, while other users won't once
+ * their session ends. This content provider enables bug reports to be uploaded even after
+ * user session ends.
+ *
+ * <p>A bugreport constists of two files: bugreport zip file and audio file. Audio file is added
+ * later through notification. {@link SimpleUploaderAsyncTask} merges two files into one zip file
+ * before uploading.
+ *
+ * <p>All files are stored under system user's {@link FileUtils#getPendingDir}.
+ */
+public class BugStorageProvider extends ContentProvider {
+    private static final String TAG = BugStorageProvider.class.getSimpleName();
+
+    private static final String AUTHORITY = "com.android.car.bugreport";
+    private static final String BUG_REPORTS_TABLE = "bugreports";
+
+    /** Deletes files associated with a bug report. */
+    static final String URL_SEGMENT_DELETE_FILES = "deleteZipFile";
+    /** Destructively deletes a bug report. */
+    static final String URL_SEGMENT_COMPLETE_DELETE = "completeDelete";
+    /** Opens bugreport file of a bug report, uses column {@link #COLUMN_BUGREPORT_FILENAME}. */
+    static final String URL_SEGMENT_OPEN_BUGREPORT_FILE = "openBugReportFile";
+    /** Opens audio file of a bug report, uses column {@link #URL_MATCHED_OPEN_AUDIO_FILE}. */
+    static final String URL_SEGMENT_OPEN_AUDIO_FILE = "openAudioFile";
+    /**
+     * Opens final bugreport zip file, uses column {@link #COLUMN_FILEPATH}.
+     *
+     * <p>NOTE: This is the old way of storing final zipped bugreport. In
+     * {@code BugStorageProvider#AUDIO_VERSION} {@link #COLUMN_FILEPATH} is dropped. But there are
+     * still some devices with this field set.
+     */
+    static final String URL_SEGMENT_OPEN_FILE = "openFile";
+
+    // URL Matcher IDs.
+    private static final int URL_MATCHED_BUG_REPORTS_URI = 1;
+    private static final int URL_MATCHED_BUG_REPORT_ID_URI = 2;
+    private static final int URL_MATCHED_DELETE_FILES = 3;
+    private static final int URL_MATCHED_COMPLETE_DELETE = 4;
+    private static final int URL_MATCHED_OPEN_BUGREPORT_FILE = 5;
+    private static final int URL_MATCHED_OPEN_AUDIO_FILE = 6;
+    private static final int URL_MATCHED_OPEN_FILE = 7;
+
+    @StringDef({
+            URL_SEGMENT_DELETE_FILES,
+            URL_SEGMENT_COMPLETE_DELETE,
+            URL_SEGMENT_OPEN_BUGREPORT_FILE,
+            URL_SEGMENT_OPEN_AUDIO_FILE,
+            URL_SEGMENT_OPEN_FILE,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @interface UriActionSegments {}
+
+    static final Uri BUGREPORT_CONTENT_URI =
+            Uri.parse("content://" + AUTHORITY + "/" + BUG_REPORTS_TABLE);
+
+    /** See {@link MetaBugReport} for column descriptions. */
+    static final String COLUMN_ID = "_ID";
+    static final String COLUMN_USERNAME = "username";
+    static final String COLUMN_TITLE = "title";
+    static final String COLUMN_TIMESTAMP = "timestamp";
+    /** not used anymore */
+    static final String COLUMN_DESCRIPTION = "description";
+    /** not used anymore, but some devices still might have bugreports with this field set. */
+    static final String COLUMN_FILEPATH = "filepath";
+    static final String COLUMN_STATUS = "status";
+    static final String COLUMN_STATUS_MESSAGE = "message";
+    static final String COLUMN_TYPE = "type";
+    static final String COLUMN_BUGREPORT_FILENAME = "bugreport_filename";
+    static final String COLUMN_AUDIO_FILENAME = "audio_filename";
+
+    private DatabaseHelper mDatabaseHelper;
+    private final UriMatcher mUriMatcher;
+    private Config mConfig;
+
+    /**
+     * A helper class to work with sqlite database.
+     */
+    private static class DatabaseHelper extends SQLiteOpenHelper {
+        private static final String TAG = DatabaseHelper.class.getSimpleName();
+
+        private static final String DATABASE_NAME = "bugreport.db";
+
+        /**
+         * All changes in database versions should be recorded here.
+         * 1: Initial version.
+         * 2: Add integer column details_needed.
+         * 3: Add string column audio_filename and bugreport_filename.
+         */
+        private static final int INITIAL_VERSION = 1;
+        private static final int TYPE_VERSION = 2;
+        private static final int AUDIO_VERSION = 3;
+        private static final int DATABASE_VERSION = AUDIO_VERSION;
+
+        private static final String CREATE_TABLE = "CREATE TABLE " + BUG_REPORTS_TABLE + " ("
+                + COLUMN_ID + " INTEGER PRIMARY KEY,"
+                + COLUMN_USERNAME + " TEXT,"
+                + COLUMN_TITLE + " TEXT,"
+                + COLUMN_TIMESTAMP + " TEXT NOT NULL,"
+                + COLUMN_DESCRIPTION + " TEXT NULL,"
+                + COLUMN_FILEPATH + " TEXT DEFAULT NULL,"
+                + COLUMN_STATUS + " INTEGER DEFAULT " + Status.STATUS_WRITE_PENDING.getValue() + ","
+                + COLUMN_STATUS_MESSAGE + " TEXT NULL,"
+                + COLUMN_TYPE + " INTEGER DEFAULT " + MetaBugReport.TYPE_INTERACTIVE + ","
+                + COLUMN_BUGREPORT_FILENAME + " TEXT DEFAULT NULL,"
+                + COLUMN_AUDIO_FILENAME + " TEXT DEFAULT NULL"
+                + ");";
+
+        DatabaseHelper(Context context) {
+            super(context, DATABASE_NAME, null, DATABASE_VERSION);
+        }
+
+        @Override
+        public void onCreate(SQLiteDatabase db) {
+            db.execSQL(CREATE_TABLE);
+        }
+
+        @Override
+        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+            Log.w(TAG, "Upgrading from " + oldVersion + " to " + newVersion);
+            if (oldVersion < TYPE_VERSION) {
+                db.execSQL("ALTER TABLE " + BUG_REPORTS_TABLE + " ADD COLUMN "
+                        + COLUMN_TYPE + " INTEGER DEFAULT " + MetaBugReport.TYPE_INTERACTIVE);
+            }
+            if (oldVersion < AUDIO_VERSION) {
+                db.execSQL("ALTER TABLE " + BUG_REPORTS_TABLE + " ADD COLUMN "
+                        + COLUMN_BUGREPORT_FILENAME + " TEXT DEFAULT NULL");
+                db.execSQL("ALTER TABLE " + BUG_REPORTS_TABLE + " ADD COLUMN "
+                        + COLUMN_AUDIO_FILENAME + " TEXT DEFAULT NULL");
+            }
+        }
+    }
+
+    /**
+     * Builds an {@link Uri} that points to the single bug report and performs an action
+     * defined by given URI segment.
+     */
+    static Uri buildUriWithSegment(int bugReportId, @UriActionSegments String segment) {
+        return Uri.parse("content://" + AUTHORITY + "/" + BUG_REPORTS_TABLE + "/"
+                + segment + "/" + bugReportId);
+    }
+
+    public BugStorageProvider() {
+        mUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
+        mUriMatcher.addURI(AUTHORITY, BUG_REPORTS_TABLE, URL_MATCHED_BUG_REPORTS_URI);
+        mUriMatcher.addURI(AUTHORITY, BUG_REPORTS_TABLE + "/#", URL_MATCHED_BUG_REPORT_ID_URI);
+        mUriMatcher.addURI(
+                AUTHORITY, BUG_REPORTS_TABLE + "/" + URL_SEGMENT_DELETE_FILES + "/#",
+                URL_MATCHED_DELETE_FILES);
+        mUriMatcher.addURI(
+                AUTHORITY, BUG_REPORTS_TABLE + "/" + URL_SEGMENT_COMPLETE_DELETE + "/#",
+                URL_MATCHED_COMPLETE_DELETE);
+        mUriMatcher.addURI(
+                AUTHORITY, BUG_REPORTS_TABLE + "/" + URL_SEGMENT_OPEN_BUGREPORT_FILE + "/#",
+                URL_MATCHED_OPEN_BUGREPORT_FILE);
+        mUriMatcher.addURI(
+                AUTHORITY, BUG_REPORTS_TABLE + "/" + URL_SEGMENT_OPEN_AUDIO_FILE + "/#",
+                URL_MATCHED_OPEN_AUDIO_FILE);
+        mUriMatcher.addURI(
+                AUTHORITY, BUG_REPORTS_TABLE + "/" + URL_SEGMENT_OPEN_FILE + "/#",
+                URL_MATCHED_OPEN_FILE);
+    }
+
+    @Override
+    public boolean onCreate() {
+        Preconditions.checkState(Config.isBugReportEnabled(), "BugReport is disabled.");
+
+        mDatabaseHelper = new DatabaseHelper(getContext());
+        mConfig = new Config();
+        mConfig.start();
+        return true;
+    }
+
+    @Override
+    public Cursor query(
+            @NonNull Uri uri,
+            @Nullable String[] projection,
+            @Nullable String selection,
+            @Nullable String[] selectionArgs,
+            @Nullable String sortOrder) {
+        return query(uri, projection, selection, selectionArgs, sortOrder, null);
+    }
+
+    @Nullable
+    @Override
+    public Cursor query(
+            @NonNull Uri uri,
+            @Nullable String[] projection,
+            @Nullable String selection,
+            @Nullable String[] selectionArgs,
+            @Nullable String sortOrder,
+            @Nullable CancellationSignal cancellationSignal) {
+        String table;
+        switch (mUriMatcher.match(uri)) {
+            // returns the list of bugreports that match the selection criteria.
+            case URL_MATCHED_BUG_REPORTS_URI:
+                table = BUG_REPORTS_TABLE;
+                break;
+            //  returns the bugreport that match the id.
+            case URL_MATCHED_BUG_REPORT_ID_URI:
+                table = BUG_REPORTS_TABLE;
+                if (selection != null || selectionArgs != null) {
+                    throw new IllegalArgumentException("selection is not allowed for "
+                            + URL_MATCHED_BUG_REPORT_ID_URI);
+                }
+                selection = COLUMN_ID + "=?";
+                selectionArgs = new String[]{ uri.getLastPathSegment() };
+                break;
+            default:
+                throw new IllegalArgumentException("Unknown URL " + uri);
+        }
+        SQLiteDatabase db = mDatabaseHelper.getReadableDatabase();
+        Cursor cursor = db.query(false, table, null, selection, selectionArgs, null, null,
+                sortOrder, null, cancellationSignal);
+        cursor.setNotificationUri(getContext().getContentResolver(), uri);
+        return cursor;
+    }
+
+    @Nullable
+    @Override
+    public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
+        String table;
+        if (values == null) {
+            throw new IllegalArgumentException("values cannot be null");
+        }
+        switch (mUriMatcher.match(uri)) {
+            case URL_MATCHED_BUG_REPORTS_URI:
+                table = BUG_REPORTS_TABLE;
+                break;
+            default:
+                throw new IllegalArgumentException("unknown uri" + uri);
+        }
+        SQLiteDatabase db = mDatabaseHelper.getWritableDatabase();
+        long rowId = db.insert(table, null, values);
+        if (rowId > 0) {
+            Uri resultUri = Uri.parse("content://" + AUTHORITY + "/" + table + "/" + rowId);
+            // notify registered content observers
+            getContext().getContentResolver().notifyChange(resultUri, null);
+            return resultUri;
+        }
+        return null;
+    }
+
+    @Nullable
+    @Override
+    public String getType(@NonNull Uri uri) {
+        switch (mUriMatcher.match(uri)) {
+            case URL_MATCHED_OPEN_BUGREPORT_FILE:
+            case URL_MATCHED_OPEN_FILE:
+                return "application/zip";
+            case URL_MATCHED_OPEN_AUDIO_FILE:
+                return "audio/3gpp";
+            default:
+                throw new IllegalArgumentException("unknown uri:" + uri);
+        }
+    }
+
+    @Override
+    public int delete(
+            @NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) {
+        SQLiteDatabase db = mDatabaseHelper.getReadableDatabase();
+        switch (mUriMatcher.match(uri)) {
+            case URL_MATCHED_DELETE_FILES:
+                if (selection != null || selectionArgs != null) {
+                    throw new IllegalArgumentException("selection is not allowed for "
+                            + URL_MATCHED_DELETE_FILES);
+                }
+                if (deleteFilesFor(getBugReportFromUri(uri))) {
+                    getContext().getContentResolver().notifyChange(uri, null);
+                    return 1;
+                }
+                return 0;
+            case URL_MATCHED_COMPLETE_DELETE:
+                if (selection != null || selectionArgs != null) {
+                    throw new IllegalArgumentException("selection is not allowed for "
+                            + URL_MATCHED_COMPLETE_DELETE);
+                }
+                selection = COLUMN_ID + " = ?";
+                selectionArgs = new String[]{uri.getLastPathSegment()};
+                // Ignore the results of zip file deletion, possibly it wasn't even created.
+                deleteFilesFor(getBugReportFromUri(uri));
+                getContext().getContentResolver().notifyChange(uri, null);
+                return db.delete(BUG_REPORTS_TABLE, selection, selectionArgs);
+            default:
+                throw new IllegalArgumentException("Unknown URL " + uri);
+        }
+    }
+
+    @Override
+    public int update(
+            @NonNull Uri uri,
+            @Nullable ContentValues values,
+            @Nullable String selection,
+            @Nullable String[] selectionArgs) {
+        if (values == null) {
+            throw new IllegalArgumentException("values cannot be null");
+        }
+        String table;
+        switch (mUriMatcher.match(uri)) {
+            case URL_MATCHED_BUG_REPORTS_URI:
+                table = BUG_REPORTS_TABLE;
+                break;
+            default:
+                throw new IllegalArgumentException("Unknown URL " + uri);
+        }
+        SQLiteDatabase db = mDatabaseHelper.getWritableDatabase();
+        int rowCount = db.update(table, values, selection, selectionArgs);
+        if (rowCount > 0) {
+            // notify registered content observers
+            getContext().getContentResolver().notifyChange(uri, null);
+        }
+        Integer status = values.getAsInteger(COLUMN_STATUS);
+        // When the status is set to STATUS_UPLOAD_PENDING, we schedule an UploadJob under the
+        // current user, which is the primary user.
+        if (status != null && status.equals(Status.STATUS_UPLOAD_PENDING.getValue())) {
+            JobSchedulingUtils.scheduleUploadJob(BugStorageProvider.this.getContext());
+        }
+        return rowCount;
+    }
+
+    /**
+     * This is called when a file is opened.
+     *
+     * <p>See {@link BugStorageUtils#openBugReportFileToWrite},
+     * {@link BugStorageUtils#openAudioMessageFileToWrite}.
+     */
+    @Nullable
+    @Override
+    public ParcelFileDescriptor openFile(@NonNull Uri uri, @NonNull String mode)
+            throws FileNotFoundException {
+        Function<MetaBugReport, String> fileNameExtractor;
+        switch (mUriMatcher.match(uri)) {
+            case URL_MATCHED_OPEN_BUGREPORT_FILE:
+                fileNameExtractor = MetaBugReport::getBugReportFileName;
+                break;
+            case URL_MATCHED_OPEN_AUDIO_FILE:
+                fileNameExtractor = MetaBugReport::getAudioFileName;
+                break;
+            case URL_MATCHED_OPEN_FILE:
+                File file = new File(getBugReportFromUri(uri).getFilePath());
+                Log.v(TAG, "Opening file " + file + " with mode " + mode);
+                return ParcelFileDescriptor.open(file, ParcelFileDescriptor.parseMode(mode));
+            default:
+                throw new IllegalArgumentException("unknown uri:" + uri);
+        }
+        // URI contains bugreport ID as the last segment, see the matched urls.
+        MetaBugReport bugReport = getBugReportFromUri(uri);
+        File file = new File(
+                FileUtils.getPendingDir(getContext()), fileNameExtractor.apply(bugReport));
+        Log.v(TAG, "Opening file " + file + " with mode " + mode);
+        int modeBits = ParcelFileDescriptor.parseMode(mode);
+        return ParcelFileDescriptor.open(file, modeBits);
+    }
+
+    private MetaBugReport getBugReportFromUri(@NonNull Uri uri) {
+        int bugreportId = Integer.parseInt(uri.getLastPathSegment());
+        return BugStorageUtils.findBugReport(getContext(), bugreportId)
+                .orElseThrow(() -> new IllegalArgumentException("No record found for " + uri));
+    }
+
+    /**
+     * Print the Provider's state into the given stream. This gets invoked if
+     * you run "dumpsys activity provider com.android.car.bugreport/.BugStorageProvider".
+     *
+     * @param fd The raw file descriptor that the dump is being sent to.
+     * @param writer The PrintWriter to which you should dump your state.  This will be
+     * closed for you after you return.
+     * @param args additional arguments to the dump request.
+     */
+    public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
+        writer.println("BugStorageProvider:");
+        mConfig.dump(/* prefix= */ "  ", writer);
+    }
+
+    private boolean deleteFilesFor(MetaBugReport bugReport) {
+        if (!Strings.isNullOrEmpty(bugReport.getFilePath())) {
+            // Old bugreports have only filePath.
+            return new File(bugReport.getFilePath()).delete();
+        }
+        File pendingDir = FileUtils.getPendingDir(getContext());
+        boolean result = true;
+        if (!Strings.isNullOrEmpty(bugReport.getAudioFileName())) {
+            result = new File(pendingDir, bugReport.getAudioFileName()).delete();
+        }
+        if (!Strings.isNullOrEmpty(bugReport.getBugReportFileName())) {
+            result = result && new File(pendingDir, bugReport.getBugReportFileName()).delete();
+        }
+        return result;
+    }
+}
diff --git a/tests/BugReportApp/src/com/android/car/bugreport/BugStorageUtils.java b/tests/BugReportApp/src/com/android/car/bugreport/BugStorageUtils.java
new file mode 100644
index 0000000..cd0a12e
--- /dev/null
+++ b/tests/BugReportApp/src/com/android/car/bugreport/BugStorageUtils.java
@@ -0,0 +1,376 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.car.bugreport;
+
+import static com.android.car.bugreport.BugStorageProvider.COLUMN_AUDIO_FILENAME;
+import static com.android.car.bugreport.BugStorageProvider.COLUMN_BUGREPORT_FILENAME;
+import static com.android.car.bugreport.BugStorageProvider.COLUMN_FILEPATH;
+import static com.android.car.bugreport.BugStorageProvider.COLUMN_ID;
+import static com.android.car.bugreport.BugStorageProvider.COLUMN_STATUS;
+import static com.android.car.bugreport.BugStorageProvider.COLUMN_STATUS_MESSAGE;
+import static com.android.car.bugreport.BugStorageProvider.COLUMN_TIMESTAMP;
+import static com.android.car.bugreport.BugStorageProvider.COLUMN_TITLE;
+import static com.android.car.bugreport.BugStorageProvider.COLUMN_TYPE;
+import static com.android.car.bugreport.BugStorageProvider.COLUMN_USERNAME;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.net.Uri;
+import android.util.Log;
+
+import com.google.api.client.auth.oauth2.TokenResponseException;
+import com.google.common.base.Strings;
+
+import java.io.FileNotFoundException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.time.Instant;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.Optional;
+
+/**
+ * A class that hides details when communicating with the bug storage provider.
+ */
+final class BugStorageUtils {
+    private static final String TAG = BugStorageUtils.class.getSimpleName();
+
+    /**
+     * When time/time-zone set incorrectly, Google API returns "400: invalid_grant" error with
+     * description containing this text.
+     */
+    private static final String CLOCK_SKEW_ERROR = "clock with skew to account";
+
+    /** When time/time-zone set incorrectly, Google API returns this error. */
+    private static final String INVALID_GRANT = "invalid_grant";
+
+    private static final DateFormat TIMESTAMP_FORMAT = new SimpleDateFormat("yyyy-MM-dd_HH-mm-ss");
+
+    /**
+     * Creates a new {@link Status#STATUS_WRITE_PENDING} bug report record in a local sqlite
+     * database.
+     *
+     * @param context   - an application context.
+     * @param title     - title of the bug report.
+     * @param timestamp - timestamp when the bug report was initiated.
+     * @param username  - current user name. Note, it's a user name, not an account name.
+     * @param type      - bug report type, {@link MetaBugReport.BugReportType}.
+     * @return an instance of {@link MetaBugReport} that was created in a database.
+     */
+    @NonNull
+    static MetaBugReport createBugReport(
+            @NonNull Context context,
+            @NonNull String title,
+            @NonNull String timestamp,
+            @NonNull String username,
+            @MetaBugReport.BugReportType int type) {
+        // insert bug report username and title
+        ContentValues values = new ContentValues();
+        values.put(COLUMN_TITLE, title);
+        values.put(COLUMN_TIMESTAMP, timestamp);
+        values.put(COLUMN_USERNAME, username);
+        values.put(COLUMN_TYPE, type);
+
+        ContentResolver r = context.getContentResolver();
+        Uri uri = r.insert(BugStorageProvider.BUGREPORT_CONTENT_URI, values);
+        return findBugReport(context, Integer.parseInt(uri.getLastPathSegment())).get();
+    }
+
+    /** Returns an output stream to write the zipped file to. */
+    @NonNull
+    static OutputStream openBugReportFileToWrite(
+            @NonNull Context context, @NonNull MetaBugReport metaBugReport)
+            throws FileNotFoundException {
+        ContentResolver r = context.getContentResolver();
+        return r.openOutputStream(BugStorageProvider.buildUriWithSegment(
+                metaBugReport.getId(), BugStorageProvider.URL_SEGMENT_OPEN_BUGREPORT_FILE));
+    }
+
+    /** Returns an output stream to write the audio message file to. */
+    static OutputStream openAudioMessageFileToWrite(
+            @NonNull Context context, @NonNull MetaBugReport metaBugReport)
+            throws FileNotFoundException {
+        ContentResolver r = context.getContentResolver();
+        return r.openOutputStream(BugStorageProvider.buildUriWithSegment(
+                metaBugReport.getId(), BugStorageProvider.URL_SEGMENT_OPEN_AUDIO_FILE));
+    }
+
+    /**
+     * Returns an input stream to read the final zip file from.
+     *
+     * <p>NOTE: This is the old way of storing final zipped bugreport. See
+     * {@link BugStorageProvider#URL_SEGMENT_OPEN_FILE} for more info.
+     */
+    static InputStream openFileToRead(Context context, MetaBugReport bug)
+            throws FileNotFoundException {
+        return context.getContentResolver().openInputStream(
+                BugStorageProvider.buildUriWithSegment(
+                        bug.getId(), BugStorageProvider.URL_SEGMENT_OPEN_FILE));
+    }
+
+    /** Returns an input stream to read the bug report zip file from. */
+    static InputStream openBugReportFileToRead(Context context, MetaBugReport bug)
+            throws FileNotFoundException {
+        return context.getContentResolver().openInputStream(
+                BugStorageProvider.buildUriWithSegment(
+                        bug.getId(), BugStorageProvider.URL_SEGMENT_OPEN_BUGREPORT_FILE));
+    }
+
+    /** Returns an input stream to read the audio file from. */
+    static InputStream openAudioFileToRead(Context context, MetaBugReport bug)
+            throws FileNotFoundException {
+        return context.getContentResolver().openInputStream(
+                BugStorageProvider.buildUriWithSegment(
+                        bug.getId(), BugStorageProvider.URL_SEGMENT_OPEN_AUDIO_FILE));
+    }
+
+    /**
+     * Deletes {@link MetaBugReport} record from a local database and deletes the associated file.
+     *
+     * <p>WARNING: destructive operation.
+     *
+     * @param context     - an application context.
+     * @param bugReportId - a bug report id.
+     * @return true if the record was deleted.
+     */
+    static boolean completeDeleteBugReport(@NonNull Context context, int bugReportId) {
+        ContentResolver r = context.getContentResolver();
+        return r.delete(BugStorageProvider.buildUriWithSegment(
+                bugReportId, BugStorageProvider.URL_SEGMENT_COMPLETE_DELETE), null, null) == 1;
+    }
+
+    /** Deletes all files for given bugreport id; doesn't delete sqlite3 record. */
+    static boolean deleteBugReportFiles(@NonNull Context context, int bugReportId) {
+        ContentResolver r = context.getContentResolver();
+        return r.delete(BugStorageProvider.buildUriWithSegment(
+                bugReportId, BugStorageProvider.URL_SEGMENT_DELETE_FILES), null, null) == 1;
+    }
+
+    /**
+     * Returns all the bugreports that are waiting to be uploaded.
+     */
+    @NonNull
+    public static List<MetaBugReport> getUploadPendingBugReports(@NonNull Context context) {
+        String selection = COLUMN_STATUS + "=?";
+        String[] selectionArgs = new String[]{
+                Integer.toString(Status.STATUS_UPLOAD_PENDING.getValue())};
+        return getBugreports(context, selection, selectionArgs, null);
+    }
+
+    /**
+     * Returns all bugreports in descending order by the ID field. ID is the index in the
+     * database.
+     */
+    @NonNull
+    public static List<MetaBugReport> getAllBugReportsDescending(@NonNull Context context) {
+        return getBugreports(context, null, null, COLUMN_ID + " DESC");
+    }
+
+    /** Returns {@link MetaBugReport} for given bugreport id. */
+    static Optional<MetaBugReport> findBugReport(Context context, int bugreportId) {
+        String selection = COLUMN_ID + " = ?";
+        String[] selectionArgs = new String[]{Integer.toString(bugreportId)};
+        List<MetaBugReport> bugs = BugStorageUtils.getBugreports(
+                context, selection, selectionArgs, null);
+        if (bugs.isEmpty()) {
+            return Optional.empty();
+        }
+        return Optional.of(bugs.get(0));
+    }
+
+    private static List<MetaBugReport> getBugreports(
+            Context context, String selection, String[] selectionArgs, String order) {
+        ArrayList<MetaBugReport> bugReports = new ArrayList<>();
+        String[] projection = {
+                COLUMN_ID,
+                COLUMN_USERNAME,
+                COLUMN_TITLE,
+                COLUMN_TIMESTAMP,
+                COLUMN_BUGREPORT_FILENAME,
+                COLUMN_AUDIO_FILENAME,
+                COLUMN_FILEPATH,
+                COLUMN_STATUS,
+                COLUMN_STATUS_MESSAGE,
+                COLUMN_TYPE};
+        ContentResolver r = context.getContentResolver();
+        Cursor c = r.query(BugStorageProvider.BUGREPORT_CONTENT_URI, projection,
+                selection, selectionArgs, order);
+
+        int count = (c != null) ? c.getCount() : 0;
+
+        if (count > 0) c.moveToFirst();
+        for (int i = 0; i < count; i++) {
+            MetaBugReport meta = MetaBugReport.builder()
+                    .setId(getInt(c, COLUMN_ID))
+                    .setTimestamp(getString(c, COLUMN_TIMESTAMP))
+                    .setUserName(getString(c, COLUMN_USERNAME))
+                    .setTitle(getString(c, COLUMN_TITLE))
+                    .setBugReportFileName(getString(c, COLUMN_BUGREPORT_FILENAME))
+                    .setAudioFileName(getString(c, COLUMN_AUDIO_FILENAME))
+                    .setFilePath(getString(c, COLUMN_FILEPATH))
+                    .setStatus(getInt(c, COLUMN_STATUS))
+                    .setStatusMessage(getString(c, COLUMN_STATUS_MESSAGE))
+                    .setType(getInt(c, COLUMN_TYPE))
+                    .build();
+            bugReports.add(meta);
+            c.moveToNext();
+        }
+        if (c != null) c.close();
+        return bugReports;
+    }
+
+    /**
+     * returns 0 if the column is not found. Otherwise returns the column value.
+     */
+    private static int getInt(Cursor c, String colName) {
+        int colIndex = c.getColumnIndex(colName);
+        if (colIndex == -1) {
+            Log.w(TAG, "Column " + colName + " not found.");
+            return 0;
+        }
+        return c.getInt(colIndex);
+    }
+
+    /**
+     * Returns the column value. If the column is not found returns empty string.
+     */
+    private static String getString(Cursor c, String colName) {
+        int colIndex = c.getColumnIndex(colName);
+        if (colIndex == -1) {
+            Log.w(TAG, "Column " + colName + " not found.");
+            return "";
+        }
+        return Strings.nullToEmpty(c.getString(colIndex));
+    }
+
+    /**
+     * Sets bugreport status to uploaded successfully.
+     */
+    public static void setUploadSuccess(Context context, MetaBugReport bugReport) {
+        setBugReportStatus(context, bugReport, Status.STATUS_UPLOAD_SUCCESS,
+                "Upload time: " + currentTimestamp());
+    }
+
+    /**
+     * Sets bugreport status pending, and update the message to last exception message.
+     *
+     * <p>Used when a transient error has occurred.
+     */
+    public static void setUploadRetry(Context context, MetaBugReport bugReport, Exception e) {
+        setBugReportStatus(context, bugReport, Status.STATUS_UPLOAD_PENDING,
+                getRootCauseMessage(e));
+    }
+
+    /**
+     * Sets bugreport status pending and update the message to last message.
+     *
+     * <p>Used when a transient error has occurred.
+     */
+    public static void setUploadRetry(Context context, MetaBugReport bugReport, String msg) {
+        setBugReportStatus(context, bugReport, Status.STATUS_UPLOAD_PENDING, msg);
+    }
+
+    /**
+     * Sets {@link MetaBugReport} status {@link Status#STATUS_EXPIRED}.
+     * Deletes the associated zip file from disk.
+     *
+     * @return true if succeeded.
+     */
+    static boolean expireBugReport(@NonNull Context context,
+            @NonNull MetaBugReport metaBugReport, @NonNull Instant expiredAt) {
+        metaBugReport = setBugReportStatus(
+                context, metaBugReport, Status.STATUS_EXPIRED, "Expired on " + expiredAt);
+        if (metaBugReport.getStatus() != Status.STATUS_EXPIRED.getValue()) {
+            return false;
+        }
+        return deleteBugReportFiles(context, metaBugReport.getId());
+    }
+
+    /** Gets the root cause of the error. */
+    @NonNull
+    private static String getRootCauseMessage(@Nullable Throwable t) {
+        if (t == null) {
+            return "No error";
+        } else if (t instanceof TokenResponseException) {
+            TokenResponseException ex = (TokenResponseException) t;
+            if (ex.getDetails().getError().equals(INVALID_GRANT)
+                    && ex.getDetails().getErrorDescription().contains(CLOCK_SKEW_ERROR)) {
+                return "Auth error. Check if time & time-zone is correct.";
+            }
+        }
+        while (t.getCause() != null) t = t.getCause();
+        return t.getMessage();
+    }
+
+    /**
+     * Updates bug report record status.
+     *
+     * <p>NOTE: When status is set to STATUS_UPLOAD_PENDING, BugStorageProvider automatically
+     * schedules the bugreport to be uploaded.
+     *
+     * @return Updated {@link MetaBugReport}.
+     */
+    static MetaBugReport setBugReportStatus(
+            Context context, MetaBugReport bugReport, Status status, String message) {
+        return update(context, bugReport.toBuilder()
+                .setStatus(status.getValue())
+                .setStatusMessage(message)
+                .build());
+    }
+
+    /**
+     * Updates bug report record status.
+     *
+     * <p>NOTE: When status is set to STATUS_UPLOAD_PENDING, BugStorageProvider automatically
+     * schedules the bugreport to be uploaded.
+     *
+     * @return Updated {@link MetaBugReport}.
+     */
+    static MetaBugReport setBugReportStatus(
+            Context context, MetaBugReport bugReport, Status status, Exception e) {
+        return setBugReportStatus(context, bugReport, status, getRootCauseMessage(e));
+    }
+
+    /**
+     * Updates the bugreport and returns the updated version.
+     *
+     * <p>NOTE: doesn't update all the fields.
+     */
+    static MetaBugReport update(Context context, MetaBugReport bugReport) {
+        // Update only necessary fields.
+        ContentValues values = new ContentValues();
+        values.put(COLUMN_BUGREPORT_FILENAME, bugReport.getBugReportFileName());
+        values.put(COLUMN_AUDIO_FILENAME, bugReport.getAudioFileName());
+        values.put(COLUMN_STATUS, bugReport.getStatus());
+        values.put(COLUMN_STATUS_MESSAGE, bugReport.getStatusMessage());
+        String where = COLUMN_ID + "=" + bugReport.getId();
+        context.getContentResolver().update(
+                BugStorageProvider.BUGREPORT_CONTENT_URI, values, where, null);
+        return findBugReport(context, bugReport.getId()).orElseThrow(
+                () -> new IllegalArgumentException("Bug " + bugReport.getId() + " not found"));
+    }
+
+    private static String currentTimestamp() {
+        return TIMESTAMP_FORMAT.format(new Date());
+    }
+}
diff --git a/tests/BugReportApp/src/com/android/car/bugreport/Config.java b/tests/BugReportApp/src/com/android/car/bugreport/Config.java
new file mode 100644
index 0000000..b37ac9e
--- /dev/null
+++ b/tests/BugReportApp/src/com/android/car/bugreport/Config.java
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.car.bugreport;
+
+import android.app.ActivityThread;
+import android.os.Build;
+import android.os.SystemProperties;
+import android.provider.DeviceConfig;
+import android.util.Log;
+
+import com.android.internal.annotations.GuardedBy;
+
+import com.google.common.collect.ImmutableSet;
+
+import java.io.PrintWriter;
+
+/**
+ * Contains config for BugReport App.
+ *
+ * <p>The config is kept synchronized with {@code car} namespace. It's not defined in
+ * {@link DeviceConfig}.
+ *
+ * <ul>To get/set the flags via adb:
+ *   <li>{@code adb shell device_config get car bugreport_upload_destination}
+ *   <li>{@code adb shell device_config put car bugreport_upload_destination gcs}
+ *   <li>{@code adb shell device_config delete car bugreport_upload_destination}
+ * </ul>
+ */
+final class Config {
+    private static final String TAG = Config.class.getSimpleName();
+
+    /**
+     * Namespace for all Android Automotive related features.
+     *
+     * <p>In the future it will move to {@code DeviceConfig#NAMESPACE_CAR}.
+     */
+    private static final String NAMESPACE_CAR = "car";
+
+    /**
+     * A string flag, can be one of {@code null} or {@link #UPLOAD_DESTINATION_GCS}.
+     */
+    private static final String KEY_BUGREPORT_UPLOAD_DESTINATION = "bugreport_upload_destination";
+
+    /**
+     * A value for {@link #KEY_BUGREPORT_UPLOAD_DESTINATION}.
+     *
+     * Upload bugreports to GCS. Only works in {@code userdebug} or {@code eng} builds.
+     */
+    private static final String UPLOAD_DESTINATION_GCS = "gcs";
+
+    /**
+     * A system property to force enable the app bypassing the {@code userdebug/eng} build check.
+     */
+    private static final String PROP_FORCE_ENABLE = "android.car.bugreport.force_enable";
+
+    /*
+     * Enable uploading new bugreports to GCS for these devices. If the device is not in this list,
+     * {@link #KEY_UPLOAD_DESTINATION} flag will be used instead.
+     */
+    private static final ImmutableSet<String> ENABLE_FORCE_UPLOAD_TO_GCS_FOR_DEVICES =
+            ImmutableSet.of("hawk");
+
+    private final Object mLock = new Object();
+
+    @GuardedBy("mLock")
+    private String mUploadDestination = null;
+
+    void start() {
+        DeviceConfig.addOnPropertiesChangedListener(NAMESPACE_CAR,
+                ActivityThread.currentApplication().getMainExecutor(), this::onPropertiesChanged);
+        updateConstants();
+    }
+
+    private void onPropertiesChanged(DeviceConfig.Properties properties) {
+        if (properties.getKeyset().contains(KEY_BUGREPORT_UPLOAD_DESTINATION)) {
+            updateConstants();
+        }
+    }
+
+    /** Returns true if bugreport app is enabled for this device. */
+    static boolean isBugReportEnabled() {
+        return Build.IS_DEBUGGABLE || SystemProperties.getBoolean(PROP_FORCE_ENABLE, false);
+    }
+
+    /** If new bugreports should be scheduled for uploading. */
+    boolean getAutoUpload() {
+        // TODO(b/144851443): Enable auto-upload only if upload destination is Gcs until
+        //                    we create a way to allow implementing OEMs custom upload logic.
+        return isUploadDestinationGcs();
+    }
+
+    /**
+     * Returns {@link true} if bugreport upload destination is GCS.
+     */
+    private boolean isUploadDestinationGcs() {
+        if (isTempForceAutoUploadGcsEnabled()) {
+            Log.d(TAG, "Setting upload dest to GCS ENABLE_AUTO_UPLOAD is true");
+            return true;
+        }
+        // NOTE: enable it only for userdebug/eng builds.
+        return UPLOAD_DESTINATION_GCS.equals(getUploadDestination()) && Build.IS_DEBUGGABLE;
+    }
+
+    private static boolean isTempForceAutoUploadGcsEnabled() {
+        return ENABLE_FORCE_UPLOAD_TO_GCS_FOR_DEVICES.contains(Build.DEVICE);
+    }
+
+    /**
+     * Returns value of a flag {@link #KEY_BUGREPORT_UPLOAD_DESTINATION}.
+     */
+    private String getUploadDestination() {
+        synchronized (mLock) {
+            return mUploadDestination;
+        }
+    }
+
+    private void updateConstants() {
+        synchronized (mLock) {
+            mUploadDestination = DeviceConfig.getString(NAMESPACE_CAR,
+                    KEY_BUGREPORT_UPLOAD_DESTINATION, /* defaultValue= */ null);
+        }
+    }
+
+    void dump(String prefix, PrintWriter pw) {
+        pw.println(prefix + "car.bugreport.Config:");
+
+        pw.print(prefix + "  ");
+        pw.print("getAutoUpload");
+        pw.print("=");
+        pw.println(getAutoUpload() ? "true" : "false");
+
+        pw.print(prefix + "  ");
+        pw.print("getUploadDestination");
+        pw.print("=");
+        pw.println(getUploadDestination());
+
+        pw.print(prefix + "  ");
+        pw.print("isUploadDestinationGcs");
+        pw.print("=");
+        pw.println(isUploadDestinationGcs());
+    }
+}
diff --git a/tests/BugReportApp/src/com/android/car/bugreport/FileUtils.java b/tests/BugReportApp/src/com/android/car/bugreport/FileUtils.java
new file mode 100644
index 0000000..bf12aa7
--- /dev/null
+++ b/tests/BugReportApp/src/com/android/car/bugreport/FileUtils.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.car.bugreport;
+
+import android.content.Context;
+
+import com.google.common.base.Preconditions;
+
+import java.io.File;
+
+/**
+ * File utilities.
+ *
+ * Thread safety and file operations: All file operations should happen on the same worker
+ * thread for thread safety. This is provided by running both bugreport service and file upload
+ * service on a asynctask. Asynctasks are by default executed on the same worker thread in serial.
+ *
+ * There is one exception to the rule above:
+ * Voice recorder works on main thread, however this is not a thread safety problem because:
+ * i. voice recorder always works before starting to collect rest of the bugreport
+ * ii. a bug report cannot be moved to upload (pending) directory before it is completely
+ * collected.
+ */
+public class FileUtils {
+    private static final String PREFIX = "bugreport-";
+    /** A directory under the system user; contains bugreport zip files and audio files. */
+    private static final String PENDING_DIR = "bug_reports_pending";
+    // Temporary directory under the current user, used for zipping files.
+    private static final String TEMP_DIR = "bug_reports_temp";
+
+    private static final String FS = "@";
+
+    static File getPendingDir(Context context) {
+        Preconditions.checkArgument(context.getUser().isSystem(),
+                "Must be called from the system user.");
+        File dir = new File(context.getDataDir(), PENDING_DIR);
+        dir.mkdirs();
+        return dir;
+    }
+
+    /**
+     * Creates and returns file directory for storing bug report files before they are zipped into
+     * a single file.
+     */
+    static File createTempDir(Context context, String timestamp) {
+        File dir = getTempDir(context, timestamp);
+        dir.mkdirs();
+        return dir;
+    }
+
+    /**
+     * Returns path to the directory for storing bug report files before they are zipped into a
+     * single file.
+     */
+    static File getTempDir(Context context, String timestamp) {
+        Preconditions.checkArgument(!context.getUser().isSystem(),
+                "Must be called from the current user.");
+        return new File(context.getDataDir(), TEMP_DIR + "/" + timestamp);
+    }
+
+    /**
+     * Constructs a bugreport zip file name.
+     *
+     * <p>Add lookup code to the filename to allow matching audio file and bugreport file in USB.
+     */
+    static String getZipFileName(MetaBugReport bug) {
+        String lookupCode = extractLookupCode(bug);
+        return PREFIX + bug.getUserName() + FS + bug.getTimestamp() + "-" + lookupCode + ".zip";
+    }
+
+    /**
+     * Constructs a audio message file name.
+     *
+     * <p>Add lookup code to the filename to allow matching audio file and bugreport file in USB.
+     *
+     * @param timestamp - current timestamp, when audio was created.
+     * @param bug       - a bug report.
+     */
+    static String getAudioFileName(String timestamp, MetaBugReport bug) {
+        String lookupCode = extractLookupCode(bug);
+        return PREFIX + bug.getUserName() + FS + timestamp + "-" + lookupCode + "-message.3gp";
+    }
+
+    private static String extractLookupCode(MetaBugReport bug) {
+        Preconditions.checkArgument(bug.getTitle().startsWith("["),
+                "Invalid bugreport title, doesn't contain lookup code. ");
+        return bug.getTitle().substring(1, BugReportActivity.LOOKUP_STRING_LENGTH + 1);
+    }
+
+    /**
+     * Returns a {@link File} object pointing to a path in a temp directory under current users
+     * {@link Context#getDataDir}.
+     *
+     * @param context       - an application context.
+     * @param timestamp     - generates file for this timestamp
+     * @param suffix        - a filename suffix.
+     * @return A file.
+     */
+    static File getFileWithSuffix(Context context, String timestamp, String suffix) {
+        return new File(createTempDir(context, timestamp), timestamp + suffix);
+    }
+
+    /**
+     * Returns a {@link File} object pointing to a path in a temp directory under current users
+     * {@link Context#getDataDir}.
+     *
+     * @param context     - an application context.
+     * @param timestamp   - generates file for this timestamp.
+     * @param name        - a filename
+     * @return A file.
+     */
+    static File getFile(Context context, String timestamp, String name) {
+        return new File(getTempDir(context, timestamp), name);
+    }
+
+    /**
+     * Deletes a directory and its contents recursively
+     *
+     * @param directory to delete
+     */
+    static void deleteDirectory(File directory) {
+        File[] files = directory.listFiles();
+        if (files != null) {
+            for (File file : files) {
+                if (!file.isDirectory()) {
+                    file.delete();
+                } else {
+                    deleteDirectory(file);
+                }
+            }
+        }
+        directory.delete();
+    }
+}
diff --git a/tests/BugReportApp/src/com/android/car/bugreport/JobSchedulingUtils.java b/tests/BugReportApp/src/com/android/car/bugreport/JobSchedulingUtils.java
new file mode 100644
index 0000000..ec09b1f
--- /dev/null
+++ b/tests/BugReportApp/src/com/android/car/bugreport/JobSchedulingUtils.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.car.bugreport;
+
+import android.app.job.JobInfo;
+import android.app.job.JobScheduler;
+import android.content.ComponentName;
+import android.content.Context;
+import android.util.Log;
+
+/**
+ * Utilities for scheduling upload jobs.
+ */
+class JobSchedulingUtils {
+    private static final String TAG = JobSchedulingUtils.class.getSimpleName();
+
+    private static final int UPLOAD_JOB_ID = 1;
+    private static final int RETRY_DELAY_IN_MS = 5_000;
+
+    /**
+     * Schedules {@link UploadJob} under the current user.
+     *
+     * <p>Make sure this method is called under the primary user.
+     *
+     * <ul>
+     *   <li>require network connectivity
+     *   <li>good quality network (large upload size)
+     *   <li>persist across reboots
+     * </ul>
+     */
+    static void scheduleUploadJob(Context context) {
+        JobScheduler jobScheduler = context.getSystemService(JobScheduler.class);
+        if (jobScheduler == null) {
+            Log.w(TAG, "Cannot get JobScheduler from context.");
+            return;
+        }
+
+        JobInfo pendingJob = jobScheduler.getPendingJob(UPLOAD_JOB_ID);
+        // if there is already a pending job, do not schedule a new one.
+        if (pendingJob != null) {
+            Log.d(TAG, "Upload job is already active, not re-scheduling");
+            return;
+        }
+
+        // NOTE: Don't set estimated network bytes, because we want bug reports to be uploaded
+        //       without any constraints.
+        jobScheduler.schedule(new JobInfo.Builder(UPLOAD_JOB_ID,
+                new ComponentName(context, UploadJob.class))
+                .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
+                .setBackoffCriteria(RETRY_DELAY_IN_MS, JobInfo.BACKOFF_POLICY_LINEAR)
+                .build());
+    }
+}
diff --git a/tests/BugReportApp/src/com/android/car/bugreport/MetaBugReport.java b/tests/BugReportApp/src/com/android/car/bugreport/MetaBugReport.java
new file mode 100644
index 0000000..e458e22
--- /dev/null
+++ b/tests/BugReportApp/src/com/android/car/bugreport/MetaBugReport.java
@@ -0,0 +1,212 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.car.bugreport;
+
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import android.annotation.IntDef;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.google.auto.value.AutoValue;
+
+import java.lang.annotation.Retention;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+/** Represents the information that a bugreport can contain. */
+@AutoValue
+abstract class MetaBugReport implements Parcelable {
+
+    private static final DateFormat BUG_REPORT_TIMESTAMP_DATE_FORMAT =
+            new SimpleDateFormat("yyyy-MM-dd_HH-mm-ss");
+
+    /** The app records audio message when initiated. Can change audio state. */
+    static final int TYPE_INTERACTIVE = 0;
+
+    /**
+     * The app doesn't show dialog and doesn't record audio when initiated. It allows user to
+     * add audio message when bugreport is collected.
+     */
+    static final int TYPE_SILENT = 1;
+
+    /** Annotation for bug report types. */
+    @Retention(SOURCE)
+    @IntDef({TYPE_INTERACTIVE, TYPE_SILENT})
+    @interface BugReportType {};
+
+    /**
+     * @return Id of the bug report. Bug report id monotonically increases and is unique.
+     */
+    public abstract int getId();
+
+    /**
+     * @return Username (LDAP) that created this bugreport
+     */
+    public abstract String getUserName();
+
+    /**
+     * @return Title of the bug.
+     */
+    public abstract String getTitle();
+
+    /**
+     * @return Timestamp when the bug report is initialized.
+     */
+    public abstract String getTimestamp();
+
+    /**
+     * @return path to the zip file stored under the system user.
+     *
+     * <p>NOTE: This is the old way of storing final zipped bugreport. See
+     * {@link BugStorageProvider#URL_SEGMENT_OPEN_FILE} for more info.
+     */
+    public abstract String getFilePath();
+
+    /**
+     * @return filename of the bug report zip file stored under the system user.
+     */
+    public abstract String getBugReportFileName();
+
+    /**
+     * @return filename of the audio message file stored under the system user.
+     */
+    public abstract String getAudioFileName();
+
+    /**
+     * @return {@link Status} of the bug upload.
+     */
+    public abstract int getStatus();
+
+    /**
+     * @return StatusMessage of the bug upload.
+     */
+    public abstract String getStatusMessage();
+
+    /**
+     * @return {@link BugReportType}.
+     */
+    public abstract int getType();
+
+    /** @return {@link Builder} from the meta bug report. */
+    public abstract Builder toBuilder();
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(getId());
+        dest.writeString(getTimestamp());
+        dest.writeString(getTitle());
+        dest.writeString(getUserName());
+        dest.writeString(getFilePath());
+        dest.writeString(getBugReportFileName());
+        dest.writeString(getAudioFileName());
+        dest.writeInt(getStatus());
+        dest.writeString(getStatusMessage());
+        dest.writeInt(getType());
+    }
+
+    /** Converts {@link Date} to bugreport timestamp. */
+    static String toBugReportTimestamp(Date date) {
+        return BUG_REPORT_TIMESTAMP_DATE_FORMAT.format(date);
+    }
+
+    /** Creates a {@link Builder} with default, non-null values. */
+    static Builder builder() {
+        return new AutoValue_MetaBugReport.Builder()
+                .setTimestamp("")
+                .setFilePath("")
+                .setBugReportFileName("")
+                .setAudioFileName("")
+                .setStatusMessage("")
+                .setTitle("")
+                .setUserName("");
+    }
+
+    /** A creator that's used by Parcelable. */
+    public static final Parcelable.Creator<MetaBugReport> CREATOR =
+            new Parcelable.Creator<MetaBugReport>() {
+                public MetaBugReport createFromParcel(Parcel in) {
+                    int id = in.readInt();
+                    String timestamp = in.readString();
+                    String title = in.readString();
+                    String username = in.readString();
+                    String filePath = in.readString();
+                    String bugReportFileName = in.readString();
+                    String audioFileName = in.readString();
+                    int status = in.readInt();
+                    String statusMessage = in.readString();
+                    int type = in.readInt();
+                    return MetaBugReport.builder()
+                            .setId(id)
+                            .setTimestamp(timestamp)
+                            .setTitle(title)
+                            .setUserName(username)
+                            .setFilePath(filePath)
+                            .setBugReportFileName(bugReportFileName)
+                            .setAudioFileName(audioFileName)
+                            .setStatus(status)
+                            .setStatusMessage(statusMessage)
+                            .setType(type)
+                            .build();
+                }
+
+                public MetaBugReport[] newArray(int size) {
+                    return new MetaBugReport[size];
+                }
+            };
+
+    /** Builder for MetaBugReport. */
+    @AutoValue.Builder
+    abstract static class Builder {
+        /** Sets id. */
+        public abstract Builder setId(int id);
+
+        /** Sets timestamp. */
+        public abstract Builder setTimestamp(String timestamp);
+
+        /** Sets title. */
+        public abstract Builder setTitle(String title);
+
+        /** Sets username. */
+        public abstract Builder setUserName(String username);
+
+        /** Sets filepath. */
+        public abstract Builder setFilePath(String filePath);
+
+        /** Sets bugReportFileName. */
+        public abstract Builder setBugReportFileName(String bugReportFileName);
+
+        /** Sets audioFileName. */
+        public abstract Builder setAudioFileName(String audioFileName);
+
+        /** Sets {@link Status}. */
+        public abstract Builder setStatus(int status);
+
+        /** Sets statusmessage. */
+        public abstract Builder setStatusMessage(String statusMessage);
+
+        /** Sets the {@link BugReportType}. */
+        public abstract Builder setType(@BugReportType int type);
+
+        public abstract MetaBugReport build();
+    }
+}
diff --git a/tests/BugReportApp/src/com/android/car/bugreport/PackageUtils.java b/tests/BugReportApp/src/com/android/car/bugreport/PackageUtils.java
new file mode 100644
index 0000000..cc8b27b
--- /dev/null
+++ b/tests/BugReportApp/src/com/android/car/bugreport/PackageUtils.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.car.bugreport;
+
+import android.content.Context;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.util.Log;
+
+/**
+ * Package related utility methods.
+ */
+final class PackageUtils {
+    private static final String TAG = PackageUtils.class.getSimpleName();
+
+    /** Returns a BugReport app version name from {@code AndroidManifest.xml}. */
+    static String getPackageVersion(Context context) {
+        try {
+            PackageInfo packageInfo = context.getPackageManager().getPackageInfo(
+                    context.getPackageName(), 0);
+            return packageInfo.versionName;
+        } catch (PackageManager.NameNotFoundException e) {
+            Log.e(TAG, "Can't get package version.", e);
+            return "unknown";
+        }
+    }
+
+    private PackageUtils() {
+    }
+}
diff --git a/tests/BugReportApp/src/com/android/car/bugreport/SimpleUploaderAsyncTask.java b/tests/BugReportApp/src/com/android/car/bugreport/SimpleUploaderAsyncTask.java
new file mode 100644
index 0000000..b5d86ec
--- /dev/null
+++ b/tests/BugReportApp/src/com/android/car/bugreport/SimpleUploaderAsyncTask.java
@@ -0,0 +1,204 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.car.bugreport;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.os.AsyncTask;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.google.api.client.extensions.android.http.AndroidHttp;
+import com.google.api.client.googleapis.auth.oauth2.GoogleCredential;
+import com.google.api.client.http.HttpTransport;
+import com.google.api.client.http.InputStreamContent;
+import com.google.api.client.json.JsonFactory;
+import com.google.api.client.json.jackson2.JacksonFactory;
+import com.google.api.services.storage.Storage;
+import com.google.api.services.storage.model.StorageObject;
+import com.google.common.base.Strings;
+import com.google.common.collect.ImmutableMap;
+
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.zip.ZipOutputStream;
+
+/**
+ * Uploads a bugreport files to GCS using a simple (no-multipart / no-resume) upload policy.
+ *
+ * <p>It merges bugreport zip file and audio file into one final zip file and uploads it.
+ *
+ * <p>Please see {@code res/values/configs.xml} and {@code res/raw/gcs_credentials.json} for the
+ * configuration.
+ */
+class SimpleUploaderAsyncTask extends AsyncTask<Void, Void, Boolean> {
+    private static final String TAG = SimpleUploaderAsyncTask.class.getSimpleName();
+
+    private static final String ACCESS_SCOPE =
+            "https://www.googleapis.com/auth/devstorage.read_write";
+
+    private static final String STORAGE_METADATA_TITLE = "title";
+
+    private final Context mContext;
+    private final Result mResult;
+
+    /**
+     * The uploader uploads only one bugreport each time it is called. This interface is
+     * used to reschedule upload job, if there are more bugreports waiting.
+     *
+     * Pass true to reschedule upload job, false not to reschedule.
+     */
+    interface Result {
+        void reschedule(boolean s);
+    }
+
+    /** Constructs SimpleUploaderAsyncTask. */
+    SimpleUploaderAsyncTask(@NonNull Context context, @NonNull Result result) {
+        mContext = context;
+        mResult = result;
+    }
+
+    private StorageObject uploadSimple(
+            Storage storage, MetaBugReport bugReport, String fileName, InputStream data)
+            throws IOException {
+        InputStreamContent mediaContent = new InputStreamContent("application/zip", data);
+
+        String bucket = mContext.getString(R.string.config_gcs_bucket);
+        if (TextUtils.isEmpty(bucket)) {
+            throw new RuntimeException("config_gcs_bucket is empty.");
+        }
+
+        // Create GCS MetaData.
+        Map<String, String> metadata = ImmutableMap.of(
+                STORAGE_METADATA_TITLE, bugReport.getTitle()
+        );
+        StorageObject object = new StorageObject()
+                .setBucket(bucket)
+                .setName(fileName)
+                .setMetadata(metadata)
+                .setContentDisposition("attachment");
+        Storage.Objects.Insert insertObject = storage.objects().insert(bucket, object,
+                mediaContent);
+
+        // The media uploader gzips content by default, and alters the Content-Encoding accordingly.
+        // GCS dutifully stores content as-uploaded. This line disables the media uploader behavior,
+        // so the service stores exactly what is in the InputStream, without transformation.
+        insertObject.getMediaHttpUploader().setDisableGZipContent(true);
+        Log.v(TAG, "started uploading object " + fileName + " to bucket " + bucket);
+        return insertObject.execute();
+    }
+
+    private void upload(MetaBugReport bugReport) throws IOException {
+        GoogleCredential credential = GoogleCredential
+                .fromStream(mContext.getResources().openRawResource(R.raw.gcs_credentials))
+                .createScoped(Collections.singleton(ACCESS_SCOPE));
+        Log.v(TAG, "Created credential");
+        HttpTransport httpTransport = AndroidHttp.newCompatibleTransport();
+        JsonFactory jsonFactory = JacksonFactory.getDefaultInstance();
+
+        Storage storage = new Storage.Builder(httpTransport, jsonFactory, credential)
+                .setApplicationName("Bugreportupload/1.0").build();
+
+        File tmpBugReportFile = zipBugReportFiles(bugReport);
+        Log.d(TAG, "Uploading file " + tmpBugReportFile);
+        try {
+            // Upload filename is bugreport filename, although, now it contains the audio message.
+            String fileName = bugReport.getBugReportFileName();
+            if (Strings.isNullOrEmpty(fileName)) {
+                // Old bugreports don't contain getBugReportFileName, fallback to getFilePath.
+                fileName = new File(bugReport.getFilePath()).getName();
+            }
+            try (FileInputStream inputStream = new FileInputStream(tmpBugReportFile)) {
+                StorageObject object = uploadSimple(storage, bugReport, fileName, inputStream);
+                Log.v(TAG, "finished uploading object " + object.getName() + " file " + fileName);
+            }
+            File pendingDir = FileUtils.getPendingDir(mContext);
+            // Delete only after successful upload; the files are needed for retry.
+            if (!Strings.isNullOrEmpty(bugReport.getAudioFileName())) {
+                Log.v(TAG, "Deleting file " + bugReport.getAudioFileName());
+                new File(pendingDir, bugReport.getAudioFileName()).delete();
+            }
+            if (!Strings.isNullOrEmpty(bugReport.getBugReportFileName())) {
+                Log.v(TAG, "Deleting file " + bugReport.getBugReportFileName());
+                new File(pendingDir, bugReport.getBugReportFileName()).delete();
+            }
+        } finally {
+            // Delete the temp file if it's not a MetaBugReport#getFilePath, because it's needed
+            // for retry.
+            if (Strings.isNullOrEmpty(bugReport.getFilePath())) {
+                Log.v(TAG, "Deleting file " + tmpBugReportFile);
+                tmpBugReportFile.delete();
+            }
+        }
+    }
+
+    private File zipBugReportFiles(MetaBugReport bugReport) throws IOException {
+        if (!Strings.isNullOrEmpty(bugReport.getFilePath())) {
+            // Old bugreports still have this field.
+            return new File(bugReport.getFilePath());
+        }
+        File finalZipFile =
+                File.createTempFile("bugreport", ".zip", mContext.getCacheDir());
+        File pendingDir = FileUtils.getPendingDir(mContext);
+        try (ZipOutputStream zipStream = new ZipOutputStream(
+                new BufferedOutputStream(new FileOutputStream(finalZipFile)))) {
+            ZipUtils.extractZippedFileToZipStream(
+                    new File(pendingDir, bugReport.getBugReportFileName()), zipStream);
+            ZipUtils.addFileToZipStream(
+                    new File(pendingDir, bugReport.getAudioFileName()), zipStream);
+        }
+        return finalZipFile;
+    }
+
+    @Override
+    protected void onPostExecute(Boolean success) {
+        mResult.reschedule(success);
+    }
+
+    /** Returns true is there are more files to upload. */
+    @Override
+    protected Boolean doInBackground(Void... voids) {
+        List<MetaBugReport> bugReports = BugStorageUtils.getUploadPendingBugReports(mContext);
+
+        for (MetaBugReport bugReport : bugReports) {
+            try {
+                if (isCancelled()) {
+                    BugStorageUtils.setUploadRetry(mContext, bugReport, "Upload Job Cancelled");
+                    return true;
+                }
+                upload(bugReport);
+                BugStorageUtils.setUploadSuccess(mContext, bugReport);
+            } catch (Exception e) {
+                Log.e(TAG, String.format("Failed uploading %s - likely a transient error",
+                        bugReport.getTimestamp()), e);
+                BugStorageUtils.setUploadRetry(mContext, bugReport, e);
+            }
+        }
+        return false;
+    }
+
+    @Override
+    protected void onCancelled(Boolean success) {
+        mResult.reschedule(true);
+    }
+}
diff --git a/tests/BugReportApp/src/com/android/car/bugreport/StartUpBootReceiver.java b/tests/BugReportApp/src/com/android/car/bugreport/StartUpBootReceiver.java
new file mode 100644
index 0000000..7528c21
--- /dev/null
+++ b/tests/BugReportApp/src/com/android/car/bugreport/StartUpBootReceiver.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.car.bugreport;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.os.UserManager;
+import android.util.Log;
+
+/**
+ * Handles device boot intents.
+ *
+ * <ul>
+ *     <li>Starts {@link UploadJob}</li>
+ * </ul>
+ */
+public class StartUpBootReceiver extends BroadcastReceiver {
+    public static final String TAG = StartUpBootReceiver.class.getSimpleName();
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        if (!Config.isBugReportEnabled()) {
+            return;
+        }
+        // Run it only once for the system user (u0) and ignore for other users.
+        UserManager userManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
+        if (!userManager.isSystemUser()) {
+            return;
+        }
+
+        if (Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction())) {
+            Log.d(TAG, "StartUpBootReceiver BOOT_COMPLETED");
+            // We removed "persisted" from UploadJob scheduling, instead we will manually schedule
+            // the job on boot, because "persisted" seems more fragile.
+            JobSchedulingUtils.scheduleUploadJob(context);
+        }
+    }
+}
diff --git a/tests/BugReportApp/src/com/android/car/bugreport/Status.java b/tests/BugReportApp/src/com/android/car/bugreport/Status.java
new file mode 100644
index 0000000..84250bb
--- /dev/null
+++ b/tests/BugReportApp/src/com/android/car/bugreport/Status.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.car.bugreport;
+
+/** Defines {@link MetaBugReport} statuses. */
+public enum Status {
+    // Bugreport is being written
+    STATUS_WRITE_PENDING(0),
+
+    // Writing bugreport failed
+    STATUS_WRITE_FAILED(1),
+
+    // Bugreport is waiting to be uploaded
+    STATUS_UPLOAD_PENDING(2),
+
+    // Bugreport uploaded successfully
+    STATUS_UPLOAD_SUCCESS(3),
+
+    // Bugreport failed to upload
+    STATUS_UPLOAD_FAILED(4),
+
+    // Bugreport is cancelled by user
+    STATUS_USER_CANCELLED(5),
+
+    // Bugreport is pending user choice on whether to upload or copy.
+    STATUS_PENDING_USER_ACTION(6),
+
+    // Bugreport was moved successfully.
+    STATUS_MOVE_SUCCESSFUL(7),
+
+    // Bugreport move has failed.
+    STATUS_MOVE_FAILED(8),
+
+    // Bugreport is moving to USB drive.
+    STATUS_MOVE_IN_PROGRESS(9),
+
+    // Bugreport is expired. Associated file is deleted from the disk.
+    STATUS_EXPIRED(10),
+
+    // Bugreport needs audio message.
+    STATUS_AUDIO_PENDING(11);
+
+    private final int mValue;
+
+    Status(int value) {
+        mValue = value;
+    }
+
+    /** Returns integer value of the status. */
+    public int getValue() {
+        return mValue;
+    }
+
+    /** Generates human-readable string from a status value. */
+    public static String toString(int value) {
+        switch (value) {
+            case 0:
+                return "Write pending";
+            case 1:
+                return "Write failed";
+            case 2:
+                return "Upload pending";
+            case 3:
+                return "Upload successful";
+            case 4:
+                return "Upload failed";
+            case 5:
+                return "User cancelled";
+            case 6:
+                return "Pending user action";
+            case 7:
+                return "Move successful";
+            case 8:
+                return "Move failed";
+            case 9:
+                return "Move in progress";
+            case 10:
+                return "Expired";
+            case 11:
+                return "Audio message pending";
+        }
+        return "unknown";
+    }
+}
diff --git a/tests/BugReportApp/src/com/android/car/bugreport/UploadJob.java b/tests/BugReportApp/src/com/android/car/bugreport/UploadJob.java
new file mode 100644
index 0000000..a0b9cec
--- /dev/null
+++ b/tests/BugReportApp/src/com/android/car/bugreport/UploadJob.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.car.bugreport;
+
+import android.app.job.JobParameters;
+import android.app.job.JobService;
+import android.util.Log;
+
+/** Executes a {@link SimpleUploaderAsyncTask}. */
+public class UploadJob extends JobService {
+    private static final String TAG = UploadJob.class.getSimpleName();
+
+    private SimpleUploaderAsyncTask mUploader;
+
+    @Override
+    public boolean onStartJob(final JobParameters jobParameters) {
+        if (!Config.isBugReportEnabled()) {
+            return false;
+        }
+        Log.v(TAG, "Starting upload job");
+        mUploader = new SimpleUploaderAsyncTask(
+                this, reschedule -> jobFinished(jobParameters, reschedule));
+        mUploader.execute();
+        return true;
+    }
+
+    @Override
+    public boolean onStopJob(JobParameters jobParameters) {
+        mUploader.cancel(true);
+        return false;
+    }
+}
diff --git a/tests/BugReportApp/src/com/android/car/bugreport/VoiceRecordingView.java b/tests/BugReportApp/src/com/android/car/bugreport/VoiceRecordingView.java
new file mode 100644
index 0000000..32e66c4
--- /dev/null
+++ b/tests/BugReportApp/src/com/android/car/bugreport/VoiceRecordingView.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.car.bugreport;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.graphics.drawable.BitmapDrawable;
+import android.media.MediaRecorder;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.View;
+
+/**
+ * A view that draws MIC icon and an animated ellipsoid. The ellipsoid animation shows the sound
+ * amplitude from {@link MediaRecorder}.
+ *
+ * <p>All the constant values are chosen experimentally.
+ */
+public class VoiceRecordingView extends View {
+    private static final String TAG = VoiceRecordingView.class.getSimpleName();
+
+    private static final float DROPOFF_STEP = 10f;
+    private static final long ANIMATION_INTERVAL_MS = 70;
+    private static final float RECORDER_AMPLITUDE_NORMALIZER_COEF = 16192.0f;
+
+    private final Paint mPaint;
+    private final BitmapDrawable mMicIconDrawable;
+
+    private float mCurrentRadius;
+    private MediaRecorder mRecorder;
+
+    public VoiceRecordingView(Context context, @Nullable AttributeSet attrs) {
+        super(context, attrs);
+        mMicIconDrawable = (BitmapDrawable) context.getDrawable(
+                android.R.drawable.ic_btn_speak_now);
+
+        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+        mPaint.setColor(Color.LTGRAY);
+        mPaint.setStyle(Paint.Style.FILL);
+    }
+
+    /** Sets MediaRecorder that will be used to animate the ellipsoid. */
+    public void setRecorder(@Nullable MediaRecorder recorder) {
+        mRecorder = recorder;
+        invalidate();
+    }
+
+    @Override
+    protected void onSizeChanged(int w, int h, int oldW, int oldH) {
+        super.onSizeChanged(w, h, oldW, oldH);
+
+        float micIconWidth = mMicIconDrawable.getBitmap().getWidth();
+        float micIconHeight = mMicIconDrawable.getBitmap().getHeight();
+        int micIconDrawableWidth = (int) (micIconWidth / micIconHeight * h);
+        int micIconDrawableLeft = (w - micIconDrawableWidth) / 2;
+        mMicIconDrawable.setBounds(
+                new Rect(micIconDrawableLeft, 0, micIconDrawableLeft + micIconDrawableWidth, h));
+    }
+
+    private void updateCurrentRadius(int width) {
+        final float maxRadius = width / 4;
+        float radius = 0;
+
+        if (mRecorder != null) {
+            try {
+                radius += maxRadius * mRecorder.getMaxAmplitude()
+                        / RECORDER_AMPLITUDE_NORMALIZER_COEF;
+            } catch (IllegalStateException e) {
+                Log.v(TAG, "Failed to get max amplitude from MediaRecorder");
+            }
+        }
+
+        if (radius > mCurrentRadius) {
+            mCurrentRadius = radius;
+        } else {
+            mCurrentRadius = Math.max(radius, mCurrentRadius - DROPOFF_STEP);
+        }
+        mCurrentRadius = Math.min(maxRadius, mCurrentRadius);
+    }
+
+    @Override
+    protected void onDraw(Canvas canvas) {
+        super.onDraw(canvas);
+
+        final int width = canvas.getWidth();
+        final int height = canvas.getHeight();
+
+        updateCurrentRadius(width);
+
+        // Draws an ellipsoid with horizontal radius calculated from MediaRecorder's amplitude.
+        final int mx = width / 2;
+        final int my = height / 2;
+        canvas.drawCircle(mx, my, height / 2, mPaint);
+        canvas.drawCircle(mx - mCurrentRadius, my, height / 2, mPaint);
+        canvas.drawCircle(mx + mCurrentRadius, my, height / 2, mPaint);
+        canvas.drawRect(mx - mCurrentRadius, 0, mx + mCurrentRadius, height, mPaint);
+
+        if (mRecorder != null) {
+            postInvalidateDelayed(ANIMATION_INTERVAL_MS);
+        }
+
+        mMicIconDrawable.draw(canvas);
+    }
+}
diff --git a/tests/BugReportApp/src/com/android/car/bugreport/ZipUtils.java b/tests/BugReportApp/src/com/android/car/bugreport/ZipUtils.java
new file mode 100644
index 0000000..4fb4cfc
--- /dev/null
+++ b/tests/BugReportApp/src/com/android/car/bugreport/ZipUtils.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.car.bugreport;
+
+import android.util.Log;
+
+import com.google.common.io.ByteStreams;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Enumeration;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+import java.util.zip.ZipOutputStream;
+
+/** Zip utility functions. */
+final class ZipUtils {
+    private static final String TAG = ZipUtils.class.getSimpleName();
+
+    /** Extracts the contents of a zip file to the zip output stream. */
+    static void extractZippedFileToZipStream(File file, ZipOutputStream zipStream) {
+        if (!file.exists()) {
+            Log.w(TAG, "File " + file + " not found");
+            return;
+        }
+        if (file.length() == 0) {
+            // If there were issues with reading from dumpstate socket, the dumpstate zip
+            // file still might be available in
+            // /data/user_de/0/com.android.shell/files/bugreports/.
+            Log.w(TAG, "Zip file " + file.getName() + " is empty, skipping.");
+            return;
+        }
+        try (ZipFile zipFile = new ZipFile(file)) {
+            Enumeration<? extends ZipEntry> entries = zipFile.entries();
+            while (entries.hasMoreElements()) {
+                ZipEntry entry = entries.nextElement();
+                try (InputStream stream = zipFile.getInputStream(entry)) {
+                    writeInputStreamToZipStream(entry.getName(), stream, zipStream);
+                }
+            }
+        } catch (IOException e) {
+            Log.w(TAG, "Failed to add " + file + " to zip", e);
+        }
+    }
+
+    /** Adds a file to the zip output stream. */
+    static void addFileToZipStream(File file, ZipOutputStream zipStream) {
+        if (!file.exists()) {
+            Log.w(TAG, "File " + file + " not found");
+            return;
+        }
+        if (file.length() == 0) {
+            Log.w(TAG, "File " + file.getName() + " is empty, skipping.");
+            return;
+        }
+        try (FileInputStream audioInput = new FileInputStream(file)) {
+            writeInputStreamToZipStream(file.getName(), audioInput, zipStream);
+        } catch (IOException e) {
+            Log.w(TAG, "Failed to add " + file + "to the final zip");
+        }
+    }
+
+    /** Writes a new file from input stream to the zip stream. */
+    static void writeInputStreamToZipStream(
+            String filename, InputStream input, ZipOutputStream zipStream) throws IOException {
+        ZipEntry entry = new ZipEntry(filename);
+        zipStream.putNextEntry(entry);
+        ByteStreams.copy(input, zipStream);
+        zipStream.closeEntry();
+    }
+
+    private ZipUtils() {}
+}
diff --git a/tests/BugReportApp/src/com/google/android/car/bugreport/BugInfoAdapter.java b/tests/BugReportApp/src/com/google/android/car/bugreport/BugInfoAdapter.java
deleted file mode 100644
index 3d01729..0000000
--- a/tests/BugReportApp/src/com/google/android/car/bugreport/BugInfoAdapter.java
+++ /dev/null
@@ -1,184 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.android.car.bugreport;
-
-import android.os.Build;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.Button;
-import android.widget.TextView;
-
-import androidx.recyclerview.widget.RecyclerView;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Shows bugreport title, status, status message and user action buttons. "Upload to Google" button
- * is enabled when the status is {@link Status#STATUS_PENDING_USER_ACTION}, "Move to USB" button is
- * enabled only when status is  {@link Status#STATUS_PENDING_USER_ACTION} and USB device is plugged
- * in.
- */
-public class BugInfoAdapter extends RecyclerView.Adapter<BugInfoAdapter.BugInfoViewHolder> {
-    static final int BUTTON_TYPE_UPLOAD = 0;
-    static final int BUTTON_TYPE_MOVE = 1;
-    static final int BUTTON_TYPE_ADD_AUDIO = 2;
-
-    /** Provides a handler for click events*/
-    interface ItemClickedListener {
-        /**
-         * Handles click events differently depending on provided buttonType and
-         * uses additional information provided in metaBugReport.
-         *
-         * @param buttonType One of {@link #BUTTON_TYPE_UPLOAD}, {@link #BUTTON_TYPE_MOVE} or
-         *                   {@link #BUTTON_TYPE_ADD_AUDIO}.
-         * @param metaBugReport Selected bugreport.
-         * @param holder ViewHolder of the clicked item.
-         */
-        void onItemClicked(int buttonType, MetaBugReport metaBugReport, BugInfoViewHolder holder);
-    }
-
-    /**
-     * Reference to each bug report info views.
-     */
-    static class BugInfoViewHolder extends RecyclerView.ViewHolder {
-        /** Title view */
-        TextView mTitleView;
-
-        /** Status View */
-        TextView mStatusView;
-
-        /** Message View */
-        TextView mMessageView;
-
-        /** Move Button */
-        Button mMoveButton;
-
-        /** Upload Button */
-        Button mUploadButton;
-
-        /** Add Audio Button */
-        Button mAddAudioButton;
-
-        BugInfoViewHolder(View v) {
-            super(v);
-            mTitleView = itemView.findViewById(R.id.bug_info_row_title);
-            mStatusView = itemView.findViewById(R.id.bug_info_row_status);
-            mMessageView = itemView.findViewById(R.id.bug_info_row_message);
-            mMoveButton = itemView.findViewById(R.id.bug_info_move_button);
-            mUploadButton = itemView.findViewById(R.id.bug_info_upload_button);
-            mAddAudioButton = itemView.findViewById(R.id.bug_info_add_audio_button);
-        }
-    }
-
-    private List<MetaBugReport> mDataset;
-    private final ItemClickedListener mItemClickedListener;
-    private final Config mConfig;
-
-    BugInfoAdapter(ItemClickedListener itemClickedListener, Config config) {
-        mItemClickedListener = itemClickedListener;
-        mDataset = new ArrayList<>();
-        mConfig = config;
-        // Allow RecyclerView to efficiently update UI; getItemId() is implemented below.
-        setHasStableIds(true);
-    }
-
-    @Override
-    public BugInfoViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
-        // create a new view
-        View v = LayoutInflater.from(parent.getContext())
-                .inflate(R.layout.bug_info_view, parent, false);
-        return new BugInfoViewHolder(v);
-    }
-
-    @Override
-    public void onBindViewHolder(BugInfoViewHolder holder, int position) {
-        MetaBugReport bugreport = mDataset.get(position);
-        holder.mTitleView.setText(bugreport.getTitle());
-        holder.mStatusView.setText(Status.toString(bugreport.getStatus()));
-        holder.mMessageView.setText(bugreport.getStatusMessage());
-        if (bugreport.getStatusMessage().isEmpty()) {
-            holder.mMessageView.setVisibility(View.GONE);
-        } else {
-            holder.mMessageView.setVisibility(View.VISIBLE);
-        }
-        boolean enableUserActionButtons =
-                bugreport.getStatus() == Status.STATUS_PENDING_USER_ACTION.getValue()
-                        || bugreport.getStatus() == Status.STATUS_MOVE_FAILED.getValue()
-                        || bugreport.getStatus() == Status.STATUS_UPLOAD_FAILED.getValue();
-        if (enableUserActionButtons) {
-            holder.mMoveButton.setEnabled(true);
-            holder.mMoveButton.setVisibility(View.VISIBLE);
-            holder.mMoveButton.setOnClickListener(
-                    view -> mItemClickedListener.onItemClicked(BUTTON_TYPE_MOVE, bugreport,
-                            holder));
-        } else {
-            holder.mMoveButton.setEnabled(false);
-            holder.mMoveButton.setVisibility(View.GONE);
-        }
-        // Enable the upload button only for userdebug/eng builds.
-        if (enableUserActionButtons && Build.IS_DEBUGGABLE) {
-            holder.mUploadButton.setText(R.string.bugreport_upload_gcs_button_text);
-            holder.mUploadButton.setEnabled(true);
-            holder.mUploadButton.setVisibility(View.VISIBLE);
-            holder.mUploadButton.setOnClickListener(
-                    view -> mItemClickedListener.onItemClicked(BUTTON_TYPE_UPLOAD, bugreport,
-                            holder));
-        } else {
-            holder.mUploadButton.setVisibility(View.GONE);
-            holder.mUploadButton.setEnabled(false);
-        }
-        if (bugreport.getStatus() == Status.STATUS_AUDIO_PENDING.getValue()) {
-            if (mConfig.getAutoUpload()) {
-                holder.mAddAudioButton.setText(R.string.bugreport_add_audio_upload_button_text);
-            } else {
-                holder.mAddAudioButton.setText(R.string.bugreport_add_audio_button_text);
-            }
-            holder.mAddAudioButton.setEnabled(true);
-            holder.mAddAudioButton.setVisibility(View.VISIBLE);
-            holder.mAddAudioButton.setOnClickListener(view ->
-                    mItemClickedListener.onItemClicked(BUTTON_TYPE_ADD_AUDIO, bugreport, holder));
-        } else {
-            holder.mAddAudioButton.setEnabled(false);
-            holder.mAddAudioButton.setVisibility(View.GONE);
-        }
-    }
-
-    /** Sets dataSet; it copies the list, because it modifies it in this adapter. */
-    void setDataset(List<MetaBugReport> bugReports) {
-        mDataset = new ArrayList<>(bugReports);
-        notifyDataSetChanged();
-    }
-
-    /** Update a bug report in the data set. */
-    void updateBugReportInDataSet(MetaBugReport bugReport, int position) {
-        if (position != RecyclerView.NO_POSITION) {
-            mDataset.set(position, bugReport);
-            notifyItemChanged(position);
-        }
-    }
-
-    @Override
-    public long getItemId(int position) {
-        return mDataset.get(position).getId();
-    }
-
-    @Override
-    public int getItemCount() {
-        return mDataset.size();
-    }
-}
diff --git a/tests/BugReportApp/src/com/google/android/car/bugreport/BugReportActivity.java b/tests/BugReportApp/src/com/google/android/car/bugreport/BugReportActivity.java
deleted file mode 100644
index 20fe5f9..0000000
--- a/tests/BugReportApp/src/com/google/android/car/bugreport/BugReportActivity.java
+++ /dev/null
@@ -1,713 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.android.car.bugreport;
-
-import static com.google.android.car.bugreport.BugReportService.MAX_PROGRESS_VALUE;
-
-import android.Manifest;
-import android.app.Activity;
-import android.car.Car;
-import android.car.CarNotConnectedException;
-import android.car.drivingstate.CarDrivingStateEvent;
-import android.car.drivingstate.CarDrivingStateManager;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.ServiceConnection;
-import android.content.pm.PackageManager;
-import android.media.AudioAttributes;
-import android.media.AudioFocusRequest;
-import android.media.AudioManager;
-import android.media.MediaRecorder;
-import android.os.AsyncTask;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.Looper;
-import android.os.UserManager;
-import android.util.Log;
-import android.view.View;
-import android.view.Window;
-import android.widget.Button;
-import android.widget.ProgressBar;
-import android.widget.TextView;
-import android.widget.Toast;
-
-import com.google.common.base.Preconditions;
-import com.google.common.io.ByteStreams;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.util.Arrays;
-import java.util.Date;
-import java.util.Random;
-
-/**
- * Activity that shows two types of dialogs: starting a new bug report and current status of already
- * in progress bug report.
- *
- * <p>If there is no in-progress bug report, it starts recording voice message. After clicking
- * submit button it initiates {@link BugReportService}.
- *
- * <p>If bug report is in-progress, it shows a progress bar.
- */
-public class BugReportActivity extends Activity {
-    private static final String TAG = BugReportActivity.class.getSimpleName();
-
-    /** Starts silent (no audio message recording) bugreporting. */
-    private static final String ACTION_START_SILENT =
-            "com.google.android.car.bugreport.action.START_SILENT";
-
-    /** This is deprecated action. Please start SILENT bugreport using {@link BugReportService}. */
-    private static final String ACTION_ADD_AUDIO =
-            "com.google.android.car.bugreport.action.ADD_AUDIO";
-
-    private static final int VOICE_MESSAGE_MAX_DURATION_MILLIS = 60 * 1000;
-    private static final int AUDIO_PERMISSIONS_REQUEST_ID = 1;
-
-    private static final String EXTRA_BUGREPORT_ID = "bugreport-id";
-
-    /**
-     * NOTE: mRecorder related messages are cleared when the activity finishes.
-     */
-    private final Handler mHandler = new Handler(Looper.getMainLooper());
-
-    /** Look up string length, e.g. [ABCDEF]. */
-    static final int LOOKUP_STRING_LENGTH = 6;
-
-    private TextView mInProgressTitleText;
-    private ProgressBar mProgressBar;
-    private TextView mProgressText;
-    private TextView mAddAudioText;
-    private VoiceRecordingView mVoiceRecordingView;
-    private View mVoiceRecordingFinishedView;
-    private View mSubmitBugReportLayout;
-    private View mInProgressLayout;
-    private View mShowBugReportsButton;
-    private Button mSubmitButton;
-
-    private boolean mBound;
-    /** Audio message recording process started (including waiting for permission). */
-    private boolean mAudioRecordingStarted;
-    /** Audio recording using MIC is running (permission given). */
-    private boolean mAudioRecordingIsRunning;
-    private boolean mIsNewBugReport;
-    private boolean mIsOnActivityStartedWithBugReportServiceBoundCalled;
-    private boolean mIsSubmitButtonClicked;
-    private BugReportService mService;
-    private MediaRecorder mRecorder;
-    private MetaBugReport mMetaBugReport;
-    private File mAudioFile;
-    private Car mCar;
-    private CarDrivingStateManager mDrivingStateManager;
-    private AudioManager mAudioManager;
-    private AudioFocusRequest mLastAudioFocusRequest;
-    private Config mConfig;
-
-    /** Defines callbacks for service binding, passed to bindService() */
-    private ServiceConnection mConnection = new ServiceConnection() {
-        @Override
-        public void onServiceConnected(ComponentName className, IBinder service) {
-            BugReportService.ServiceBinder binder = (BugReportService.ServiceBinder) service;
-            mService = binder.getService();
-            mBound = true;
-            onActivityStartedWithBugReportServiceBound();
-        }
-
-        @Override
-        public void onServiceDisconnected(ComponentName arg0) {
-            // called when service connection breaks unexpectedly.
-            mBound = false;
-        }
-    };
-
-    /**
-     * Builds an intent that starts {@link BugReportActivity} to add audio message to the existing
-     * bug report.
-     */
-    static Intent buildAddAudioIntent(Context context, MetaBugReport bug) {
-        Intent addAudioIntent = new Intent(context, BugReportActivity.class);
-        addAudioIntent.setAction(ACTION_ADD_AUDIO);
-        addAudioIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
-        addAudioIntent.putExtra(EXTRA_BUGREPORT_ID, bug.getId());
-        return addAudioIntent;
-    }
-
-    @Override
-    public void onCreate(Bundle savedInstanceState) {
-        Preconditions.checkState(Config.isBugReportEnabled(), "BugReport is disabled.");
-
-        super.onCreate(savedInstanceState);
-        requestWindowFeature(Window.FEATURE_NO_TITLE);
-
-        // Bind to BugReportService.
-        Intent intent = new Intent(this, BugReportService.class);
-        bindService(intent, mConnection, BIND_AUTO_CREATE);
-    }
-
-    @Override
-    protected void onStart() {
-        super.onStart();
-
-        if (mBound) {
-            onActivityStartedWithBugReportServiceBound();
-        }
-    }
-
-    @Override
-    protected void onStop() {
-        super.onStop();
-        // If SUBMIT button is clicked, cancelling audio has been taken care of.
-        if (!mIsSubmitButtonClicked) {
-            cancelAudioMessageRecording();
-        }
-        if (mBound) {
-            mService.removeBugReportProgressListener();
-        }
-        // Reset variables for the next onStart().
-        mAudioRecordingStarted = false;
-        mAudioRecordingIsRunning = false;
-        mIsSubmitButtonClicked = false;
-        mIsOnActivityStartedWithBugReportServiceBoundCalled = false;
-        mMetaBugReport = null;
-        mAudioFile = null;
-    }
-
-    @Override
-    public void onDestroy() {
-        super.onDestroy();
-
-        if (mRecorder != null) {
-            mHandler.removeCallbacksAndMessages(/* token= */ mRecorder);
-        }
-        if (mBound) {
-            unbindService(mConnection);
-            mBound = false;
-        }
-        if (mCar != null && mCar.isConnected()) {
-            mCar.disconnect();
-            mCar = null;
-        }
-    }
-
-    private void onCarDrivingStateChanged(CarDrivingStateEvent event) {
-        if (mShowBugReportsButton == null) {
-            Log.w(TAG, "Cannot handle driving state change, UI is not ready");
-            return;
-        }
-        // When adding audio message to the existing bugreport, do not show "Show Bug Reports"
-        // button, users either should explicitly Submit or Cancel.
-        if (mAudioRecordingStarted && !mIsNewBugReport) {
-            mShowBugReportsButton.setVisibility(View.GONE);
-            return;
-        }
-        if (event.eventValue == CarDrivingStateEvent.DRIVING_STATE_PARKED
-                || event.eventValue == CarDrivingStateEvent.DRIVING_STATE_IDLING) {
-            mShowBugReportsButton.setVisibility(View.VISIBLE);
-        } else {
-            mShowBugReportsButton.setVisibility(View.GONE);
-        }
-    }
-
-    private void onProgressChanged(float progress) {
-        int progressValue = (int) progress;
-        mProgressBar.setProgress(progressValue);
-        mProgressText.setText(progressValue + "%");
-        if (progressValue == MAX_PROGRESS_VALUE) {
-            mInProgressTitleText.setText(R.string.bugreport_dialog_in_progress_title_finished);
-        }
-    }
-
-    private void prepareUi() {
-        if (mSubmitBugReportLayout != null) {
-            return;
-        }
-        setContentView(R.layout.bug_report_activity);
-
-        // Connect to the services here, because they are used only when showing the dialog.
-        // We need to minimize system state change when performing SILENT bug report.
-        mConfig = new Config();
-        mConfig.start();
-        mCar = Car.createCar(this, /* handler= */ null,
-                Car.CAR_WAIT_TIMEOUT_DO_NOT_WAIT, this::onCarLifecycleChanged);
-
-        mInProgressTitleText = findViewById(R.id.in_progress_title_text);
-        mProgressBar = findViewById(R.id.progress_bar);
-        mProgressText = findViewById(R.id.progress_text);
-        mAddAudioText = findViewById(R.id.bug_report_add_audio_to_existing);
-        mVoiceRecordingView = findViewById(R.id.voice_recording_view);
-        mVoiceRecordingFinishedView = findViewById(R.id.voice_recording_finished_text_view);
-        mSubmitBugReportLayout = findViewById(R.id.submit_bug_report_layout);
-        mInProgressLayout = findViewById(R.id.in_progress_layout);
-        mShowBugReportsButton = findViewById(R.id.button_show_bugreports);
-        mSubmitButton = findViewById(R.id.button_submit);
-
-        mShowBugReportsButton.setOnClickListener(this::buttonShowBugReportsClick);
-        mSubmitButton.setOnClickListener(this::buttonSubmitClick);
-        findViewById(R.id.button_cancel).setOnClickListener(this::buttonCancelClick);
-        findViewById(R.id.button_close).setOnClickListener(this::buttonCancelClick);
-
-        if (mIsNewBugReport) {
-            mSubmitButton.setText(R.string.bugreport_dialog_submit);
-        } else {
-            mSubmitButton.setText(mConfig.getAutoUpload()
-                    ? R.string.bugreport_dialog_upload : R.string.bugreport_dialog_save);
-        }
-    }
-
-    private void onCarLifecycleChanged(Car car, boolean ready) {
-        if (!ready) {
-            mDrivingStateManager = null;
-            mCar = null;
-            Log.d(TAG, "Car service is not ready, ignoring");
-            // If car service is not ready for this activity, just ignore it - as it's only
-            // used to control UX restrictions.
-            return;
-        }
-        try {
-            mDrivingStateManager = (CarDrivingStateManager) car.getCarManager(
-                    Car.CAR_DRIVING_STATE_SERVICE);
-            mDrivingStateManager.registerListener(
-                    BugReportActivity.this::onCarDrivingStateChanged);
-            // Call onCarDrivingStateChanged(), because it's not called when Car is connected.
-            onCarDrivingStateChanged(mDrivingStateManager.getCurrentCarDrivingState());
-        } catch (CarNotConnectedException e) {
-            Log.w(TAG, "Failed to get CarDrivingStateManager", e);
-        }
-    }
-
-    private void showInProgressUi() {
-        mSubmitBugReportLayout.setVisibility(View.GONE);
-        mInProgressLayout.setVisibility(View.VISIBLE);
-        mInProgressTitleText.setText(R.string.bugreport_dialog_in_progress_title);
-        onProgressChanged(mService.getBugReportProgress());
-    }
-
-    private void showSubmitBugReportUi(boolean isRecording) {
-        mSubmitBugReportLayout.setVisibility(View.VISIBLE);
-        mInProgressLayout.setVisibility(View.GONE);
-        if (isRecording) {
-            mVoiceRecordingFinishedView.setVisibility(View.GONE);
-            mVoiceRecordingView.setVisibility(View.VISIBLE);
-        } else {
-            mVoiceRecordingFinishedView.setVisibility(View.VISIBLE);
-            mVoiceRecordingView.setVisibility(View.GONE);
-        }
-        // NOTE: mShowBugReportsButton visibility is also handled in #onCarDrivingStateChanged().
-        mShowBugReportsButton.setVisibility(View.GONE);
-        if (mDrivingStateManager != null) {
-            try {
-                onCarDrivingStateChanged(mDrivingStateManager.getCurrentCarDrivingState());
-            } catch (CarNotConnectedException e) {
-                Log.e(TAG, "Failed to get current driving state.", e);
-            }
-        }
-    }
-
-    /**
-     * Initializes MetaBugReport in a local DB and starts audio recording.
-     *
-     * <p>This method expected to be called when the activity is started and bound to the service.
-     */
-    private void onActivityStartedWithBugReportServiceBound() {
-        if (mIsOnActivityStartedWithBugReportServiceBoundCalled) {
-            return;
-        }
-        mIsOnActivityStartedWithBugReportServiceBoundCalled = true;
-
-        if (mService.isCollectingBugReport()) {
-            Log.i(TAG, "Bug report is already being collected.");
-            mService.setBugReportProgressListener(this::onProgressChanged);
-            prepareUi();
-            showInProgressUi();
-            return;
-        }
-
-        if (ACTION_START_SILENT.equals(getIntent().getAction())) {
-            Log.i(TAG, "Starting a silent bugreport.");
-            MetaBugReport bugReport = createBugReport(this, MetaBugReport.TYPE_SILENT);
-            startBugReportCollection(bugReport);
-            finish();
-            return;
-        }
-
-        // Close the notification shade and other dialogs when showing BugReportActivity dialog.
-        sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS));
-
-        if (ACTION_ADD_AUDIO.equals(getIntent().getAction())) {
-            addAudioToExistingBugReport(
-                    getIntent().getIntExtra(EXTRA_BUGREPORT_ID, /* defaultValue= */ -1));
-            return;
-        }
-
-        Log.i(TAG, "Starting an interactive bugreport.");
-        createNewBugReportWithAudioMessage();
-    }
-
-    private void addAudioToExistingBugReport(int bugreportId) {
-        MetaBugReport bug = BugStorageUtils.findBugReport(this, bugreportId).orElseThrow(
-                () -> new RuntimeException("Failed to find bug report with id " + bugreportId));
-        Log.i(TAG, "Adding audio to the existing bugreport " + bug.getTimestamp());
-        if (bug.getStatus() != Status.STATUS_AUDIO_PENDING.getValue()) {
-            Log.e(TAG, "Failed to add audio, bad status, expected "
-                    + Status.STATUS_AUDIO_PENDING.getValue() + ", got " + bug.getStatus());
-            finish();
-        }
-        File audioFile;
-        try {
-            audioFile = File.createTempFile("audio", "mp3", getCacheDir());
-        } catch (IOException e) {
-            throw new RuntimeException("failed to create temp audio file");
-        }
-        startAudioMessageRecording(/* isNewBugReport= */ false, bug, audioFile);
-    }
-
-    private void createNewBugReportWithAudioMessage() {
-        MetaBugReport bug = createBugReport(this, MetaBugReport.TYPE_INTERACTIVE);
-        startAudioMessageRecording(
-                /* isNewBugReport= */ true,
-                bug,
-                FileUtils.getFileWithSuffix(this, bug.getTimestamp(), "-message.3gp"));
-    }
-
-    /** Shows a dialog UI and starts recording audio message. */
-    private void startAudioMessageRecording(
-            boolean isNewBugReport, MetaBugReport bug, File audioFile) {
-        if (mAudioRecordingStarted) {
-            Log.i(TAG, "Audio message recording is already started.");
-            return;
-        }
-        mAudioRecordingStarted = true;
-        mAudioManager = getSystemService(AudioManager.class);
-        mIsNewBugReport = isNewBugReport;
-        mMetaBugReport = bug;
-        mAudioFile = audioFile;
-        prepareUi();
-        showSubmitBugReportUi(/* isRecording= */ true);
-        if (isNewBugReport) {
-            mAddAudioText.setVisibility(View.GONE);
-        } else {
-            mAddAudioText.setVisibility(View.VISIBLE);
-            mAddAudioText.setText(String.format(
-                    getString(R.string.bugreport_dialog_add_audio_to_existing),
-                    mMetaBugReport.getTimestamp()));
-        }
-
-        if (!hasRecordPermissions()) {
-            requestRecordPermissions();
-        } else {
-            startRecordingWithPermission();
-        }
-    }
-
-    /**
-     * Cancels bugreporting by stopping audio recording and deleting temp files.
-     */
-    private void cancelAudioMessageRecording() {
-        // If audio recording is not running, most likely there were permission issues,
-        // so leave the bugreport as is without cancelling it.
-        if (!mAudioRecordingIsRunning) {
-            Log.w(TAG, "Cannot cancel, audio recording is not running.");
-            return;
-        }
-        stopAudioRecording();
-        if (mIsNewBugReport) {
-            // The app creates a temp dir only for new INTERACTIVE bugreports.
-            File tempDir = FileUtils.getTempDir(this, mMetaBugReport.getTimestamp());
-            new DeleteFilesAndDirectoriesAsyncTask().execute(tempDir);
-        } else {
-            BugStorageUtils.deleteBugReportFiles(this, mMetaBugReport.getId());
-            new DeleteFilesAndDirectoriesAsyncTask().execute(mAudioFile);
-        }
-        BugStorageUtils.setBugReportStatus(
-                this, mMetaBugReport, Status.STATUS_USER_CANCELLED, "");
-        Log.i(TAG, "Bug report " + mMetaBugReport.getTimestamp() + " is cancelled");
-        mAudioRecordingStarted = false;
-        mAudioRecordingIsRunning = false;
-    }
-
-    private void buttonCancelClick(View view) {
-        finish();
-    }
-
-    private void buttonSubmitClick(View view) {
-        stopAudioRecording();
-        mIsSubmitButtonClicked = true;
-        if (mIsNewBugReport) {
-            Log.i(TAG, "Starting bugreport service.");
-            startBugReportCollection(mMetaBugReport);
-        } else {
-            Log.i(TAG, "Adding audio file to the bugreport " + mMetaBugReport.getTimestamp());
-            new AddAudioToBugReportAsyncTask(this, mConfig, mMetaBugReport, mAudioFile).execute();
-        }
-        setResult(Activity.RESULT_OK);
-        finish();
-    }
-
-    /** Starts the {@link BugReportService} to collect bug report. */
-    private void startBugReportCollection(MetaBugReport bug) {
-        Bundle bundle = new Bundle();
-        bundle.putParcelable(BugReportService.EXTRA_META_BUG_REPORT, bug);
-        Intent intent = new Intent(this, BugReportService.class);
-        intent.putExtras(bundle);
-        startForegroundService(intent);
-    }
-
-    /**
-     * Starts {@link BugReportInfoActivity} and finishes current activity, so it won't be running
-     * in the background and closing {@link BugReportInfoActivity} will not open the current
-     * activity again.
-     */
-    private void buttonShowBugReportsClick(View view) {
-        // First cancel the audio recording, then delete the bug report from database.
-        cancelAudioMessageRecording();
-        // Delete the bugreport from database, otherwise pressing "Show Bugreports" button will
-        // create unnecessary cancelled bugreports.
-        if (mMetaBugReport != null) {
-            BugStorageUtils.completeDeleteBugReport(this, mMetaBugReport.getId());
-        }
-        Intent intent = new Intent(this, BugReportInfoActivity.class);
-        startActivity(intent);
-        finish();
-    }
-
-    private void requestRecordPermissions() {
-        requestPermissions(
-                new String[]{Manifest.permission.RECORD_AUDIO}, AUDIO_PERMISSIONS_REQUEST_ID);
-    }
-
-    private boolean hasRecordPermissions() {
-        return checkSelfPermission(Manifest.permission.RECORD_AUDIO)
-                == PackageManager.PERMISSION_GRANTED;
-    }
-
-    @Override
-    public void onRequestPermissionsResult(
-            int requestCode, String[] permissions, int[] grantResults) {
-        if (requestCode != AUDIO_PERMISSIONS_REQUEST_ID) {
-            return;
-        }
-        for (int i = 0; i < grantResults.length; i++) {
-            if (Manifest.permission.RECORD_AUDIO.equals(permissions[i])
-                    && grantResults[i] == PackageManager.PERMISSION_GRANTED) {
-                // Start recording from UI thread, otherwise when MediaRecord#start() fails,
-                // stack trace gets confusing.
-                mHandler.post(this::startRecordingWithPermission);
-                return;
-            }
-        }
-        handleNoPermission(permissions);
-    }
-
-    private void handleNoPermission(String[] permissions) {
-        String text = this.getText(R.string.toast_permissions_denied) + " : "
-                + Arrays.toString(permissions);
-        Log.w(TAG, text);
-        Toast.makeText(this, text, Toast.LENGTH_LONG).show();
-        if (mIsNewBugReport) {
-            BugStorageUtils.setBugReportStatus(this, mMetaBugReport,
-                    Status.STATUS_USER_CANCELLED, text);
-        } else {
-            BugStorageUtils.setBugReportStatus(this, mMetaBugReport,
-                    Status.STATUS_AUDIO_PENDING, text);
-        }
-        finish();
-    }
-
-    private void startRecordingWithPermission() {
-        Log.i(TAG, "Started voice recording, and saving audio to " + mAudioFile);
-
-        mLastAudioFocusRequest = new AudioFocusRequest.Builder(
-                        AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE)
-                .setOnAudioFocusChangeListener(focusChange ->
-                        Log.d(TAG, "AudioManager focus change " + focusChange))
-                .setAudioAttributes(new AudioAttributes.Builder()
-                        .setUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION)
-                        .setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
-                        .build())
-                .setAcceptsDelayedFocusGain(true)
-                .build();
-        int focusGranted = mAudioManager.requestAudioFocus(mLastAudioFocusRequest);
-        // NOTE: We will record even if the audio focus was not granted.
-        Log.d(TAG,
-                "AudioFocus granted " + (focusGranted == AudioManager.AUDIOFOCUS_REQUEST_GRANTED));
-
-        mRecorder = new MediaRecorder();
-        mRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
-        mRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
-        mRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
-        mRecorder.setOnInfoListener((MediaRecorder recorder, int what, int extra) ->
-                Log.i(TAG, "OnMediaRecorderInfo: what=" + what + ", extra=" + extra));
-        mRecorder.setOnErrorListener((MediaRecorder recorder, int what, int extra) ->
-                Log.i(TAG, "OnMediaRecorderError: what=" + what + ", extra=" + extra));
-        mRecorder.setOutputFile(mAudioFile);
-
-        try {
-            mRecorder.prepare();
-        } catch (IOException e) {
-            Log.e(TAG, "Failed on MediaRecorder#prepare(), filename: " + mAudioFile, e);
-            finish();
-            return;
-        }
-
-        mRecorder.start();
-        mVoiceRecordingView.setRecorder(mRecorder);
-        mAudioRecordingIsRunning = true;
-
-        // Messages with token mRecorder are cleared when the activity finishes or recording stops.
-        mHandler.postDelayed(() -> {
-            Log.i(TAG, "Timed out while recording voice message, cancelling.");
-            stopAudioRecording();
-            showSubmitBugReportUi(/* isRecording= */ false);
-        }, /* token= */ mRecorder, VOICE_MESSAGE_MAX_DURATION_MILLIS);
-    }
-
-    private void stopAudioRecording() {
-        if (mRecorder != null) {
-            Log.i(TAG, "Recording ended, stopping the MediaRecorder.");
-            mHandler.removeCallbacksAndMessages(/* token= */ mRecorder);
-            try {
-                mRecorder.stop();
-            } catch (RuntimeException e) {
-                // Sometimes MediaRecorder doesn't start and stopping it throws an error.
-                // We just log these cases, no need to crash the app.
-                Log.w(TAG, "Couldn't stop media recorder", e);
-            }
-            mRecorder.release();
-            mRecorder = null;
-        }
-        if (mLastAudioFocusRequest != null) {
-            int focusAbandoned = mAudioManager.abandonAudioFocusRequest(mLastAudioFocusRequest);
-            Log.d(TAG, "Audio focus abandoned "
-                    + (focusAbandoned == AudioManager.AUDIOFOCUS_REQUEST_GRANTED));
-            mLastAudioFocusRequest = null;
-        }
-        mVoiceRecordingView.setRecorder(null);
-    }
-
-    private static String getCurrentUserName(Context context) {
-        UserManager um = UserManager.get(context);
-        return um.getUserName();
-    }
-
-    /**
-     * Creates a {@link MetaBugReport} and saves it in a local sqlite database.
-     *
-     * @param context an Android context.
-     * @param type bug report type, {@link MetaBugReport.BugReportType}.
-     */
-    static MetaBugReport createBugReport(Context context, int type) {
-        String timestamp = MetaBugReport.toBugReportTimestamp(new Date());
-        String username = getCurrentUserName(context);
-        String title = BugReportTitleGenerator.generateBugReportTitle(timestamp, username);
-        return BugStorageUtils.createBugReport(context, title, timestamp, username, type);
-    }
-
-    /** A helper class to generate bugreport title. */
-    private static final class BugReportTitleGenerator {
-        /** Contains easily readable characters. */
-        private static final char[] CHARS_FOR_RANDOM_GENERATOR =
-                new char[]{'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'J', 'K', 'L', 'M', 'N', 'P',
-                        'R', 'S', 'T', 'U', 'W', 'X', 'Y', 'Z'};
-
-        /**
-         * Generates a bugreport title from given timestamp and username.
-         *
-         * <p>Example: "[A45E8] Feedback from user Driver at 2019-09-21_12:00:00"
-         */
-        static String generateBugReportTitle(String timestamp, String username) {
-            // Lookup string is used to search a bug in Buganizer (see b/130915969).
-            String lookupString = generateRandomString(LOOKUP_STRING_LENGTH);
-            return "[" + lookupString + "] Feedback from user " + username + " at " + timestamp;
-        }
-
-        private static String generateRandomString(int length) {
-            Random random = new Random();
-            StringBuilder builder = new StringBuilder();
-            for (int i = 0; i < length; i++) {
-                int randomIndex = random.nextInt(CHARS_FOR_RANDOM_GENERATOR.length);
-                builder.append(CHARS_FOR_RANDOM_GENERATOR[randomIndex]);
-            }
-            return builder.toString();
-        }
-    }
-
-    /** AsyncTask that recursively deletes files and directories. */
-    private static class DeleteFilesAndDirectoriesAsyncTask extends AsyncTask<File, Void, Void> {
-        @Override
-        protected Void doInBackground(File... files) {
-            for (File file : files) {
-                Log.i(TAG, "Deleting " + file.getAbsolutePath());
-                if (file.isFile()) {
-                    file.delete();
-                } else {
-                    FileUtils.deleteDirectory(file);
-                }
-            }
-            return null;
-        }
-    }
-
-    /**
-     * AsyncTask that moves audio file to the system user's {@link FileUtils#getPendingDir} and
-     * sets status to either STATUS_UPLOAD_PENDING or STATUS_PENDING_USER_ACTION.
-     */
-    private static class AddAudioToBugReportAsyncTask extends AsyncTask<Void, Void, Void> {
-        private final Context mContext;
-        private final Config mConfig;
-        private final File mAudioFile;
-        private final MetaBugReport mOriginalBug;
-
-        AddAudioToBugReportAsyncTask(
-                Context context, Config config, MetaBugReport bug, File audioFile) {
-            mContext = context;
-            mConfig = config;
-            mOriginalBug = bug;
-            mAudioFile = audioFile;
-        }
-
-        @Override
-        protected Void doInBackground(Void... voids) {
-            String audioFileName = FileUtils.getAudioFileName(
-                    MetaBugReport.toBugReportTimestamp(new Date()), mOriginalBug);
-            MetaBugReport bug = BugStorageUtils.update(mContext,
-                    mOriginalBug.toBuilder().setAudioFileName(audioFileName).build());
-            try (OutputStream out = BugStorageUtils.openAudioMessageFileToWrite(mContext, bug);
-                 InputStream input = new FileInputStream(mAudioFile)) {
-                ByteStreams.copy(input, out);
-            } catch (IOException e) {
-                BugStorageUtils.setBugReportStatus(mContext, bug,
-                        com.google.android.car.bugreport.Status.STATUS_WRITE_FAILED,
-                        "Failed to write audio to bug report");
-                Log.e(TAG, "Failed to write audio to bug report", e);
-                return null;
-            }
-            if (mConfig.getAutoUpload()) {
-                BugStorageUtils.setBugReportStatus(mContext, bug,
-                        com.google.android.car.bugreport.Status.STATUS_UPLOAD_PENDING, "");
-            } else {
-                BugStorageUtils.setBugReportStatus(mContext, bug,
-                        com.google.android.car.bugreport.Status.STATUS_PENDING_USER_ACTION, "");
-                BugReportService.showBugReportFinishedNotification(mContext, bug);
-            }
-            mAudioFile.delete();
-            return null;
-        }
-    }
-}
diff --git a/tests/BugReportApp/src/com/google/android/car/bugreport/BugReportInfoActivity.java b/tests/BugReportApp/src/com/google/android/car/bugreport/BugReportInfoActivity.java
deleted file mode 100644
index 5503706..0000000
--- a/tests/BugReportApp/src/com/google/android/car/bugreport/BugReportInfoActivity.java
+++ /dev/null
@@ -1,389 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.android.car.bugreport;
-
-import static com.google.android.car.bugreport.PackageUtils.getPackageVersion;
-
-import android.app.Activity;
-import android.app.NotificationManager;
-import android.content.ContentResolver;
-import android.content.Intent;
-import android.content.res.AssetFileDescriptor;
-import android.database.ContentObserver;
-import android.net.Uri;
-import android.os.AsyncTask;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.UserHandle;
-import android.provider.DocumentsContract;
-import android.util.Log;
-import android.view.View;
-import android.widget.TextView;
-
-import androidx.recyclerview.widget.DividerItemDecoration;
-import androidx.recyclerview.widget.LinearLayoutManager;
-import androidx.recyclerview.widget.RecyclerView;
-
-import com.google.common.base.Preconditions;
-import com.google.common.base.Strings;
-import com.google.common.io.ByteStreams;
-
-import java.io.BufferedOutputStream;
-import java.io.File;
-import java.io.FileDescriptor;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.io.PrintWriter;
-import java.lang.ref.WeakReference;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.zip.ZipEntry;
-import java.util.zip.ZipInputStream;
-import java.util.zip.ZipOutputStream;
-
-/**
- * Provides an activity that provides information on the bugreports that are filed.
- */
-public class BugReportInfoActivity extends Activity {
-    public static final String TAG = BugReportInfoActivity.class.getSimpleName();
-
-    /** Used for moving bug reports to a new location (e.g. USB drive). */
-    private static final int SELECT_DIRECTORY_REQUEST_CODE = 1;
-
-    /** Used to start {@link BugReportActivity} to add audio message. */
-    private static final int ADD_AUDIO_MESSAGE_REQUEST_CODE = 2;
-
-    private RecyclerView mRecyclerView;
-    private BugInfoAdapter mBugInfoAdapter;
-    private RecyclerView.LayoutManager mLayoutManager;
-    private NotificationManager mNotificationManager;
-    private MetaBugReport mLastSelectedBugReport;
-    private BugInfoAdapter.BugInfoViewHolder mLastSelectedBugInfoViewHolder;
-    private BugStorageObserver mBugStorageObserver;
-    private Config mConfig;
-    private boolean mAudioRecordingStarted;
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        Preconditions.checkState(Config.isBugReportEnabled(), "BugReport is disabled.");
-
-        super.onCreate(savedInstanceState);
-        setContentView(R.layout.bug_report_info_activity);
-
-        mNotificationManager = getSystemService(NotificationManager.class);
-
-        mRecyclerView = findViewById(R.id.rv_bug_report_info);
-        mRecyclerView.setHasFixedSize(true);
-        // use a linear layout manager
-        mLayoutManager = new LinearLayoutManager(this);
-        mRecyclerView.setLayoutManager(mLayoutManager);
-        mRecyclerView.addItemDecoration(new DividerItemDecoration(mRecyclerView.getContext(),
-                DividerItemDecoration.VERTICAL));
-
-        mConfig = new Config();
-        mConfig.start();
-
-        mBugInfoAdapter = new BugInfoAdapter(this::onBugReportItemClicked, mConfig);
-        mRecyclerView.setAdapter(mBugInfoAdapter);
-
-        mBugStorageObserver = new BugStorageObserver(this, new Handler());
-
-        findViewById(R.id.quit_button).setOnClickListener(this::onQuitButtonClick);
-        findViewById(R.id.start_bug_report_button).setOnClickListener(
-                this::onStartBugReportButtonClick);
-        ((TextView) findViewById(R.id.version_text_view)).setText(
-                String.format("v%s", getPackageVersion(this)));
-
-        cancelBugReportFinishedNotification();
-    }
-
-    @Override
-    protected void onStart() {
-        super.onStart();
-        new BugReportsLoaderAsyncTask(this).execute();
-        // As BugStorageProvider is running under user0, we register using USER_ALL.
-        getContentResolver().registerContentObserver(BugStorageProvider.BUGREPORT_CONTENT_URI, true,
-                mBugStorageObserver, UserHandle.USER_ALL);
-    }
-
-    @Override
-    protected void onStop() {
-        super.onStop();
-        getContentResolver().unregisterContentObserver(mBugStorageObserver);
-    }
-
-    /**
-     * Dismisses {@link BugReportService#BUGREPORT_FINISHED_NOTIF_ID}, otherwise the notification
-     * will stay there forever if this activity opened through the App Launcher.
-     */
-    private void cancelBugReportFinishedNotification() {
-        mNotificationManager.cancel(BugReportService.BUGREPORT_FINISHED_NOTIF_ID);
-    }
-
-    private void onBugReportItemClicked(
-            int buttonType, MetaBugReport bugReport, BugInfoAdapter.BugInfoViewHolder holder) {
-        if (buttonType == BugInfoAdapter.BUTTON_TYPE_UPLOAD) {
-            Log.i(TAG, "Uploading " + bugReport.getTimestamp());
-            BugStorageUtils.setBugReportStatus(this, bugReport, Status.STATUS_UPLOAD_PENDING, "");
-            // Refresh the UI to reflect the new status.
-            new BugReportsLoaderAsyncTask(this).execute();
-        } else if (buttonType == BugInfoAdapter.BUTTON_TYPE_MOVE) {
-            Log.i(TAG, "Moving " + bugReport.getTimestamp());
-            mLastSelectedBugReport = bugReport;
-            mLastSelectedBugInfoViewHolder = holder;
-            startActivityForResult(new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE),
-                    SELECT_DIRECTORY_REQUEST_CODE);
-        } else if (buttonType == BugInfoAdapter.BUTTON_TYPE_ADD_AUDIO) {
-            // Check mAudioRecordingStarted to prevent double click to BUTTON_TYPE_ADD_AUDIO.
-            if (!mAudioRecordingStarted) {
-                mAudioRecordingStarted = true;
-                startActivityForResult(BugReportActivity.buildAddAudioIntent(this, bugReport),
-                        ADD_AUDIO_MESSAGE_REQUEST_CODE);
-            }
-        } else {
-            throw new IllegalStateException("unreachable");
-        }
-    }
-
-    @Override
-    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
-        super.onActivityResult(requestCode, resultCode, data);
-        if (requestCode == SELECT_DIRECTORY_REQUEST_CODE && resultCode == RESULT_OK) {
-            int takeFlags =
-                    data.getFlags() & (Intent.FLAG_GRANT_READ_URI_PERMISSION
-                            | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
-            Uri destDirUri = data.getData();
-            getContentResolver().takePersistableUriPermission(destDirUri, takeFlags);
-            if (mLastSelectedBugReport == null || mLastSelectedBugInfoViewHolder == null) {
-                Log.w(TAG, "No bug report is selected.");
-                return;
-            }
-            MetaBugReport updatedBugReport = BugStorageUtils.setBugReportStatus(this,
-                    mLastSelectedBugReport, Status.STATUS_MOVE_IN_PROGRESS, "");
-            mBugInfoAdapter.updateBugReportInDataSet(
-                    updatedBugReport, mLastSelectedBugInfoViewHolder.getAdapterPosition());
-            new AsyncMoveFilesTask(
-                this,
-                    mBugInfoAdapter,
-                    updatedBugReport,
-                    mLastSelectedBugInfoViewHolder,
-                    destDirUri).execute();
-        }
-    }
-
-    private void onQuitButtonClick(View view) {
-        finish();
-    }
-
-    private void onStartBugReportButtonClick(View view) {
-        Intent intent = new Intent(this, BugReportActivity.class);
-        // Clear top is needed, otherwise multiple BugReportActivity-ies get opened and
-        // MediaRecorder crashes.
-        intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
-        startActivity(intent);
-    }
-
-    /**
-     * Print the Provider's state into the given stream. This gets invoked if
-     * you run "adb shell dumpsys activity BugReportInfoActivity".
-     *
-     * @param prefix Desired prefix to prepend at each line of output.
-     * @param fd The raw file descriptor that the dump is being sent to.
-     * @param writer The PrintWriter to which you should dump your state.  This will be
-     * closed for you after you return.
-     * @param args additional arguments to the dump request.
-     */
-    public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
-        super.dump(prefix, fd, writer, args);
-        mConfig.dump(prefix, writer);
-    }
-
-    /**
-     * Moves bugreport zip to USB drive and updates RecyclerView.
-     *
-     * <p>It merges bugreport zip file and audio file into one final zip file and moves it.
-     */
-    private static final class AsyncMoveFilesTask extends AsyncTask<Void, Void, MetaBugReport> {
-        private final BugReportInfoActivity mActivity;
-        private final MetaBugReport mBugReport;
-        private final Uri mDestinationDirUri;
-        /** RecyclerView.Adapter that contains all the bug reports. */
-        private final BugInfoAdapter mBugInfoAdapter;
-        /** ViewHolder for {@link #mBugReport}. */
-        private final BugInfoAdapter.BugInfoViewHolder mBugViewHolder;
-        private final ContentResolver mResolver;
-
-        AsyncMoveFilesTask(BugReportInfoActivity activity, BugInfoAdapter bugInfoAdapter,
-                MetaBugReport bugReport, BugInfoAdapter.BugInfoViewHolder holder,
-                Uri destinationDir) {
-            mActivity = activity;
-            mBugInfoAdapter = bugInfoAdapter;
-            mBugReport = bugReport;
-            mBugViewHolder = holder;
-            mDestinationDirUri = destinationDir;
-            mResolver = mActivity.getContentResolver();
-        }
-
-        /** Moves the bugreport to the USB drive and returns the updated {@link MetaBugReport}. */
-        @Override
-        protected MetaBugReport doInBackground(Void... params) {
-            try {
-                return copyFilesToUsb();
-            } catch (IOException e) {
-                Log.e(TAG, "Failed to copy bugreport "
-                        + mBugReport.getTimestamp() + " to USB", e);
-                return BugStorageUtils.setBugReportStatus(
-                    mActivity, mBugReport,
-                    com.google.android.car.bugreport.Status.STATUS_MOVE_FAILED, e);
-            }
-        }
-
-        private MetaBugReport copyFilesToUsb() throws IOException {
-            String documentId = DocumentsContract.getTreeDocumentId(mDestinationDirUri);
-            Uri parentDocumentUri =
-                    DocumentsContract.buildDocumentUriUsingTree(mDestinationDirUri, documentId);
-            if (!Strings.isNullOrEmpty(mBugReport.getFilePath())) {
-                // There are still old bugreports with deprecated filePath.
-                Uri sourceUri = BugStorageProvider.buildUriWithSegment(
-                        mBugReport.getId(), BugStorageProvider.URL_SEGMENT_OPEN_FILE);
-                copyFileToUsb(
-                        new File(mBugReport.getFilePath()).getName(), sourceUri, parentDocumentUri);
-            } else {
-                mergeFilesAndCopyToUsb(parentDocumentUri);
-            }
-            Log.d(TAG, "Deleting local bug report files.");
-            BugStorageUtils.deleteBugReportFiles(mActivity, mBugReport.getId());
-            return BugStorageUtils.setBugReportStatus(mActivity, mBugReport,
-                    com.google.android.car.bugreport.Status.STATUS_MOVE_SUCCESSFUL,
-                    "Moved to: " + mDestinationDirUri.getPath());
-        }
-
-        private void mergeFilesAndCopyToUsb(Uri parentDocumentUri) throws IOException {
-            Uri sourceBugReport = BugStorageProvider.buildUriWithSegment(
-                    mBugReport.getId(), BugStorageProvider.URL_SEGMENT_OPEN_BUGREPORT_FILE);
-            Uri sourceAudio = BugStorageProvider.buildUriWithSegment(
-                    mBugReport.getId(), BugStorageProvider.URL_SEGMENT_OPEN_AUDIO_FILE);
-            String mimeType = mResolver.getType(sourceBugReport); // It's a zip file.
-            Uri newFileUri = DocumentsContract.createDocument(
-                    mResolver, parentDocumentUri, mimeType, mBugReport.getBugReportFileName());
-            if (newFileUri == null) {
-                throw new IOException(
-                        "Unable to create a file " + mBugReport.getBugReportFileName() + " in USB");
-            }
-            try (InputStream bugReportInput = mResolver.openInputStream(sourceBugReport);
-                 AssetFileDescriptor fd = mResolver.openAssetFileDescriptor(newFileUri, "w");
-                 OutputStream outputStream = fd.createOutputStream();
-                 ZipOutputStream zipOutStream =
-                         new ZipOutputStream(new BufferedOutputStream(outputStream))) {
-                // Extract bugreport zip file to the final zip file in USB drive.
-                ZipInputStream zipInStream = new ZipInputStream(bugReportInput);
-                ZipEntry entry;
-                while ((entry = zipInStream.getNextEntry()) != null) {
-                    ZipUtils.writeInputStreamToZipStream(
-                            entry.getName(), zipInStream, zipOutStream);
-                }
-                // Add audio file to the final zip file.
-                if (!Strings.isNullOrEmpty(mBugReport.getAudioFileName())) {
-                    try (InputStream audioInput = mResolver.openInputStream(sourceAudio)) {
-                        ZipUtils.writeInputStreamToZipStream(
-                                mBugReport.getAudioFileName(), audioInput, zipOutStream);
-                    }
-                }
-            }
-            try (AssetFileDescriptor fd = mResolver.openAssetFileDescriptor(newFileUri, "w")) {
-                // Force sync the written data from memory to the disk.
-                fd.getFileDescriptor().sync();
-            }
-            Log.d(TAG, "Writing to " + newFileUri + " finished");
-        }
-
-        private void copyFileToUsb(String filename, Uri sourceUri, Uri parentDocumentUri)
-                throws IOException {
-            String mimeType = mResolver.getType(sourceUri);
-            Uri newFileUri = DocumentsContract.createDocument(
-                    mResolver, parentDocumentUri, mimeType, filename);
-            if (newFileUri == null) {
-                throw new IOException("Unable to create a file " + filename + " in USB");
-            }
-            try (InputStream input = mResolver.openInputStream(sourceUri);
-                 AssetFileDescriptor fd = mResolver.openAssetFileDescriptor(newFileUri, "w")) {
-                OutputStream output = fd.createOutputStream();
-                ByteStreams.copy(input, output);
-                // Force sync the written data from memory to the disk.
-                fd.getFileDescriptor().sync();
-            }
-        }
-
-        @Override
-        protected void onPostExecute(MetaBugReport updatedBugReport) {
-            // Refresh the UI to reflect the new status.
-            mBugInfoAdapter.updateBugReportInDataSet(
-                    updatedBugReport, mBugViewHolder.getAdapterPosition());
-        }
-    }
-
-    /** Asynchronously loads bugreports from {@link BugStorageProvider}. */
-    private static final class BugReportsLoaderAsyncTask extends
-            AsyncTask<Void, Void, List<MetaBugReport>> {
-        private final WeakReference<BugReportInfoActivity> mBugReportInfoActivityWeakReference;
-
-        BugReportsLoaderAsyncTask(BugReportInfoActivity activity) {
-            mBugReportInfoActivityWeakReference = new WeakReference<>(activity);
-        }
-
-        @Override
-        protected List<MetaBugReport> doInBackground(Void... voids) {
-            BugReportInfoActivity activity = mBugReportInfoActivityWeakReference.get();
-            if (activity == null) {
-                Log.w(TAG, "Activity is gone, cancelling BugReportsLoaderAsyncTask.");
-                return new ArrayList<>();
-            }
-            return BugStorageUtils.getAllBugReportsDescending(activity);
-        }
-
-        @Override
-        protected void onPostExecute(List<MetaBugReport> result) {
-            BugReportInfoActivity activity = mBugReportInfoActivityWeakReference.get();
-            if (activity == null) {
-                Log.w(TAG, "Activity is gone, cancelling onPostExecute.");
-                return;
-            }
-            activity.mBugInfoAdapter.setDataset(result);
-        }
-    }
-
-    /** Observer for {@link BugStorageProvider}. */
-    private static class BugStorageObserver extends ContentObserver {
-        private final BugReportInfoActivity mInfoActivity;
-
-        /**
-         * Creates a content observer.
-         *
-         * @param activity A {@link BugReportInfoActivity} instance.
-         * @param handler The handler to run {@link #onChange} on, or null if none.
-         */
-        BugStorageObserver(BugReportInfoActivity activity, Handler handler) {
-            super(handler);
-            mInfoActivity = activity;
-        }
-
-        @Override
-        public void onChange(boolean selfChange) {
-            new BugReportsLoaderAsyncTask(mInfoActivity).execute();
-        }
-    }
-}
diff --git a/tests/BugReportApp/src/com/google/android/car/bugreport/BugReportService.java b/tests/BugReportApp/src/com/google/android/car/bugreport/BugReportService.java
deleted file mode 100644
index 2bebd72..0000000
--- a/tests/BugReportApp/src/com/google/android/car/bugreport/BugReportService.java
+++ /dev/null
@@ -1,601 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.android.car.bugreport;
-
-import static android.car.CarBugreportManager.CarBugreportManagerCallback.CAR_BUGREPORT_DUMPSTATE_CONNECTION_FAILED;
-import static android.car.CarBugreportManager.CarBugreportManagerCallback.CAR_BUGREPORT_DUMPSTATE_FAILED;
-import static android.car.CarBugreportManager.CarBugreportManagerCallback.CAR_BUGREPORT_SERVICE_NOT_AVAILABLE;
-
-import static com.google.android.car.bugreport.PackageUtils.getPackageVersion;
-
-import android.annotation.FloatRange;
-import android.annotation.StringRes;
-import android.app.Notification;
-import android.app.NotificationChannel;
-import android.app.NotificationManager;
-import android.app.PendingIntent;
-import android.app.Service;
-import android.car.Car;
-import android.car.CarBugreportManager;
-import android.car.CarNotConnectedException;
-import android.content.Context;
-import android.content.Intent;
-import android.media.AudioManager;
-import android.media.Ringtone;
-import android.media.RingtoneManager;
-import android.net.Uri;
-import android.os.Binder;
-import android.os.Build;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.Message;
-import android.os.ParcelFileDescriptor;
-import android.util.Log;
-import android.widget.Toast;
-
-import com.google.common.base.Preconditions;
-import com.google.common.io.ByteStreams;
-import com.google.common.util.concurrent.AtomicDouble;
-
-import java.io.BufferedOutputStream;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.OutputStream;
-import java.util.concurrent.Executors;
-import java.util.concurrent.ScheduledExecutorService;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.zip.ZipOutputStream;
-
-/**
- * Service that captures screenshot and bug report using dumpstate and bluetooth snoop logs.
- *
- * <p>After collecting all the logs it sets the {@link MetaBugReport} status to
- * {@link Status#STATUS_AUDIO_PENDING} or {@link Status#STATUS_PENDING_USER_ACTION} depending
- * on {@link MetaBugReport#getType}.
- *
- * <p>If the service is started with action {@link #ACTION_START_SILENT}, it will start
- * bugreporting without showing dialog and recording audio message, see
- * {@link MetaBugReport#TYPE_SILENT}.
- */
-public class BugReportService extends Service {
-    private static final String TAG = BugReportService.class.getSimpleName();
-
-    /**
-     * Extra data from intent - current bug report.
-     */
-    static final String EXTRA_META_BUG_REPORT = "meta_bug_report";
-
-    /** Starts silent (no audio message recording) bugreporting. */
-    private static final String ACTION_START_SILENT =
-            "com.google.android.car.bugreport.action.START_SILENT";
-
-    // Wait a short time before starting to capture the bugreport and the screen, so that
-    // bugreport activity can detach from the view tree.
-    // It is ugly to have a timeout, but it is ok here because such a delay should not really
-    // cause bugreport to be tainted with so many other events. If in the future we want to change
-    // this, the best option is probably to wait for onDetach events from view tree.
-    private static final int ACTIVITY_FINISH_DELAY_MILLIS = 1000;
-
-    /** Stop the service only after some delay, to allow toasts to show on the screen. */
-    private static final int STOP_SERVICE_DELAY_MILLIS = 1000;
-
-    /**
-     * Wait a short time before showing "bugreport started" toast message, because the service
-     * will take a screenshot of the screen.
-     */
-    private static final int BUGREPORT_STARTED_TOAST_DELAY_MILLIS = 2000;
-
-    private static final String BT_SNOOP_LOG_LOCATION = "/data/misc/bluetooth/logs/btsnoop_hci.log";
-    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
-
-    /** Notifications on this channel will silently appear in notification bar. */
-    private static final String PROGRESS_CHANNEL_ID = "BUGREPORT_PROGRESS_CHANNEL";
-
-    /** Notifications on this channel will pop-up. */
-    private static final String STATUS_CHANNEL_ID = "BUGREPORT_STATUS_CHANNEL";
-
-    /** Persistent notification is shown when bugreport is in progress or waiting for audio. */
-    private static final int BUGREPORT_IN_PROGRESS_NOTIF_ID = 1;
-
-    /** Dismissible notification is shown when bugreport is collected. */
-    static final int BUGREPORT_FINISHED_NOTIF_ID = 2;
-
-    private static final String OUTPUT_ZIP_FILE = "output_file.zip";
-    private static final String EXTRA_OUTPUT_ZIP_FILE = "extra_output_file.zip";
-
-    private static final String MESSAGE_FAILURE_DUMPSTATE = "Failed to grab dumpstate";
-    private static final String MESSAGE_FAILURE_ZIP = "Failed to zip files";
-
-    private static final int PROGRESS_HANDLER_EVENT_PROGRESS = 1;
-    private static final String PROGRESS_HANDLER_DATA_PROGRESS = "progress";
-
-    static final float MAX_PROGRESS_VALUE = 100f;
-
-    /** Binder given to clients. */
-    private final IBinder mBinder = new ServiceBinder();
-
-    /** True if {@link BugReportService} is already collecting bugreport, including zipping. */
-    private final AtomicBoolean mIsCollectingBugReport = new AtomicBoolean(false);
-    private final AtomicDouble mBugReportProgress = new AtomicDouble(0);
-
-    private MetaBugReport mMetaBugReport;
-    private NotificationManager mNotificationManager;
-    private ScheduledExecutorService mSingleThreadExecutor;
-    private BugReportProgressListener mBugReportProgressListener;
-    private Car mCar;
-    private CarBugreportManager mBugreportManager;
-    private CarBugreportManager.CarBugreportManagerCallback mCallback;
-    private Config mConfig;
-
-    /** A handler on the main thread. */
-    private Handler mHandler;
-    /**
-     * A handler to the main thread to show toast messages, it will be cleared when the service
-     * finishes. We need to clear it otherwise when bugreport fails, it will show "bugreport start"
-     * toast, which will confuse users.
-     */
-    private Handler mHandlerStartedToast;
-
-    /** A listener that's notified when bugreport progress changes. */
-    interface BugReportProgressListener {
-        /**
-         * Called when bug report progress changes.
-         *
-         * @param progress - a bug report progress in [0.0, 100.0].
-         */
-        void onProgress(float progress);
-    }
-
-    /** Client binder. */
-    public class ServiceBinder extends Binder {
-        BugReportService getService() {
-            // Return this instance of LocalService so clients can call public methods
-            return BugReportService.this;
-        }
-    }
-
-    /** A handler on the main thread. */
-    private class BugReportHandler extends Handler {
-        @Override
-        public void handleMessage(Message message) {
-            switch (message.what) {
-                case PROGRESS_HANDLER_EVENT_PROGRESS:
-                    if (mBugReportProgressListener != null) {
-                        float progress = message.getData().getFloat(PROGRESS_HANDLER_DATA_PROGRESS);
-                        mBugReportProgressListener.onProgress(progress);
-                    }
-                    showProgressNotification();
-                    break;
-                default:
-                    Log.d(TAG, "Unknown event " + message.what + ", ignoring.");
-            }
-        }
-    }
-
-    @Override
-    public void onCreate() {
-        Preconditions.checkState(Config.isBugReportEnabled(), "BugReport is disabled.");
-
-        mNotificationManager = getSystemService(NotificationManager.class);
-        mNotificationManager.createNotificationChannel(new NotificationChannel(
-                PROGRESS_CHANNEL_ID,
-                getString(R.string.notification_bugreport_channel_name),
-                NotificationManager.IMPORTANCE_DEFAULT));
-        mNotificationManager.createNotificationChannel(new NotificationChannel(
-                STATUS_CHANNEL_ID,
-                getString(R.string.notification_bugreport_channel_name),
-                NotificationManager.IMPORTANCE_HIGH));
-        mSingleThreadExecutor = Executors.newSingleThreadScheduledExecutor();
-        mHandler = new BugReportHandler();
-        mHandlerStartedToast = new Handler();
-        mConfig = new Config();
-        mConfig.start();
-    }
-
-    @Override
-    public void onDestroy() {
-        if (DEBUG) {
-            Log.d(TAG, "Service destroyed");
-        }
-        disconnectFromCarService();
-    }
-
-    @Override
-    public int onStartCommand(final Intent intent, int flags, int startId) {
-        if (mIsCollectingBugReport.getAndSet(true)) {
-            Log.w(TAG, "bug report is already being collected, ignoring");
-            Toast.makeText(this, R.string.toast_bug_report_in_progress, Toast.LENGTH_SHORT).show();
-            return START_NOT_STICKY;
-        }
-
-        Log.i(TAG, String.format("Will start collecting bug report, version=%s",
-                getPackageVersion(this)));
-
-        if (ACTION_START_SILENT.equals(intent.getAction())) {
-            Log.i(TAG, "Starting a silent bugreport.");
-            mMetaBugReport = BugReportActivity.createBugReport(this, MetaBugReport.TYPE_SILENT);
-        } else {
-            Bundle extras = intent.getExtras();
-            mMetaBugReport = extras.getParcelable(EXTRA_META_BUG_REPORT);
-        }
-
-        mBugReportProgress.set(0);
-
-        startForeground(BUGREPORT_IN_PROGRESS_NOTIF_ID, buildProgressNotification());
-        showProgressNotification();
-
-        collectBugReport();
-
-        // Show a short lived "bugreport started" toast message after a short delay.
-        mHandlerStartedToast.postDelayed(() -> {
-            Toast.makeText(this,
-                    getText(R.string.toast_bug_report_started), Toast.LENGTH_LONG).show();
-        }, BUGREPORT_STARTED_TOAST_DELAY_MILLIS);
-
-        // If the service process gets killed due to heavy memory pressure, do not restart.
-        return START_NOT_STICKY;
-    }
-
-    private void onCarLifecycleChanged(Car car, boolean ready) {
-        // not ready - car service is crashed or is restarting.
-        if (!ready) {
-            mBugreportManager = null;
-            mCar = null;
-
-            // NOTE: dumpstate still might be running, but we can't kill it or reconnect to it
-            //       so we ignore it.
-            handleBugReportManagerError(CAR_BUGREPORT_SERVICE_NOT_AVAILABLE);
-            return;
-        }
-        try {
-            mBugreportManager = (CarBugreportManager) car.getCarManager(Car.CAR_BUGREPORT_SERVICE);
-        } catch (CarNotConnectedException | NoClassDefFoundError e) {
-            throw new IllegalStateException("Failed to get CarBugreportManager.", e);
-        }
-    }
-
-    /** Shows an updated progress notification. */
-    private void showProgressNotification() {
-        if (isCollectingBugReport()) {
-            mNotificationManager.notify(
-                    BUGREPORT_IN_PROGRESS_NOTIF_ID, buildProgressNotification());
-        }
-    }
-
-    private Notification buildProgressNotification() {
-        Intent intent = new Intent(getApplicationContext(), BugReportInfoActivity.class);
-        PendingIntent startBugReportInfoActivity =
-                PendingIntent.getActivity(getApplicationContext(), 0, intent, 0);
-        return new Notification.Builder(this, PROGRESS_CHANNEL_ID)
-                .setContentTitle(getText(R.string.notification_bugreport_in_progress))
-                .setContentText(mMetaBugReport.getTitle())
-                .setSubText(String.format("%.1f%%", mBugReportProgress.get()))
-                .setSmallIcon(R.drawable.download_animation)
-                .setCategory(Notification.CATEGORY_STATUS)
-                .setOngoing(true)
-                .setProgress((int) MAX_PROGRESS_VALUE, (int) mBugReportProgress.get(), false)
-                .setContentIntent(startBugReportInfoActivity)
-                .build();
-    }
-
-    /** Returns true if bugreporting is in progress. */
-    public boolean isCollectingBugReport() {
-        return mIsCollectingBugReport.get();
-    }
-
-    /** Returns current bugreport progress. */
-    public float getBugReportProgress() {
-        return (float) mBugReportProgress.get();
-    }
-
-    /** Sets a bugreport progress listener. The listener is called on a main thread. */
-    public void setBugReportProgressListener(BugReportProgressListener listener) {
-        mBugReportProgressListener = listener;
-    }
-
-    /** Removes the bugreport progress listener. */
-    public void removeBugReportProgressListener() {
-        mBugReportProgressListener = null;
-    }
-
-    @Override
-    public IBinder onBind(Intent intent) {
-        return mBinder;
-    }
-
-    private void showToast(@StringRes int resId) {
-        // run on ui thread.
-        mHandler.post(() -> Toast.makeText(this, getText(resId), Toast.LENGTH_LONG).show());
-    }
-
-    private void disconnectFromCarService() {
-        if (mCar != null) {
-            mCar.disconnect();
-            mCar = null;
-        }
-        mBugreportManager = null;
-    }
-
-    private void connectToCarServiceSync() {
-        if (mCar == null || !(mCar.isConnected() || mCar.isConnecting())) {
-            mCar = Car.createCar(this, /* handler= */ null,
-                    Car.CAR_WAIT_TIMEOUT_WAIT_FOREVER, this::onCarLifecycleChanged);
-        }
-    }
-
-    private void collectBugReport() {
-        // Connect to the car service before collecting bugreport, because when car service crashes,
-        // BugReportService doesn't automatically reconnect to it.
-        connectToCarServiceSync();
-
-        if (Build.IS_USERDEBUG || Build.IS_ENG) {
-            mSingleThreadExecutor.schedule(
-                    this::grabBtSnoopLog, ACTIVITY_FINISH_DELAY_MILLIS, TimeUnit.MILLISECONDS);
-        }
-        mSingleThreadExecutor.schedule(
-                this::saveBugReport, ACTIVITY_FINISH_DELAY_MILLIS, TimeUnit.MILLISECONDS);
-    }
-
-    private void grabBtSnoopLog() {
-        Log.i(TAG, "Grabbing bt snoop log");
-        File result = FileUtils.getFileWithSuffix(this, mMetaBugReport.getTimestamp(),
-                "-btsnoop.bin.log");
-        File snoopFile = new File(BT_SNOOP_LOG_LOCATION);
-        if (!snoopFile.exists()) {
-            Log.w(TAG, BT_SNOOP_LOG_LOCATION + " not found, skipping");
-            return;
-        }
-        try (FileInputStream input = new FileInputStream(snoopFile);
-             FileOutputStream output = new FileOutputStream(result)) {
-            ByteStreams.copy(input, output);
-        } catch (IOException e) {
-            // this regularly happens when snooplog is not enabled so do not log as an error
-            Log.i(TAG, "Failed to grab bt snooplog, continuing to take bug report.", e);
-        }
-    }
-
-    private void saveBugReport() {
-        Log.i(TAG, "Dumpstate to file");
-        File outputFile = FileUtils.getFile(this, mMetaBugReport.getTimestamp(), OUTPUT_ZIP_FILE);
-        File extraOutputFile = FileUtils.getFile(this, mMetaBugReport.getTimestamp(),
-                EXTRA_OUTPUT_ZIP_FILE);
-        try (ParcelFileDescriptor outFd = ParcelFileDescriptor.open(outputFile,
-                ParcelFileDescriptor.MODE_CREATE | ParcelFileDescriptor.MODE_READ_WRITE);
-             ParcelFileDescriptor extraOutFd = ParcelFileDescriptor.open(extraOutputFile,
-                ParcelFileDescriptor.MODE_CREATE | ParcelFileDescriptor.MODE_READ_WRITE)) {
-            requestBugReport(outFd, extraOutFd);
-        } catch (IOException | RuntimeException e) {
-            Log.e(TAG, "Failed to grab dump state", e);
-            BugStorageUtils.setBugReportStatus(this, mMetaBugReport, Status.STATUS_WRITE_FAILED,
-                    MESSAGE_FAILURE_DUMPSTATE);
-            showToast(R.string.toast_status_dump_state_failed);
-            disconnectFromCarService();
-            mIsCollectingBugReport.set(false);
-        }
-    }
-
-    private void sendProgressEventToHandler(float progress) {
-        Message message = new Message();
-        message.what = PROGRESS_HANDLER_EVENT_PROGRESS;
-        message.getData().putFloat(PROGRESS_HANDLER_DATA_PROGRESS, progress);
-        mHandler.sendMessage(message);
-    }
-
-    private void requestBugReport(ParcelFileDescriptor outFd, ParcelFileDescriptor extraOutFd) {
-        if (DEBUG) {
-            Log.d(TAG, "Requesting a bug report from CarBugReportManager.");
-        }
-        mCallback = new CarBugreportManager.CarBugreportManagerCallback() {
-            @Override
-            public void onError(@CarBugreportErrorCode int errorCode) {
-                Log.e(TAG, "CarBugreportManager failed: " + errorCode);
-                disconnectFromCarService();
-                handleBugReportManagerError(errorCode);
-            }
-
-            @Override
-            public void onProgress(@FloatRange(from = 0f, to = MAX_PROGRESS_VALUE) float progress) {
-                mBugReportProgress.set(progress);
-                sendProgressEventToHandler(progress);
-            }
-
-            @Override
-            public void onFinished() {
-                Log.d(TAG, "CarBugreportManager finished");
-                disconnectFromCarService();
-                mBugReportProgress.set(MAX_PROGRESS_VALUE);
-                sendProgressEventToHandler(MAX_PROGRESS_VALUE);
-                mSingleThreadExecutor.submit(BugReportService.this::zipDirectoryAndUpdateStatus);
-            }
-        };
-        if (mBugreportManager == null) {
-            mHandler.post(() -> Toast.makeText(this,
-                    "Car service is not ready", Toast.LENGTH_LONG).show());
-            Log.e(TAG, "CarBugReportManager is not ready");
-            return;
-        }
-        mBugreportManager.requestBugreport(outFd, extraOutFd, mCallback);
-    }
-
-    private void handleBugReportManagerError(
-            @CarBugreportManager.CarBugreportManagerCallback.CarBugreportErrorCode int errorCode) {
-        if (mMetaBugReport == null) {
-            Log.w(TAG, "No bugreport is running");
-            mIsCollectingBugReport.set(false);
-            return;
-        }
-        // We let the UI know that bug reporting is finished, because the next step is to
-        // zip everything and upload.
-        mBugReportProgress.set(MAX_PROGRESS_VALUE);
-        sendProgressEventToHandler(MAX_PROGRESS_VALUE);
-        showToast(R.string.toast_status_failed);
-        BugStorageUtils.setBugReportStatus(
-                BugReportService.this, mMetaBugReport,
-                Status.STATUS_WRITE_FAILED, getBugReportFailureStatusMessage(errorCode));
-        mHandler.postDelayed(() -> {
-            mNotificationManager.cancel(BUGREPORT_IN_PROGRESS_NOTIF_ID);
-            stopForeground(true);
-        }, STOP_SERVICE_DELAY_MILLIS);
-        mHandlerStartedToast.removeCallbacksAndMessages(null);
-        mMetaBugReport = null;
-        mIsCollectingBugReport.set(false);
-    }
-
-    private static String getBugReportFailureStatusMessage(
-            @CarBugreportManager.CarBugreportManagerCallback.CarBugreportErrorCode int errorCode) {
-        switch (errorCode) {
-            case CAR_BUGREPORT_DUMPSTATE_CONNECTION_FAILED:
-            case CAR_BUGREPORT_DUMPSTATE_FAILED:
-                return "Failed to connect to dumpstate. Retry again after a minute.";
-            case CAR_BUGREPORT_SERVICE_NOT_AVAILABLE:
-                return "Car service is not available. Retry again.";
-            default:
-                return "Car service bugreport collection failed: " + errorCode;
-        }
-    }
-
-    /**
-     * Shows a clickable bugreport finished notification. When clicked it opens
-     * {@link BugReportInfoActivity}.
-     */
-    static void showBugReportFinishedNotification(Context context, MetaBugReport bug) {
-        Intent intent = new Intent(context, BugReportInfoActivity.class);
-        PendingIntent startBugReportInfoActivity =
-                PendingIntent.getActivity(context, 0, intent, 0);
-        Notification notification = new Notification
-                .Builder(context, STATUS_CHANNEL_ID)
-                .setContentTitle(context.getText(R.string.notification_bugreport_finished_title))
-                .setContentText(bug.getTitle())
-                .setCategory(Notification.CATEGORY_STATUS)
-                .setSmallIcon(R.drawable.ic_upload)
-                .setContentIntent(startBugReportInfoActivity)
-                .build();
-        context.getSystemService(NotificationManager.class)
-                .notify(BUGREPORT_FINISHED_NOTIF_ID, notification);
-    }
-
-    /**
-     * Zips the temp directory, writes to the system user's {@link FileUtils#getPendingDir} and
-     * updates the bug report status.
-     *
-     * <p>For {@link MetaBugReport#TYPE_INTERACTIVE}: Sets status to either STATUS_UPLOAD_PENDING or
-     * STATUS_PENDING_USER_ACTION and shows a regular notification.
-     *
-     * <p>For {@link MetaBugReport#TYPE_SILENT}: Sets status to STATUS_AUDIO_PENDING and shows
-     * a dialog to record audio message.
-     */
-    private void zipDirectoryAndUpdateStatus() {
-        try {
-            // All the generated zip files, images and audio messages are located in this dir.
-            // This is located under the current user.
-            String bugreportFileName = FileUtils.getZipFileName(mMetaBugReport);
-            Log.d(TAG, "Zipping bugreport into " + bugreportFileName);
-            mMetaBugReport = BugStorageUtils.update(this,
-                    mMetaBugReport.toBuilder().setBugReportFileName(bugreportFileName).build());
-            File bugReportTempDir = FileUtils.createTempDir(this, mMetaBugReport.getTimestamp());
-            zipDirectoryToOutputStream(bugReportTempDir,
-                    BugStorageUtils.openBugReportFileToWrite(this, mMetaBugReport));
-        } catch (IOException e) {
-            Log.e(TAG, "Failed to zip files", e);
-            BugStorageUtils.setBugReportStatus(this, mMetaBugReport, Status.STATUS_WRITE_FAILED,
-                    MESSAGE_FAILURE_ZIP);
-            showToast(R.string.toast_status_failed);
-            return;
-        }
-        if (mMetaBugReport.getType() == MetaBugReport.TYPE_SILENT) {
-            BugStorageUtils.setBugReportStatus(BugReportService.this,
-                    mMetaBugReport, Status.STATUS_AUDIO_PENDING, /* message= */ "");
-            playNotificationSound();
-            startActivity(BugReportActivity.buildAddAudioIntent(this, mMetaBugReport));
-        } else {
-            // NOTE: If bugreport type is INTERACTIVE, it will already contain an audio message.
-            Status status = mConfig.getAutoUpload()
-                    ? Status.STATUS_UPLOAD_PENDING : Status.STATUS_PENDING_USER_ACTION;
-            BugStorageUtils.setBugReportStatus(BugReportService.this,
-                    mMetaBugReport, status, /* message= */ "");
-            showBugReportFinishedNotification(this, mMetaBugReport);
-        }
-        mHandler.post(() -> {
-            mNotificationManager.cancel(BUGREPORT_IN_PROGRESS_NOTIF_ID);
-            stopForeground(true);
-        });
-        mHandlerStartedToast.removeCallbacksAndMessages(null);
-        mMetaBugReport = null;
-        mIsCollectingBugReport.set(false);
-    }
-
-    private void playNotificationSound() {
-        Uri notification = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
-        Ringtone ringtone = RingtoneManager.getRingtone(getApplicationContext(), notification);
-        if (ringtone == null) {
-            Log.w(TAG, "No notification ringtone found.");
-            return;
-        }
-        float volume = ringtone.getVolume();
-        // Use volume from audio manager, otherwise default ringtone volume can be too loud.
-        AudioManager audioManager = getSystemService(AudioManager.class);
-        if (audioManager != null) {
-            int currentVolume = audioManager.getStreamVolume(AudioManager.STREAM_NOTIFICATION);
-            int maxVolume = audioManager.getStreamMaxVolume(AudioManager.STREAM_NOTIFICATION);
-            volume = (currentVolume + 0.0f) / maxVolume;
-        }
-        Log.v(TAG, "Using volume " + volume);
-        ringtone.setVolume(volume);
-        ringtone.play();
-    }
-
-    /**
-     * Compresses a directory into a zip file. The method is not recursive. Any sub-directory
-     * contained in the main directory and any files contained in the sub-directories will be
-     * skipped.
-     *
-     * @param dirToZip  The path of the directory to zip
-     * @param outStream The output stream to write the zip file to
-     * @throws IOException if the directory does not exist, its files cannot be read, or the output
-     *                     zip file cannot be written.
-     */
-    private void zipDirectoryToOutputStream(File dirToZip, OutputStream outStream)
-            throws IOException {
-        if (!dirToZip.isDirectory()) {
-            throw new IOException("zip directory does not exist");
-        }
-        Log.v(TAG, "zipping directory " + dirToZip.getAbsolutePath());
-
-        File[] listFiles = dirToZip.listFiles();
-        try (ZipOutputStream zipStream = new ZipOutputStream(new BufferedOutputStream(outStream))) {
-            for (File file : listFiles) {
-                if (file.isDirectory()) {
-                    continue;
-                }
-                String filename = file.getName();
-                // only for the zipped output file, we add individual entries to zip file.
-                if (filename.equals(OUTPUT_ZIP_FILE) || filename.equals(EXTRA_OUTPUT_ZIP_FILE)) {
-                    ZipUtils.extractZippedFileToZipStream(file, zipStream);
-                } else {
-                    ZipUtils.addFileToZipStream(file, zipStream);
-                }
-            }
-        } finally {
-            outStream.close();
-        }
-        // Zipping successful, now cleanup the temp dir.
-        FileUtils.deleteDirectory(dirToZip);
-    }
-}
diff --git a/tests/BugReportApp/src/com/google/android/car/bugreport/BugStorageProvider.java b/tests/BugReportApp/src/com/google/android/car/bugreport/BugStorageProvider.java
deleted file mode 100644
index d9e271e..0000000
--- a/tests/BugReportApp/src/com/google/android/car/bugreport/BugStorageProvider.java
+++ /dev/null
@@ -1,435 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.android.car.bugreport;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.annotation.StringDef;
-import android.content.ContentProvider;
-import android.content.ContentValues;
-import android.content.Context;
-import android.content.UriMatcher;
-import android.database.Cursor;
-import android.database.sqlite.SQLiteDatabase;
-import android.database.sqlite.SQLiteOpenHelper;
-import android.net.Uri;
-import android.os.CancellationSignal;
-import android.os.ParcelFileDescriptor;
-import android.util.Log;
-
-import com.google.common.base.Preconditions;
-import com.google.common.base.Strings;
-
-import java.io.File;
-import java.io.FileDescriptor;
-import java.io.FileNotFoundException;
-import java.io.PrintWriter;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.util.function.Function;
-
-
-/**
- * Provides a bug storage interface to save and upload bugreports filed from all users.
- * In Android Automotive user 0 runs as the system and all the time, while other users won't once
- * their session ends. This content provider enables bug reports to be uploaded even after
- * user session ends.
- *
- * <p>A bugreport constists of two files: bugreport zip file and audio file. Audio file is added
- * later through notification. {@link SimpleUploaderAsyncTask} merges two files into one zip file
- * before uploading.
- *
- * <p>All files are stored under system user's {@link FileUtils#getPendingDir}.
- */
-public class BugStorageProvider extends ContentProvider {
-    private static final String TAG = BugStorageProvider.class.getSimpleName();
-
-    private static final String AUTHORITY = "com.google.android.car.bugreport";
-    private static final String BUG_REPORTS_TABLE = "bugreports";
-
-    /** Deletes files associated with a bug report. */
-    static final String URL_SEGMENT_DELETE_FILES = "deleteZipFile";
-    /** Destructively deletes a bug report. */
-    static final String URL_SEGMENT_COMPLETE_DELETE = "completeDelete";
-    /** Opens bugreport file of a bug report, uses column {@link #COLUMN_BUGREPORT_FILENAME}. */
-    static final String URL_SEGMENT_OPEN_BUGREPORT_FILE = "openBugReportFile";
-    /** Opens audio file of a bug report, uses column {@link #URL_MATCHED_OPEN_AUDIO_FILE}. */
-    static final String URL_SEGMENT_OPEN_AUDIO_FILE = "openAudioFile";
-    /**
-     * Opens final bugreport zip file, uses column {@link #COLUMN_FILEPATH}.
-     *
-     * <p>NOTE: This is the old way of storing final zipped bugreport. In
-     * {@code BugStorageProvider#AUDIO_VERSION} {@link #COLUMN_FILEPATH} is dropped. But there are
-     * still some devices with this field set.
-     */
-    static final String URL_SEGMENT_OPEN_FILE = "openFile";
-
-    // URL Matcher IDs.
-    private static final int URL_MATCHED_BUG_REPORTS_URI = 1;
-    private static final int URL_MATCHED_BUG_REPORT_ID_URI = 2;
-    private static final int URL_MATCHED_DELETE_FILES = 3;
-    private static final int URL_MATCHED_COMPLETE_DELETE = 4;
-    private static final int URL_MATCHED_OPEN_BUGREPORT_FILE = 5;
-    private static final int URL_MATCHED_OPEN_AUDIO_FILE = 6;
-    private static final int URL_MATCHED_OPEN_FILE = 7;
-
-    @StringDef({
-            URL_SEGMENT_DELETE_FILES,
-            URL_SEGMENT_COMPLETE_DELETE,
-            URL_SEGMENT_OPEN_BUGREPORT_FILE,
-            URL_SEGMENT_OPEN_AUDIO_FILE,
-            URL_SEGMENT_OPEN_FILE,
-    })
-    @Retention(RetentionPolicy.SOURCE)
-    @interface UriActionSegments {}
-
-    static final Uri BUGREPORT_CONTENT_URI =
-            Uri.parse("content://" + AUTHORITY + "/" + BUG_REPORTS_TABLE);
-
-    /** See {@link MetaBugReport} for column descriptions. */
-    static final String COLUMN_ID = "_ID";
-    static final String COLUMN_USERNAME = "username";
-    static final String COLUMN_TITLE = "title";
-    static final String COLUMN_TIMESTAMP = "timestamp";
-    /** not used anymore */
-    static final String COLUMN_DESCRIPTION = "description";
-    /** not used anymore, but some devices still might have bugreports with this field set. */
-    static final String COLUMN_FILEPATH = "filepath";
-    static final String COLUMN_STATUS = "status";
-    static final String COLUMN_STATUS_MESSAGE = "message";
-    static final String COLUMN_TYPE = "type";
-    static final String COLUMN_BUGREPORT_FILENAME = "bugreport_filename";
-    static final String COLUMN_AUDIO_FILENAME = "audio_filename";
-
-    private DatabaseHelper mDatabaseHelper;
-    private final UriMatcher mUriMatcher;
-    private Config mConfig;
-
-    /**
-     * A helper class to work with sqlite database.
-     */
-    private static class DatabaseHelper extends SQLiteOpenHelper {
-        private static final String TAG = DatabaseHelper.class.getSimpleName();
-
-        private static final String DATABASE_NAME = "bugreport.db";
-
-        /**
-         * All changes in database versions should be recorded here.
-         * 1: Initial version.
-         * 2: Add integer column details_needed.
-         * 3: Add string column audio_filename and bugreport_filename.
-         */
-        private static final int INITIAL_VERSION = 1;
-        private static final int TYPE_VERSION = 2;
-        private static final int AUDIO_VERSION = 3;
-        private static final int DATABASE_VERSION = AUDIO_VERSION;
-
-        private static final String CREATE_TABLE = "CREATE TABLE " + BUG_REPORTS_TABLE + " ("
-                + COLUMN_ID + " INTEGER PRIMARY KEY,"
-                + COLUMN_USERNAME + " TEXT,"
-                + COLUMN_TITLE + " TEXT,"
-                + COLUMN_TIMESTAMP + " TEXT NOT NULL,"
-                + COLUMN_DESCRIPTION + " TEXT NULL,"
-                + COLUMN_FILEPATH + " TEXT DEFAULT NULL,"
-                + COLUMN_STATUS + " INTEGER DEFAULT " + Status.STATUS_WRITE_PENDING.getValue() + ","
-                + COLUMN_STATUS_MESSAGE + " TEXT NULL,"
-                + COLUMN_TYPE + " INTEGER DEFAULT " + MetaBugReport.TYPE_INTERACTIVE + ","
-                + COLUMN_BUGREPORT_FILENAME + " TEXT DEFAULT NULL,"
-                + COLUMN_AUDIO_FILENAME + " TEXT DEFAULT NULL"
-                + ");";
-
-        DatabaseHelper(Context context) {
-            super(context, DATABASE_NAME, null, DATABASE_VERSION);
-        }
-
-        @Override
-        public void onCreate(SQLiteDatabase db) {
-            db.execSQL(CREATE_TABLE);
-        }
-
-        @Override
-        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
-            Log.w(TAG, "Upgrading from " + oldVersion + " to " + newVersion);
-            if (oldVersion < TYPE_VERSION) {
-                db.execSQL("ALTER TABLE " + BUG_REPORTS_TABLE + " ADD COLUMN "
-                        + COLUMN_TYPE + " INTEGER DEFAULT " + MetaBugReport.TYPE_INTERACTIVE);
-            }
-            if (oldVersion < AUDIO_VERSION) {
-                db.execSQL("ALTER TABLE " + BUG_REPORTS_TABLE + " ADD COLUMN "
-                        + COLUMN_BUGREPORT_FILENAME + " TEXT DEFAULT NULL");
-                db.execSQL("ALTER TABLE " + BUG_REPORTS_TABLE + " ADD COLUMN "
-                        + COLUMN_AUDIO_FILENAME + " TEXT DEFAULT NULL");
-            }
-        }
-    }
-
-    /**
-     * Builds an {@link Uri} that points to the single bug report and performs an action
-     * defined by given URI segment.
-     */
-    static Uri buildUriWithSegment(int bugReportId, @UriActionSegments String segment) {
-        return Uri.parse("content://" + AUTHORITY + "/" + BUG_REPORTS_TABLE + "/"
-                + segment + "/" + bugReportId);
-    }
-
-    public BugStorageProvider() {
-        mUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
-        mUriMatcher.addURI(AUTHORITY, BUG_REPORTS_TABLE, URL_MATCHED_BUG_REPORTS_URI);
-        mUriMatcher.addURI(AUTHORITY, BUG_REPORTS_TABLE + "/#", URL_MATCHED_BUG_REPORT_ID_URI);
-        mUriMatcher.addURI(
-                AUTHORITY, BUG_REPORTS_TABLE + "/" + URL_SEGMENT_DELETE_FILES + "/#",
-                URL_MATCHED_DELETE_FILES);
-        mUriMatcher.addURI(
-                AUTHORITY, BUG_REPORTS_TABLE + "/" + URL_SEGMENT_COMPLETE_DELETE + "/#",
-                URL_MATCHED_COMPLETE_DELETE);
-        mUriMatcher.addURI(
-                AUTHORITY, BUG_REPORTS_TABLE + "/" + URL_SEGMENT_OPEN_BUGREPORT_FILE + "/#",
-                URL_MATCHED_OPEN_BUGREPORT_FILE);
-        mUriMatcher.addURI(
-                AUTHORITY, BUG_REPORTS_TABLE + "/" + URL_SEGMENT_OPEN_AUDIO_FILE + "/#",
-                URL_MATCHED_OPEN_AUDIO_FILE);
-        mUriMatcher.addURI(
-                AUTHORITY, BUG_REPORTS_TABLE + "/" + URL_SEGMENT_OPEN_FILE + "/#",
-                URL_MATCHED_OPEN_FILE);
-    }
-
-    @Override
-    public boolean onCreate() {
-        Preconditions.checkState(Config.isBugReportEnabled(), "BugReport is disabled.");
-
-        mDatabaseHelper = new DatabaseHelper(getContext());
-        mConfig = new Config();
-        mConfig.start();
-        return true;
-    }
-
-    @Override
-    public Cursor query(
-            @NonNull Uri uri,
-            @Nullable String[] projection,
-            @Nullable String selection,
-            @Nullable String[] selectionArgs,
-            @Nullable String sortOrder) {
-        return query(uri, projection, selection, selectionArgs, sortOrder, null);
-    }
-
-    @Nullable
-    @Override
-    public Cursor query(
-            @NonNull Uri uri,
-            @Nullable String[] projection,
-            @Nullable String selection,
-            @Nullable String[] selectionArgs,
-            @Nullable String sortOrder,
-            @Nullable CancellationSignal cancellationSignal) {
-        String table;
-        switch (mUriMatcher.match(uri)) {
-            // returns the list of bugreports that match the selection criteria.
-            case URL_MATCHED_BUG_REPORTS_URI:
-                table = BUG_REPORTS_TABLE;
-                break;
-            //  returns the bugreport that match the id.
-            case URL_MATCHED_BUG_REPORT_ID_URI:
-                table = BUG_REPORTS_TABLE;
-                if (selection != null || selectionArgs != null) {
-                    throw new IllegalArgumentException("selection is not allowed for "
-                            + URL_MATCHED_BUG_REPORT_ID_URI);
-                }
-                selection = COLUMN_ID + "=?";
-                selectionArgs = new String[]{ uri.getLastPathSegment() };
-                break;
-            default:
-                throw new IllegalArgumentException("Unknown URL " + uri);
-        }
-        SQLiteDatabase db = mDatabaseHelper.getReadableDatabase();
-        Cursor cursor = db.query(false, table, null, selection, selectionArgs, null, null,
-                sortOrder, null, cancellationSignal);
-        cursor.setNotificationUri(getContext().getContentResolver(), uri);
-        return cursor;
-    }
-
-    @Nullable
-    @Override
-    public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
-        String table;
-        if (values == null) {
-            throw new IllegalArgumentException("values cannot be null");
-        }
-        switch (mUriMatcher.match(uri)) {
-            case URL_MATCHED_BUG_REPORTS_URI:
-                table = BUG_REPORTS_TABLE;
-                break;
-            default:
-                throw new IllegalArgumentException("unknown uri" + uri);
-        }
-        SQLiteDatabase db = mDatabaseHelper.getWritableDatabase();
-        long rowId = db.insert(table, null, values);
-        if (rowId > 0) {
-            Uri resultUri = Uri.parse("content://" + AUTHORITY + "/" + table + "/" + rowId);
-            // notify registered content observers
-            getContext().getContentResolver().notifyChange(resultUri, null);
-            return resultUri;
-        }
-        return null;
-    }
-
-    @Nullable
-    @Override
-    public String getType(@NonNull Uri uri) {
-        switch (mUriMatcher.match(uri)) {
-            case URL_MATCHED_OPEN_BUGREPORT_FILE:
-            case URL_MATCHED_OPEN_FILE:
-                return "application/zip";
-            case URL_MATCHED_OPEN_AUDIO_FILE:
-                return "audio/3gpp";
-            default:
-                throw new IllegalArgumentException("unknown uri:" + uri);
-        }
-    }
-
-    @Override
-    public int delete(
-            @NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) {
-        SQLiteDatabase db = mDatabaseHelper.getReadableDatabase();
-        switch (mUriMatcher.match(uri)) {
-            case URL_MATCHED_DELETE_FILES:
-                if (selection != null || selectionArgs != null) {
-                    throw new IllegalArgumentException("selection is not allowed for "
-                            + URL_MATCHED_DELETE_FILES);
-                }
-                if (deleteFilesFor(getBugReportFromUri(uri))) {
-                    getContext().getContentResolver().notifyChange(uri, null);
-                    return 1;
-                }
-                return 0;
-            case URL_MATCHED_COMPLETE_DELETE:
-                if (selection != null || selectionArgs != null) {
-                    throw new IllegalArgumentException("selection is not allowed for "
-                            + URL_MATCHED_COMPLETE_DELETE);
-                }
-                selection = COLUMN_ID + " = ?";
-                selectionArgs = new String[]{uri.getLastPathSegment()};
-                // Ignore the results of zip file deletion, possibly it wasn't even created.
-                deleteFilesFor(getBugReportFromUri(uri));
-                getContext().getContentResolver().notifyChange(uri, null);
-                return db.delete(BUG_REPORTS_TABLE, selection, selectionArgs);
-            default:
-                throw new IllegalArgumentException("Unknown URL " + uri);
-        }
-    }
-
-    @Override
-    public int update(
-            @NonNull Uri uri,
-            @Nullable ContentValues values,
-            @Nullable String selection,
-            @Nullable String[] selectionArgs) {
-        if (values == null) {
-            throw new IllegalArgumentException("values cannot be null");
-        }
-        String table;
-        switch (mUriMatcher.match(uri)) {
-            case URL_MATCHED_BUG_REPORTS_URI:
-                table = BUG_REPORTS_TABLE;
-                break;
-            default:
-                throw new IllegalArgumentException("Unknown URL " + uri);
-        }
-        SQLiteDatabase db = mDatabaseHelper.getWritableDatabase();
-        int rowCount = db.update(table, values, selection, selectionArgs);
-        if (rowCount > 0) {
-            // notify registered content observers
-            getContext().getContentResolver().notifyChange(uri, null);
-        }
-        Integer status = values.getAsInteger(COLUMN_STATUS);
-        // When the status is set to STATUS_UPLOAD_PENDING, we schedule an UploadJob under the
-        // current user, which is the primary user.
-        if (status != null && status.equals(Status.STATUS_UPLOAD_PENDING.getValue())) {
-            JobSchedulingUtils.scheduleUploadJob(BugStorageProvider.this.getContext());
-        }
-        return rowCount;
-    }
-
-    /**
-     * This is called when a file is opened.
-     *
-     * <p>See {@link BugStorageUtils#openBugReportFileToWrite},
-     * {@link BugStorageUtils#openAudioMessageFileToWrite}.
-     */
-    @Nullable
-    @Override
-    public ParcelFileDescriptor openFile(@NonNull Uri uri, @NonNull String mode)
-            throws FileNotFoundException {
-        Function<MetaBugReport, String> fileNameExtractor;
-        switch (mUriMatcher.match(uri)) {
-            case URL_MATCHED_OPEN_BUGREPORT_FILE:
-                fileNameExtractor = MetaBugReport::getBugReportFileName;
-                break;
-            case URL_MATCHED_OPEN_AUDIO_FILE:
-                fileNameExtractor = MetaBugReport::getAudioFileName;
-                break;
-            case URL_MATCHED_OPEN_FILE:
-                File file = new File(getBugReportFromUri(uri).getFilePath());
-                Log.v(TAG, "Opening file " + file + " with mode " + mode);
-                return ParcelFileDescriptor.open(file, ParcelFileDescriptor.parseMode(mode));
-            default:
-                throw new IllegalArgumentException("unknown uri:" + uri);
-        }
-        // URI contains bugreport ID as the last segment, see the matched urls.
-        MetaBugReport bugReport = getBugReportFromUri(uri);
-        File file = new File(
-                FileUtils.getPendingDir(getContext()), fileNameExtractor.apply(bugReport));
-        Log.v(TAG, "Opening file " + file + " with mode " + mode);
-        int modeBits = ParcelFileDescriptor.parseMode(mode);
-        return ParcelFileDescriptor.open(file, modeBits);
-    }
-
-    private MetaBugReport getBugReportFromUri(@NonNull Uri uri) {
-        int bugreportId = Integer.parseInt(uri.getLastPathSegment());
-        return BugStorageUtils.findBugReport(getContext(), bugreportId)
-                .orElseThrow(() -> new IllegalArgumentException("No record found for " + uri));
-    }
-
-    /**
-     * Print the Provider's state into the given stream. This gets invoked if
-     * you run "dumpsys activity provider com.google.android.car.bugreport/.BugStorageProvider".
-     *
-     * @param fd The raw file descriptor that the dump is being sent to.
-     * @param writer The PrintWriter to which you should dump your state.  This will be
-     * closed for you after you return.
-     * @param args additional arguments to the dump request.
-     */
-    public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
-        writer.println("BugStorageProvider:");
-        mConfig.dump(/* prefix= */ "  ", writer);
-    }
-
-    private boolean deleteFilesFor(MetaBugReport bugReport) {
-        if (!Strings.isNullOrEmpty(bugReport.getFilePath())) {
-            // Old bugreports have only filePath.
-            return new File(bugReport.getFilePath()).delete();
-        }
-        File pendingDir = FileUtils.getPendingDir(getContext());
-        boolean result = true;
-        if (!Strings.isNullOrEmpty(bugReport.getAudioFileName())) {
-            result = new File(pendingDir, bugReport.getAudioFileName()).delete();
-        }
-        if (!Strings.isNullOrEmpty(bugReport.getBugReportFileName())) {
-            result = result && new File(pendingDir, bugReport.getBugReportFileName()).delete();
-        }
-        return result;
-    }
-}
diff --git a/tests/BugReportApp/src/com/google/android/car/bugreport/BugStorageUtils.java b/tests/BugReportApp/src/com/google/android/car/bugreport/BugStorageUtils.java
deleted file mode 100644
index a009129..0000000
--- a/tests/BugReportApp/src/com/google/android/car/bugreport/BugStorageUtils.java
+++ /dev/null
@@ -1,376 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.android.car.bugreport;
-
-import static com.google.android.car.bugreport.BugStorageProvider.COLUMN_AUDIO_FILENAME;
-import static com.google.android.car.bugreport.BugStorageProvider.COLUMN_BUGREPORT_FILENAME;
-import static com.google.android.car.bugreport.BugStorageProvider.COLUMN_FILEPATH;
-import static com.google.android.car.bugreport.BugStorageProvider.COLUMN_ID;
-import static com.google.android.car.bugreport.BugStorageProvider.COLUMN_STATUS;
-import static com.google.android.car.bugreport.BugStorageProvider.COLUMN_STATUS_MESSAGE;
-import static com.google.android.car.bugreport.BugStorageProvider.COLUMN_TIMESTAMP;
-import static com.google.android.car.bugreport.BugStorageProvider.COLUMN_TITLE;
-import static com.google.android.car.bugreport.BugStorageProvider.COLUMN_TYPE;
-import static com.google.android.car.bugreport.BugStorageProvider.COLUMN_USERNAME;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.content.ContentResolver;
-import android.content.ContentValues;
-import android.content.Context;
-import android.database.Cursor;
-import android.net.Uri;
-import android.util.Log;
-
-import com.google.api.client.auth.oauth2.TokenResponseException;
-import com.google.common.base.Strings;
-
-import java.io.FileNotFoundException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.text.DateFormat;
-import java.text.SimpleDateFormat;
-import java.time.Instant;
-import java.util.ArrayList;
-import java.util.Date;
-import java.util.List;
-import java.util.Optional;
-
-/**
- * A class that hides details when communicating with the bug storage provider.
- */
-final class BugStorageUtils {
-    private static final String TAG = BugStorageUtils.class.getSimpleName();
-
-    /**
-     * When time/time-zone set incorrectly, Google API returns "400: invalid_grant" error with
-     * description containing this text.
-     */
-    private static final String CLOCK_SKEW_ERROR = "clock with skew to account";
-
-    /** When time/time-zone set incorrectly, Google API returns this error. */
-    private static final String INVALID_GRANT = "invalid_grant";
-
-    private static final DateFormat TIMESTAMP_FORMAT = new SimpleDateFormat("yyyy-MM-dd_HH-mm-ss");
-
-    /**
-     * Creates a new {@link Status#STATUS_WRITE_PENDING} bug report record in a local sqlite
-     * database.
-     *
-     * @param context   - an application context.
-     * @param title     - title of the bug report.
-     * @param timestamp - timestamp when the bug report was initiated.
-     * @param username  - current user name. Note, it's a user name, not an account name.
-     * @param type      - bug report type, {@link MetaBugReport.BugReportType}.
-     * @return an instance of {@link MetaBugReport} that was created in a database.
-     */
-    @NonNull
-    static MetaBugReport createBugReport(
-            @NonNull Context context,
-            @NonNull String title,
-            @NonNull String timestamp,
-            @NonNull String username,
-            @MetaBugReport.BugReportType int type) {
-        // insert bug report username and title
-        ContentValues values = new ContentValues();
-        values.put(COLUMN_TITLE, title);
-        values.put(COLUMN_TIMESTAMP, timestamp);
-        values.put(COLUMN_USERNAME, username);
-        values.put(COLUMN_TYPE, type);
-
-        ContentResolver r = context.getContentResolver();
-        Uri uri = r.insert(BugStorageProvider.BUGREPORT_CONTENT_URI, values);
-        return findBugReport(context, Integer.parseInt(uri.getLastPathSegment())).get();
-    }
-
-    /** Returns an output stream to write the zipped file to. */
-    @NonNull
-    static OutputStream openBugReportFileToWrite(
-            @NonNull Context context, @NonNull MetaBugReport metaBugReport)
-            throws FileNotFoundException {
-        ContentResolver r = context.getContentResolver();
-        return r.openOutputStream(BugStorageProvider.buildUriWithSegment(
-                metaBugReport.getId(), BugStorageProvider.URL_SEGMENT_OPEN_BUGREPORT_FILE));
-    }
-
-    /** Returns an output stream to write the audio message file to. */
-    static OutputStream openAudioMessageFileToWrite(
-            @NonNull Context context, @NonNull MetaBugReport metaBugReport)
-            throws FileNotFoundException {
-        ContentResolver r = context.getContentResolver();
-        return r.openOutputStream(BugStorageProvider.buildUriWithSegment(
-                metaBugReport.getId(), BugStorageProvider.URL_SEGMENT_OPEN_AUDIO_FILE));
-    }
-
-    /**
-     * Returns an input stream to read the final zip file from.
-     *
-     * <p>NOTE: This is the old way of storing final zipped bugreport. See
-     * {@link BugStorageProvider#URL_SEGMENT_OPEN_FILE} for more info.
-     */
-    static InputStream openFileToRead(Context context, MetaBugReport bug)
-            throws FileNotFoundException {
-        return context.getContentResolver().openInputStream(
-                BugStorageProvider.buildUriWithSegment(
-                        bug.getId(), BugStorageProvider.URL_SEGMENT_OPEN_FILE));
-    }
-
-    /** Returns an input stream to read the bug report zip file from. */
-    static InputStream openBugReportFileToRead(Context context, MetaBugReport bug)
-            throws FileNotFoundException {
-        return context.getContentResolver().openInputStream(
-                BugStorageProvider.buildUriWithSegment(
-                        bug.getId(), BugStorageProvider.URL_SEGMENT_OPEN_BUGREPORT_FILE));
-    }
-
-    /** Returns an input stream to read the audio file from. */
-    static InputStream openAudioFileToRead(Context context, MetaBugReport bug)
-            throws FileNotFoundException {
-        return context.getContentResolver().openInputStream(
-                BugStorageProvider.buildUriWithSegment(
-                        bug.getId(), BugStorageProvider.URL_SEGMENT_OPEN_AUDIO_FILE));
-    }
-
-    /**
-     * Deletes {@link MetaBugReport} record from a local database and deletes the associated file.
-     *
-     * <p>WARNING: destructive operation.
-     *
-     * @param context     - an application context.
-     * @param bugReportId - a bug report id.
-     * @return true if the record was deleted.
-     */
-    static boolean completeDeleteBugReport(@NonNull Context context, int bugReportId) {
-        ContentResolver r = context.getContentResolver();
-        return r.delete(BugStorageProvider.buildUriWithSegment(
-                bugReportId, BugStorageProvider.URL_SEGMENT_COMPLETE_DELETE), null, null) == 1;
-    }
-
-    /** Deletes all files for given bugreport id; doesn't delete sqlite3 record. */
-    static boolean deleteBugReportFiles(@NonNull Context context, int bugReportId) {
-        ContentResolver r = context.getContentResolver();
-        return r.delete(BugStorageProvider.buildUriWithSegment(
-                bugReportId, BugStorageProvider.URL_SEGMENT_DELETE_FILES), null, null) == 1;
-    }
-
-    /**
-     * Returns all the bugreports that are waiting to be uploaded.
-     */
-    @NonNull
-    public static List<MetaBugReport> getUploadPendingBugReports(@NonNull Context context) {
-        String selection = COLUMN_STATUS + "=?";
-        String[] selectionArgs = new String[]{
-                Integer.toString(Status.STATUS_UPLOAD_PENDING.getValue())};
-        return getBugreports(context, selection, selectionArgs, null);
-    }
-
-    /**
-     * Returns all bugreports in descending order by the ID field. ID is the index in the
-     * database.
-     */
-    @NonNull
-    public static List<MetaBugReport> getAllBugReportsDescending(@NonNull Context context) {
-        return getBugreports(context, null, null, COLUMN_ID + " DESC");
-    }
-
-    /** Returns {@link MetaBugReport} for given bugreport id. */
-    static Optional<MetaBugReport> findBugReport(Context context, int bugreportId) {
-        String selection = COLUMN_ID + " = ?";
-        String[] selectionArgs = new String[]{Integer.toString(bugreportId)};
-        List<MetaBugReport> bugs = BugStorageUtils.getBugreports(
-                context, selection, selectionArgs, null);
-        if (bugs.isEmpty()) {
-            return Optional.empty();
-        }
-        return Optional.of(bugs.get(0));
-    }
-
-    private static List<MetaBugReport> getBugreports(
-            Context context, String selection, String[] selectionArgs, String order) {
-        ArrayList<MetaBugReport> bugReports = new ArrayList<>();
-        String[] projection = {
-                COLUMN_ID,
-                COLUMN_USERNAME,
-                COLUMN_TITLE,
-                COLUMN_TIMESTAMP,
-                COLUMN_BUGREPORT_FILENAME,
-                COLUMN_AUDIO_FILENAME,
-                COLUMN_FILEPATH,
-                COLUMN_STATUS,
-                COLUMN_STATUS_MESSAGE,
-                COLUMN_TYPE};
-        ContentResolver r = context.getContentResolver();
-        Cursor c = r.query(BugStorageProvider.BUGREPORT_CONTENT_URI, projection,
-                selection, selectionArgs, order);
-
-        int count = (c != null) ? c.getCount() : 0;
-
-        if (count > 0) c.moveToFirst();
-        for (int i = 0; i < count; i++) {
-            MetaBugReport meta = MetaBugReport.builder()
-                    .setId(getInt(c, COLUMN_ID))
-                    .setTimestamp(getString(c, COLUMN_TIMESTAMP))
-                    .setUserName(getString(c, COLUMN_USERNAME))
-                    .setTitle(getString(c, COLUMN_TITLE))
-                    .setBugReportFileName(getString(c, COLUMN_BUGREPORT_FILENAME))
-                    .setAudioFileName(getString(c, COLUMN_AUDIO_FILENAME))
-                    .setFilePath(getString(c, COLUMN_FILEPATH))
-                    .setStatus(getInt(c, COLUMN_STATUS))
-                    .setStatusMessage(getString(c, COLUMN_STATUS_MESSAGE))
-                    .setType(getInt(c, COLUMN_TYPE))
-                    .build();
-            bugReports.add(meta);
-            c.moveToNext();
-        }
-        if (c != null) c.close();
-        return bugReports;
-    }
-
-    /**
-     * returns 0 if the column is not found. Otherwise returns the column value.
-     */
-    private static int getInt(Cursor c, String colName) {
-        int colIndex = c.getColumnIndex(colName);
-        if (colIndex == -1) {
-            Log.w(TAG, "Column " + colName + " not found.");
-            return 0;
-        }
-        return c.getInt(colIndex);
-    }
-
-    /**
-     * Returns the column value. If the column is not found returns empty string.
-     */
-    private static String getString(Cursor c, String colName) {
-        int colIndex = c.getColumnIndex(colName);
-        if (colIndex == -1) {
-            Log.w(TAG, "Column " + colName + " not found.");
-            return "";
-        }
-        return Strings.nullToEmpty(c.getString(colIndex));
-    }
-
-    /**
-     * Sets bugreport status to uploaded successfully.
-     */
-    public static void setUploadSuccess(Context context, MetaBugReport bugReport) {
-        setBugReportStatus(context, bugReport, Status.STATUS_UPLOAD_SUCCESS,
-                "Upload time: " + currentTimestamp());
-    }
-
-    /**
-     * Sets bugreport status pending, and update the message to last exception message.
-     *
-     * <p>Used when a transient error has occurred.
-     */
-    public static void setUploadRetry(Context context, MetaBugReport bugReport, Exception e) {
-        setBugReportStatus(context, bugReport, Status.STATUS_UPLOAD_PENDING,
-                getRootCauseMessage(e));
-    }
-
-    /**
-     * Sets bugreport status pending and update the message to last message.
-     *
-     * <p>Used when a transient error has occurred.
-     */
-    public static void setUploadRetry(Context context, MetaBugReport bugReport, String msg) {
-        setBugReportStatus(context, bugReport, Status.STATUS_UPLOAD_PENDING, msg);
-    }
-
-    /**
-     * Sets {@link MetaBugReport} status {@link Status#STATUS_EXPIRED}.
-     * Deletes the associated zip file from disk.
-     *
-     * @return true if succeeded.
-     */
-    static boolean expireBugReport(@NonNull Context context,
-            @NonNull MetaBugReport metaBugReport, @NonNull Instant expiredAt) {
-        metaBugReport = setBugReportStatus(
-                context, metaBugReport, Status.STATUS_EXPIRED, "Expired on " + expiredAt);
-        if (metaBugReport.getStatus() != Status.STATUS_EXPIRED.getValue()) {
-            return false;
-        }
-        return deleteBugReportFiles(context, metaBugReport.getId());
-    }
-
-    /** Gets the root cause of the error. */
-    @NonNull
-    private static String getRootCauseMessage(@Nullable Throwable t) {
-        if (t == null) {
-            return "No error";
-        } else if (t instanceof TokenResponseException) {
-            TokenResponseException ex = (TokenResponseException) t;
-            if (ex.getDetails().getError().equals(INVALID_GRANT)
-                    && ex.getDetails().getErrorDescription().contains(CLOCK_SKEW_ERROR)) {
-                return "Auth error. Check if time & time-zone is correct.";
-            }
-        }
-        while (t.getCause() != null) t = t.getCause();
-        return t.getMessage();
-    }
-
-    /**
-     * Updates bug report record status.
-     *
-     * <p>NOTE: When status is set to STATUS_UPLOAD_PENDING, BugStorageProvider automatically
-     * schedules the bugreport to be uploaded.
-     *
-     * @return Updated {@link MetaBugReport}.
-     */
-    static MetaBugReport setBugReportStatus(
-            Context context, MetaBugReport bugReport, Status status, String message) {
-        return update(context, bugReport.toBuilder()
-                .setStatus(status.getValue())
-                .setStatusMessage(message)
-                .build());
-    }
-
-    /**
-     * Updates bug report record status.
-     *
-     * <p>NOTE: When status is set to STATUS_UPLOAD_PENDING, BugStorageProvider automatically
-     * schedules the bugreport to be uploaded.
-     *
-     * @return Updated {@link MetaBugReport}.
-     */
-    static MetaBugReport setBugReportStatus(
-            Context context, MetaBugReport bugReport, Status status, Exception e) {
-        return setBugReportStatus(context, bugReport, status, getRootCauseMessage(e));
-    }
-
-    /**
-     * Updates the bugreport and returns the updated version.
-     *
-     * <p>NOTE: doesn't update all the fields.
-     */
-    static MetaBugReport update(Context context, MetaBugReport bugReport) {
-        // Update only necessary fields.
-        ContentValues values = new ContentValues();
-        values.put(COLUMN_BUGREPORT_FILENAME, bugReport.getBugReportFileName());
-        values.put(COLUMN_AUDIO_FILENAME, bugReport.getAudioFileName());
-        values.put(COLUMN_STATUS, bugReport.getStatus());
-        values.put(COLUMN_STATUS_MESSAGE, bugReport.getStatusMessage());
-        String where = COLUMN_ID + "=" + bugReport.getId();
-        context.getContentResolver().update(
-                BugStorageProvider.BUGREPORT_CONTENT_URI, values, where, null);
-        return findBugReport(context, bugReport.getId()).orElseThrow(
-                () -> new IllegalArgumentException("Bug " + bugReport.getId() + " not found"));
-    }
-
-    private static String currentTimestamp() {
-        return TIMESTAMP_FORMAT.format(new Date());
-    }
-}
diff --git a/tests/BugReportApp/src/com/google/android/car/bugreport/Config.java b/tests/BugReportApp/src/com/google/android/car/bugreport/Config.java
deleted file mode 100644
index b253caf..0000000
--- a/tests/BugReportApp/src/com/google/android/car/bugreport/Config.java
+++ /dev/null
@@ -1,155 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.android.car.bugreport;
-
-import android.app.ActivityThread;
-import android.os.Build;
-import android.os.SystemProperties;
-import android.provider.DeviceConfig;
-import android.util.Log;
-
-import com.android.internal.annotations.GuardedBy;
-
-import com.google.common.collect.ImmutableSet;
-
-import java.io.PrintWriter;
-
-/**
- * Contains config for BugReport App.
- *
- * <p>The config is kept synchronized with {@code car} namespace. It's not defined in
- * {@link DeviceConfig}.
- *
- * <ul>To get/set the flags via adb:
- *   <li>{@code adb shell device_config get car bugreport_upload_destination}
- *   <li>{@code adb shell device_config put car bugreport_upload_destination gcs}
- *   <li>{@code adb shell device_config delete car bugreport_upload_destination}
- * </ul>
- */
-final class Config {
-    private static final String TAG = Config.class.getSimpleName();
-
-    /**
-     * Namespace for all Android Automotive related features.
-     *
-     * <p>In the future it will move to {@code DeviceConfig#NAMESPACE_CAR}.
-     */
-    private static final String NAMESPACE_CAR = "car";
-
-    /**
-     * A string flag, can be one of {@code null} or {@link #UPLOAD_DESTINATION_GCS}.
-     */
-    private static final String KEY_BUGREPORT_UPLOAD_DESTINATION = "bugreport_upload_destination";
-
-    /**
-     * A value for {@link #KEY_BUGREPORT_UPLOAD_DESTINATION}.
-     *
-     * Upload bugreports to GCS. Only works in {@code userdebug} or {@code eng} builds.
-     */
-    private static final String UPLOAD_DESTINATION_GCS = "gcs";
-
-    /**
-     * A system property to force enable the app bypassing the {@code userdebug/eng} build check.
-     */
-    private static final String PROP_FORCE_ENABLE = "android.car.bugreport.force_enable";
-
-    /*
-     * Enable uploading new bugreports to GCS for these devices. If the device is not in this list,
-     * {@link #KEY_UPLOAD_DESTINATION} flag will be used instead.
-     */
-    private static final ImmutableSet<String> ENABLE_FORCE_UPLOAD_TO_GCS_FOR_DEVICES =
-            ImmutableSet.of("hawk");
-
-    private final Object mLock = new Object();
-
-    @GuardedBy("mLock")
-    private String mUploadDestination = null;
-
-    void start() {
-        DeviceConfig.addOnPropertiesChangedListener(NAMESPACE_CAR,
-                ActivityThread.currentApplication().getMainExecutor(), this::onPropertiesChanged);
-        updateConstants();
-    }
-
-    private void onPropertiesChanged(DeviceConfig.Properties properties) {
-        if (properties.getKeyset().contains(KEY_BUGREPORT_UPLOAD_DESTINATION)) {
-            updateConstants();
-        }
-    }
-
-    /** Returns true if bugreport app is enabled for this device. */
-    static boolean isBugReportEnabled() {
-        return Build.IS_DEBUGGABLE || SystemProperties.getBoolean(PROP_FORCE_ENABLE, false);
-    }
-
-    /** If new bugreports should be scheduled for uploading. */
-    boolean getAutoUpload() {
-        // TODO(b/144851443): Enable auto-upload only if upload destination is Gcs until
-        //                    we create a way to allow implementing OEMs custom upload logic.
-        return isUploadDestinationGcs();
-    }
-
-    /**
-     * Returns {@link true} if bugreport upload destination is GCS.
-     */
-    private boolean isUploadDestinationGcs() {
-        if (isTempForceAutoUploadGcsEnabled()) {
-            Log.d(TAG, "Setting upload dest to GCS ENABLE_AUTO_UPLOAD is true");
-            return true;
-        }
-        // NOTE: enable it only for userdebug/eng builds.
-        return UPLOAD_DESTINATION_GCS.equals(getUploadDestination()) && Build.IS_DEBUGGABLE;
-    }
-
-    private static boolean isTempForceAutoUploadGcsEnabled() {
-        return ENABLE_FORCE_UPLOAD_TO_GCS_FOR_DEVICES.contains(Build.DEVICE);
-    }
-
-    /**
-     * Returns value of a flag {@link #KEY_BUGREPORT_UPLOAD_DESTINATION}.
-     */
-    private String getUploadDestination() {
-        synchronized (mLock) {
-            return mUploadDestination;
-        }
-    }
-
-    private void updateConstants() {
-        synchronized (mLock) {
-            mUploadDestination = DeviceConfig.getString(NAMESPACE_CAR,
-                    KEY_BUGREPORT_UPLOAD_DESTINATION, /* defaultValue= */ null);
-        }
-    }
-
-    void dump(String prefix, PrintWriter pw) {
-        pw.println(prefix + "car.bugreport.Config:");
-
-        pw.print(prefix + "  ");
-        pw.print("getAutoUpload");
-        pw.print("=");
-        pw.println(getAutoUpload() ? "true" : "false");
-
-        pw.print(prefix + "  ");
-        pw.print("getUploadDestination");
-        pw.print("=");
-        pw.println(getUploadDestination());
-
-        pw.print(prefix + "  ");
-        pw.print("isUploadDestinationGcs");
-        pw.print("=");
-        pw.println(isUploadDestinationGcs());
-    }
-}
diff --git a/tests/BugReportApp/src/com/google/android/car/bugreport/FileUtils.java b/tests/BugReportApp/src/com/google/android/car/bugreport/FileUtils.java
deleted file mode 100644
index b30035e..0000000
--- a/tests/BugReportApp/src/com/google/android/car/bugreport/FileUtils.java
+++ /dev/null
@@ -1,147 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.android.car.bugreport;
-
-import android.content.Context;
-
-import com.google.common.base.Preconditions;
-
-import java.io.File;
-
-/**
- * File utilities.
- *
- * Thread safety and file operations: All file operations should happen on the same worker
- * thread for thread safety. This is provided by running both bugreport service and file upload
- * service on a asynctask. Asynctasks are by default executed on the same worker thread in serial.
- *
- * There is one exception to the rule above:
- * Voice recorder works on main thread, however this is not a thread safety problem because:
- * i. voice recorder always works before starting to collect rest of the bugreport
- * ii. a bug report cannot be moved to upload (pending) directory before it is completely
- * collected.
- */
-public class FileUtils {
-    private static final String PREFIX = "bugreport-";
-    /** A directory under the system user; contains bugreport zip files and audio files. */
-    private static final String PENDING_DIR = "bug_reports_pending";
-    // Temporary directory under the current user, used for zipping files.
-    private static final String TEMP_DIR = "bug_reports_temp";
-
-    private static final String FS = "@";
-
-    static File getPendingDir(Context context) {
-        Preconditions.checkArgument(context.getUser().isSystem(),
-                "Must be called from the system user.");
-        File dir = new File(context.getDataDir(), PENDING_DIR);
-        dir.mkdirs();
-        return dir;
-    }
-
-    /**
-     * Creates and returns file directory for storing bug report files before they are zipped into
-     * a single file.
-     */
-    static File createTempDir(Context context, String timestamp) {
-        File dir = getTempDir(context, timestamp);
-        dir.mkdirs();
-        return dir;
-    }
-
-    /**
-     * Returns path to the directory for storing bug report files before they are zipped into a
-     * single file.
-     */
-    static File getTempDir(Context context, String timestamp) {
-        Preconditions.checkArgument(!context.getUser().isSystem(),
-                "Must be called from the current user.");
-        return new File(context.getDataDir(), TEMP_DIR + "/" + timestamp);
-    }
-
-    /**
-     * Constructs a bugreport zip file name.
-     *
-     * <p>Add lookup code to the filename to allow matching audio file and bugreport file in USB.
-     */
-    static String getZipFileName(MetaBugReport bug) {
-        String lookupCode = extractLookupCode(bug);
-        return PREFIX + bug.getUserName() + FS + bug.getTimestamp() + "-" + lookupCode + ".zip";
-    }
-
-    /**
-     * Constructs a audio message file name.
-     *
-     * <p>Add lookup code to the filename to allow matching audio file and bugreport file in USB.
-     *
-     * @param timestamp - current timestamp, when audio was created.
-     * @param bug       - a bug report.
-     */
-    static String getAudioFileName(String timestamp, MetaBugReport bug) {
-        String lookupCode = extractLookupCode(bug);
-        return PREFIX + bug.getUserName() + FS + timestamp + "-" + lookupCode + "-message.3gp";
-    }
-
-    private static String extractLookupCode(MetaBugReport bug) {
-        Preconditions.checkArgument(bug.getTitle().startsWith("["),
-                "Invalid bugreport title, doesn't contain lookup code. ");
-        return bug.getTitle().substring(1, BugReportActivity.LOOKUP_STRING_LENGTH + 1);
-    }
-
-    /**
-     * Returns a {@link File} object pointing to a path in a temp directory under current users
-     * {@link Context#getDataDir}.
-     *
-     * @param context       - an application context.
-     * @param timestamp     - generates file for this timestamp
-     * @param suffix        - a filename suffix.
-     * @return A file.
-     */
-    static File getFileWithSuffix(Context context, String timestamp, String suffix) {
-        return new File(createTempDir(context, timestamp), timestamp + suffix);
-    }
-
-    /**
-     * Returns a {@link File} object pointing to a path in a temp directory under current users
-     * {@link Context#getDataDir}.
-     *
-     * @param context     - an application context.
-     * @param timestamp   - generates file for this timestamp.
-     * @param name        - a filename
-     * @return A file.
-     */
-    static File getFile(Context context, String timestamp, String name) {
-        return new File(getTempDir(context, timestamp), name);
-    }
-
-    /**
-     * Deletes a directory and its contents recursively
-     *
-     * @param directory to delete
-     */
-    static void deleteDirectory(File directory) {
-        File[] files = directory.listFiles();
-        if (files != null) {
-            for (File file : files) {
-                if (!file.isDirectory()) {
-                    file.delete();
-                } else {
-                    deleteDirectory(file);
-                }
-            }
-        }
-        directory.delete();
-    }
-}
diff --git a/tests/BugReportApp/src/com/google/android/car/bugreport/JobSchedulingUtils.java b/tests/BugReportApp/src/com/google/android/car/bugreport/JobSchedulingUtils.java
deleted file mode 100644
index eac8b9a..0000000
--- a/tests/BugReportApp/src/com/google/android/car/bugreport/JobSchedulingUtils.java
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.android.car.bugreport;
-
-import android.app.job.JobInfo;
-import android.app.job.JobScheduler;
-import android.content.ComponentName;
-import android.content.Context;
-import android.util.Log;
-
-/**
- * Utilities for scheduling upload jobs.
- */
-class JobSchedulingUtils {
-    private static final String TAG = JobSchedulingUtils.class.getSimpleName();
-
-    private static final int UPLOAD_JOB_ID = 1;
-    private static final int RETRY_DELAY_IN_MS = 5_000;
-
-    /**
-     * Schedules {@link UploadJob} under the current user.
-     *
-     * <p>Make sure this method is called under the primary user.
-     *
-     * <ul>
-     *   <li>require network connectivity
-     *   <li>good quality network (large upload size)
-     *   <li>persist across reboots
-     * </ul>
-     */
-    static void scheduleUploadJob(Context context) {
-        JobScheduler jobScheduler = context.getSystemService(JobScheduler.class);
-        if (jobScheduler == null) {
-            Log.w(TAG, "Cannot get JobScheduler from context.");
-            return;
-        }
-
-        JobInfo pendingJob = jobScheduler.getPendingJob(UPLOAD_JOB_ID);
-        // if there is already a pending job, do not schedule a new one.
-        if (pendingJob != null) {
-            Log.d(TAG, "Upload job is already active, not re-scheduling");
-            return;
-        }
-
-        // NOTE: Don't set estimated network bytes, because we want bug reports to be uploaded
-        //       without any constraints.
-        jobScheduler.schedule(new JobInfo.Builder(UPLOAD_JOB_ID,
-                new ComponentName(context, UploadJob.class))
-                .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
-                .setBackoffCriteria(RETRY_DELAY_IN_MS, JobInfo.BACKOFF_POLICY_LINEAR)
-                .build());
-    }
-}
diff --git a/tests/BugReportApp/src/com/google/android/car/bugreport/MetaBugReport.java b/tests/BugReportApp/src/com/google/android/car/bugreport/MetaBugReport.java
deleted file mode 100644
index fcdb5b7..0000000
--- a/tests/BugReportApp/src/com/google/android/car/bugreport/MetaBugReport.java
+++ /dev/null
@@ -1,212 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.android.car.bugreport;
-
-import static java.lang.annotation.RetentionPolicy.SOURCE;
-
-import android.annotation.IntDef;
-import android.os.Parcel;
-import android.os.Parcelable;
-
-import com.google.auto.value.AutoValue;
-
-import java.lang.annotation.Retention;
-import java.text.DateFormat;
-import java.text.SimpleDateFormat;
-import java.util.Date;
-
-/** Represents the information that a bugreport can contain. */
-@AutoValue
-abstract class MetaBugReport implements Parcelable {
-
-    private static final DateFormat BUG_REPORT_TIMESTAMP_DATE_FORMAT =
-            new SimpleDateFormat("yyyy-MM-dd_HH-mm-ss");
-
-    /** The app records audio message when initiated. Can change audio state. */
-    static final int TYPE_INTERACTIVE = 0;
-
-    /**
-     * The app doesn't show dialog and doesn't record audio when initiated. It allows user to
-     * add audio message when bugreport is collected.
-     */
-    static final int TYPE_SILENT = 1;
-
-    /** Annotation for bug report types. */
-    @Retention(SOURCE)
-    @IntDef({TYPE_INTERACTIVE, TYPE_SILENT})
-    @interface BugReportType {};
-
-    /**
-     * @return Id of the bug report. Bug report id monotonically increases and is unique.
-     */
-    public abstract int getId();
-
-    /**
-     * @return Username (LDAP) that created this bugreport
-     */
-    public abstract String getUserName();
-
-    /**
-     * @return Title of the bug.
-     */
-    public abstract String getTitle();
-
-    /**
-     * @return Timestamp when the bug report is initialized.
-     */
-    public abstract String getTimestamp();
-
-    /**
-     * @return path to the zip file stored under the system user.
-     *
-     * <p>NOTE: This is the old way of storing final zipped bugreport. See
-     * {@link BugStorageProvider#URL_SEGMENT_OPEN_FILE} for more info.
-     */
-    public abstract String getFilePath();
-
-    /**
-     * @return filename of the bug report zip file stored under the system user.
-     */
-    public abstract String getBugReportFileName();
-
-    /**
-     * @return filename of the audio message file stored under the system user.
-     */
-    public abstract String getAudioFileName();
-
-    /**
-     * @return {@link Status} of the bug upload.
-     */
-    public abstract int getStatus();
-
-    /**
-     * @return StatusMessage of the bug upload.
-     */
-    public abstract String getStatusMessage();
-
-    /**
-     * @return {@link BugReportType}.
-     */
-    public abstract int getType();
-
-    /** @return {@link Builder} from the meta bug report. */
-    public abstract Builder toBuilder();
-
-    @Override
-    public int describeContents() {
-        return 0;
-    }
-
-    @Override
-    public void writeToParcel(Parcel dest, int flags) {
-        dest.writeInt(getId());
-        dest.writeString(getTimestamp());
-        dest.writeString(getTitle());
-        dest.writeString(getUserName());
-        dest.writeString(getFilePath());
-        dest.writeString(getBugReportFileName());
-        dest.writeString(getAudioFileName());
-        dest.writeInt(getStatus());
-        dest.writeString(getStatusMessage());
-        dest.writeInt(getType());
-    }
-
-    /** Converts {@link Date} to bugreport timestamp. */
-    static String toBugReportTimestamp(Date date) {
-        return BUG_REPORT_TIMESTAMP_DATE_FORMAT.format(date);
-    }
-
-    /** Creates a {@link Builder} with default, non-null values. */
-    static Builder builder() {
-        return new AutoValue_MetaBugReport.Builder()
-                .setTimestamp("")
-                .setFilePath("")
-                .setBugReportFileName("")
-                .setAudioFileName("")
-                .setStatusMessage("")
-                .setTitle("")
-                .setUserName("");
-    }
-
-    /** A creator that's used by Parcelable. */
-    public static final Parcelable.Creator<MetaBugReport> CREATOR =
-            new Parcelable.Creator<MetaBugReport>() {
-                public MetaBugReport createFromParcel(Parcel in) {
-                    int id = in.readInt();
-                    String timestamp = in.readString();
-                    String title = in.readString();
-                    String username = in.readString();
-                    String filePath = in.readString();
-                    String bugReportFileName = in.readString();
-                    String audioFileName = in.readString();
-                    int status = in.readInt();
-                    String statusMessage = in.readString();
-                    int type = in.readInt();
-                    return MetaBugReport.builder()
-                            .setId(id)
-                            .setTimestamp(timestamp)
-                            .setTitle(title)
-                            .setUserName(username)
-                            .setFilePath(filePath)
-                            .setBugReportFileName(bugReportFileName)
-                            .setAudioFileName(audioFileName)
-                            .setStatus(status)
-                            .setStatusMessage(statusMessage)
-                            .setType(type)
-                            .build();
-                }
-
-                public MetaBugReport[] newArray(int size) {
-                    return new MetaBugReport[size];
-                }
-            };
-
-    /** Builder for MetaBugReport. */
-    @AutoValue.Builder
-    abstract static class Builder {
-        /** Sets id. */
-        public abstract Builder setId(int id);
-
-        /** Sets timestamp. */
-        public abstract Builder setTimestamp(String timestamp);
-
-        /** Sets title. */
-        public abstract Builder setTitle(String title);
-
-        /** Sets username. */
-        public abstract Builder setUserName(String username);
-
-        /** Sets filepath. */
-        public abstract Builder setFilePath(String filePath);
-
-        /** Sets bugReportFileName. */
-        public abstract Builder setBugReportFileName(String bugReportFileName);
-
-        /** Sets audioFileName. */
-        public abstract Builder setAudioFileName(String audioFileName);
-
-        /** Sets {@link Status}. */
-        public abstract Builder setStatus(int status);
-
-        /** Sets statusmessage. */
-        public abstract Builder setStatusMessage(String statusMessage);
-
-        /** Sets the {@link BugReportType}. */
-        public abstract Builder setType(@BugReportType int type);
-
-        public abstract MetaBugReport build();
-    }
-}
diff --git a/tests/BugReportApp/src/com/google/android/car/bugreport/PackageUtils.java b/tests/BugReportApp/src/com/google/android/car/bugreport/PackageUtils.java
deleted file mode 100644
index 26afc43..0000000
--- a/tests/BugReportApp/src/com/google/android/car/bugreport/PackageUtils.java
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.android.car.bugreport;
-
-import android.content.Context;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageManager;
-import android.util.Log;
-
-/**
- * Package related utility methods.
- */
-final class PackageUtils {
-    private static final String TAG = PackageUtils.class.getSimpleName();
-
-    /** Returns a BugReport app version name from {@code AndroidManifest.xml}. */
-    static String getPackageVersion(Context context) {
-        try {
-            PackageInfo packageInfo = context.getPackageManager().getPackageInfo(
-                    context.getPackageName(), 0);
-            return packageInfo.versionName;
-        } catch (PackageManager.NameNotFoundException e) {
-            Log.e(TAG, "Can't get package version.", e);
-            return "unknown";
-        }
-    }
-
-    private PackageUtils() {
-    }
-}
diff --git a/tests/BugReportApp/src/com/google/android/car/bugreport/SimpleUploaderAsyncTask.java b/tests/BugReportApp/src/com/google/android/car/bugreport/SimpleUploaderAsyncTask.java
deleted file mode 100644
index 0e92010..0000000
--- a/tests/BugReportApp/src/com/google/android/car/bugreport/SimpleUploaderAsyncTask.java
+++ /dev/null
@@ -1,204 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.android.car.bugreport;
-
-import android.annotation.NonNull;
-import android.content.Context;
-import android.os.AsyncTask;
-import android.text.TextUtils;
-import android.util.Log;
-
-import com.google.api.client.extensions.android.http.AndroidHttp;
-import com.google.api.client.googleapis.auth.oauth2.GoogleCredential;
-import com.google.api.client.http.HttpTransport;
-import com.google.api.client.http.InputStreamContent;
-import com.google.api.client.json.JsonFactory;
-import com.google.api.client.json.jackson2.JacksonFactory;
-import com.google.api.services.storage.Storage;
-import com.google.api.services.storage.model.StorageObject;
-import com.google.common.base.Strings;
-import com.google.common.collect.ImmutableMap;
-
-import java.io.BufferedOutputStream;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-import java.util.zip.ZipOutputStream;
-
-/**
- * Uploads a bugreport files to GCS using a simple (no-multipart / no-resume) upload policy.
- *
- * <p>It merges bugreport zip file and audio file into one final zip file and uploads it.
- *
- * <p>Please see {@code res/values/configs.xml} and {@code res/raw/gcs_credentials.json} for the
- * configuration.
- */
-class SimpleUploaderAsyncTask extends AsyncTask<Void, Void, Boolean> {
-    private static final String TAG = SimpleUploaderAsyncTask.class.getSimpleName();
-
-    private static final String ACCESS_SCOPE =
-            "https://www.googleapis.com/auth/devstorage.read_write";
-
-    private static final String STORAGE_METADATA_TITLE = "title";
-
-    private final Context mContext;
-    private final Result mResult;
-
-    /**
-     * The uploader uploads only one bugreport each time it is called. This interface is
-     * used to reschedule upload job, if there are more bugreports waiting.
-     *
-     * Pass true to reschedule upload job, false not to reschedule.
-     */
-    interface Result {
-        void reschedule(boolean s);
-    }
-
-    /** Constructs SimpleUploaderAsyncTask. */
-    SimpleUploaderAsyncTask(@NonNull Context context, @NonNull Result result) {
-        mContext = context;
-        mResult = result;
-    }
-
-    private StorageObject uploadSimple(
-            Storage storage, MetaBugReport bugReport, String fileName, InputStream data)
-            throws IOException {
-        InputStreamContent mediaContent = new InputStreamContent("application/zip", data);
-
-        String bucket = mContext.getString(R.string.config_gcs_bucket);
-        if (TextUtils.isEmpty(bucket)) {
-            throw new RuntimeException("config_gcs_bucket is empty.");
-        }
-
-        // Create GCS MetaData.
-        Map<String, String> metadata = ImmutableMap.of(
-                STORAGE_METADATA_TITLE, bugReport.getTitle()
-        );
-        StorageObject object = new StorageObject()
-                .setBucket(bucket)
-                .setName(fileName)
-                .setMetadata(metadata)
-                .setContentDisposition("attachment");
-        Storage.Objects.Insert insertObject = storage.objects().insert(bucket, object,
-                mediaContent);
-
-        // The media uploader gzips content by default, and alters the Content-Encoding accordingly.
-        // GCS dutifully stores content as-uploaded. This line disables the media uploader behavior,
-        // so the service stores exactly what is in the InputStream, without transformation.
-        insertObject.getMediaHttpUploader().setDisableGZipContent(true);
-        Log.v(TAG, "started uploading object " + fileName + " to bucket " + bucket);
-        return insertObject.execute();
-    }
-
-    private void upload(MetaBugReport bugReport) throws IOException {
-        GoogleCredential credential = GoogleCredential
-                .fromStream(mContext.getResources().openRawResource(R.raw.gcs_credentials))
-                .createScoped(Collections.singleton(ACCESS_SCOPE));
-        Log.v(TAG, "Created credential");
-        HttpTransport httpTransport = AndroidHttp.newCompatibleTransport();
-        JsonFactory jsonFactory = JacksonFactory.getDefaultInstance();
-
-        Storage storage = new Storage.Builder(httpTransport, jsonFactory, credential)
-                .setApplicationName("Bugreportupload/1.0").build();
-
-        File tmpBugReportFile = zipBugReportFiles(bugReport);
-        Log.d(TAG, "Uploading file " + tmpBugReportFile);
-        try {
-            // Upload filename is bugreport filename, although, now it contains the audio message.
-            String fileName = bugReport.getBugReportFileName();
-            if (Strings.isNullOrEmpty(fileName)) {
-                // Old bugreports don't contain getBugReportFileName, fallback to getFilePath.
-                fileName = new File(bugReport.getFilePath()).getName();
-            }
-            try (FileInputStream inputStream = new FileInputStream(tmpBugReportFile)) {
-                StorageObject object = uploadSimple(storage, bugReport, fileName, inputStream);
-                Log.v(TAG, "finished uploading object " + object.getName() + " file " + fileName);
-            }
-            File pendingDir = FileUtils.getPendingDir(mContext);
-            // Delete only after successful upload; the files are needed for retry.
-            if (!Strings.isNullOrEmpty(bugReport.getAudioFileName())) {
-                Log.v(TAG, "Deleting file " + bugReport.getAudioFileName());
-                new File(pendingDir, bugReport.getAudioFileName()).delete();
-            }
-            if (!Strings.isNullOrEmpty(bugReport.getBugReportFileName())) {
-                Log.v(TAG, "Deleting file " + bugReport.getBugReportFileName());
-                new File(pendingDir, bugReport.getBugReportFileName()).delete();
-            }
-        } finally {
-            // Delete the temp file if it's not a MetaBugReport#getFilePath, because it's needed
-            // for retry.
-            if (Strings.isNullOrEmpty(bugReport.getFilePath())) {
-                Log.v(TAG, "Deleting file " + tmpBugReportFile);
-                tmpBugReportFile.delete();
-            }
-        }
-    }
-
-    private File zipBugReportFiles(MetaBugReport bugReport) throws IOException {
-        if (!Strings.isNullOrEmpty(bugReport.getFilePath())) {
-            // Old bugreports still have this field.
-            return new File(bugReport.getFilePath());
-        }
-        File finalZipFile =
-                File.createTempFile("bugreport", ".zip", mContext.getCacheDir());
-        File pendingDir = FileUtils.getPendingDir(mContext);
-        try (ZipOutputStream zipStream = new ZipOutputStream(
-                new BufferedOutputStream(new FileOutputStream(finalZipFile)))) {
-            ZipUtils.extractZippedFileToZipStream(
-                    new File(pendingDir, bugReport.getBugReportFileName()), zipStream);
-            ZipUtils.addFileToZipStream(
-                    new File(pendingDir, bugReport.getAudioFileName()), zipStream);
-        }
-        return finalZipFile;
-    }
-
-    @Override
-    protected void onPostExecute(Boolean success) {
-        mResult.reschedule(success);
-    }
-
-    /** Returns true is there are more files to upload. */
-    @Override
-    protected Boolean doInBackground(Void... voids) {
-        List<MetaBugReport> bugReports = BugStorageUtils.getUploadPendingBugReports(mContext);
-
-        for (MetaBugReport bugReport : bugReports) {
-            try {
-                if (isCancelled()) {
-                    BugStorageUtils.setUploadRetry(mContext, bugReport, "Upload Job Cancelled");
-                    return true;
-                }
-                upload(bugReport);
-                BugStorageUtils.setUploadSuccess(mContext, bugReport);
-            } catch (Exception e) {
-                Log.e(TAG, String.format("Failed uploading %s - likely a transient error",
-                        bugReport.getTimestamp()), e);
-                BugStorageUtils.setUploadRetry(mContext, bugReport, e);
-            }
-        }
-        return false;
-    }
-
-    @Override
-    protected void onCancelled(Boolean success) {
-        mResult.reschedule(true);
-    }
-}
diff --git a/tests/BugReportApp/src/com/google/android/car/bugreport/StartUpBootReceiver.java b/tests/BugReportApp/src/com/google/android/car/bugreport/StartUpBootReceiver.java
deleted file mode 100644
index 7e89d2d..0000000
--- a/tests/BugReportApp/src/com/google/android/car/bugreport/StartUpBootReceiver.java
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.android.car.bugreport;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.os.UserManager;
-import android.util.Log;
-
-/**
- * Handles device boot intents.
- *
- * <ul>
- *     <li>Starts {@link UploadJob}</li>
- * </ul>
- */
-public class StartUpBootReceiver extends BroadcastReceiver {
-    public static final String TAG = StartUpBootReceiver.class.getSimpleName();
-
-    @Override
-    public void onReceive(Context context, Intent intent) {
-        if (!Config.isBugReportEnabled()) {
-            return;
-        }
-        // Run it only once for the system user (u0) and ignore for other users.
-        UserManager userManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
-        if (!userManager.isSystemUser()) {
-            return;
-        }
-
-        if (Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction())) {
-            Log.d(TAG, "StartUpBootReceiver BOOT_COMPLETED");
-            // We removed "persisted" from UploadJob scheduling, instead we will manually schedule
-            // the job on boot, because "persisted" seems more fragile.
-            JobSchedulingUtils.scheduleUploadJob(context);
-        }
-    }
-}
diff --git a/tests/BugReportApp/src/com/google/android/car/bugreport/Status.java b/tests/BugReportApp/src/com/google/android/car/bugreport/Status.java
deleted file mode 100644
index 380944e..0000000
--- a/tests/BugReportApp/src/com/google/android/car/bugreport/Status.java
+++ /dev/null
@@ -1,97 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.android.car.bugreport;
-
-/** Defines {@link MetaBugReport} statuses. */
-public enum Status {
-    // Bugreport is being written
-    STATUS_WRITE_PENDING(0),
-
-    // Writing bugreport failed
-    STATUS_WRITE_FAILED(1),
-
-    // Bugreport is waiting to be uploaded
-    STATUS_UPLOAD_PENDING(2),
-
-    // Bugreport uploaded successfully
-    STATUS_UPLOAD_SUCCESS(3),
-
-    // Bugreport failed to upload
-    STATUS_UPLOAD_FAILED(4),
-
-    // Bugreport is cancelled by user
-    STATUS_USER_CANCELLED(5),
-
-    // Bugreport is pending user choice on whether to upload or copy.
-    STATUS_PENDING_USER_ACTION(6),
-
-    // Bugreport was moved successfully.
-    STATUS_MOVE_SUCCESSFUL(7),
-
-    // Bugreport move has failed.
-    STATUS_MOVE_FAILED(8),
-
-    // Bugreport is moving to USB drive.
-    STATUS_MOVE_IN_PROGRESS(9),
-
-    // Bugreport is expired. Associated file is deleted from the disk.
-    STATUS_EXPIRED(10),
-
-    // Bugreport needs audio message.
-    STATUS_AUDIO_PENDING(11);
-
-    private final int mValue;
-
-    Status(int value) {
-        mValue = value;
-    }
-
-    /** Returns integer value of the status. */
-    public int getValue() {
-        return mValue;
-    }
-
-    /** Generates human-readable string from a status value. */
-    public static String toString(int value) {
-        switch (value) {
-            case 0:
-                return "Write pending";
-            case 1:
-                return "Write failed";
-            case 2:
-                return "Upload pending";
-            case 3:
-                return "Upload successful";
-            case 4:
-                return "Upload failed";
-            case 5:
-                return "User cancelled";
-            case 6:
-                return "Pending user action";
-            case 7:
-                return "Move successful";
-            case 8:
-                return "Move failed";
-            case 9:
-                return "Move in progress";
-            case 10:
-                return "Expired";
-            case 11:
-                return "Audio message pending";
-        }
-        return "unknown";
-    }
-}
diff --git a/tests/BugReportApp/src/com/google/android/car/bugreport/UploadJob.java b/tests/BugReportApp/src/com/google/android/car/bugreport/UploadJob.java
deleted file mode 100644
index b2c17e9..0000000
--- a/tests/BugReportApp/src/com/google/android/car/bugreport/UploadJob.java
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.android.car.bugreport;
-
-import android.app.job.JobParameters;
-import android.app.job.JobService;
-import android.util.Log;
-
-/** Executes a {@link SimpleUploaderAsyncTask}. */
-public class UploadJob extends JobService {
-    private static final String TAG = UploadJob.class.getSimpleName();
-
-    private SimpleUploaderAsyncTask mUploader;
-
-    @Override
-    public boolean onStartJob(final JobParameters jobParameters) {
-        if (!Config.isBugReportEnabled()) {
-            return false;
-        }
-        Log.v(TAG, "Starting upload job");
-        mUploader = new SimpleUploaderAsyncTask(
-                this, reschedule -> jobFinished(jobParameters, reschedule));
-        mUploader.execute();
-        return true;
-    }
-
-    @Override
-    public boolean onStopJob(JobParameters jobParameters) {
-        mUploader.cancel(true);
-        return false;
-    }
-}
diff --git a/tests/BugReportApp/src/com/google/android/car/bugreport/VoiceRecordingView.java b/tests/BugReportApp/src/com/google/android/car/bugreport/VoiceRecordingView.java
deleted file mode 100644
index 56d5679..0000000
--- a/tests/BugReportApp/src/com/google/android/car/bugreport/VoiceRecordingView.java
+++ /dev/null
@@ -1,121 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.android.car.bugreport;
-
-import android.annotation.Nullable;
-import android.content.Context;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Paint;
-import android.graphics.Rect;
-import android.graphics.drawable.BitmapDrawable;
-import android.media.MediaRecorder;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.view.View;
-
-/**
- * A view that draws MIC icon and an animated ellipsoid. The ellipsoid animation shows the sound
- * amplitude from {@link MediaRecorder}.
- *
- * <p>All the constant values are chosen experimentally.
- */
-public class VoiceRecordingView extends View {
-    private static final String TAG = VoiceRecordingView.class.getSimpleName();
-
-    private static final float DROPOFF_STEP = 10f;
-    private static final long ANIMATION_INTERVAL_MS = 70;
-    private static final float RECORDER_AMPLITUDE_NORMALIZER_COEF = 16192.0f;
-
-    private final Paint mPaint;
-    private final BitmapDrawable mMicIconDrawable;
-
-    private float mCurrentRadius;
-    private MediaRecorder mRecorder;
-
-    public VoiceRecordingView(Context context, @Nullable AttributeSet attrs) {
-        super(context, attrs);
-        mMicIconDrawable = (BitmapDrawable) context.getDrawable(
-                android.R.drawable.ic_btn_speak_now);
-
-        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
-        mPaint.setColor(Color.LTGRAY);
-        mPaint.setStyle(Paint.Style.FILL);
-    }
-
-    /** Sets MediaRecorder that will be used to animate the ellipsoid. */
-    public void setRecorder(@Nullable MediaRecorder recorder) {
-        mRecorder = recorder;
-        invalidate();
-    }
-
-    @Override
-    protected void onSizeChanged(int w, int h, int oldW, int oldH) {
-        super.onSizeChanged(w, h, oldW, oldH);
-
-        float micIconWidth = mMicIconDrawable.getBitmap().getWidth();
-        float micIconHeight = mMicIconDrawable.getBitmap().getHeight();
-        int micIconDrawableWidth = (int) (micIconWidth / micIconHeight * h);
-        int micIconDrawableLeft = (w - micIconDrawableWidth) / 2;
-        mMicIconDrawable.setBounds(
-                new Rect(micIconDrawableLeft, 0, micIconDrawableLeft + micIconDrawableWidth, h));
-    }
-
-    private void updateCurrentRadius(int width) {
-        final float maxRadius = width / 4;
-        float radius = 0;
-
-        if (mRecorder != null) {
-            try {
-                radius += maxRadius * mRecorder.getMaxAmplitude()
-                        / RECORDER_AMPLITUDE_NORMALIZER_COEF;
-            } catch (IllegalStateException e) {
-                Log.v(TAG, "Failed to get max amplitude from MediaRecorder");
-            }
-        }
-
-        if (radius > mCurrentRadius) {
-            mCurrentRadius = radius;
-        } else {
-            mCurrentRadius = Math.max(radius, mCurrentRadius - DROPOFF_STEP);
-        }
-        mCurrentRadius = Math.min(maxRadius, mCurrentRadius);
-    }
-
-    @Override
-    protected void onDraw(Canvas canvas) {
-        super.onDraw(canvas);
-
-        final int width = canvas.getWidth();
-        final int height = canvas.getHeight();
-
-        updateCurrentRadius(width);
-
-        // Draws an ellipsoid with horizontal radius calculated from MediaRecorder's amplitude.
-        final int mx = width / 2;
-        final int my = height / 2;
-        canvas.drawCircle(mx, my, height / 2, mPaint);
-        canvas.drawCircle(mx - mCurrentRadius, my, height / 2, mPaint);
-        canvas.drawCircle(mx + mCurrentRadius, my, height / 2, mPaint);
-        canvas.drawRect(mx - mCurrentRadius, 0, mx + mCurrentRadius, height, mPaint);
-
-        if (mRecorder != null) {
-            postInvalidateDelayed(ANIMATION_INTERVAL_MS);
-        }
-
-        mMicIconDrawable.draw(canvas);
-    }
-}
diff --git a/tests/BugReportApp/src/com/google/android/car/bugreport/ZipUtils.java b/tests/BugReportApp/src/com/google/android/car/bugreport/ZipUtils.java
deleted file mode 100644
index 6c50d18..0000000
--- a/tests/BugReportApp/src/com/google/android/car/bugreport/ZipUtils.java
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.android.car.bugreport;
-
-import android.util.Log;
-
-import com.google.common.io.ByteStreams;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.Enumeration;
-import java.util.zip.ZipEntry;
-import java.util.zip.ZipFile;
-import java.util.zip.ZipOutputStream;
-
-/** Zip utility functions. */
-final class ZipUtils {
-    private static final String TAG = ZipUtils.class.getSimpleName();
-
-    /** Extracts the contents of a zip file to the zip output stream. */
-    static void extractZippedFileToZipStream(File file, ZipOutputStream zipStream) {
-        if (!file.exists()) {
-            Log.w(TAG, "File " + file + " not found");
-            return;
-        }
-        if (file.length() == 0) {
-            // If there were issues with reading from dumpstate socket, the dumpstate zip
-            // file still might be available in
-            // /data/user_de/0/com.android.shell/files/bugreports/.
-            Log.w(TAG, "Zip file " + file.getName() + " is empty, skipping.");
-            return;
-        }
-        try (ZipFile zipFile = new ZipFile(file)) {
-            Enumeration<? extends ZipEntry> entries = zipFile.entries();
-            while (entries.hasMoreElements()) {
-                ZipEntry entry = entries.nextElement();
-                try (InputStream stream = zipFile.getInputStream(entry)) {
-                    writeInputStreamToZipStream(entry.getName(), stream, zipStream);
-                }
-            }
-        } catch (IOException e) {
-            Log.w(TAG, "Failed to add " + file + " to zip", e);
-        }
-    }
-
-    /** Adds a file to the zip output stream. */
-    static void addFileToZipStream(File file, ZipOutputStream zipStream) {
-        if (!file.exists()) {
-            Log.w(TAG, "File " + file + " not found");
-            return;
-        }
-        if (file.length() == 0) {
-            Log.w(TAG, "File " + file.getName() + " is empty, skipping.");
-            return;
-        }
-        try (FileInputStream audioInput = new FileInputStream(file)) {
-            writeInputStreamToZipStream(file.getName(), audioInput, zipStream);
-        } catch (IOException e) {
-            Log.w(TAG, "Failed to add " + file + "to the final zip");
-        }
-    }
-
-    /** Writes a new file from input stream to the zip stream. */
-    static void writeInputStreamToZipStream(
-            String filename, InputStream input, ZipOutputStream zipStream) throws IOException {
-        ZipEntry entry = new ZipEntry(filename);
-        zipStream.putNextEntry(entry);
-        ByteStreams.copy(input, zipStream);
-        zipStream.closeEntry();
-    }
-
-    private ZipUtils() {}
-}
diff --git a/tests/BugReportApp/tests/Android.bp b/tests/BugReportApp/tests/Android.bp
new file mode 100644
index 0000000..9f54a75
--- /dev/null
+++ b/tests/BugReportApp/tests/Android.bp
@@ -0,0 +1,42 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+android_test {
+    name: "BugReportAppTest",
+
+    srcs: ["src/**/*.java"],
+
+    instrumentation_for: "BugReportApp",
+
+    certificate: "platform",
+    optimize: {
+        enabled: false,
+    },
+    platform_apis: true,
+
+    libs: [
+        "android.test.base",
+        "android.test.mock",
+        "android.test.runner",
+    ],
+
+    static_libs: [
+        "android-support-test",
+        "truth-prebuilt",
+    ],
+
+    test_suites: [
+        "device-tests",
+    ],
+}
diff --git a/tests/BugReportApp/tests/Android.mk b/tests/BugReportApp/tests/Android.mk
deleted file mode 100644
index 2a6ab88..0000000
--- a/tests/BugReportApp/tests/Android.mk
+++ /dev/null
@@ -1,40 +0,0 @@
-# Copyright (C) 2019 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-LOCAL_PATH:= $(call my-dir)
-
-include $(CLEAR_VARS)
-
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-
-LOCAL_PACKAGE_NAME := BugReportAppTest
-LOCAL_INSTRUMENTATION_FOR := BugReportApp
-
-LOCAL_MODULE_TAGS := tests
-LOCAL_CERTIFICATE := platform
-LOCAL_PROGUARD_ENABLED := disabled
-LOCAL_PRIVATE_PLATFORM_APIS := true
-
-LOCAL_JAVA_LIBRARIES := \
-    android.test.base \
-    android.test.mock \
-    android.test.runner
-
-LOCAL_STATIC_JAVA_LIBRARIES := \
-    android-support-test \
-    truth-prebuilt
-
-include $(BUILD_PACKAGE)
-
-include $(CLEAR_VARS)
diff --git a/tests/BugReportApp/tests/AndroidManifest.xml b/tests/BugReportApp/tests/AndroidManifest.xml
index e6a8537..26736c6 100644
--- a/tests/BugReportApp/tests/AndroidManifest.xml
+++ b/tests/BugReportApp/tests/AndroidManifest.xml
@@ -16,7 +16,7 @@
   -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.google.android.car.bugreport.tests" >
+    package="com.android.car.bugreport.tests" >
 
     <application>
         <uses-library android:name="android.test.runner" />
@@ -25,5 +25,5 @@
     <instrumentation
             android:name="android.support.test.runner.AndroidJUnitRunner"
             android:label="BugReportAppTest"
-            android:targetPackage="com.google.android.car.bugreport" />
+            android:targetPackage="com.android.car.bugreport" />
 </manifest>
diff --git a/tests/BugReportApp/tests/src/com/google/android/car/bugreport/BugStorageUtilsTest.java b/tests/BugReportApp/tests/src/com/google/android/car/bugreport/BugStorageUtilsTest.java
index 747cac4..29dcc19 100644
--- a/tests/BugReportApp/tests/src/com/google/android/car/bugreport/BugStorageUtilsTest.java
+++ b/tests/BugReportApp/tests/src/com/google/android/car/bugreport/BugStorageUtilsTest.java
@@ -13,10 +13,11 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.google.android.car.bugreport;
+package com.android.car.bugreport;
 
-import static com.google.android.car.bugreport.MetaBugReport.TYPE_INTERACTIVE;
-import static com.google.android.car.bugreport.Status.STATUS_PENDING_USER_ACTION;
+import static com.android.car.bugreport.MetaBugReport.TYPE_INTERACTIVE;
+import static com.android.car.bugreport.Status.STATUS_PENDING_USER_ACTION;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.junit.Assert.assertTrue;
diff --git a/tests/BugReportApp/utils/bugreport_app_tester.py b/tests/BugReportApp/utils/bugreport_app_tester.py
index e86f827..66d6f11 100755
--- a/tests/BugReportApp/utils/bugreport_app_tester.py
+++ b/tests/BugReportApp/utils/bugreport_app_tester.py
@@ -53,13 +53,13 @@
 
 VERSION = '0.2.0'
 
-BUGREPORT_PACKAGE = 'com.google.android.car.bugreport'
+BUGREPORT_PACKAGE = 'com.android.car.bugreport'
 PENDING_BUGREPORTS_DIR = ('/data/user/0/%s/bug_reports_pending' %
                           BUGREPORT_PACKAGE)
 SQLITE_DB_DIR = '/data/user/0/%s/databases' % BUGREPORT_PACKAGE
 SQLITE_DB_PATH = SQLITE_DB_DIR + '/bugreport.db'
 
-# The statuses are from `src/com/google/android/car/bugreport/Status.java.
+# The statuses are from `src/com.android.car.bugreport/Status.java.
 STATUS_WRITE_PENDING = 0
 STATUS_WRITE_FAILED = 1
 STATUS_UPLOAD_PENDING = 2
diff --git a/tests/BugReportApp/utils/bugreport_app_tester_test.py b/tests/BugReportApp/utils/bugreport_app_tester_test.py
index fd655f4..3a7198f 100644
--- a/tests/BugReportApp/utils/bugreport_app_tester_test.py
+++ b/tests/BugReportApp/utils/bugreport_app_tester_test.py
@@ -81,13 +81,13 @@
 
   def test_delete_all_bugreports(self):
     self._subject._delete_all_bugreports()
-    self.assertEqual(len(self._mock_device.adbx.calls), 2)
-    self.assertEqual(self._mock_device.adbx.calls[0]['args'][0], [
-        'shell', 'rm', '-f', '/data/user/0/com.google.android.car.bugreport/'
+    self.assertEqual(len(self._mock_device.adb.calls), 2)
+    self.assertEqual(self._mock_device.adb.calls[0]['args'][0], [
+        'shell', 'rm', '-f', '/data/user/0/com.android.car.bugreport/'
         'bug_reports_pending/*.zip'
     ])
-    self.assertEqual(self._mock_device.adbx.calls[1]['args'][0], [
-        'shell', 'sqlite3', '/data/user/0/com.google.android.car.bugreport/'
+    self.assertEqual(self._mock_device.adb.calls[1]['args'][0], [
+        'shell', 'sqlite3', '/data/user/0/com.android.car.bugreport/'
         'databases/bugreport.db', "'delete from bugreports;'"
     ])
 
@@ -96,7 +96,7 @@
     self.assertEqual(len(self._mock_device.adbx.calls), 1)
     self.assertEqual(self._mock_device.adbx.calls[0]['args'][0], [
         'shell', 'am', 'start',
-        'com.google.android.car.bugreport/.BugReportActivity'
+        'com.android.car.bugreport/.BugReportActivity'
     ])
 
 
diff --git a/tests/CarCtsDummyLauncher/Android.mk b/tests/CarCtsDummyLauncher/Android.mk
index 6382471..fec1106 100644
--- a/tests/CarCtsDummyLauncher/Android.mk
+++ b/tests/CarCtsDummyLauncher/Android.mk
@@ -17,17 +17,15 @@
 LOCAL_PATH:= $(call my-dir)
 include $(CLEAR_VARS)
 
-LOCAL_MODULE_TAGS := tests
+LOCAL_MODULE_TAGS := optional
 
 # Only compile source java files in this apk.
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
 LOCAL_PACKAGE_NAME := CarCtsDummyLauncher
 
-LOCAL_SDK_VERSION := current
+LOCAL_PRIVATE_PLATFORM_APIS := true
 
-LOCAL_DEX_PREOPT := false
-
-LOCAL_CERTIFICATE := platform
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
 
 include $(BUILD_PACKAGE)
diff --git a/tests/CarCtsDummyLauncher/AndroidManifest.xml b/tests/CarCtsDummyLauncher/AndroidManifest.xml
index 7f88233..32ada71 100644
--- a/tests/CarCtsDummyLauncher/AndroidManifest.xml
+++ b/tests/CarCtsDummyLauncher/AndroidManifest.xml
@@ -24,8 +24,14 @@
         <activity
             android:name=".LauncherActivity"
             android:label="@string/app_name"
-            android:theme="@android:style/Theme.Material.Light.DarkActionBar"
-            android:launchMode="singleTop">
+            android:configChanges="uiMode|mcc|mnc"
+            android:alwaysRetainTaskState="true"
+            android:launchMode="singleTask"
+            android:clearTaskOnLaunch="true"
+            android:stateNotNeeded="true"
+            android:resumeWhilePausing="true"
+            android:windowSoftInputMode="adjustPan"
+            android:theme="@android:style/Theme.NoTitleBar">
             <meta-data android:name="distractionOptimized" android:value="true"/>
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
diff --git a/tests/CarCtsDummyLauncher/res/layout/launcher_activity.xml b/tests/CarCtsDummyLauncher/res/layout/launcher_activity.xml
index 224404f..350f4f3 100644
--- a/tests/CarCtsDummyLauncher/res/layout/launcher_activity.xml
+++ b/tests/CarCtsDummyLauncher/res/layout/launcher_activity.xml
@@ -14,7 +14,8 @@
      limitations under the License.
 -->
 
-<TextView xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/text"
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/message"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     android:gravity="center_vertical|center_horizontal"
diff --git a/tests/CarCtsDummyLauncher/src/com/android/car/dummylauncher/LauncherActivity.java b/tests/CarCtsDummyLauncher/src/com/android/car/dummylauncher/LauncherActivity.java
index 71b3c2b..7111700 100644
--- a/tests/CarCtsDummyLauncher/src/com/android/car/dummylauncher/LauncherActivity.java
+++ b/tests/CarCtsDummyLauncher/src/com/android/car/dummylauncher/LauncherActivity.java
@@ -17,8 +17,13 @@
 package com.android.car.dummylauncher;
 
 import android.app.Activity;
+import android.content.Intent;
 import android.os.Bundle;
+import android.os.Trace;
+import android.os.UserHandle;
+import android.util.Log;
 import android.view.View;
+import android.widget.TextView;
 
 /**
  * A dummy launcher for CTS.
@@ -27,13 +32,76 @@
  * for CTS and they make CTS fail.
  */
 public class LauncherActivity extends Activity {
-    @Override
-    public void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
+    private static final String TAG = "DummyLauncher";
 
+    private int mUserId = UserHandle.USER_NULL;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        mUserId = UserHandle.myUserId();
+        Log.i(TAG, "pre.onCreate(): userId=" + mUserId);
+        super.onCreate(savedInstanceState);
+        Log.i(TAG, "post.onCreate(): userId=" + mUserId);
         View view = getLayoutInflater().inflate(R.layout.launcher_activity, null);
         setContentView(view);
+
+        TextView message = findViewById(R.id.message);
+        message.setText(message.getText() + "\n\nI am user " + mUserId);
         reportFullyDrawn();
+        Log.i(TAG, "done.onCreate(): userId=" + mUserId);
+    }
+
+    @Override
+    protected void onResume() {
+        Trace.traceBegin(Trace.TRACE_TAG_APP, "onResume-" + mUserId);
+        Log.i(TAG, "pre.onResume(): userId=" + mUserId);
+        super.onResume();
+        Log.i(TAG, "post.onResume(): userId=" + mUserId);
+        Trace.traceEnd(Trace.TRACE_TAG_APP);
+    }
+
+    @Override
+    protected void onPostResume() {
+        Trace.traceBegin(Trace.TRACE_TAG_APP, "onPostResume-" + mUserId);
+        Log.i(TAG, "pre.onPostResume(): userId=" + mUserId);
+        super.onPostResume();
+        Log.i(TAG, "post.onPostResume(): userId=" + mUserId);
+        Trace.traceEnd(Trace.TRACE_TAG_APP);
+    }
+
+    @Override
+    protected void onRestart() {
+        Log.i(TAG, "pre.onRestart(): userId=" + mUserId);
+        super.onRestart();
+        Log.i(TAG, "post.onRestart(): userId=" + mUserId);
+    }
+
+    @Override
+    public void onActivityReenter(int resultCode, Intent data) {
+        Log.i(TAG, "pre.onActivityReenter(): userId=" + mUserId);
+        super.onActivityReenter(resultCode, data);
+        Log.i(TAG, "post.onActivityReenter(): userId=" + mUserId);
+    }
+
+    @Override
+    public void onEnterAnimationComplete() {
+        Log.i(TAG, "pre.onEnterAnimationComplete(): userId=" + mUserId);
+        super.onEnterAnimationComplete();
+        Log.i(TAG, "post.onEnterAnimationComplete(): userId=" + mUserId);
+    }
+
+    @Override
+    protected void onStop() {
+        Log.i(TAG, "pre.onStop(): userId=" + mUserId);
+        super.onStop();
+        Log.i(TAG, "post.onStop(): userId=" + mUserId);
+    }
+
+    @Override
+    protected void onDestroy() {
+        Log.i(TAG, "pre.onDestroy(): userId=" + mUserId);
+        super.onDestroy();
+        Log.i(TAG, "post.onDestroy(): userId=" + mUserId);
     }
 }
 
diff --git a/tests/CarDeveloperOptions/Android.mk b/tests/CarDeveloperOptions/Android.mk
index 4ae3a4f..4523962 100644
--- a/tests/CarDeveloperOptions/Android.mk
+++ b/tests/CarDeveloperOptions/Android.mk
@@ -29,7 +29,7 @@
 LOCAL_PACKAGE_NAME := CarDeveloperOptions
 LOCAL_PRIVATE_PLATFORM_APIS := true
 LOCAL_CERTIFICATE := platform
-LOCAL_PRODUCT_MODULE := true
+LOCAL_SYSTEM_EXT_MODULE := true
 LOCAL_PRIVILEGED_MODULE := true
 LOCAL_REQUIRED_MODULES := privapp_whitelist_com.android.car.developeroptions
 LOCAL_MODULE_TAGS := optional
@@ -65,6 +65,7 @@
     carsettings-contextual-card-protos-lite \
     carsettings-log-bridge-protos-lite \
     carsettings-logtags \
+    statslog-settings \
     zxing-core-1.7
 
 LOCAL_PROGUARD_FLAG_FILES := proguard.flags
diff --git a/tests/CarDeveloperOptions/AndroidManifest.xml b/tests/CarDeveloperOptions/AndroidManifest.xml
index 046b386..436d7f0 100644
--- a/tests/CarDeveloperOptions/AndroidManifest.xml
+++ b/tests/CarDeveloperOptions/AndroidManifest.xml
@@ -53,6 +53,7 @@
     <uses-permission android:name="android.permission.INTERNET" />
     <uses-permission android:name="android.permission.CLEAR_APP_USER_DATA" />
     <uses-permission android:name="android.permission.READ_PHONE_STATE" />
+    <uses-permission android:name="android.permission.READ_PRIVILEGED_PHONE_STATE" />
     <uses-permission android:name="android.permission.MODIFY_PHONE_STATE" />
     <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
     <uses-permission android:name="android.permission.WRITE_APN_SETTINGS"/>
@@ -84,7 +85,7 @@
     <uses-permission android:name="android.permission.CONFIGURE_WIFI_DISPLAY" />
     <uses-permission android:name="android.permission.CONFIGURE_DISPLAY_COLOR_MODE" />
     <uses-permission android:name="android.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS" />
-    <uses-permission android:name="android.permission.SET_TIME" />
+    <uses-permission android:name="android.permission.SUGGEST_MANUAL_TIME_AND_ZONE" />
     <uses-permission android:name="android.permission.ACCESS_NOTIFICATIONS" />
     <uses-permission android:name="android.permission.REBOOT" />
     <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
diff --git a/tests/CarDeveloperOptions/CleanSpec.mk b/tests/CarDeveloperOptions/CleanSpec.mk
index 332cf56..18b7928 100644
--- a/tests/CarDeveloperOptions/CleanSpec.mk
+++ b/tests/CarDeveloperOptions/CleanSpec.mk
@@ -47,6 +47,8 @@
 $(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/APPS/Settings_intermediates)
 $(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/APPS/Settings_intermediates)
 $(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/priv-app/Settings)
+$(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/product/priv-app/CarDeveloperOptions)
+$(call add-clean-step, rm -rf $(PRODUCT_OUT)/product/priv-app/CarDeveloperOptions)
 
 # ************************************************
 # NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST
diff --git a/tests/CarDeveloperOptions/OWNERS b/tests/CarDeveloperOptions/OWNERS
index efbf20d..aefc8fb 100644
--- a/tests/CarDeveloperOptions/OWNERS
+++ b/tests/CarDeveloperOptions/OWNERS
@@ -7,15 +7,13 @@
 stenning@google.com
 
 # People who can originally approve code for Settings.
-asapperstein@google.com
-asargent@google.com
-dehboxturtle@google.com
-dhnishi@google.com
-dling@google.com
-jackqdyulei@google.com
-mfritze@google.com
+edgarwang@google.com
+emilychuang@google.com
+rafftsai@google.com
+tmfang@google.com
+
+# Emergency approvers in case the above are not available
 zhfan@google.com
-miket@google.com
 
 # Exempt resource files (because they are in a flat directory and too hard to manage via OWNERS)
-per-file *.xml=*
\ No newline at end of file
+per-file *.xml=*
diff --git a/tests/CarDeveloperOptions/res/values-af/config.xml b/tests/CarDeveloperOptions/res/values-af/config.xml
index b05e073..04c8bf8 100644
--- a/tests/CarDeveloperOptions/res/values-af/config.xml
+++ b/tests/CarDeveloperOptions/res/values-af/config.xml
@@ -17,5 +17,4 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="config_backup_settings_label" msgid="5230713622702899641"></string>
-    <string name="config_grayscale_settings_intent" msgid="7661066612238245852"></string>
 </resources>
diff --git a/tests/CarDeveloperOptions/res/values-af/strings.xml b/tests/CarDeveloperOptions/res/values-af/strings.xml
index 6b63259..740beee 100644
--- a/tests/CarDeveloperOptions/res/values-af/strings.xml
+++ b/tests/CarDeveloperOptions/res/values-af/strings.xml
@@ -668,7 +668,6 @@
       <item quantity="other">Moet minder as <xliff:g id="NUMBER_1">%d</xliff:g> syfers wees</item>
       <item quantity="one">Moet minder as <xliff:g id="NUMBER_0">%d</xliff:g> syfer wees</item>
     </plurals>
-    <string name="lockpassword_pin_contains_non_digits" msgid="1501729742023195556">"Mag net syfers 0-9 bevat"</string>
     <string name="lockpassword_pin_recently_used" msgid="5556148774911741103">"Toesteladministrateur laat nie toe dat jy \'n onlangse PIN gebruik nie"</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="2752037810571200697">"Jou IT-administrateur blokkeer algemene PIN\'e. Probeer \'n ander PIN."</string>
     <string name="lockpassword_illegal_character" msgid="3577712947676261351">"Dit mag nie \'n ongeldige karakter insluit nie"</string>
@@ -699,6 +698,10 @@
       <item quantity="other">Moet minstens <xliff:g id="COUNT">%d</xliff:g> nieletterkarakters bevat</item>
       <item quantity="one">Moet minstens 1 nieletterkarakter bevat</item>
     </plurals>
+    <plurals name="lockpassword_password_requires_nonnumerical" formatted="false" msgid="6943707011152180150">
+      <item quantity="other">Moet minstens <xliff:g id="COUNT">%d</xliff:g> nienumeriese karakters bevat</item>
+      <item quantity="one">Moet minstens 1 nienumeriese karakter bevat</item>
+    </plurals>
     <string name="lockpassword_password_recently_used" msgid="6944729699375087431">"Toesteladministrateur laat nie toe dat jy \'n onlangse wagwoord gebruik nie"</string>
     <string name="lockpassword_password_blacklisted_by_admin" msgid="4988166770148440755">"Jou IT-administrateur blokkeer algemene wagwoorde. Probeer \'n ander wagwoord."</string>
     <string name="lockpassword_pin_no_sequential_digits" msgid="3902387296149848324">"Stygende, dalende of herhalende volgorde van syfers word nie toegelaat nie"</string>
diff --git a/tests/CarDeveloperOptions/res/values-am/config.xml b/tests/CarDeveloperOptions/res/values-am/config.xml
index b05e073..04c8bf8 100644
--- a/tests/CarDeveloperOptions/res/values-am/config.xml
+++ b/tests/CarDeveloperOptions/res/values-am/config.xml
@@ -17,5 +17,4 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="config_backup_settings_label" msgid="5230713622702899641"></string>
-    <string name="config_grayscale_settings_intent" msgid="7661066612238245852"></string>
 </resources>
diff --git a/tests/CarDeveloperOptions/res/values-am/strings.xml b/tests/CarDeveloperOptions/res/values-am/strings.xml
index 8cc3ee5..7ba65c3 100644
--- a/tests/CarDeveloperOptions/res/values-am/strings.xml
+++ b/tests/CarDeveloperOptions/res/values-am/strings.xml
@@ -668,7 +668,6 @@
       <item quantity="one">ከ<xliff:g id="NUMBER_1">%d</xliff:g> አሃዞች ያነሰ መሆን አለበት</item>
       <item quantity="other">ከ<xliff:g id="NUMBER_1">%d</xliff:g> አሃዞች ያነሰ መሆን አለበት</item>
     </plurals>
-    <string name="lockpassword_pin_contains_non_digits" msgid="1501729742023195556">"ከ0-9 የሆኑ አሃዞችን ብቻ ነው መያዝ አለበት"</string>
     <string name="lockpassword_pin_recently_used" msgid="5556148774911741103">"የመሣሪያ አስተዳዳሪው የቅርብ ጊዜ ፒን መጠቀም አይፈቅድም"</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="2752037810571200697">"የተለመዱ ፒኖች በአይቲ አስተዳዳሪዎ የታገዱ ናቸው። የተለየ ፒን ይሞክሩ።"</string>
     <string name="lockpassword_illegal_character" msgid="3577712947676261351">"ይህ ልክ ያልሆነ ቁምፊ ማካተት አይችልም"</string>
@@ -699,6 +698,10 @@
       <item quantity="one">ቢያንስ <xliff:g id="COUNT">%d</xliff:g> ፊደል ያልሆኑ ቁምፊዎችን መያዝ አለበት</item>
       <item quantity="other">ቢያንስ <xliff:g id="COUNT">%d</xliff:g> ፊደል ያልሆኑ ቁምፊዎችን መያዝ አለበት</item>
     </plurals>
+    <plurals name="lockpassword_password_requires_nonnumerical" formatted="false" msgid="6943707011152180150">
+      <item quantity="one">ቢያንስ <xliff:g id="COUNT">%d</xliff:g> ቁጥር ያልሆኑ ቁምፊዎች ሊኖረው ይገባል</item>
+      <item quantity="other">ቢያንስ <xliff:g id="COUNT">%d</xliff:g> ቁጥር ያልሆኑ ቁምፊዎች ሊኖረው ይገባል</item>
+    </plurals>
     <string name="lockpassword_password_recently_used" msgid="6944729699375087431">"የመሣሪያ አስተዳዳሪው የቅርብ ጊዜ የይለፍ ቃልን መጠቀም አይፈቅድም"</string>
     <string name="lockpassword_password_blacklisted_by_admin" msgid="4988166770148440755">"የተለመዱ የይለፍ ቃላት በአይቲ አስተዳዳሪዎ የታገዱ ናቸው። የተለየ የይለፍ ቃል ይሞክሩ።"</string>
     <string name="lockpassword_pin_no_sequential_digits" msgid="3902387296149848324">"ሽቅብ፣ ቁልቁል ወይም ተደጋጋሚ የአኃዞች ተከታታይ አይፈቀድም"</string>
diff --git a/tests/CarDeveloperOptions/res/values-ar/config.xml b/tests/CarDeveloperOptions/res/values-ar/config.xml
index b05e073..04c8bf8 100644
--- a/tests/CarDeveloperOptions/res/values-ar/config.xml
+++ b/tests/CarDeveloperOptions/res/values-ar/config.xml
@@ -17,5 +17,4 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="config_backup_settings_label" msgid="5230713622702899641"></string>
-    <string name="config_grayscale_settings_intent" msgid="7661066612238245852"></string>
 </resources>
diff --git a/tests/CarDeveloperOptions/res/values-ar/strings.xml b/tests/CarDeveloperOptions/res/values-ar/strings.xml
index ca36a5f..1eb5f82 100644
--- a/tests/CarDeveloperOptions/res/values-ar/strings.xml
+++ b/tests/CarDeveloperOptions/res/values-ar/strings.xml
@@ -136,7 +136,7 @@
     <string name="device_picker" msgid="8345264486071697705">"اختيار جهاز البلوتوث"</string>
     <string name="bluetooth_ask_enablement" msgid="8716802066127746062">"يريد <xliff:g id="APP_NAME">%1$s</xliff:g> تفعيل البلوتوث"</string>
     <string name="bluetooth_ask_disablement" msgid="7125319551097350783">"يريد <xliff:g id="APP_NAME">%1$s</xliff:g> إيقاف البلوتوث"</string>
-    <string name="bluetooth_ask_enablement_no_name" msgid="6105893027185475233">"يريد أحد التطبيقات تفعيل بلوتوث"</string>
+    <string name="bluetooth_ask_enablement_no_name" msgid="6105893027185475233">"يريد أحد التطبيقات تشغيل بلوتوث"</string>
     <string name="bluetooth_ask_disablement_no_name" msgid="8648888502291681310">"يريد أحد التطبيقات إيقاف البلوتوث"</string>
     <string name="bluetooth_ask_discovery" product="tablet" msgid="6871595755186170115">"يريد <xliff:g id="APP_NAME">%1$s</xliff:g> أن يكون جهازك اللوحي مرئيًا لأجهزة بلوتوث الأخرى لمدة <xliff:g id="TIMEOUT">%2$d</xliff:g> من الثواني."</string>
     <string name="bluetooth_ask_discovery" product="default" msgid="3388041479101348095">"يريد <xliff:g id="APP_NAME">%1$s</xliff:g> أن يكون هاتفك مرئيًا لأجهزة بلوتوث الأخرى لمدة <xliff:g id="TIMEOUT">%2$d</xliff:g> من الثواني."</string>
@@ -183,7 +183,7 @@
     <string name="connected_device_available_call_title" msgid="6774859446815858428">"أجهزة الاتصال المتاحة"</string>
     <string name="connected_device_connected_title" msgid="6255107326608785482">"الأجهزة المتصلة حاليًا"</string>
     <string name="connected_device_saved_title" msgid="8270136893488475163">"الأجهزة المحفوظة"</string>
-    <string name="connected_device_add_device_summary" msgid="7960491471270158891">"سيتم تفعيل بلوتوث للإقران"</string>
+    <string name="connected_device_add_device_summary" msgid="7960491471270158891">"سيتم تشغيل بلوتوث للإقران"</string>
     <string name="connected_device_connections_title" msgid="9205000271382018428">"إعدادات الاتصال المفضّلة"</string>
     <string name="connected_device_previously_connected_title" msgid="225918223397410428">"الأجهزة المتصلة سابقًا"</string>
     <string name="connected_device_previously_connected_screen_title" msgid="2018789662358162716">"الأجهزة المتصلة سابقًا"</string>
@@ -700,7 +700,6 @@
       <item quantity="other">يجب ألا يزيد رقم التعريف الشخصي عن <xliff:g id="NUMBER_1">%d</xliff:g> رقم.</item>
       <item quantity="one">يجب ألا يزيد رقم التعريف الشخصي عن رقم واحد (<xliff:g id="NUMBER_0">%d</xliff:g>).</item>
     </plurals>
-    <string name="lockpassword_pin_contains_non_digits" msgid="1501729742023195556">"يجب أن يحتوي رقم التعريف الشخصي على أرقام من ۰–٩ فقط."</string>
     <string name="lockpassword_pin_recently_used" msgid="5556148774911741103">"لا يسمح مشرف الجهاز باستخدام رقم تعريف شخصي تم استخدامه مؤخرًا"</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="2752037810571200697">"حظرَ مشرف تكنولوجيا المعلومات استخدام أرقام التعريف الشخصية الشائعة. جرِّب استخدام رقم تعريف شخصي مختلف."</string>
     <string name="lockpassword_illegal_character" msgid="3577712947676261351">"لا يجب أن تتضمن كلمة المرور حرفًا غير صالح."</string>
@@ -755,6 +754,14 @@
       <item quantity="other">يجب أن تحتوي كلمة المرور على <xliff:g id="COUNT">%d</xliff:g> حرف ليس من الأحرف الأبجدية على الأقل</item>
       <item quantity="one">يجب أن تحتوي كلمة المرور على حرف واحد ليس من الأحرف الأبجدية على الأقل</item>
     </plurals>
+    <plurals name="lockpassword_password_requires_nonnumerical" formatted="false" msgid="6943707011152180150">
+      <item quantity="zero">يجب أن تحتوي كلمة المرور على <xliff:g id="COUNT">%d</xliff:g> حرف أبجدي على الأقل.</item>
+      <item quantity="two">يجب أن تحتوي كلمة المرور على حرفين (<xliff:g id="COUNT">%d</xliff:g>) أبجديين على الأقل.</item>
+      <item quantity="few">يجب أن تحتوي كلمة المرور على <xliff:g id="COUNT">%d</xliff:g> أحرف أبجدية على الأقل.</item>
+      <item quantity="many">يجب أن تحتوي كلمة المرور على <xliff:g id="COUNT">%d</xliff:g> حرفًا أبجديًا على الأقل.</item>
+      <item quantity="other">يجب أن تحتوي كلمة المرور على <xliff:g id="COUNT">%d</xliff:g> حرف أبجدي على الأقل.</item>
+      <item quantity="one">يجب أن تحتوي كلمة المرور على حرف أبجدي واحد على الأقل.</item>
+    </plurals>
     <string name="lockpassword_password_recently_used" msgid="6944729699375087431">"لا يسمح مشرف الجهاز باستخدام كلمة مرور تم استخدامها مؤخرًا"</string>
     <string name="lockpassword_password_blacklisted_by_admin" msgid="4988166770148440755">"حظرَ مشرف تكنولوجيا المعلومات استخدام كلمات المرور الشائعة. جرِّب استخدام كلمة مرور مختلفة."</string>
     <string name="lockpassword_pin_no_sequential_digits" msgid="3902387296149848324">"غير مسموح باستخدام الترتيب التصاعدي أو التنازلي أو المكرر للأرقام"</string>
@@ -885,7 +892,7 @@
     <string name="nfc_quick_toggle_title" msgid="4990697912813795002">"الاتصالات قصيرة المدى (NFC)"</string>
     <string name="nfc_quick_toggle_summary" product="tablet" msgid="983451155092850657">"السماح بتبادل البيانات عندما يلمس الجهاز اللوحي جهازًا آخر"</string>
     <string name="nfc_quick_toggle_summary" product="default" msgid="7141056939052895142">"السماح بتبادل البيانات عندما يلمس الهاتف جهازًا آخر"</string>
-    <string name="nfc_disclaimer_title" msgid="4860231267351602970">"تفعيل NFC"</string>
+    <string name="nfc_disclaimer_title" msgid="4860231267351602970">"تشغيل NFC"</string>
     <string name="nfc_disclaimer_content" msgid="3066113577854565782">"يتم من خلال تقنية الاتصال بالحقل القريب (NFC) تبادل البيانات بين هذا الجهاز والأجهزة أو الأهداف المجاورة الأخرى، مثل محطات الدفع وبرامج قراءة الوصول والعلامات أو الإعلانات التفاعلية."</string>
     <string name="nfc_secure_settings_title" msgid="5153751163174916581">"تأمين الاتصال بالحقل القريب (NFC)"</string>
     <string name="nfc_secure_toggle_summary" product="default" msgid="7631183023440112192">"السماح باستخدام \"الدفع والتقل عبر تقنية NFC\" فقط عندما تكون الشاشة مفتوحة"</string>
@@ -2597,7 +2604,7 @@
     <skip />
     <string name="battery_saver_seekbar_title_placeholder" msgid="2321082163892561703">"تفعيل"</string>
     <string name="battery_saver_master_switch_title" msgid="8241862826825517212">"استخدام الوضع \"توفير شحن البطارية\""</string>
-    <string name="battery_saver_turn_on_automatically_title" msgid="7316920304024245838">"التفعيل تلقائيًا"</string>
+    <string name="battery_saver_turn_on_automatically_title" msgid="7316920304024245838">"التشغيل تلقائيًا"</string>
     <string name="battery_saver_turn_on_automatically_never" msgid="2623381258359775227">"أبدًا"</string>
     <string name="battery_saver_turn_on_automatically_pct" msgid="434270432432390307">"البطارية عند مستوى <xliff:g id="PERCENT">%1$s</xliff:g>"</string>
     <string name="battery_percentage" msgid="7782252476471033843">"نسبة شحن البطارية"</string>
@@ -2917,7 +2924,7 @@
     <string name="vpn_replace_always_on_vpn_enable_message" msgid="4149931501300203205">"سيتم استبدال شبكتك الافتراضية الخاصة (VPN) الحالية ولن تتمكن من الاتصال بالإنترنت إلا بعد أن ينجح اتصال الشبكة الافتراضية الخاصة (VPN)"</string>
     <string name="vpn_replace_always_on_vpn_disable_message" msgid="4924947523200883088">"أنت متصل حاليًا بشبكة افتراضية خاصة (VPN) مضبوطة على وضع التشغيل الدائم. وإذا اتصلت بشبكة افتراضية خاصة أخرى، فسيتم استبدال شبكتك الافتراضية الخاصة الحالية، كما سيتم إيقاف وضع التشغيل الدائم."</string>
     <string name="vpn_replace_vpn_message" msgid="8009433728523145393">"أنت متصل حاليًا بشبكة افتراضية خاصة (VPN). وإذا اتصلت بشبكة افتراضية خاصة أخرى، فسيتم استبدال شبكتك الافتراضية الخاصة الحالية."</string>
-    <string name="vpn_turn_on" msgid="2334736319093953055">"تفعيل"</string>
+    <string name="vpn_turn_on" msgid="2334736319093953055">"تشغيل"</string>
     <string name="vpn_cant_connect_title" msgid="5803347131129293771">"يتعذر الاتصال بـ <xliff:g id="VPN_NAME">%1$s</xliff:g>"</string>
     <string name="vpn_cant_connect_message" msgid="7729125036551401236">"لا يتوافق هذا التطبيق مع الشبكة الافتراضية الخاصة (VPN) التي يتم تشغيلها دائمًا"</string>
     <string name="vpn_title" msgid="7801918186865029203">"الشبكات الافتراضية الخاصة"</string>
@@ -3328,7 +3335,7 @@
       <item quantity="one">تم تفعيل قاعدة واحدة.</item>
     </plurals>
     <string name="zen_mode_settings_title" msgid="3425263414594779244">"وضع \"عدم الإزعاج\""</string>
-    <string name="zen_mode_settings_turn_on_dialog_title" msgid="3062548369931058282">"تفعيل وضع \"الرجاء عدم الإزعاج\""</string>
+    <string name="zen_mode_settings_turn_on_dialog_title" msgid="3062548369931058282">"تشغيل وضع \"الرجاء عدم الإزعاج\""</string>
     <string name="zen_mode_behavior_settings_title" msgid="423125904296667490">"الاستثناءات"</string>
     <string name="zen_mode_duration_settings_title" msgid="5522668871014735728">"المدة التلقائية"</string>
     <string name="zen_mode_behavior_allow_title" msgid="2440627647424280842">"السماح بالأصوات والاهتزازات من"</string>
@@ -3388,7 +3395,7 @@
     <string name="zen_mode_no_exceptions" msgid="3099578343994492857">"بدون"</string>
     <string name="zen_mode_other_options" msgid="7216192179063769057">"خيارات أخرى"</string>
     <string name="zen_mode_add" msgid="2533484377786927366">"إضافة"</string>
-    <string name="zen_mode_enable_dialog_turn_on" msgid="6396050543542026184">"تفعيل"</string>
+    <string name="zen_mode_enable_dialog_turn_on" msgid="6396050543542026184">"تشغيل"</string>
     <string name="zen_mode_button_turn_on" msgid="1097964136225943415">"التفعيل الآن"</string>
     <string name="zen_mode_button_turn_off" msgid="3990967728457149454">"الإيقاف الآن"</string>
     <string name="zen_mode_settings_dnd_manual_end_time" msgid="4307574188962071429">"وضع \"الرجاء عدم الإزعاج\" مفعَّل حتى <xliff:g id="FORMATTED_TIME">%s</xliff:g>"</string>
@@ -3633,9 +3640,9 @@
     <string name="zen_mode_unknown_app_set_behavior" msgid="5666462954329932302">"لا يمكن تغيير هذه الإعدادات الآن. لقد اختار أحد التطبيقات تفعيل وضع \"الرجاء عدم الإزعاج\" تلقائيًا مع سلوك مخصّص."</string>
     <string name="zen_mode_qs_set_behavior" msgid="788646569296973998">"لا يمكن تغيير هذه الإعدادات الآن. تم تفعيل وضع \"الرجاء عدم الإزعاج\" يدويًا مع سلوك مخصّص."</string>
     <string name="zen_schedule_rule_type_name" msgid="4516851728113801329">"وقت"</string>
-    <string name="zen_schedule_rule_enabled_toast" msgid="1742354493045049048">"تم ضبط القاعدة التلقائية على تفعيل وضع \"عدم الإزعاج\" أثناء أوقات محددة"</string>
+    <string name="zen_schedule_rule_enabled_toast" msgid="1742354493045049048">"تم ضبط القاعدة التلقائية على تشغيل وضع \"عدم الإزعاج\" أثناء أوقات محددة"</string>
     <string name="zen_event_rule_type_name" msgid="7467729997336583342">"حدث"</string>
-    <string name="zen_event_rule_enabled_toast" msgid="7087368268966855976">"تم ضبط القاعدة التلقائية على تفعيل وضع \"عدم الإزعاج\" أثناء أحداث محددة"</string>
+    <string name="zen_event_rule_enabled_toast" msgid="7087368268966855976">"تم ضبط القاعدة التلقائية على تشغيل وضع \"عدم الإزعاج\" أثناء أحداث محددة"</string>
     <string name="zen_mode_event_rule_calendar" msgid="6088077103908487442">"أثناء الأحداث لـ"</string>
     <string name="zen_mode_event_rule_summary_calendar_template" msgid="4027207992040792657">"أثناء الأحداث للتقويم <xliff:g id="CALENDAR">%1$s</xliff:g>"</string>
     <string name="zen_mode_event_rule_summary_any_calendar" msgid="7590085295784895885">"أي تقويم"</string>
@@ -3716,7 +3723,7 @@
     <string name="zen_mode_calls_summary_two" msgid="9017678770532673578">"السماح بالمكالمات من <xliff:g id="CALLER_TYPE">%1$s</xliff:g> و<xliff:g id="CALLERT_TPYE">%2$s</xliff:g>"</string>
     <string name="zen_mode_repeat_callers_summary" msgid="1752513516040525545">"في حال اتصال الشخص نفسه للمرة الثانية في غضون <xliff:g id="MINUTES">%d</xliff:g> دقيقة"</string>
     <string name="zen_mode_behavior_summary_custom" msgid="3909532602640130841">"مخصص"</string>
-    <string name="zen_mode_when" msgid="7835420687948416255">"تفعيل تلقائي"</string>
+    <string name="zen_mode_when" msgid="7835420687948416255">"تشغيل تلقائي"</string>
     <string name="zen_mode_when_never" msgid="4506296396609224524">"مطلقًا"</string>
     <string name="zen_mode_when_every_night" msgid="3942151668791426194">"كل ليلة"</string>
     <string name="zen_mode_when_weeknights" msgid="2412709309122408474">"ليالي الأسبوع"</string>
@@ -4119,7 +4126,7 @@
     <string name="admin_profile_owner_user_message" msgid="2991249382056855531">"يمكن للمشرف مراقبة التطبيقات والبيانات المرتبطة بهذا المستخدم وإدارتها، بما في ذلك الإعدادات والأذونات والدخول إلى المؤسسة ونشاط الشبكة ومعلومات موقع الجهاز."</string>
     <string name="admin_device_owner_message" msgid="1823477572459610869">"يمكن للمشرف مراقبة التطبيقات والبيانات المرتبطة بهذا الجهاز وإدارتها، بما في ذلك الإعدادات والأذونات والدخول إلى المؤسسة ونشاط الشبكة ومعلومات موقع الجهاز."</string>
     <string name="condition_turn_off" msgid="4395150881365143558">"إيقاف"</string>
-    <string name="condition_turn_on" msgid="1699088245481841159">"تفعيل"</string>
+    <string name="condition_turn_on" msgid="1699088245481841159">"تشغيل"</string>
     <string name="condition_expand_show" msgid="4118818022763913777">"إظهار"</string>
     <string name="condition_expand_hide" msgid="1112721783024332643">"إخفاء"</string>
     <string name="condition_hotspot_title" msgid="4143299802283098506">"نقطة الاتصال مفعّلة"</string>
@@ -4128,7 +4135,7 @@
     <string name="condition_zen_title" msgid="2128184708916052585">"تمّ تفعيل \"الرجاء عدم الإزعاج\""</string>
     <string name="condition_zen_summary_phone_muted" msgid="4396050395522974654">"تم كتم صوت الهاتف."</string>
     <string name="condition_zen_summary_with_exceptions" msgid="3435216391993785818">"مع بعض الاستثناءات"</string>
-    <string name="condition_battery_title" msgid="6704870010912986274">"تم تفعيل وضع توفير شحن البطارية"</string>
+    <string name="condition_battery_title" msgid="6704870010912986274">"تم تشغيل وضع توفير شحن البطارية"</string>
     <string name="condition_battery_summary" msgid="1236078243905690620">"تم تقييد الميزات."</string>
     <string name="condition_cellular_title" msgid="6605277435894307935">"تم إيقاف بيانات الجوّال"</string>
     <string name="condition_cellular_summary" msgid="3607459310548343777">"يتوفّر الإنترنت من خلال Wi‑Fi فقط."</string>
diff --git a/tests/CarDeveloperOptions/res/values-as/config.xml b/tests/CarDeveloperOptions/res/values-as/config.xml
index b05e073..04c8bf8 100644
--- a/tests/CarDeveloperOptions/res/values-as/config.xml
+++ b/tests/CarDeveloperOptions/res/values-as/config.xml
@@ -17,5 +17,4 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="config_backup_settings_label" msgid="5230713622702899641"></string>
-    <string name="config_grayscale_settings_intent" msgid="7661066612238245852"></string>
 </resources>
diff --git a/tests/CarDeveloperOptions/res/values-as/strings.xml b/tests/CarDeveloperOptions/res/values-as/strings.xml
index 2e436ba..83840d7 100644
--- a/tests/CarDeveloperOptions/res/values-as/strings.xml
+++ b/tests/CarDeveloperOptions/res/values-as/strings.xml
@@ -667,7 +667,6 @@
       <item quantity="one"><xliff:g id="NUMBER_1">%d</xliff:g>টা সংখ্যাতকৈ কম হ\'বই লাগিব</item>
       <item quantity="other"><xliff:g id="NUMBER_1">%d</xliff:g>টা সংখ্যাতকৈ কম হ\'বই লাগিব</item>
     </plurals>
-    <string name="lockpassword_pin_contains_non_digits" msgid="1501729742023195556">"কেৱল অংকহে থাকিব লাগিব ০-৯"</string>
     <string name="lockpassword_pin_recently_used" msgid="5556148774911741103">"ডিভাইচ এডমিনে আপুনি ব্যৱহাৰ কৰা অন্তিমটো পিন আকৌ ব্যৱহাৰ কৰিবলৈ অনুমতি দিয়া নাই"</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="2752037810571200697">"আপোনাৰ আইটি প্ৰশাসকে বিশেষত্বহীন পিনবোৰ অৱৰোধ কৰি থৈছে৷ এটা পৃথক পিনেৰে চেষ্টা কৰক৷"</string>
     <string name="lockpassword_illegal_character" msgid="3577712947676261351">"কেৱল বৈধ বৰ্ণহে দিয়ক"</string>
@@ -698,6 +697,10 @@
       <item quantity="one">অতি কমেও <xliff:g id="COUNT">%d</xliff:g>টা সংখ্যা বা প্ৰতীক থাকিব লাগিব</item>
       <item quantity="other">অতি কমেও <xliff:g id="COUNT">%d</xliff:g>টা সংখ্যা বা প্ৰতীক থাকিব লাগিব</item>
     </plurals>
+    <plurals name="lockpassword_password_requires_nonnumerical" formatted="false" msgid="6943707011152180150">
+      <item quantity="one">অতি কমেও <xliff:g id="COUNT">%d</xliff:g>টা এনেকুৱা বৰ্ণ থাকিবই লাগিব যিটো সংখ্যা নহয়</item>
+      <item quantity="other">অতি কমেও <xliff:g id="COUNT">%d</xliff:g>টা এনেকুৱা বৰ্ণ থাকিবই লাগিব যিটো সংখ্যা নহয়</item>
+    </plurals>
     <string name="lockpassword_password_recently_used" msgid="6944729699375087431">"ডিভাইচৰ প্ৰশাসকে শেহতীয়া পাছৱৰ্ড ব্যৱহাৰ কৰিব নিদিয়ে"</string>
     <string name="lockpassword_password_blacklisted_by_admin" msgid="4988166770148440755">"আইটি এডমিনে উমৈহতীয়া পাছৱৰ্ডসমূহ অৱৰোধ কৰি থৈছে। এটা পৃথক পাছৱৰ্ডেৰে চেষ্টা কৰক।"</string>
     <string name="lockpassword_pin_no_sequential_digits" msgid="3902387296149848324">"সংখ্যাবোৰৰ ঊৰ্দ্ধগামী, অধোগামী বা পুনৰাবৃত্তি ক্ৰম অনুমোদিত নহয়"</string>
diff --git a/tests/CarDeveloperOptions/res/values-az/config.xml b/tests/CarDeveloperOptions/res/values-az/config.xml
index b05e073..04c8bf8 100644
--- a/tests/CarDeveloperOptions/res/values-az/config.xml
+++ b/tests/CarDeveloperOptions/res/values-az/config.xml
@@ -17,5 +17,4 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="config_backup_settings_label" msgid="5230713622702899641"></string>
-    <string name="config_grayscale_settings_intent" msgid="7661066612238245852"></string>
 </resources>
diff --git a/tests/CarDeveloperOptions/res/values-az/strings.xml b/tests/CarDeveloperOptions/res/values-az/strings.xml
index 0011fd3..e2398a6 100644
--- a/tests/CarDeveloperOptions/res/values-az/strings.xml
+++ b/tests/CarDeveloperOptions/res/values-az/strings.xml
@@ -668,7 +668,6 @@
       <item quantity="other"><xliff:g id="NUMBER_1">%d</xliff:g> rəqəmdən az olmalıdır</item>
       <item quantity="one"><xliff:g id="NUMBER_0">%d</xliff:g> rəqəmdən az olmalıdır</item>
     </plurals>
-    <string name="lockpassword_pin_contains_non_digits" msgid="1501729742023195556">"Yalnız 0-9 rəqəmlərindən ibarət olmalıdır"</string>
     <string name="lockpassword_pin_recently_used" msgid="5556148774911741103">"Cihaz admini son vaxtlardakı PIN kodu istifadə etməyə icazə vermir"</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="2752037810571200697">"Ümumi PIN-lər IT admini tərəfindən blok edilib. Fərqli PIN-i sınayın."</string>
     <string name="lockpassword_illegal_character" msgid="3577712947676261351">"Yalnış simvol daxil edə bilməzsiniz"</string>
@@ -699,6 +698,10 @@
       <item quantity="other">Ən azı hərf olmayan <xliff:g id="COUNT">%d</xliff:g> simvol olmalıdır</item>
       <item quantity="one">Ən azı hərf olmayan 1 simvol olmalıdır</item>
     </plurals>
+    <plurals name="lockpassword_password_requires_nonnumerical" formatted="false" msgid="6943707011152180150">
+      <item quantity="other">Ən azı rəqəm olmayan <xliff:g id="COUNT">%d</xliff:g> simvol olmalıdır</item>
+      <item quantity="one">Ən azı rəqəm olmayan 1 simvol olmalıdır</item>
+    </plurals>
     <string name="lockpassword_password_recently_used" msgid="6944729699375087431">"Cihaz admini son vaxtlardakı parolu istifadə etməyə icazə vermir"</string>
     <string name="lockpassword_password_blacklisted_by_admin" msgid="4988166770148440755">"Ümumi parollar IT admini tərəfindən blok edilib. Fərqli parolu sınayın."</string>
     <string name="lockpassword_pin_no_sequential_digits" msgid="3902387296149848324">"Artan, azalan və ya təkrarlanan rəqəm ardıcıllığı qadağandır"</string>
diff --git a/tests/CarDeveloperOptions/res/values-b+sr+Latn/config.xml b/tests/CarDeveloperOptions/res/values-b+sr+Latn/config.xml
index b05e073..04c8bf8 100644
--- a/tests/CarDeveloperOptions/res/values-b+sr+Latn/config.xml
+++ b/tests/CarDeveloperOptions/res/values-b+sr+Latn/config.xml
@@ -17,5 +17,4 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="config_backup_settings_label" msgid="5230713622702899641"></string>
-    <string name="config_grayscale_settings_intent" msgid="7661066612238245852"></string>
 </resources>
diff --git a/tests/CarDeveloperOptions/res/values-b+sr+Latn/strings.xml b/tests/CarDeveloperOptions/res/values-b+sr+Latn/strings.xml
index be6b529..714da21 100644
--- a/tests/CarDeveloperOptions/res/values-b+sr+Latn/strings.xml
+++ b/tests/CarDeveloperOptions/res/values-b+sr+Latn/strings.xml
@@ -676,7 +676,6 @@
       <item quantity="few">Mora da sadrži manje od <xliff:g id="NUMBER_1">%d</xliff:g> cifre</item>
       <item quantity="other">Mora da sadrži manje od <xliff:g id="NUMBER_1">%d</xliff:g> cifara</item>
     </plurals>
-    <string name="lockpassword_pin_contains_non_digits" msgid="1501729742023195556">"Mora da sadrži samo cifre 0–9"</string>
     <string name="lockpassword_pin_recently_used" msgid="5556148774911741103">"Administrator uređaja ne dozvoljava upotrebu nedavno korišćenog PIN-a"</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="2752037810571200697">"IT administrator blokira česte PIN-ove. Izaberite drugi PIN."</string>
     <string name="lockpassword_illegal_character" msgid="3577712947676261351">"Ne sme da obuhvata nevažeći znak"</string>
@@ -713,6 +712,11 @@
       <item quantity="few">Mora da sadrži najmanje <xliff:g id="COUNT">%d</xliff:g> znaka koji nisu slova</item>
       <item quantity="other">Mora da sadrži najmanje <xliff:g id="COUNT">%d</xliff:g> znakova koji nisu slova</item>
     </plurals>
+    <plurals name="lockpassword_password_requires_nonnumerical" formatted="false" msgid="6943707011152180150">
+      <item quantity="one">Mora da sadrži najmanje <xliff:g id="COUNT">%d</xliff:g> znak koji nije broj</item>
+      <item quantity="few">Mora da sadrži najmanje <xliff:g id="COUNT">%d</xliff:g> znaka koji nisu brojevi</item>
+      <item quantity="other">Mora da sadrži najmanje <xliff:g id="COUNT">%d</xliff:g> znakova koji nisu brojevi</item>
+    </plurals>
     <string name="lockpassword_password_recently_used" msgid="6944729699375087431">"Administrator uređaja ne dozvoljava upotrebu nedavno korišćene lozinke"</string>
     <string name="lockpassword_password_blacklisted_by_admin" msgid="4988166770148440755">"IT administrator blokira česte lozinke. Izaberite drugu lozinku."</string>
     <string name="lockpassword_pin_no_sequential_digits" msgid="3902387296149848324">"Rastući, opadajući ili ponovljeni niz cifara nije dozvoljen"</string>
diff --git a/tests/CarDeveloperOptions/res/values-be/config.xml b/tests/CarDeveloperOptions/res/values-be/config.xml
index b05e073..04c8bf8 100644
--- a/tests/CarDeveloperOptions/res/values-be/config.xml
+++ b/tests/CarDeveloperOptions/res/values-be/config.xml
@@ -17,5 +17,4 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="config_backup_settings_label" msgid="5230713622702899641"></string>
-    <string name="config_grayscale_settings_intent" msgid="7661066612238245852"></string>
 </resources>
diff --git a/tests/CarDeveloperOptions/res/values-be/strings.xml b/tests/CarDeveloperOptions/res/values-be/strings.xml
index 74871d2..cec1ae7 100644
--- a/tests/CarDeveloperOptions/res/values-be/strings.xml
+++ b/tests/CarDeveloperOptions/res/values-be/strings.xml
@@ -684,7 +684,6 @@
       <item quantity="many">PIN-код павінен змяшчаць менш за <xliff:g id="NUMBER_1">%d</xliff:g> лічбаў</item>
       <item quantity="other">PIN-код павінен змяшчаць менш за <xliff:g id="NUMBER_1">%d</xliff:g> лічбы</item>
     </plurals>
-    <string name="lockpassword_pin_contains_non_digits" msgid="1501729742023195556">"Павінен змяшчаць толькі лічбы 0-9"</string>
     <string name="lockpassword_pin_recently_used" msgid="5556148774911741103">"Адміністратар прылады не дазваляе выкарыстоўваць апошні PIN-код"</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="2752037810571200697">"IT адміністратар блакіруе папулярныя PIN-коды. Паспрабуйце іншы PIN-код."</string>
     <string name="lockpassword_illegal_character" msgid="3577712947676261351">"Не можа змяшчаць недапушчальны сімвал"</string>
@@ -727,6 +726,12 @@
       <item quantity="many">Павінен змяшчаць не менш за <xliff:g id="COUNT">%d</xliff:g> сімвалаў, якія не з\'яўляюцца літарамі</item>
       <item quantity="other">Павінен змяшчаць не менш за <xliff:g id="COUNT">%d</xliff:g> сімвала, якія не з\'яўляюцца літарамі</item>
     </plurals>
+    <plurals name="lockpassword_password_requires_nonnumerical" formatted="false" msgid="6943707011152180150">
+      <item quantity="one">Павінен змяшчаць не менш за <xliff:g id="COUNT">%d</xliff:g> сімвал, які не з\'яўляецца лічбай</item>
+      <item quantity="few">Павінен змяшчаць не менш за <xliff:g id="COUNT">%d</xliff:g> сімвалы, якія не з\'яўляюцца лічбамі</item>
+      <item quantity="many">Павінен змяшчаць не менш за <xliff:g id="COUNT">%d</xliff:g> сімвалаў, якія не з\'яўляюцца лічбамі</item>
+      <item quantity="other">Павінен змяшчаць не менш за <xliff:g id="COUNT">%d</xliff:g> сімвала, якія не з\'яўляюцца лічбамі</item>
+    </plurals>
     <string name="lockpassword_password_recently_used" msgid="6944729699375087431">"Адміністратар прылады не дазваляе выкарыстоўваць апошні пароль"</string>
     <string name="lockpassword_password_blacklisted_by_admin" msgid="4988166770148440755">"IT адміністратар блакіруе папулярныя паролі. Паспрабуйце іншы пароль."</string>
     <string name="lockpassword_pin_no_sequential_digits" msgid="3902387296149848324">"Забаронена ўводзіць узрастаючую, убываючую або паўторную паслядоўнасць лічбаў"</string>
diff --git a/tests/CarDeveloperOptions/res/values-bg/config.xml b/tests/CarDeveloperOptions/res/values-bg/config.xml
index b05e073..04c8bf8 100644
--- a/tests/CarDeveloperOptions/res/values-bg/config.xml
+++ b/tests/CarDeveloperOptions/res/values-bg/config.xml
@@ -17,5 +17,4 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="config_backup_settings_label" msgid="5230713622702899641"></string>
-    <string name="config_grayscale_settings_intent" msgid="7661066612238245852"></string>
 </resources>
diff --git a/tests/CarDeveloperOptions/res/values-bg/strings.xml b/tests/CarDeveloperOptions/res/values-bg/strings.xml
index b34c516..7d3fe5c 100644
--- a/tests/CarDeveloperOptions/res/values-bg/strings.xml
+++ b/tests/CarDeveloperOptions/res/values-bg/strings.xml
@@ -668,7 +668,6 @@
       <item quantity="other">Трябва да съдържа по-малко от <xliff:g id="NUMBER_1">%d</xliff:g> цифри</item>
       <item quantity="one">Трябва да съдържа по-малко от <xliff:g id="NUMBER_0">%d</xliff:g> цифра</item>
     </plurals>
-    <string name="lockpassword_pin_contains_non_digits" msgid="1501729742023195556">"Трябва да съдържа само цифри (от 0 до 9)"</string>
     <string name="lockpassword_pin_recently_used" msgid="5556148774911741103">"Администраторът на у-вото не разрешава ползването на скорошен ПИН"</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="2752037810571200697">"Често срещаните ПИН кодове се блокират от системния ви администратор. Опитайте с друг ПИН код."</string>
     <string name="lockpassword_illegal_character" msgid="3577712947676261351">"Не може да включва невалиден знак"</string>
@@ -699,6 +698,10 @@
       <item quantity="other">Трябва да съдържа поне <xliff:g id="COUNT">%d</xliff:g> небуквени знака</item>
       <item quantity="one">Трябва да съдържа поне 1 небуквен знак</item>
     </plurals>
+    <plurals name="lockpassword_password_requires_nonnumerical" formatted="false" msgid="6943707011152180150">
+      <item quantity="other">Трябва да съдържа поне <xliff:g id="COUNT">%d</xliff:g> нецифрени знака</item>
+      <item quantity="one">Трябва да съдържа поне 1 нецифрен знак</item>
+    </plurals>
     <string name="lockpassword_password_recently_used" msgid="6944729699375087431">"Администраторът на у-вото не разрешава ползването на скорошна парола"</string>
     <string name="lockpassword_password_blacklisted_by_admin" msgid="4988166770148440755">"Често срещаните пароли се блокират от системния ви администратор. Опитайте с друга парола."</string>
     <string name="lockpassword_pin_no_sequential_digits" msgid="3902387296149848324">"Възходящите, низходящите и повтарящите се поредици от цифри не са разрешени"</string>
diff --git a/tests/CarDeveloperOptions/res/values-bn/config.xml b/tests/CarDeveloperOptions/res/values-bn/config.xml
index b05e073..04c8bf8 100644
--- a/tests/CarDeveloperOptions/res/values-bn/config.xml
+++ b/tests/CarDeveloperOptions/res/values-bn/config.xml
@@ -17,5 +17,4 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="config_backup_settings_label" msgid="5230713622702899641"></string>
-    <string name="config_grayscale_settings_intent" msgid="7661066612238245852"></string>
 </resources>
diff --git a/tests/CarDeveloperOptions/res/values-bn/strings.xml b/tests/CarDeveloperOptions/res/values-bn/strings.xml
index 89caa50..8626cab 100644
--- a/tests/CarDeveloperOptions/res/values-bn/strings.xml
+++ b/tests/CarDeveloperOptions/res/values-bn/strings.xml
@@ -668,7 +668,6 @@
       <item quantity="one">অবশ্যই <xliff:g id="NUMBER_1">%d</xliff:g>টি সংখ্যার থেকে কম হতে হবে</item>
       <item quantity="other">অবশ্যই <xliff:g id="NUMBER_1">%d</xliff:g>টি সংখ্যার থেকে কম হতে হবে</item>
     </plurals>
-    <string name="lockpassword_pin_contains_non_digits" msgid="1501729742023195556">"শুধুমাত্র ০-৯ সংখ্যাগুলি থাকবে"</string>
     <string name="lockpassword_pin_recently_used" msgid="5556148774911741103">"ডিভাইস প্রশাসক একটি সাম্প্রতিক পিন ব্যবহার করতে দেয় না"</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="2752037810571200697">"একইধরনের পিন আপনার আইটি অ্যাডমিনের মাধ্যমে ব্লক করা হয়। অন্য একটি পিন ব্যবহার করার চেষ্টা করুন।"</string>
     <string name="lockpassword_illegal_character" msgid="3577712947676261351">"এতে কোনো অবৈধ অক্ষর অন্তর্ভুক্ত করা যাবে না"</string>
@@ -699,6 +698,10 @@
       <item quantity="one">কমপক্ষে এমন <xliff:g id="COUNT">%d</xliff:g>টি অক্ষর থাকতে হবে যেটি বর্ণ নয়</item>
       <item quantity="other">কমপক্ষে এমন <xliff:g id="COUNT">%d</xliff:g>টি অক্ষর থাকতে হবে যেটি বর্ণ নয়</item>
     </plurals>
+    <plurals name="lockpassword_password_requires_nonnumerical" formatted="false" msgid="6943707011152180150">
+      <item quantity="one">কমপক্ষে এমন <xliff:g id="COUNT">%d</xliff:g>টি অক্ষর থাকতে হবে যেটি সংখ্যা নয়</item>
+      <item quantity="other">কমপক্ষে এমন <xliff:g id="COUNT">%d</xliff:g>টি অক্ষর থাকতে হবে যেটি সংখ্যা নয়</item>
+    </plurals>
     <string name="lockpassword_password_recently_used" msgid="6944729699375087431">"ডিভাইস প্রশাসক একটি সাম্প্রতিক পাসওয়ার্ড ব্যবহার করতে দেয় না"</string>
     <string name="lockpassword_password_blacklisted_by_admin" msgid="4988166770148440755">"একইধরনের পাসওয়ার্ড আপনার আইটি অ্যাডমিনের মাধ্যমে ব্লক করা হয়। অন্য একটি পাসওয়ার্ড ব্যবহার করার চেষ্টা করুন।"</string>
     <string name="lockpassword_pin_no_sequential_digits" msgid="3902387296149848324">"সংখ্যাগুলি ছোট থেকে বড় ক্রমে, বড় থেকে ছোট ক্রমে বা একই সংখ্যাকে বার বার লেখা যাবে না"</string>
diff --git a/tests/CarDeveloperOptions/res/values-bs/config.xml b/tests/CarDeveloperOptions/res/values-bs/config.xml
index b05e073..04c8bf8 100644
--- a/tests/CarDeveloperOptions/res/values-bs/config.xml
+++ b/tests/CarDeveloperOptions/res/values-bs/config.xml
@@ -17,5 +17,4 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="config_backup_settings_label" msgid="5230713622702899641"></string>
-    <string name="config_grayscale_settings_intent" msgid="7661066612238245852"></string>
 </resources>
diff --git a/tests/CarDeveloperOptions/res/values-bs/strings.xml b/tests/CarDeveloperOptions/res/values-bs/strings.xml
index ab18137..372210d 100644
--- a/tests/CarDeveloperOptions/res/values-bs/strings.xml
+++ b/tests/CarDeveloperOptions/res/values-bs/strings.xml
@@ -676,7 +676,6 @@
       <item quantity="few">Ukupan broj cifara mora biti manji od <xliff:g id="NUMBER_1">%d</xliff:g></item>
       <item quantity="other">Ukupan broj cifara mora biti manji od <xliff:g id="NUMBER_1">%d</xliff:g></item>
     </plurals>
-    <string name="lockpassword_pin_contains_non_digits" msgid="1501729742023195556">"Mora sadržavati isključivo cifre 0-9."</string>
     <string name="lockpassword_pin_recently_used" msgid="5556148774911741103">"Administrator uređaja ne dozvoljava korištenje nedavnog PIN-a"</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="2752037810571200697">"Vaš IT administrator je blokirao česte PIN kôdove. Probajte drugi PIN kôd."</string>
     <string name="lockpassword_illegal_character" msgid="3577712947676261351">"Ne može sadržavati nevažeći znak"</string>
@@ -713,6 +712,11 @@
       <item quantity="few">Mora sadržavati najmanje <xliff:g id="COUNT">%d</xliff:g> znaka koja nisu slova</item>
       <item quantity="other">Mora sadržavati najmanje <xliff:g id="COUNT">%d</xliff:g> znakova koji nisu slova</item>
     </plurals>
+    <plurals name="lockpassword_password_requires_nonnumerical" formatted="false" msgid="6943707011152180150">
+      <item quantity="one">Mora sadržavati najmanje <xliff:g id="COUNT">%d</xliff:g> znak koji nije broj</item>
+      <item quantity="few">Mora sadržavati najmanje <xliff:g id="COUNT">%d</xliff:g> znaka koja nisu brojevi</item>
+      <item quantity="other">Mora sadržavati najmanje <xliff:g id="COUNT">%d</xliff:g> znakova koji nisu brojevi</item>
+    </plurals>
     <string name="lockpassword_password_recently_used" msgid="6944729699375087431">"Administrator uređaja ne dozvoljava korištenje nedavne lozinke"</string>
     <string name="lockpassword_password_blacklisted_by_admin" msgid="4988166770148440755">"Vaš IT administrator je blokirao česte lozinke. Probajte drugu lozinku."</string>
     <string name="lockpassword_pin_no_sequential_digits" msgid="3902387296149848324">"Nije dozvoljen rastući, opadajući ili ponavljajući niz cifara"</string>
diff --git a/tests/CarDeveloperOptions/res/values-ca/config.xml b/tests/CarDeveloperOptions/res/values-ca/config.xml
index b05e073..04c8bf8 100644
--- a/tests/CarDeveloperOptions/res/values-ca/config.xml
+++ b/tests/CarDeveloperOptions/res/values-ca/config.xml
@@ -17,5 +17,4 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="config_backup_settings_label" msgid="5230713622702899641"></string>
-    <string name="config_grayscale_settings_intent" msgid="7661066612238245852"></string>
 </resources>
diff --git a/tests/CarDeveloperOptions/res/values-ca/strings.xml b/tests/CarDeveloperOptions/res/values-ca/strings.xml
index 03cc92f..3ba237f 100644
--- a/tests/CarDeveloperOptions/res/values-ca/strings.xml
+++ b/tests/CarDeveloperOptions/res/values-ca/strings.xml
@@ -668,7 +668,6 @@
       <item quantity="other">Ha de tenir <xliff:g id="NUMBER_1">%d</xliff:g> dígits o menys</item>
       <item quantity="one">Ha de tenir <xliff:g id="NUMBER_0">%d</xliff:g> dígit o menys</item>
     </plurals>
-    <string name="lockpassword_pin_contains_non_digits" msgid="1501729742023195556">"Només pot contenir dígits del 0 al 9"</string>
     <string name="lockpassword_pin_recently_used" msgid="5556148774911741103">"L\'administrador del dispositiu no permet que s\'utilitzi un PIN recent"</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="2752037810571200697">"L\'administrador de TI ha bloquejat els PIN més comuns. Prova un altre PIN."</string>
     <string name="lockpassword_illegal_character" msgid="3577712947676261351">"No pot incloure un caràcter no vàlid"</string>
@@ -699,6 +698,10 @@
       <item quantity="other">Ha de contenir com a mínim <xliff:g id="COUNT">%d</xliff:g> caràcters que no siguin lletres</item>
       <item quantity="one">Ha de contenir com a mínim 1 caràcter que no sigui una lletra</item>
     </plurals>
+    <plurals name="lockpassword_password_requires_nonnumerical" formatted="false" msgid="6943707011152180150">
+      <item quantity="other">Ha de contenir com a mínim <xliff:g id="COUNT">%d</xliff:g> caràcters que no siguin números</item>
+      <item quantity="one">Ha de contenir com a mínim 1 caràcter que no sigui un número</item>
+    </plurals>
     <string name="lockpassword_password_recently_used" msgid="6944729699375087431">"L\'administrador del dispositiu no permet que s\'utilitzi una contrasenya recent"</string>
     <string name="lockpassword_password_blacklisted_by_admin" msgid="4988166770148440755">"L\'administrador de TI ha bloquejat les contrasenyes més comunes. Prova una altra contrasenya."</string>
     <string name="lockpassword_pin_no_sequential_digits" msgid="3902387296149848324">"No es permet cap seqüència de dígits ascendents, descendents ni repetits"</string>
diff --git a/tests/CarDeveloperOptions/res/values-cs/config.xml b/tests/CarDeveloperOptions/res/values-cs/config.xml
index b05e073..04c8bf8 100644
--- a/tests/CarDeveloperOptions/res/values-cs/config.xml
+++ b/tests/CarDeveloperOptions/res/values-cs/config.xml
@@ -17,5 +17,4 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="config_backup_settings_label" msgid="5230713622702899641"></string>
-    <string name="config_grayscale_settings_intent" msgid="7661066612238245852"></string>
 </resources>
diff --git a/tests/CarDeveloperOptions/res/values-cs/strings.xml b/tests/CarDeveloperOptions/res/values-cs/strings.xml
index d3caaf0..77d05bb 100644
--- a/tests/CarDeveloperOptions/res/values-cs/strings.xml
+++ b/tests/CarDeveloperOptions/res/values-cs/strings.xml
@@ -684,7 +684,6 @@
       <item quantity="other">Musí obsahovat méně než <xliff:g id="NUMBER_1">%d</xliff:g> číslic</item>
       <item quantity="one">Musí obsahovat méně než <xliff:g id="NUMBER_0">%d</xliff:g> číslici</item>
     </plurals>
-    <string name="lockpassword_pin_contains_non_digits" msgid="1501729742023195556">"PIN smí obsahovat pouze číslice 0 až 9."</string>
     <string name="lockpassword_pin_recently_used" msgid="5556148774911741103">"Administrátor zařízení nedovoluje použití nedávno použitého kódu PIN"</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="2752037810571200697">"Obvyklé kódy PIN jsou blokovány administrátorem IT. Použijte jiný PIN."</string>
     <string name="lockpassword_illegal_character" msgid="3577712947676261351">"Heslo nesmí obsahovat neplatné znaky."</string>
@@ -727,6 +726,12 @@
       <item quantity="other">Heslo musí obsahovat alespoň <xliff:g id="COUNT">%d</xliff:g> znaků jiných než písmeno</item>
       <item quantity="one">Heslo musí obsahovat alespoň 1 znak jiný než písmeno</item>
     </plurals>
+    <plurals name="lockpassword_password_requires_nonnumerical" formatted="false" msgid="6943707011152180150">
+      <item quantity="few">Musí obsahovat alespoň <xliff:g id="COUNT">%d</xliff:g> znaky jiné než číslice</item>
+      <item quantity="many">Musí obsahovat alespoň <xliff:g id="COUNT">%d</xliff:g> znaku jiného než číslici</item>
+      <item quantity="other">Musí obsahovat alespoň <xliff:g id="COUNT">%d</xliff:g> znaků jiných než číslici</item>
+      <item quantity="one">Musí obsahovat alespoň 1 znak jiný než číslici</item>
+    </plurals>
     <string name="lockpassword_password_recently_used" msgid="6944729699375087431">"Administrátor zařízení neumožňuje použít heslo, které jste použili nedávno"</string>
     <string name="lockpassword_password_blacklisted_by_admin" msgid="4988166770148440755">"Obvyklá hesla jsou blokována administrátorem IT. Použijte jiné heslo."</string>
     <string name="lockpassword_pin_no_sequential_digits" msgid="3902387296149848324">"Posloupnost rostoucích, klesajících nebo opakujících se číslic není povolena"</string>
diff --git a/tests/CarDeveloperOptions/res/values-da/config.xml b/tests/CarDeveloperOptions/res/values-da/config.xml
index b05e073..04c8bf8 100644
--- a/tests/CarDeveloperOptions/res/values-da/config.xml
+++ b/tests/CarDeveloperOptions/res/values-da/config.xml
@@ -17,5 +17,4 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="config_backup_settings_label" msgid="5230713622702899641"></string>
-    <string name="config_grayscale_settings_intent" msgid="7661066612238245852"></string>
 </resources>
diff --git a/tests/CarDeveloperOptions/res/values-da/strings.xml b/tests/CarDeveloperOptions/res/values-da/strings.xml
index beadd32..5c97281 100644
--- a/tests/CarDeveloperOptions/res/values-da/strings.xml
+++ b/tests/CarDeveloperOptions/res/values-da/strings.xml
@@ -668,7 +668,6 @@
       <item quantity="one">Skal indeholde færre end <xliff:g id="NUMBER_1">%d</xliff:g> ciffer</item>
       <item quantity="other">Skal indeholde færre end <xliff:g id="NUMBER_1">%d</xliff:g> cifre</item>
     </plurals>
-    <string name="lockpassword_pin_contains_non_digits" msgid="1501729742023195556">"Må kun indeholde tallene 0-9"</string>
     <string name="lockpassword_pin_recently_used" msgid="5556148774911741103">"Enhedens administrator tillader ikke brug af en nylig brugt pinkode"</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="2752037810571200697">"Brug af almindelige pinkoder er blokeret af din it-administrator. Prøv en anden pinkode."</string>
     <string name="lockpassword_illegal_character" msgid="3577712947676261351">"Der kan ikke bruges et ugyldigt tegn"</string>
@@ -699,6 +698,10 @@
       <item quantity="one">Skal indeholde mindst <xliff:g id="COUNT">%d</xliff:g> tegn, der ikke er et bogstav</item>
       <item quantity="other">Skal indeholde mindst <xliff:g id="COUNT">%d</xliff:g> tegn, der ikke er bogstaver</item>
     </plurals>
+    <plurals name="lockpassword_password_requires_nonnumerical" formatted="false" msgid="6943707011152180150">
+      <item quantity="one">Skal indeholde mindst <xliff:g id="COUNT">%d</xliff:g> tegn, som ikke er et tal</item>
+      <item quantity="other">Skal indeholde mindst <xliff:g id="COUNT">%d</xliff:g> tegn, som ikke er et tal</item>
+    </plurals>
     <string name="lockpassword_password_recently_used" msgid="6944729699375087431">"Enhedens administrator tillader ikke brug af en nylig brugt adgangskode"</string>
     <string name="lockpassword_password_blacklisted_by_admin" msgid="4988166770148440755">"Brug af almindelige adgangskoder er blokeret af din it-administrator. Prøv en anden adgangskode."</string>
     <string name="lockpassword_pin_no_sequential_digits" msgid="3902387296149848324">"Stigende eller faldende talsekvens og gentagne tal er ikke tilladt"</string>
diff --git a/tests/CarDeveloperOptions/res/values-de/config.xml b/tests/CarDeveloperOptions/res/values-de/config.xml
index b05e073..04c8bf8 100644
--- a/tests/CarDeveloperOptions/res/values-de/config.xml
+++ b/tests/CarDeveloperOptions/res/values-de/config.xml
@@ -17,5 +17,4 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="config_backup_settings_label" msgid="5230713622702899641"></string>
-    <string name="config_grayscale_settings_intent" msgid="7661066612238245852"></string>
 </resources>
diff --git a/tests/CarDeveloperOptions/res/values-de/strings.xml b/tests/CarDeveloperOptions/res/values-de/strings.xml
index 61c564f..3c8be5f 100644
--- a/tests/CarDeveloperOptions/res/values-de/strings.xml
+++ b/tests/CarDeveloperOptions/res/values-de/strings.xml
@@ -667,7 +667,6 @@
       <item quantity="other">Muss weniger als <xliff:g id="NUMBER_1">%d</xliff:g> Ziffern haben</item>
       <item quantity="one">Muss weniger als <xliff:g id="NUMBER_0">%d</xliff:g> Ziffer haben</item>
     </plurals>
-    <string name="lockpassword_pin_contains_non_digits" msgid="1501729742023195556">"Es dürfen nur Ziffern von 0 bis 9 enthalten sein"</string>
     <string name="lockpassword_pin_recently_used" msgid="5556148774911741103">"Der Geräteadministrator lässt die Verwendung einer früheren PIN nicht zu"</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="2752037810571200697">"Häufig verwendete PINs werden von deinem IT-Administrator blockiert. Versuch es mit einer anderen PIN."</string>
     <string name="lockpassword_illegal_character" msgid="3577712947676261351">"Es sind keine ungültigen Zeichen zulässig"</string>
@@ -698,6 +697,10 @@
       <item quantity="other">Das Passwort muss mindestens <xliff:g id="COUNT">%d</xliff:g> Zeichen enthalten, die keine Buchstaben sind</item>
       <item quantity="one">Das Passwort muss mindestens ein Zeichen enthalten, das kein Buchstabe ist</item>
     </plurals>
+    <plurals name="lockpassword_password_requires_nonnumerical" formatted="false" msgid="6943707011152180150">
+      <item quantity="other">Passwort muss mindestens <xliff:g id="COUNT">%d</xliff:g> Zeichen enthalten, die keine Ziffern sind</item>
+      <item quantity="one">Passwort muss mindestens ein Zeichen enthalten, das keine Ziffer ist</item>
+    </plurals>
     <string name="lockpassword_password_recently_used" msgid="6944729699375087431">"Der Geräteadministrator lässt die Verwendung eines früheren Passworts nicht zu"</string>
     <string name="lockpassword_password_blacklisted_by_admin" msgid="4988166770148440755">"Häufig verwendete Passwörter werden von deinem IT-Administrator blockiert. Versuch es mit einem anderen Passwort."</string>
     <string name="lockpassword_pin_no_sequential_digits" msgid="3902387296149848324">"Aufsteigende, absteigende oder wiederholte Ziffernfolgen sind unzulässig"</string>
diff --git a/tests/CarDeveloperOptions/res/values-el/config.xml b/tests/CarDeveloperOptions/res/values-el/config.xml
index b05e073..04c8bf8 100644
--- a/tests/CarDeveloperOptions/res/values-el/config.xml
+++ b/tests/CarDeveloperOptions/res/values-el/config.xml
@@ -17,5 +17,4 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="config_backup_settings_label" msgid="5230713622702899641"></string>
-    <string name="config_grayscale_settings_intent" msgid="7661066612238245852"></string>
 </resources>
diff --git a/tests/CarDeveloperOptions/res/values-el/strings.xml b/tests/CarDeveloperOptions/res/values-el/strings.xml
index 0641a09..94f7c3b 100644
--- a/tests/CarDeveloperOptions/res/values-el/strings.xml
+++ b/tests/CarDeveloperOptions/res/values-el/strings.xml
@@ -668,7 +668,6 @@
       <item quantity="other">Πρέπει να αποτελείται από λιγότερα από <xliff:g id="NUMBER_1">%d</xliff:g> ψηφία</item>
       <item quantity="one">Πρέπει να αποτελείται από λιγότερα από <xliff:g id="NUMBER_0">%d</xliff:g> ψηφίο</item>
     </plurals>
-    <string name="lockpassword_pin_contains_non_digits" msgid="1501729742023195556">"Πρέπει να περιέχει μόνο ψηφία από το 0 έως το 9"</string>
     <string name="lockpassword_pin_recently_used" msgid="5556148774911741103">"Ο διαχειριστής της συσκευής δεν επιτρέπει τη χρήση πρόσφατου PIN"</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="2752037810571200697">"Τα συνηθισμένα PIN αποκλείονται από τον διαχειριστή IT. Δοκιμάστε να χρησιμοποιήσετε διαφορετικό PIN."</string>
     <string name="lockpassword_illegal_character" msgid="3577712947676261351">"Δεν μπορεί να περιλαμβάνει μη έγκυρο χαρακτήρα"</string>
@@ -699,6 +698,10 @@
       <item quantity="other">Πρέπει να περιέχει τουλάχιστον <xliff:g id="COUNT">%d</xliff:g> χαρακτήρες που δεν είναι γράμματα</item>
       <item quantity="one">Πρέπει να περιέχει τουλάχιστον 1 χαρακτήρα που δεν είναι γράμμα</item>
     </plurals>
+    <plurals name="lockpassword_password_requires_nonnumerical" formatted="false" msgid="6943707011152180150">
+      <item quantity="other">Πρέπει να περιέχει τουλάχιστον <xliff:g id="COUNT">%d</xliff:g> μη αριθμητικούς χαρακτήρες</item>
+      <item quantity="one">Πρέπει να περιέχει τουλάχιστον 1 μη αριθμητικό χαρακτήρα</item>
+    </plurals>
     <string name="lockpassword_password_recently_used" msgid="6944729699375087431">"Ο διαχειριστής της συσκευής δεν επιτρέπει τη χρήση πρόσφατου κωδικού πρόσβασης"</string>
     <string name="lockpassword_password_blacklisted_by_admin" msgid="4988166770148440755">"Οι συνηθισμένοι κωδικοί πρόσβασης αποκλείονται από τον διαχειριστή IT. Δοκιμάστε να χρησιμοποιήσετε διαφορετικό κωδικό πρόσβασης."</string>
     <string name="lockpassword_pin_no_sequential_digits" msgid="3902387296149848324">"Δεν επιτρέπεται η αύξουσα, φθίνουσα ή επαναλαμβανόμενη ακολουθία ψηφίων"</string>
diff --git a/tests/CarDeveloperOptions/res/values-en-rAU/config.xml b/tests/CarDeveloperOptions/res/values-en-rAU/config.xml
index b05e073..04c8bf8 100644
--- a/tests/CarDeveloperOptions/res/values-en-rAU/config.xml
+++ b/tests/CarDeveloperOptions/res/values-en-rAU/config.xml
@@ -17,5 +17,4 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="config_backup_settings_label" msgid="5230713622702899641"></string>
-    <string name="config_grayscale_settings_intent" msgid="7661066612238245852"></string>
 </resources>
diff --git a/tests/CarDeveloperOptions/res/values-en-rAU/strings.xml b/tests/CarDeveloperOptions/res/values-en-rAU/strings.xml
index 0cf4a48..26efa62 100644
--- a/tests/CarDeveloperOptions/res/values-en-rAU/strings.xml
+++ b/tests/CarDeveloperOptions/res/values-en-rAU/strings.xml
@@ -668,7 +668,6 @@
       <item quantity="other">Must be fewer than <xliff:g id="NUMBER_1">%d</xliff:g> digits</item>
       <item quantity="one">Must be fewer than <xliff:g id="NUMBER_0">%d</xliff:g> digit</item>
     </plurals>
-    <string name="lockpassword_pin_contains_non_digits" msgid="1501729742023195556">"Must only contain digits 0-9"</string>
     <string name="lockpassword_pin_recently_used" msgid="5556148774911741103">"Device admin doesn\'t allow using a recent PIN"</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="2752037810571200697">"Common PINs are blocked by your IT admin. Try a different PIN."</string>
     <string name="lockpassword_illegal_character" msgid="3577712947676261351">"This can\'t include an invalid character"</string>
@@ -699,6 +698,10 @@
       <item quantity="other">Must contain at least <xliff:g id="COUNT">%d</xliff:g> non-letter characters</item>
       <item quantity="one">Must contain at least 1 non-letter character</item>
     </plurals>
+    <plurals name="lockpassword_password_requires_nonnumerical" formatted="false" msgid="6943707011152180150">
+      <item quantity="other">Must contain at least <xliff:g id="COUNT">%d</xliff:g> non-numeric characters</item>
+      <item quantity="one">Must contain at least 1 non-numeric character</item>
+    </plurals>
     <string name="lockpassword_password_recently_used" msgid="6944729699375087431">"Device admin doesn\'t allow using a recent password"</string>
     <string name="lockpassword_password_blacklisted_by_admin" msgid="4988166770148440755">"Common passwords are blocked by your IT admin. Try a different password."</string>
     <string name="lockpassword_pin_no_sequential_digits" msgid="3902387296149848324">"Ascending, descending or repeated sequence of digits isn\'t allowed"</string>
diff --git a/tests/CarDeveloperOptions/res/values-en-rCA/config.xml b/tests/CarDeveloperOptions/res/values-en-rCA/config.xml
index b05e073..04c8bf8 100644
--- a/tests/CarDeveloperOptions/res/values-en-rCA/config.xml
+++ b/tests/CarDeveloperOptions/res/values-en-rCA/config.xml
@@ -17,5 +17,4 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="config_backup_settings_label" msgid="5230713622702899641"></string>
-    <string name="config_grayscale_settings_intent" msgid="7661066612238245852"></string>
 </resources>
diff --git a/tests/CarDeveloperOptions/res/values-en-rCA/strings.xml b/tests/CarDeveloperOptions/res/values-en-rCA/strings.xml
index f6ca7a1..90f6710 100644
--- a/tests/CarDeveloperOptions/res/values-en-rCA/strings.xml
+++ b/tests/CarDeveloperOptions/res/values-en-rCA/strings.xml
@@ -668,7 +668,6 @@
       <item quantity="other">Must be fewer than <xliff:g id="NUMBER_1">%d</xliff:g> digits</item>
       <item quantity="one">Must be fewer than <xliff:g id="NUMBER_0">%d</xliff:g> digit</item>
     </plurals>
-    <string name="lockpassword_pin_contains_non_digits" msgid="1501729742023195556">"Must only contain digits 0-9"</string>
     <string name="lockpassword_pin_recently_used" msgid="5556148774911741103">"Device admin doesn\'t allow using a recent PIN"</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="2752037810571200697">"Common PINs are blocked by your IT admin. Try a different PIN."</string>
     <string name="lockpassword_illegal_character" msgid="3577712947676261351">"This can\'t include an invalid character"</string>
@@ -699,6 +698,10 @@
       <item quantity="other">Must contain at least <xliff:g id="COUNT">%d</xliff:g> non-letter characters</item>
       <item quantity="one">Must contain at least 1 non-letter character</item>
     </plurals>
+    <plurals name="lockpassword_password_requires_nonnumerical" formatted="false" msgid="6943707011152180150">
+      <item quantity="other">Must contain at least <xliff:g id="COUNT">%d</xliff:g> non-numeric characters</item>
+      <item quantity="one">Must contain at least 1 non-numeric character</item>
+    </plurals>
     <string name="lockpassword_password_recently_used" msgid="6944729699375087431">"Device admin doesn\'t allow using a recent password"</string>
     <string name="lockpassword_password_blacklisted_by_admin" msgid="4988166770148440755">"Common passwords are blocked by your IT admin. Try a different password."</string>
     <string name="lockpassword_pin_no_sequential_digits" msgid="3902387296149848324">"Ascending, descending or repeated sequence of digits isn\'t allowed"</string>
diff --git a/tests/CarDeveloperOptions/res/values-en-rGB/config.xml b/tests/CarDeveloperOptions/res/values-en-rGB/config.xml
index b05e073..04c8bf8 100644
--- a/tests/CarDeveloperOptions/res/values-en-rGB/config.xml
+++ b/tests/CarDeveloperOptions/res/values-en-rGB/config.xml
@@ -17,5 +17,4 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="config_backup_settings_label" msgid="5230713622702899641"></string>
-    <string name="config_grayscale_settings_intent" msgid="7661066612238245852"></string>
 </resources>
diff --git a/tests/CarDeveloperOptions/res/values-en-rGB/strings.xml b/tests/CarDeveloperOptions/res/values-en-rGB/strings.xml
index 0cf4a48..26efa62 100644
--- a/tests/CarDeveloperOptions/res/values-en-rGB/strings.xml
+++ b/tests/CarDeveloperOptions/res/values-en-rGB/strings.xml
@@ -668,7 +668,6 @@
       <item quantity="other">Must be fewer than <xliff:g id="NUMBER_1">%d</xliff:g> digits</item>
       <item quantity="one">Must be fewer than <xliff:g id="NUMBER_0">%d</xliff:g> digit</item>
     </plurals>
-    <string name="lockpassword_pin_contains_non_digits" msgid="1501729742023195556">"Must only contain digits 0-9"</string>
     <string name="lockpassword_pin_recently_used" msgid="5556148774911741103">"Device admin doesn\'t allow using a recent PIN"</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="2752037810571200697">"Common PINs are blocked by your IT admin. Try a different PIN."</string>
     <string name="lockpassword_illegal_character" msgid="3577712947676261351">"This can\'t include an invalid character"</string>
@@ -699,6 +698,10 @@
       <item quantity="other">Must contain at least <xliff:g id="COUNT">%d</xliff:g> non-letter characters</item>
       <item quantity="one">Must contain at least 1 non-letter character</item>
     </plurals>
+    <plurals name="lockpassword_password_requires_nonnumerical" formatted="false" msgid="6943707011152180150">
+      <item quantity="other">Must contain at least <xliff:g id="COUNT">%d</xliff:g> non-numeric characters</item>
+      <item quantity="one">Must contain at least 1 non-numeric character</item>
+    </plurals>
     <string name="lockpassword_password_recently_used" msgid="6944729699375087431">"Device admin doesn\'t allow using a recent password"</string>
     <string name="lockpassword_password_blacklisted_by_admin" msgid="4988166770148440755">"Common passwords are blocked by your IT admin. Try a different password."</string>
     <string name="lockpassword_pin_no_sequential_digits" msgid="3902387296149848324">"Ascending, descending or repeated sequence of digits isn\'t allowed"</string>
diff --git a/tests/CarDeveloperOptions/res/values-en-rIN/config.xml b/tests/CarDeveloperOptions/res/values-en-rIN/config.xml
index b05e073..04c8bf8 100644
--- a/tests/CarDeveloperOptions/res/values-en-rIN/config.xml
+++ b/tests/CarDeveloperOptions/res/values-en-rIN/config.xml
@@ -17,5 +17,4 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="config_backup_settings_label" msgid="5230713622702899641"></string>
-    <string name="config_grayscale_settings_intent" msgid="7661066612238245852"></string>
 </resources>
diff --git a/tests/CarDeveloperOptions/res/values-en-rIN/strings.xml b/tests/CarDeveloperOptions/res/values-en-rIN/strings.xml
index 0cf4a48..26efa62 100644
--- a/tests/CarDeveloperOptions/res/values-en-rIN/strings.xml
+++ b/tests/CarDeveloperOptions/res/values-en-rIN/strings.xml
@@ -668,7 +668,6 @@
       <item quantity="other">Must be fewer than <xliff:g id="NUMBER_1">%d</xliff:g> digits</item>
       <item quantity="one">Must be fewer than <xliff:g id="NUMBER_0">%d</xliff:g> digit</item>
     </plurals>
-    <string name="lockpassword_pin_contains_non_digits" msgid="1501729742023195556">"Must only contain digits 0-9"</string>
     <string name="lockpassword_pin_recently_used" msgid="5556148774911741103">"Device admin doesn\'t allow using a recent PIN"</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="2752037810571200697">"Common PINs are blocked by your IT admin. Try a different PIN."</string>
     <string name="lockpassword_illegal_character" msgid="3577712947676261351">"This can\'t include an invalid character"</string>
@@ -699,6 +698,10 @@
       <item quantity="other">Must contain at least <xliff:g id="COUNT">%d</xliff:g> non-letter characters</item>
       <item quantity="one">Must contain at least 1 non-letter character</item>
     </plurals>
+    <plurals name="lockpassword_password_requires_nonnumerical" formatted="false" msgid="6943707011152180150">
+      <item quantity="other">Must contain at least <xliff:g id="COUNT">%d</xliff:g> non-numeric characters</item>
+      <item quantity="one">Must contain at least 1 non-numeric character</item>
+    </plurals>
     <string name="lockpassword_password_recently_used" msgid="6944729699375087431">"Device admin doesn\'t allow using a recent password"</string>
     <string name="lockpassword_password_blacklisted_by_admin" msgid="4988166770148440755">"Common passwords are blocked by your IT admin. Try a different password."</string>
     <string name="lockpassword_pin_no_sequential_digits" msgid="3902387296149848324">"Ascending, descending or repeated sequence of digits isn\'t allowed"</string>
diff --git a/tests/CarDeveloperOptions/res/values-en-rXC/config.xml b/tests/CarDeveloperOptions/res/values-en-rXC/config.xml
index b05e073..04c8bf8 100644
--- a/tests/CarDeveloperOptions/res/values-en-rXC/config.xml
+++ b/tests/CarDeveloperOptions/res/values-en-rXC/config.xml
@@ -17,5 +17,4 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="config_backup_settings_label" msgid="5230713622702899641"></string>
-    <string name="config_grayscale_settings_intent" msgid="7661066612238245852"></string>
 </resources>
diff --git a/tests/CarDeveloperOptions/res/values-en-rXC/strings.xml b/tests/CarDeveloperOptions/res/values-en-rXC/strings.xml
index 562c28d..a8c34fd 100644
--- a/tests/CarDeveloperOptions/res/values-en-rXC/strings.xml
+++ b/tests/CarDeveloperOptions/res/values-en-rXC/strings.xml
@@ -667,7 +667,6 @@
       <item quantity="other">‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‎‏‎‏‏‎‎‏‎‎‏‏‏‏‏‎‏‎‏‏‎‏‏‏‎‎‎‎‏‏‏‏‏‎‏‏‏‎‎‏‏‏‎‎‎‏‏‏‏‏‎‎‏‎‎‏‎‎‎‏‏‎‎Must be fewer than ‎‏‎‎‏‏‎<xliff:g id="NUMBER_1">%d</xliff:g>‎‏‎‎‏‏‏‎ digits‎‏‎‎‏‎</item>
       <item quantity="one">‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‎‏‎‏‏‎‎‏‎‎‏‏‏‏‏‎‏‎‏‏‎‏‏‏‎‎‎‎‏‏‏‏‏‎‏‏‏‎‎‏‏‏‎‎‎‏‏‏‏‏‎‎‏‎‎‏‎‎‎‏‏‎‎Must be fewer than ‎‏‎‎‏‏‎<xliff:g id="NUMBER_0">%d</xliff:g>‎‏‎‎‏‏‏‎ digit‎‏‎‎‏‎</item>
     </plurals>
-    <string name="lockpassword_pin_contains_non_digits" msgid="1501729742023195556">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‎‏‏‎‏‎‎‏‏‎‏‎‏‏‏‎‎‏‏‎‏‏‏‎‎‏‏‏‏‏‎‎‏‏‎‏‏‎‏‏‏‏‎‏‏‏‎‎‏‏‏‏‎‏‏‏‎‏‎‎‏‎‎‎Must contain only digits 0-9‎‏‎‎‏‎"</string>
     <string name="lockpassword_pin_recently_used" msgid="5556148774911741103">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‎‎‏‏‎‏‎‎‎‏‏‎‏‏‎‏‏‎‎‏‏‏‏‏‎‏‏‏‏‏‎‏‏‏‏‏‏‏‏‏‎‏‎‏‏‎‎‎‎‎‏‏‎‎‏‎‏‎‏‏‏‏‎Device admin doesn\'t allow using a recent PIN‎‏‎‎‏‎"</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="2752037810571200697">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‎‏‎‎‏‏‎‎‎‏‏‎‎‎‏‎‎‏‏‎‎‏‏‏‏‎‎‏‏‏‎‏‏‎‎‏‎‏‏‏‎‎‎‏‎‏‎‎‏‎‎‏‎‎‎‏‎‏‏‏‎‎‏‎Common PINs are blocked by your IT admin. Try a different PIN.‎‏‎‎‏‎"</string>
     <string name="lockpassword_illegal_character" msgid="3577712947676261351">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‎‏‏‎‎‎‏‏‎‏‎‎‏‏‎‏‎‎‏‎‏‏‎‏‏‏‏‏‎‎‎‎‏‏‎‏‎‏‎‏‎‎‎‏‏‏‏‎‎‏‎‏‎‏‏‏‏‏‎‎‏‏‏‎This can\'t include an invalid character‎‏‎‎‏‎"</string>
@@ -698,6 +697,10 @@
       <item quantity="other">‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‎‏‏‎‎‏‏‎‎‏‏‏‏‏‎‎‏‎‏‏‎‎‎‎‏‎‎‎‎‎‏‏‏‎‎‎‏‎‏‏‏‏‏‎‏‎‎‎‎‎‎‎‏‎‎‎‏‎‏‏‎‏‏‎Must contain at least ‎‏‎‎‏‏‎<xliff:g id="COUNT">%d</xliff:g>‎‏‎‎‏‏‏‎ non-letter characters‎‏‎‎‏‎</item>
       <item quantity="one">‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‎‏‏‎‎‏‏‎‎‏‏‏‏‏‎‎‏‎‏‏‎‎‎‎‏‎‎‎‎‎‏‏‏‎‎‎‏‎‏‏‏‏‏‎‏‎‎‎‎‎‎‎‏‎‎‎‏‎‏‏‎‏‏‎Must contain at least 1 non-letter character‎‏‎‎‏‎</item>
     </plurals>
+    <plurals name="lockpassword_password_requires_nonnumerical" formatted="false" msgid="6943707011152180150">
+      <item quantity="other">‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‎‎‎‎‎‎‏‎‏‏‏‎‏‎‎‎‎‎‎‎‎‏‎‏‏‏‏‎‎‏‏‎‎‎‎‎‎‎‎‎‏‏‏‎‏‎‏‏‏‎‎‏‏‏‎‏‏‎‏‏‎‎Must contain at least ‎‏‎‎‏‏‎<xliff:g id="COUNT">%d</xliff:g>‎‏‎‎‏‏‏‎ non-numerical characters‎‏‎‎‏‎</item>
+      <item quantity="one">‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‎‎‎‎‎‎‏‎‏‏‏‎‏‎‎‎‎‎‎‎‎‏‎‏‏‏‏‎‎‏‏‎‎‎‎‎‎‎‎‎‏‏‏‎‏‎‏‏‏‎‎‏‏‏‎‏‏‎‏‏‎‎Must contain at least 1 non-numerical character‎‏‎‎‏‎</item>
+    </plurals>
     <string name="lockpassword_password_recently_used" msgid="6944729699375087431">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‎‎‎‎‎‎‏‏‎‎‎‎‎‏‎‏‎‎‎‏‎‏‏‎‏‏‏‎‏‏‏‏‎‏‎‎‎‎‏‎‏‏‎‏‏‎‏‏‏‏‏‏‏‎‏‎‎‎‏‏‏‎Device admin doesn\'t allow using a recent password‎‏‎‎‏‎"</string>
     <string name="lockpassword_password_blacklisted_by_admin" msgid="4988166770148440755">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‎‎‎‏‎‏‎‎‏‏‏‎‎‏‏‎‎‎‎‏‏‏‎‎‏‏‏‏‏‏‏‎‏‏‏‏‏‏‎‎‎‏‏‎‏‎‏‏‏‏‏‎‏‎‏‎‏‏‎‎‏‏‎Common passwords are blocked by your IT admin. Try a different password.‎‏‎‎‏‎"</string>
     <string name="lockpassword_pin_no_sequential_digits" msgid="3902387296149848324">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‎‏‏‎‏‏‎‎‎‏‎‏‎‎‎‎‎‎‏‎‎‎‎‏‎‎‏‎‎‎‏‏‏‏‏‎‎‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‎‏‎‎‎‎‎‏‎‎‎Ascending, descending, or repeated sequence of digits isn\'t allowed‎‏‎‎‏‎"</string>
diff --git a/tests/CarDeveloperOptions/res/values-es-rUS/config.xml b/tests/CarDeveloperOptions/res/values-es-rUS/config.xml
index b05e073..04c8bf8 100644
--- a/tests/CarDeveloperOptions/res/values-es-rUS/config.xml
+++ b/tests/CarDeveloperOptions/res/values-es-rUS/config.xml
@@ -17,5 +17,4 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="config_backup_settings_label" msgid="5230713622702899641"></string>
-    <string name="config_grayscale_settings_intent" msgid="7661066612238245852"></string>
 </resources>
diff --git a/tests/CarDeveloperOptions/res/values-es-rUS/strings.xml b/tests/CarDeveloperOptions/res/values-es-rUS/strings.xml
index a6d75a6..eacce38 100644
--- a/tests/CarDeveloperOptions/res/values-es-rUS/strings.xml
+++ b/tests/CarDeveloperOptions/res/values-es-rUS/strings.xml
@@ -668,7 +668,6 @@
       <item quantity="other">Debe tener menos de <xliff:g id="NUMBER_1">%d</xliff:g> dígitos</item>
       <item quantity="one">Debe tener menos de <xliff:g id="NUMBER_0">%d</xliff:g> dígito</item>
     </plurals>
-    <string name="lockpassword_pin_contains_non_digits" msgid="1501729742023195556">"Solo puede contener dígitos entre el 0 y el 9"</string>
     <string name="lockpassword_pin_recently_used" msgid="5556148774911741103">"El administrador del dispositivo no permite el uso de un PIN reciente"</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="2752037810571200697">"Tu administrador de TI bloquea los PIN comunes. Prueba uno diferente."</string>
     <string name="lockpassword_illegal_character" msgid="3577712947676261351">"No puede incluir un carácter no válido"</string>
@@ -699,6 +698,10 @@
       <item quantity="other">Debe tener al menos <xliff:g id="COUNT">%d</xliff:g> caracteres que no sean letras</item>
       <item quantity="one">Debe tener al menos 1 carácter que no sea una letra</item>
     </plurals>
+    <plurals name="lockpassword_password_requires_nonnumerical" formatted="false" msgid="6943707011152180150">
+      <item quantity="other">Debe tener al menos <xliff:g id="COUNT">%d</xliff:g> caracteres no numéricos</item>
+      <item quantity="one">Debe tener al menos 1 carácter no numérico</item>
+    </plurals>
     <string name="lockpassword_password_recently_used" msgid="6944729699375087431">"El administrador del dispositivo no permite el uso de contraseñas recientes"</string>
     <string name="lockpassword_password_blacklisted_by_admin" msgid="4988166770148440755">"Tu administrador de TI bloquea las contraseñas comunes. Prueba una diferente."</string>
     <string name="lockpassword_pin_no_sequential_digits" msgid="3902387296149848324">"No se permiten secuencias de dígitos ascendentes, descendentes ni repetidas"</string>
diff --git a/tests/CarDeveloperOptions/res/values-es/config.xml b/tests/CarDeveloperOptions/res/values-es/config.xml
index b05e073..04c8bf8 100644
--- a/tests/CarDeveloperOptions/res/values-es/config.xml
+++ b/tests/CarDeveloperOptions/res/values-es/config.xml
@@ -17,5 +17,4 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="config_backup_settings_label" msgid="5230713622702899641"></string>
-    <string name="config_grayscale_settings_intent" msgid="7661066612238245852"></string>
 </resources>
diff --git a/tests/CarDeveloperOptions/res/values-es/strings.xml b/tests/CarDeveloperOptions/res/values-es/strings.xml
index a89f196..fdbbdf2 100644
--- a/tests/CarDeveloperOptions/res/values-es/strings.xml
+++ b/tests/CarDeveloperOptions/res/values-es/strings.xml
@@ -324,7 +324,7 @@
     <string name="date_and_time_settings_title_setup_wizard" msgid="1573030770187844365">"Establecer fecha y hora"</string>
     <string name="date_and_time_settings_summary" msgid="4617979434474713417">"Establecer fecha, hora, zona horaria y formatos"</string>
     <string name="date_time_auto" msgid="2679132152303750218">"Usar hora de la red"</string>
-    <string name="zone_auto_title" msgid="5500880975376882488">"Usar la zona horaria proporcionada por la red"</string>
+    <string name="zone_auto_title" msgid="5500880975376882488">"Usar zona horaria proporcionada por la red"</string>
     <string name="date_time_24hour_auto" msgid="7499659679134962547">"Usar la configuración regional predeterminada"</string>
     <string name="date_time_24hour_title" msgid="6209923858891621283">"Formato de 24 horas"</string>
     <string name="date_time_24hour" msgid="1265706705061608742">"Usar formato de 24 horas"</string>
@@ -668,7 +668,6 @@
       <item quantity="other">Debe tener menos de <xliff:g id="NUMBER_1">%d</xliff:g> dígitos</item>
       <item quantity="one">Debe tener menos de <xliff:g id="NUMBER_0">%d</xliff:g> dígito</item>
     </plurals>
-    <string name="lockpassword_pin_contains_non_digits" msgid="1501729742023195556">"Solo debe tener dígitos comprendidos entre el 0 y el 9"</string>
     <string name="lockpassword_pin_recently_used" msgid="5556148774911741103">"El administrador de dispositivos no permite utilizar un PIN reciente"</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="2752037810571200697">"El administrador de TI bloquea los PIN comunes. Prueba a utilizar otro PIN."</string>
     <string name="lockpassword_illegal_character" msgid="3577712947676261351">"No puede incluir un carácter que no sea válido"</string>
@@ -699,6 +698,10 @@
       <item quantity="other">Debe tener al menos <xliff:g id="COUNT">%d</xliff:g> caracteres que no sean una letra</item>
       <item quantity="one">Debe tener al menos 1 carácter que no sea una letra</item>
     </plurals>
+    <plurals name="lockpassword_password_requires_nonnumerical" formatted="false" msgid="6943707011152180150">
+      <item quantity="other">Debe incluir al menos <xliff:g id="COUNT">%d</xliff:g> caracteres que no sean números</item>
+      <item quantity="one">Debe incluir al menos 1 carácter que no sea un número</item>
+    </plurals>
     <string name="lockpassword_password_recently_used" msgid="6944729699375087431">"El administrador de dispositivos no permite utilizar una contraseña reciente"</string>
     <string name="lockpassword_password_blacklisted_by_admin" msgid="4988166770148440755">"El administrador de TI bloquea las contraseñas comunes. Prueba a utilizar otra contraseña."</string>
     <string name="lockpassword_pin_no_sequential_digits" msgid="3902387296149848324">"No se permite utilizar una secuencia ascendente, descendente ni repetida de dígitos"</string>
@@ -1067,7 +1070,7 @@
     <string name="wifi_hotspot_ap_band_title" msgid="3485744480410441949">"Banda del punto de acceso"</string>
     <string name="wifi_hotspot_footer_info_regular" msgid="3876006922622827363">"Utiliza un punto de acceso para crear una red Wi‑Fi que puedan usar otros dispositivos. Los puntos de acceso permiten acceder a Internet con una conexión de datos móviles. Es posible que se apliquen cargos adicionales por el uso de datos."</string>
     <string name="wifi_hotspot_footer_info_local_only" msgid="3339582350894639261">"Las aplicaciones pueden crear un punto de acceso para compartir contenido con dispositivos cercanos."</string>
-    <string name="wifi_hotspot_auto_off_title" msgid="7416022590415189590">"Desactivar punto de acceso automáticamente"</string>
+    <string name="wifi_hotspot_auto_off_title" msgid="7416022590415189590">"Desactivar el punto de acceso automáticamente"</string>
     <string name="wifi_hotspot_auto_off_summary" msgid="3866769400624802105">"El punto de acceso Wi‑Fi se desactivará si no hay dispositivos conectados"</string>
     <string name="wifi_tether_starting" msgid="7676952148471297900">"Activando zona Wi-Fi…"</string>
     <string name="wifi_tether_stopping" msgid="7478561853791953349">"Desactivando zona Wi-Fi…"</string>
@@ -1125,7 +1128,7 @@
     <string name="notification_volume_title" msgid="6022562909288085275">"Notificación"</string>
     <string name="checkbox_notification_same_as_incoming_call" msgid="7312942422655861175">"Utilizar volumen de llamada entrante para notificaciones"</string>
     <string name="home_work_profile_not_supported" msgid="6137073723297076818">"No admite perfiles de trabajo"</string>
-    <string name="notification_sound_dialog_title" msgid="6653341809710423276">"Sonido de notificación predeterminado"</string>
+    <string name="notification_sound_dialog_title" msgid="6653341809710423276">"Sonido notif. predet."</string>
     <string name="media_volume_title" msgid="1030438549497800914">"Multimedia"</string>
     <string name="media_volume_summary" msgid="3142433516297061652">"Establecer volumen para música y vídeos"</string>
     <string name="alarm_volume_title" msgid="8902277801531496243">"Alarma"</string>
@@ -2486,8 +2489,8 @@
     <string name="menu_duration_6h" msgid="6169009210638008417">"6 horas"</string>
     <string name="menu_duration_12h" msgid="1435242738163843797">"12 horas"</string>
     <string name="menu_duration_1d" msgid="6476370834372352174">"1 día"</string>
-    <string name="menu_show_system" msgid="6315865548558708248">"Mostrar sistema"</string>
-    <string name="menu_hide_system" msgid="8457027118873733782">"Ocultar sistema"</string>
+    <string name="menu_show_system" msgid="6315865548558708248">"Mostrar aplicaciones del sistema"</string>
+    <string name="menu_hide_system" msgid="8457027118873733782">"Ocultar aplicaciones del sistema"</string>
     <string name="menu_show_percentage" msgid="6983272380729890884">"Mostrar porcentajes"</string>
     <string name="menu_use_uss" msgid="3765054705208926803">"Usar USS"</string>
     <string name="menu_proc_stats_type" msgid="2680179749566186247">"Tipo de estadísticas"</string>
@@ -3132,7 +3135,7 @@
     <string name="media_volume_option_title" msgid="3553411883305505682">"Volumen de multimedia"</string>
     <string name="remote_media_volume_option_title" msgid="6355710054191873836">"Volumen de envío"</string>
     <string name="call_volume_option_title" msgid="5028003296631037334">"Volumen de llamada"</string>
-    <string name="alarm_volume_option_title" msgid="3184076022438477047">"Volumen de alarma"</string>
+    <string name="alarm_volume_option_title" msgid="3184076022438477047">"Volumen de la alarma"</string>
     <string name="ring_volume_option_title" msgid="2038924918468372264">"Volumen del tono"</string>
     <string name="notification_volume_option_title" msgid="1358512611511348260">"Volumen de notificaciones"</string>
     <string name="ringtone_title" msgid="1409086028485922583">"Tono del teléfono"</string>
diff --git a/tests/CarDeveloperOptions/res/values-et/config.xml b/tests/CarDeveloperOptions/res/values-et/config.xml
index b05e073..04c8bf8 100644
--- a/tests/CarDeveloperOptions/res/values-et/config.xml
+++ b/tests/CarDeveloperOptions/res/values-et/config.xml
@@ -17,5 +17,4 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="config_backup_settings_label" msgid="5230713622702899641"></string>
-    <string name="config_grayscale_settings_intent" msgid="7661066612238245852"></string>
 </resources>
diff --git a/tests/CarDeveloperOptions/res/values-et/strings.xml b/tests/CarDeveloperOptions/res/values-et/strings.xml
index 627f509..2a56fc7 100644
--- a/tests/CarDeveloperOptions/res/values-et/strings.xml
+++ b/tests/CarDeveloperOptions/res/values-et/strings.xml
@@ -668,7 +668,6 @@
       <item quantity="other">Peab olema alla <xliff:g id="NUMBER_1">%d</xliff:g> numbri</item>
       <item quantity="one">Peab olema alla <xliff:g id="NUMBER_0">%d</xliff:g> numbri</item>
     </plurals>
-    <string name="lockpassword_pin_contains_non_digits" msgid="1501729742023195556">"Tohib sisaldada ainult numbreid 0–9."</string>
     <string name="lockpassword_pin_recently_used" msgid="5556148774911741103">"Seadme administraator ei luba kasutada viimast PIN-koodi"</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="2752037810571200697">"IT-administraator on levinud PIN-koodid blokeerinud. Proovige muud PIN-koodi."</string>
     <string name="lockpassword_illegal_character" msgid="3577712947676261351">"See ei tohi sisaldada sobimatut tähemärki"</string>
@@ -699,6 +698,10 @@
       <item quantity="other">Peab sisaldama vähemalt <xliff:g id="COUNT">%d</xliff:g> tähemärki, mis ei ole täht</item>
       <item quantity="one">Peab sisaldama vähemalt 1 tähemärki, mis ei ole täht</item>
     </plurals>
+    <plurals name="lockpassword_password_requires_nonnumerical" formatted="false" msgid="6943707011152180150">
+      <item quantity="other">Peab sisaldama vähemalt <xliff:g id="COUNT">%d</xliff:g> tähemärki, mis ei ole number</item>
+      <item quantity="one">Peab sisaldama vähemalt 1 tähemärki, mis ei ole number</item>
+    </plurals>
     <string name="lockpassword_password_recently_used" msgid="6944729699375087431">"Seadme administraator ei luba kasutada hiljutist parooli"</string>
     <string name="lockpassword_password_blacklisted_by_admin" msgid="4988166770148440755">"IT-administraator on levinud paroolid blokeerinud. Proovige muud parooli."</string>
     <string name="lockpassword_pin_no_sequential_digits" msgid="3902387296149848324">"Kasvavad, kahanevad või korduvad numbrijadad on keelatud"</string>
diff --git a/tests/CarDeveloperOptions/res/values-eu/config.xml b/tests/CarDeveloperOptions/res/values-eu/config.xml
index b05e073..04c8bf8 100644
--- a/tests/CarDeveloperOptions/res/values-eu/config.xml
+++ b/tests/CarDeveloperOptions/res/values-eu/config.xml
@@ -17,5 +17,4 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="config_backup_settings_label" msgid="5230713622702899641"></string>
-    <string name="config_grayscale_settings_intent" msgid="7661066612238245852"></string>
 </resources>
diff --git a/tests/CarDeveloperOptions/res/values-eu/strings.xml b/tests/CarDeveloperOptions/res/values-eu/strings.xml
index e407e59..aefeda7 100644
--- a/tests/CarDeveloperOptions/res/values-eu/strings.xml
+++ b/tests/CarDeveloperOptions/res/values-eu/strings.xml
@@ -93,14 +93,14 @@
     <string name="sdcard_setting" product="nosdcard" msgid="1533784309105748696">"USB bidezko memoria"</string>
     <string name="sdcard_setting" product="default" msgid="8398782065765523178">"SD txartela"</string>
     <string name="bluetooth" msgid="1564520421786841227">"Bluetooth-a"</string>
-    <string name="bluetooth_is_discoverable" msgid="6748888489356326898">"Inguruko Bluetooth bidezko gailuetarako ikusgai (<xliff:g id="DISCOVERABLE_TIME_PERIOD">%1$s</xliff:g>)"</string>
-    <string name="bluetooth_is_discoverable_always" msgid="6835934878803488274">"Inguruko Bluetooth bidezko gailuetarako ikusgai"</string>
-    <string name="bluetooth_not_visible_to_other_devices" msgid="9134284066024557293">"Bluetooth bidezko beste gailuetarako ikusgaitz"</string>
+    <string name="bluetooth_is_discoverable" msgid="6748888489356326898">"Inguruko Bluetooth gailu guztietarako ikusgai (<xliff:g id="DISCOVERABLE_TIME_PERIOD">%1$s</xliff:g>)"</string>
+    <string name="bluetooth_is_discoverable_always" msgid="6835934878803488274">"Inguruko Bluetooth gailu guztietarako ikusgai"</string>
+    <string name="bluetooth_not_visible_to_other_devices" msgid="9134284066024557293">"Ez da beste Bluetooth gailuetarako ikusgai"</string>
     <string name="bluetooth_only_visible_to_paired_devices" msgid="3830247336229883519">"Bikotetutako gailuetarako soilik ikusgai"</string>
     <string name="bluetooth_visibility_timeout" msgid="4804679276398564496">"Ikusgaitasunaren denbora-muga gainditu da"</string>
     <string name="bluetooth_lock_voice_dialing" msgid="1600385868298081015">"Blokeatu ahots bidezko markatzea"</string>
     <string name="bluetooth_lock_voice_dialing_summary" msgid="5005776616112427980">"Galarazi Bluetooth telefonoa erabiltzea pantaila blokeatuta dagoenean"</string>
-    <string name="bluetooth_devices" msgid="4143880830505625666">"Bluetooth bidezko gailuak"</string>
+    <string name="bluetooth_devices" msgid="4143880830505625666">"Bluetooth gailuak"</string>
     <string name="bluetooth_device_name" msgid="3682016026866302981">"Gailuaren izena"</string>
     <string name="bluetooth_device_details" msgid="2500840679106321361">"Gailuaren ezarpenak"</string>
     <string name="bluetooth_profile_details" msgid="1785505059738682493">"Profilaren ezarpenak"</string>
@@ -123,7 +123,7 @@
     <string name="bluetooth_broadcasting" msgid="8926408584599563760">"Igorpena"</string>
     <string name="bluetooth_device" msgid="3170974107364990008">"Izenik gabeko Bluetooth bidezko gailua"</string>
     <string name="progress_scanning" msgid="633923400401041181">"Bilatzen"</string>
-    <string name="bluetooth_no_devices_found" msgid="4396050022213494322">"Bluetooth bidezko gailurik ez inguruan."</string>
+    <string name="bluetooth_no_devices_found" msgid="4396050022213494322">"Ez da Bluetooth gailurik aurkitu inguruan."</string>
     <string name="bluetooth_notif_ticker" msgid="8398481099943141819">"Bluetooth bidez parekatzeko eskaera"</string>
     <string name="bluetooth_notif_title" msgid="5090288898529286011">"Bikotetzeko eskaera"</string>
     <string name="bluetooth_notif_message" msgid="6612367890895077938">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> gailuarekin parekatzeko, sakatu hau."</string>
@@ -134,14 +134,14 @@
     <string name="bluetooth_ask_disablement" msgid="7125319551097350783">"<xliff:g id="APP_NAME">%1$s</xliff:g> aplikazioak Bluetooth konexioa desaktibatu nahi du"</string>
     <string name="bluetooth_ask_enablement_no_name" msgid="6105893027185475233">"Aplikazio batek Bluetooth konexioa aktibatu nahi du"</string>
     <string name="bluetooth_ask_disablement_no_name" msgid="8648888502291681310">"Aplikazio batek Bluetooth konexioa desaktibatu nahi du"</string>
-    <string name="bluetooth_ask_discovery" product="tablet" msgid="6871595755186170115">"<xliff:g id="APP_NAME">%1$s</xliff:g> aplikazioak tableta Bluetooth bidezko beste gailu batzuen aurrean ikusgai ezarri nahi du <xliff:g id="TIMEOUT">%2$d</xliff:g> segundoz."</string>
-    <string name="bluetooth_ask_discovery" product="default" msgid="3388041479101348095">"<xliff:g id="APP_NAME">%1$s</xliff:g> aplikazioak telefonoa Bluetooth bidezko beste gailu batzuen aurrean ikusgai ezarri nahi du <xliff:g id="TIMEOUT">%2$d</xliff:g> segundoz."</string>
-    <string name="bluetooth_ask_discovery_no_name" product="tablet" msgid="1472358802231150345">"Aplikazio batek tableta Bluetooth bidezko beste gailu batzuen aurrean ikusgai ezarri nahi du <xliff:g id="TIMEOUT">%1$d</xliff:g> segundoz."</string>
-    <string name="bluetooth_ask_discovery_no_name" product="default" msgid="6195796094297507404">"Aplikazio batek telefonoa Bluetooth bidezko beste gailu batzuen aurrean ikusgai ezarri nahi du <xliff:g id="TIMEOUT">%1$d</xliff:g> segundoz."</string>
-    <string name="bluetooth_ask_lasting_discovery" product="tablet" msgid="2702942027812132427">"<xliff:g id="APP_NAME">%1$s</xliff:g> aplikazioak tableta beste Bluetooth bidezko gailu batzuen aurrean ikusgai ezarri nahi du. Bluetooth-aren ezarpenetan alda dezakezu hori geroago."</string>
-    <string name="bluetooth_ask_lasting_discovery" product="default" msgid="7796723473397303412">"<xliff:g id="APP_NAME">%1$s</xliff:g> aplikazioak telefonoa beste Bluetooth bidezko gailu batzuen aurrean ikusgai ezarri nahi du. Bluetooth-aren ezarpenetan alda dezakezu hori geroago."</string>
-    <string name="bluetooth_ask_lasting_discovery_no_name" product="tablet" msgid="5961921359655434504">"Aplikazio batek tableta beste Bluetooth bidezko gailu batzuen aurrean ikusgai ezarri nahi du. Bluetooth-aren ezarpenetan alda dezakezu hori geroago."</string>
-    <string name="bluetooth_ask_lasting_discovery_no_name" product="default" msgid="3585910858758443872">"Aplikazio batek telefonoa beste Bluetooth bidezko gailu batzuen aurrean ikusgai ezarri nahi du. Bluetooth-aren ezarpenetan alda dezakezu hori geroago."</string>
+    <string name="bluetooth_ask_discovery" product="tablet" msgid="6871595755186170115">"<xliff:g id="APP_NAME">%1$s</xliff:g> aplikazioak tableta beste Bluetooth gailu batzuen aurrean ikusgai ezarri nahi du <xliff:g id="TIMEOUT">%2$d</xliff:g> segundoz."</string>
+    <string name="bluetooth_ask_discovery" product="default" msgid="3388041479101348095">"<xliff:g id="APP_NAME">%1$s</xliff:g> aplikazioak telefonoa beste Bluetooth gailu batzuen aurrean ikusgai ezarri nahi du <xliff:g id="TIMEOUT">%2$d</xliff:g> segundoz."</string>
+    <string name="bluetooth_ask_discovery_no_name" product="tablet" msgid="1472358802231150345">"Aplikazio batek tableta beste Bluetooth gailu batzuen aurrean ikusgai ezarri nahi du <xliff:g id="TIMEOUT">%1$d</xliff:g> segundoz."</string>
+    <string name="bluetooth_ask_discovery_no_name" product="default" msgid="6195796094297507404">"Aplikazio batek telefonoa beste Bluetooth gailu batzuen aurrean ikusgai ezarri nahi du <xliff:g id="TIMEOUT">%1$d</xliff:g> segundoz."</string>
+    <string name="bluetooth_ask_lasting_discovery" product="tablet" msgid="2702942027812132427">"<xliff:g id="APP_NAME">%1$s</xliff:g> aplikazioak tableta beste Bluetooth gailu batzuen aurrean ikusgai ezarri nahi du. Bluetooth ezarpenetan alda dezakezu hori geroago."</string>
+    <string name="bluetooth_ask_lasting_discovery" product="default" msgid="7796723473397303412">"<xliff:g id="APP_NAME">%1$s</xliff:g> aplikazioak telefonoa beste Bluetooth gailu batzuen aurrean ikusgai ezarri nahi du. Bluetooth ezarpenetan alda dezakezu hori geroago."</string>
+    <string name="bluetooth_ask_lasting_discovery_no_name" product="tablet" msgid="5961921359655434504">"Aplikazio batek tableta beste Bluetooth gailu batzuen aurrean ikusgai ezarri nahi du. Bluetooth ezarpenetan alda dezakezu hori geroago."</string>
+    <string name="bluetooth_ask_lasting_discovery_no_name" product="default" msgid="3585910858758443872">"Aplikazio batek telefonoa beste Bluetooth gailu batzuen aurrean ikusgai ezarri nahi du. Bluetooth ezarpenetan alda dezakezu hori geroago."</string>
     <string name="bluetooth_ask_enablement_and_discovery" product="tablet" msgid="5676466923424941153">"<xliff:g id="APP_NAME">%1$s</xliff:g> aplikazioak Bluetooth konexioa aktibatu eta tableta beste gailu batzuen aurrean ikusgai ezarri nahi du <xliff:g id="TIMEOUT">%2$d</xliff:g> segundoz."</string>
     <string name="bluetooth_ask_enablement_and_discovery" product="default" msgid="507088376226791063">"<xliff:g id="APP_NAME">%1$s</xliff:g> aplikazioak Bluetooth konexioa aktibatu eta telefonoa beste gailu batzuen aurrean ikusgai ezarri nahi du <xliff:g id="TIMEOUT">%2$d</xliff:g> segundoz."</string>
     <string name="bluetooth_ask_enablement_and_discovery_no_name" product="tablet" msgid="1164681893121736219">"Aplikazio batek Bluetooth konexioa aktibatu eta tableta beste gailu batzuen aurrean ikusgai ezarri nahi du <xliff:g id="TIMEOUT">%1$d</xliff:g> segundoz."</string>
@@ -167,9 +167,9 @@
     <string name="bluetooth_off_footer" msgid="7658444560543730571">"Aktibatu Bluetooth konexioa beste gailu batzuetara konektatzeko."</string>
     <string name="bluetooth_paired_device_title" msgid="8361860197780425286">"Zure gailuak"</string>
     <string name="bluetooth_pairing_page_title" msgid="9053463656712597709">"Parekatu gailu batekin"</string>
-    <string name="bluetooth_pref_summary" product="tablet" msgid="3601662966604648212">"Baimendu tabletari inguruko Bluetooth bidezko gailuekin komunikatzea"</string>
-    <string name="bluetooth_pref_summary" product="device" msgid="2286727776570956969">"Eman gailuari inguruko Bluetooth bidezko gailuekin komunikatzeko baimena"</string>
-    <string name="bluetooth_pref_summary" product="default" msgid="863659221858781186">"Baimendu telefonoari inguruko Bluetooth bidezko gailuekin komunikatzea"</string>
+    <string name="bluetooth_pref_summary" product="tablet" msgid="3601662966604648212">"Baimendu tabletari inguruko Bluetooth gailuekin komunikatzea"</string>
+    <string name="bluetooth_pref_summary" product="device" msgid="2286727776570956969">"Baimendu gailuari inguruko Bluetooth gailuekin komunikatzea"</string>
+    <string name="bluetooth_pref_summary" product="default" msgid="863659221858781186">"Baimendu telefonoari inguruko Bluetooth gailuekin komunikatzea"</string>
     <string name="bluetooth_disable_a2dp_hw_offload" msgid="293429878480958234">"Desgaitu Bluetooth A2DP hardwarea deskargatzeko aukera"</string>
     <string name="bluetooth_disable_a2dp_hw_offload_dialog_title" msgid="7362106962085861626">"Gailua berrabiarazi?"</string>
     <string name="bluetooth_disable_a2dp_hw_offload_dialog_message" msgid="4837282201316413412">"Ezarpen hau aldatzeko, gailua berrabiarazi egin behar duzu."</string>
@@ -668,7 +668,6 @@
       <item quantity="other"><xliff:g id="NUMBER_1">%d</xliff:g> digitu baino gutxiago izan behar ditu</item>
       <item quantity="one"><xliff:g id="NUMBER_0">%d</xliff:g> digitu baino gutxiago izan behar du</item>
     </plurals>
-    <string name="lockpassword_pin_contains_non_digits" msgid="1501729742023195556">"0 eta 9 arteko digituak soilik izan ditzake"</string>
     <string name="lockpassword_pin_recently_used" msgid="5556148774911741103">"Gailuaren administratzaileak ez du eman beste PIN kode bat erabiltzeko baimenik"</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="2752037810571200697">"IKT administratzaileak blokeatu egiten ditu asmatzen errazak diren PIN kodeak. Erabili beste PIN bat."</string>
     <string name="lockpassword_illegal_character" msgid="3577712947676261351">"Ezin da erabili onartzen ez den karaktererik"</string>
@@ -699,6 +698,10 @@
       <item quantity="other">Hizkiak ez diren <xliff:g id="COUNT">%d</xliff:g> karaktere bat izan behar du gutxienez</item>
       <item quantity="one">Hizkia ez den karaktere bat izan behar du gutxienez</item>
     </plurals>
+    <plurals name="lockpassword_password_requires_nonnumerical" formatted="false" msgid="6943707011152180150">
+      <item quantity="other">Gutxienez, zenbakizkoak ez diren <xliff:g id="COUNT">%d</xliff:g> karaktere izan behar ditu</item>
+      <item quantity="one">Gutxienez, zenbakizkoa ez den 1 karaktere izan behar du</item>
+    </plurals>
     <string name="lockpassword_password_recently_used" msgid="6944729699375087431">"Gailuaren administratzaileak ez du eman beste pasahitz bat erabiltzeko baimenik"</string>
     <string name="lockpassword_password_blacklisted_by_admin" msgid="4988166770148440755">"IKT administratzaileak blokeatu egiten ditu asmatzen errazak diren pasahitzak. Erabili beste pasahitz bat."</string>
     <string name="lockpassword_pin_no_sequential_digits" msgid="3902387296149848324">"Ezin da erabili goranzko, beheranzko edo errepikatutako digitu-sekuentziarik"</string>
@@ -763,9 +766,9 @@
     <string name="bluetooth_device_context_connect_advanced" msgid="423463405499392444">"Aukerak…"</string>
     <string name="bluetooth_menu_advanced" msgid="7566858513372603652">"Aurreratuak"</string>
     <string name="bluetooth_advanced_titlebar" msgid="6459469494039004784">"Bluetooth ezarpen aurreratuak"</string>
-    <string name="bluetooth_empty_list_bluetooth_off" msgid="6255367297830430459">"Bluetooth-a aktibatuta badago, inguruko Bluetooth bidezko gailuekin komunika daiteke gailua."</string>
-    <string name="bluetooth_scanning_on_info_message" msgid="5460370815156050550">"Bluetooth-a aktibatuta badago, inguruko Bluetooth bidezko gailuekin komunika daiteke gailua.\n\nGailuaren erabilera hobetzeko, aplikazioek eta zerbitzuek wifi-sareak bilatzen jarraituko dute, baita wifi-konexioa desaktibatuta dagoenean ere. Besteak beste, kokapenean oinarritutako eginbideak eta zerbitzuak hobetzeko erabil daiteke. Aukera hori aldatzeko, joan "<annotation id="link">"gailuak bilatzeko ezarpenetara"</annotation>"."</string>
-    <string name="ble_scan_notify_text" msgid="6290170236546386932">"Kokapenaren zehaztasuna hobetzeko, sistemaren aplikazioek eta zerbitzuek Bluetooth bidezko gailuak hautematen jarraituko dute. Hori aldatzeko, zoaz <xliff:g id="LINK_BEGIN_0">LINK_BEGIN</xliff:g>gailuak bilatzeko ezarpenetara<xliff:g id="LINK_END_1">LINK_END</xliff:g>."</string>
+    <string name="bluetooth_empty_list_bluetooth_off" msgid="6255367297830430459">"Bluetooth aktibatuta badago, inguruko Bluetooth gailuekin komunika daiteke gailua."</string>
+    <string name="bluetooth_scanning_on_info_message" msgid="5460370815156050550">"Bluetooth konexioa aktibatuta dagoenean, inguruko Bluetooth gailuekin komunika daiteke gailua.\n\nGailuaren erabilera hobetzeko, aplikazioek eta zerbitzuek wifi-sareak bilatzen jarraituko dute, baita wifi-konexioa desaktibatuta dagoenean ere. Besteak beste, kokapenean oinarritutako eginbideak eta zerbitzuak hobetzeko erabil daiteke. Aukera hori aldatzeko, joan "<annotation id="link">"gailuak bilatzeko ezarpenetara"</annotation>"."</string>
+    <string name="ble_scan_notify_text" msgid="6290170236546386932">"Kokapenaren zehaztasuna hobetzeko, sistemaren aplikazioek eta zerbitzuek Bluetooth gailuak hautematen jarraituko dute. Hori aldatzeko, zoaz <xliff:g id="LINK_BEGIN_0">LINK_BEGIN</xliff:g>gailuak bilatzeko ezarpenetara<xliff:g id="LINK_END_1">LINK_END</xliff:g>."</string>
     <string name="bluetooth_connect_failed" msgid="1151234676456333786">"Ezin izan da konektatu. Saiatu berriro."</string>
     <string name="device_details_title" msgid="726517818032923222">"Gailuaren xehetasunak"</string>
     <string name="bluetooth_device_mac_address" msgid="5328203122581150405">"Gailuaren Bluetooth helbidea: <xliff:g id="ADDRESS">%1$s</xliff:g>"</string>
@@ -3302,7 +3305,7 @@
     <string name="hide_silent_icons_summary" msgid="2624346914488256888">"Ezkutatu jakinarazpen isilen ikonoak egoera-barran"</string>
     <string name="notification_badging_title" msgid="6311699476970264712">"Baimendu jakinarazpen-biribiltxoak"</string>
     <string name="notification_bubbles_title" msgid="9196562435741861317">"Burbuilak"</string>
-    <string name="notification_bubbles_summary" msgid="4624512775901949578">"Atzitu bizkor aplikazioetako edukia lasterbide gainerakorrak erabilita"</string>
+    <string name="notification_bubbles_summary" msgid="4624512775901949578">"Atzitu bizkor aplikazioetako edukia lasterbide flotagarriak erabilita"</string>
     <string name="bubbles_feature_education" msgid="8979109826818881018">"Jakinarazpen batzuk eta bestelako edukia burbuila gisa ager daitezke pantailan. Burbuilak irekitzeko, saka itzazu. Baztertzeko, ordea, arrasta itzazu pantailan behera."</string>
     <string name="bubbles_app_toggle_title" msgid="6401217027603326439">"Burbuilak"</string>
     <string name="bubbles_app_toggle_summary" msgid="7707611139796553855">"Baimendu <xliff:g id="APP_NAME">%1$s</xliff:g> aplikazioari jakinarazpen batzuk burbuila gisa erakustea"</string>
@@ -4328,7 +4331,7 @@
     <string name="prevent_ringing_option_mute_summary" msgid="3509459199090688328">"Aktibatuta (audioa desaktibatuta)"</string>
     <string name="prevent_ringing_option_none_summary" msgid="5152618221093037451">"Desaktibatuta"</string>
     <string name="pref_title_network_details" msgid="3971074015034595956">"Sarearen xehetasunak"</string>
-    <string name="about_phone_device_name_warning" msgid="9088572775969880106">"Mugikorreko aplikazioek gailuaren izena ikus dezakete. Halaber, jendeak ere ikus dezake Bluetooth bidezko gailuetara konektatzean edo wifi-sare publiko bat konfiguratzean."</string>
+    <string name="about_phone_device_name_warning" msgid="9088572775969880106">"Mugikorreko aplikazioek gailuaren izena ikus dezakete. Halaber, jendeak ere ikus dezake Bluetooth gailuetara konektatzean edo Wi-Fi sare publiko bat konfiguratzean."</string>
     <string name="devices_title" msgid="4768432575951993648">"Gailuak"</string>
     <string name="homepage_all_settings" msgid="3201220879559136116">"Ezarpen guztiak"</string>
     <string name="homepage_personal_settings" msgid="7472638597249114564">"Iradokizunak"</string>
@@ -4453,7 +4456,7 @@
       <item quantity="other"><xliff:g id="NUMBER_DEVICE_COUNT_1">%1$d</xliff:g> gailu daude konektatuta</item>
       <item quantity="one"><xliff:g id="NUMBER_DEVICE_COUNT_0">%1$d</xliff:g> gailu dago konektatuta</item>
     </plurals>
-    <string name="no_bluetooth_devices" msgid="4338224958734305244">"Ez dago Bluetooth bidezko gailurik"</string>
+    <string name="no_bluetooth_devices" msgid="4338224958734305244">"Ez dago Bluetooth gailurik"</string>
     <string name="bluetooth_left_name" msgid="4393660998014637355">"Ezkerrekoa"</string>
     <string name="bluetooth_right_name" msgid="8356091262762973801">"Eskuinekoa"</string>
     <string name="bluetooth_middle_name" msgid="1489185200445352103">"Zorroa"</string>
diff --git a/tests/CarDeveloperOptions/res/values-fa/config.xml b/tests/CarDeveloperOptions/res/values-fa/config.xml
index b05e073..04c8bf8 100644
--- a/tests/CarDeveloperOptions/res/values-fa/config.xml
+++ b/tests/CarDeveloperOptions/res/values-fa/config.xml
@@ -17,5 +17,4 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="config_backup_settings_label" msgid="5230713622702899641"></string>
-    <string name="config_grayscale_settings_intent" msgid="7661066612238245852"></string>
 </resources>
diff --git a/tests/CarDeveloperOptions/res/values-fa/strings.xml b/tests/CarDeveloperOptions/res/values-fa/strings.xml
index 23f8022..9ee2162 100644
--- a/tests/CarDeveloperOptions/res/values-fa/strings.xml
+++ b/tests/CarDeveloperOptions/res/values-fa/strings.xml
@@ -668,7 +668,6 @@
       <item quantity="one">باید کمتر از <xliff:g id="NUMBER_1">%d</xliff:g> رقم باشد</item>
       <item quantity="other">باید کمتر از <xliff:g id="NUMBER_1">%d</xliff:g> رقم باشد</item>
     </plurals>
-    <string name="lockpassword_pin_contains_non_digits" msgid="1501729742023195556">"فقط می‌تواند شامل اعداد ۰ تا ۹ باشد"</string>
     <string name="lockpassword_pin_recently_used" msgid="5556148774911741103">"سرپرست دستگاه اجازه استفاده از پین اخیر را نمی‌دهد"</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="2752037810571200697">"پین‌های رایج توسط سرپرست فناوری اطلاعات شما مسدود شده‌اند. پین متفاوتی را امتحان کنید."</string>
     <string name="lockpassword_illegal_character" msgid="3577712947676261351">"نمی‌تواند نویسه نامعتبر داشته باشد"</string>
@@ -699,6 +698,10 @@
       <item quantity="one">باید حداقل <xliff:g id="COUNT">%d</xliff:g> نویسه غیرحرف داشته باشد</item>
       <item quantity="other">باید حداقل <xliff:g id="COUNT">%d</xliff:g> نویسه غیرحرف داشته باشد</item>
     </plurals>
+    <plurals name="lockpassword_password_requires_nonnumerical" formatted="false" msgid="6943707011152180150">
+      <item quantity="one">باید حداقل <xliff:g id="COUNT">%d</xliff:g> نویسه غیرعددی داشته باشد</item>
+      <item quantity="other">باید حداقل <xliff:g id="COUNT">%d</xliff:g> نویسه غیرعددی داشته باشد</item>
+    </plurals>
     <string name="lockpassword_password_recently_used" msgid="6944729699375087431">"سرپرست دستگاه اجازه استفاده از گذرواژه اخیر را نمی‌دهد"</string>
     <string name="lockpassword_password_blacklisted_by_admin" msgid="4988166770148440755">"گذرواژه‌های رایج توسط سرپرست فناوری اطلاعات شما مسدود شده‌اند. گذرواژه متفاوتی را امتحان کنید."</string>
     <string name="lockpassword_pin_no_sequential_digits" msgid="3902387296149848324">"ترتیب صعودی، نزولی یا تکراری ارقام مجاز نیست"</string>
diff --git a/tests/CarDeveloperOptions/res/values-fi/config.xml b/tests/CarDeveloperOptions/res/values-fi/config.xml
index b05e073..04c8bf8 100644
--- a/tests/CarDeveloperOptions/res/values-fi/config.xml
+++ b/tests/CarDeveloperOptions/res/values-fi/config.xml
@@ -17,5 +17,4 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="config_backup_settings_label" msgid="5230713622702899641"></string>
-    <string name="config_grayscale_settings_intent" msgid="7661066612238245852"></string>
 </resources>
diff --git a/tests/CarDeveloperOptions/res/values-fi/strings.xml b/tests/CarDeveloperOptions/res/values-fi/strings.xml
index 64e8db2..b88338b 100644
--- a/tests/CarDeveloperOptions/res/values-fi/strings.xml
+++ b/tests/CarDeveloperOptions/res/values-fi/strings.xml
@@ -668,7 +668,6 @@
       <item quantity="other">PIN-koodissa saa olla enintään <xliff:g id="NUMBER_1">%d</xliff:g> numeroa.</item>
       <item quantity="one">PIN-koodissa saa olla enintään <xliff:g id="NUMBER_0">%d</xliff:g> numero.</item>
     </plurals>
-    <string name="lockpassword_pin_contains_non_digits" msgid="1501729742023195556">"PIN-koodi saa sisältää vain numeroita 0–9."</string>
     <string name="lockpassword_pin_recently_used" msgid="5556148774911741103">"Järjestelmänvalvoja esti PIN-koodin, koska sitä on käytetty hiljattain."</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="2752037810571200697">"IT-järjestelmänvalvoja on estänyt yleiset PIN-koodit. Kokeile eri PIN-koodia."</string>
     <string name="lockpassword_illegal_character" msgid="3577712947676261351">"Salasanassa ei saa olla virheellisiä merkkejä."</string>
@@ -699,6 +698,10 @@
       <item quantity="other">Salasanassa on oltava vähintään <xliff:g id="COUNT">%d</xliff:g> merkkiä, jotka eivät ole kirjaimia.</item>
       <item quantity="one">Salasanassa on oltava vähintään 1 merkki, joka ei ole kirjain.</item>
     </plurals>
+    <plurals name="lockpassword_password_requires_nonnumerical" formatted="false" msgid="6943707011152180150">
+      <item quantity="other">Salasanassa on oltava vähintään <xliff:g id="COUNT">%d</xliff:g> merkki, joka ei ole numero</item>
+      <item quantity="one">Salasanassa on oltava vähintään 1 merkki, joka ei ole numero</item>
+    </plurals>
     <string name="lockpassword_password_recently_used" msgid="6944729699375087431">"Järjestelmänvalvoja esti salasanan, koska sitä on käytetty hiljattain."</string>
     <string name="lockpassword_password_blacklisted_by_admin" msgid="4988166770148440755">"IT-järjestelmänvalvoja on estänyt yleiset salasanat. Kokeile eri salasanaa."</string>
     <string name="lockpassword_pin_no_sequential_digits" msgid="3902387296149848324">"Nousevat, laskevat tai toistuvat numerosarjat on kielletty."</string>
diff --git a/tests/CarDeveloperOptions/res/values-fr-rCA/config.xml b/tests/CarDeveloperOptions/res/values-fr-rCA/config.xml
index b05e073..04c8bf8 100644
--- a/tests/CarDeveloperOptions/res/values-fr-rCA/config.xml
+++ b/tests/CarDeveloperOptions/res/values-fr-rCA/config.xml
@@ -17,5 +17,4 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="config_backup_settings_label" msgid="5230713622702899641"></string>
-    <string name="config_grayscale_settings_intent" msgid="7661066612238245852"></string>
 </resources>
diff --git a/tests/CarDeveloperOptions/res/values-fr-rCA/strings.xml b/tests/CarDeveloperOptions/res/values-fr-rCA/strings.xml
index 4f4a8c2..67dcd0d 100644
--- a/tests/CarDeveloperOptions/res/values-fr-rCA/strings.xml
+++ b/tests/CarDeveloperOptions/res/values-fr-rCA/strings.xml
@@ -668,7 +668,6 @@
       <item quantity="one">Doit contenir moins de <xliff:g id="NUMBER_1">%d</xliff:g> chiffre</item>
       <item quantity="other">Doit contenir moins de <xliff:g id="NUMBER_1">%d</xliff:g> chiffres</item>
     </plurals>
-    <string name="lockpassword_pin_contains_non_digits" msgid="1501729742023195556">"Doit contenir uniquement des chiffres contenus entre 0 et 9"</string>
     <string name="lockpassword_pin_recently_used" msgid="5556148774911741103">"L\'administrateur de l\'appareil ne permet pas l\'utilisation d\'un NIP récent"</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="2752037810571200697">"Les NIP communs sont bloqués par l\'administrateur de votre service informatique. Essayez un NIP différent."</string>
     <string name="lockpassword_illegal_character" msgid="3577712947676261351">"Vous ne pouvez pas inclure de caractère non valide"</string>
@@ -699,6 +698,10 @@
       <item quantity="one">Doit contenir au moins <xliff:g id="COUNT">%d</xliff:g> caractère autre qu\'une lettre</item>
       <item quantity="other">Doit contenir au moins <xliff:g id="COUNT">%d</xliff:g> caractères autres qu\'une lettre</item>
     </plurals>
+    <plurals name="lockpassword_password_requires_nonnumerical" formatted="false" msgid="6943707011152180150">
+      <item quantity="one">Doit contenir au moins <xliff:g id="COUNT">%d</xliff:g> caractère non numérique</item>
+      <item quantity="other">Doit contenir au moins <xliff:g id="COUNT">%d</xliff:g> caractères non numériques</item>
+    </plurals>
     <string name="lockpassword_password_recently_used" msgid="6944729699375087431">"L\'administrateur de l\'appareil ne permet pas l\'utilisation d\'un mot de passe récent"</string>
     <string name="lockpassword_password_blacklisted_by_admin" msgid="4988166770148440755">"Les mots de passe communs sont bloqués par l\'administrateur de votre service informatique. Essayez un mot de passe différent."</string>
     <string name="lockpassword_pin_no_sequential_digits" msgid="3902387296149848324">"Les suites croissantes, décroissantes ou répétitives de chiffres ne sont pas autorisées"</string>
diff --git a/tests/CarDeveloperOptions/res/values-fr/arrays.xml b/tests/CarDeveloperOptions/res/values-fr/arrays.xml
index fb365ad..27ba552 100644
--- a/tests/CarDeveloperOptions/res/values-fr/arrays.xml
+++ b/tests/CarDeveloperOptions/res/values-fr/arrays.xml
@@ -256,7 +256,7 @@
     <item msgid="7565226799008076833">"volume général"</item>
     <item msgid="5420704980305018295">"volume de la voix"</item>
     <item msgid="5797363115508970204">"volume des sonneries"</item>
-    <item msgid="8233154098550715999">"volume des contenus multimédias"</item>
+    <item msgid="8233154098550715999">"volume multimédia"</item>
     <item msgid="5196715605078153950">"volume des alarmes"</item>
     <item msgid="394030698764284577">"volume des notifications"</item>
     <item msgid="8952898972491680178">"volume Bluetooth"</item>
@@ -323,7 +323,7 @@
     <item msgid="6844485713404805301">"Volume général"</item>
     <item msgid="1600379420669104929">"Volume de la voix"</item>
     <item msgid="6296768210470214866">"Volume de la sonnerie"</item>
-    <item msgid="510690696071629241">"Volume des contenus multimédias"</item>
+    <item msgid="510690696071629241">"Volume multimédia"</item>
     <item msgid="406861638631430109">"Volume de l\'alarme"</item>
     <item msgid="4715864795872233884">"Volume des notifications"</item>
     <item msgid="2311478519251301183">"Volume Bluetooth"</item>
diff --git a/tests/CarDeveloperOptions/res/values-fr/config.xml b/tests/CarDeveloperOptions/res/values-fr/config.xml
index b05e073..04c8bf8 100644
--- a/tests/CarDeveloperOptions/res/values-fr/config.xml
+++ b/tests/CarDeveloperOptions/res/values-fr/config.xml
@@ -17,5 +17,4 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="config_backup_settings_label" msgid="5230713622702899641"></string>
-    <string name="config_grayscale_settings_intent" msgid="7661066612238245852"></string>
 </resources>
diff --git a/tests/CarDeveloperOptions/res/values-fr/strings.xml b/tests/CarDeveloperOptions/res/values-fr/strings.xml
index f243009..e806a9f 100644
--- a/tests/CarDeveloperOptions/res/values-fr/strings.xml
+++ b/tests/CarDeveloperOptions/res/values-fr/strings.xml
@@ -668,7 +668,6 @@
       <item quantity="one">Le code doit contenir moins de <xliff:g id="NUMBER_1">%d</xliff:g> chiffre</item>
       <item quantity="other">Le code doit contenir moins de <xliff:g id="NUMBER_1">%d</xliff:g> chiffres</item>
     </plurals>
-    <string name="lockpassword_pin_contains_non_digits" msgid="1501729742023195556">"Le code ne doit comporter que des chiffres compris entre 0 et 9"</string>
     <string name="lockpassword_pin_recently_used" msgid="5556148774911741103">"L\'administrateur de l\'appareil n\'autorise pas l\'utilisation d\'un code récent"</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="2752037810571200697">"Les codes courants sont bloqués par votre administrateur informatique. Choisissez un autre code."</string>
     <string name="lockpassword_illegal_character" msgid="3577712947676261351">"Vous ne pouvez pas inclure de caractère non valide."</string>
@@ -699,6 +698,10 @@
       <item quantity="one">Le mot de passe doit comporter au moins <xliff:g id="COUNT">%d</xliff:g> caractère autre qu\'une lettre</item>
       <item quantity="other">Le mot de passe doit comporter au moins <xliff:g id="COUNT">%d</xliff:g> caractères autre qu\'une lettre</item>
     </plurals>
+    <plurals name="lockpassword_password_requires_nonnumerical" formatted="false" msgid="6943707011152180150">
+      <item quantity="one">Doit contenir au moins <xliff:g id="COUNT">%d</xliff:g> caractère non numérique</item>
+      <item quantity="other">Doit contenir au moins <xliff:g id="COUNT">%d</xliff:g> caractères non numériques</item>
+    </plurals>
     <string name="lockpassword_password_recently_used" msgid="6944729699375087431">"L\'administrateur de l\'appareil n\'autorise pas l\'utilisation d\'un mot de passe récent"</string>
     <string name="lockpassword_password_blacklisted_by_admin" msgid="4988166770148440755">"Les mots de passe courants sont bloqués par votre administrateur informatique. Choisissez un autre mot de passe."</string>
     <string name="lockpassword_pin_no_sequential_digits" msgid="3902387296149848324">"Les suites de chiffres croissantes, décroissantes ou répétitives ne sont pas autorisées"</string>
diff --git a/tests/CarDeveloperOptions/res/values-gl/config.xml b/tests/CarDeveloperOptions/res/values-gl/config.xml
index b05e073..04c8bf8 100644
--- a/tests/CarDeveloperOptions/res/values-gl/config.xml
+++ b/tests/CarDeveloperOptions/res/values-gl/config.xml
@@ -17,5 +17,4 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="config_backup_settings_label" msgid="5230713622702899641"></string>
-    <string name="config_grayscale_settings_intent" msgid="7661066612238245852"></string>
 </resources>
diff --git a/tests/CarDeveloperOptions/res/values-gl/strings.xml b/tests/CarDeveloperOptions/res/values-gl/strings.xml
index 87311b3..3b7ee0a 100644
--- a/tests/CarDeveloperOptions/res/values-gl/strings.xml
+++ b/tests/CarDeveloperOptions/res/values-gl/strings.xml
@@ -668,7 +668,6 @@
       <item quantity="other">Debe conter menos de <xliff:g id="NUMBER_1">%d</xliff:g> díxitos</item>
       <item quantity="one">Debe conter menos de <xliff:g id="NUMBER_0">%d</xliff:g> díxito</item>
     </plurals>
-    <string name="lockpassword_pin_contains_non_digits" msgid="1501729742023195556">"Só pode conter díxitos comprendidos entre o 0 e o 9"</string>
     <string name="lockpassword_pin_recently_used" msgid="5556148774911741103">"O administrador de dispositivos non permite o uso dun PIN recente"</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="2752037810571200697">"O teu administrador de TI bloqueou os PIN comúns. Proba cun PIN diferente."</string>
     <string name="lockpassword_illegal_character" msgid="3577712947676261351">"Non pode conter un carácter non válido"</string>
@@ -699,6 +698,10 @@
       <item quantity="other">Debe conter polo menos <xliff:g id="COUNT">%d</xliff:g> caracteres que non sexan letras</item>
       <item quantity="one">Debe conter polo menos 1 carácter que non sexa unha letra</item>
     </plurals>
+    <plurals name="lockpassword_password_requires_nonnumerical" formatted="false" msgid="6943707011152180150">
+      <item quantity="other">Debe conter polo menos <xliff:g id="COUNT">%d</xliff:g> caracteres que non sexan números</item>
+      <item quantity="one">Debe conter polo menos 1 carácter que non sexa un número</item>
+    </plurals>
     <string name="lockpassword_password_recently_used" msgid="6944729699375087431">"O administrador de dispositivos non permite o uso dun contrasinal recente"</string>
     <string name="lockpassword_password_blacklisted_by_admin" msgid="4988166770148440755">"O teu administrador de TI bloqueou os contrasinais comúns. Proba cun contrasinal diferente."</string>
     <string name="lockpassword_pin_no_sequential_digits" msgid="3902387296149848324">"Non se permite unha secuencia de díxitos ascendente, descendente nin repetida"</string>
diff --git a/tests/CarDeveloperOptions/res/values-gu/config.xml b/tests/CarDeveloperOptions/res/values-gu/config.xml
index b05e073..04c8bf8 100644
--- a/tests/CarDeveloperOptions/res/values-gu/config.xml
+++ b/tests/CarDeveloperOptions/res/values-gu/config.xml
@@ -17,5 +17,4 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="config_backup_settings_label" msgid="5230713622702899641"></string>
-    <string name="config_grayscale_settings_intent" msgid="7661066612238245852"></string>
 </resources>
diff --git a/tests/CarDeveloperOptions/res/values-gu/strings.xml b/tests/CarDeveloperOptions/res/values-gu/strings.xml
index 9b4b855..24d8f86 100644
--- a/tests/CarDeveloperOptions/res/values-gu/strings.xml
+++ b/tests/CarDeveloperOptions/res/values-gu/strings.xml
@@ -668,7 +668,6 @@
       <item quantity="one"><xliff:g id="NUMBER_1">%d</xliff:g> કરતાં ઓછા અંક હોવા જરૂરી છે</item>
       <item quantity="other"><xliff:g id="NUMBER_1">%d</xliff:g> કરતાં ઓછા અંક હોવા જરૂરી છે</item>
     </plurals>
-    <string name="lockpassword_pin_contains_non_digits" msgid="1501729742023195556">"માત્ર 0-9 અંક શામેલ હોવા આવશ્યક છે"</string>
     <string name="lockpassword_pin_recently_used" msgid="5556148774911741103">"ઉપકરણ વ્યવસ્થાપક તાજેતરનાં પિનનો ઉપયોગ કરવાની મંજૂરી આપતા નથી"</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="2752037810571200697">"સામાન્ય પિન તમારા IT વ્યવસ્થાપક દ્વારા બ્લૉક કરવામાં આવે છે. એક અલગ પિન અજમાવી જુઓ."</string>
     <string name="lockpassword_illegal_character" msgid="3577712947676261351">"આમાં અમાન્ય અક્ષરોનો સમાવેશ થઈ શકતો નથી"</string>
@@ -699,6 +698,10 @@
       <item quantity="one">ઓછામાં ઓછા <xliff:g id="COUNT">%d</xliff:g> વર્ણ સિવાયના અક્ષર ધરાવતો હોવો જોઈએ</item>
       <item quantity="other">ઓછામાં ઓછા <xliff:g id="COUNT">%d</xliff:g> વર્ણ સિવાયના અક્ષર ધરાવતો હોવો જોઈએ</item>
     </plurals>
+    <plurals name="lockpassword_password_requires_nonnumerical" formatted="false" msgid="6943707011152180150">
+      <item quantity="one">ઓછામાં ઓછો <xliff:g id="COUNT">%d</xliff:g> અક્ષર એવો હોવો જોઈએ કે જે સંખ્યા ન હોય</item>
+      <item quantity="other">ઓછામાં ઓછા <xliff:g id="COUNT">%d</xliff:g> અક્ષર એવા હોવા જોઈએ કે જે સંખ્યા ન હોય</item>
+    </plurals>
     <string name="lockpassword_password_recently_used" msgid="6944729699375087431">"ઉપકરણ વ્યવસ્થાપક તાજેતરનાં પાસવર્ડનો ઉપયોગ કરવાની મંજૂરી આપતા નથી"</string>
     <string name="lockpassword_password_blacklisted_by_admin" msgid="4988166770148440755">"સામાન્ય પાસવર્ડ તમારા IT વ્યવસ્થાપક દ્વારા બ્લૉક કરવામાં આવે છે. એક અલગ પાસવર્ડ અજમાવી જુઓ."</string>
     <string name="lockpassword_pin_no_sequential_digits" msgid="3902387296149848324">"અંકોના ચઢતા ક્રમની, ઉતરતા ક્રમની અથવા પુનરાવર્તિત અનુક્રમની મંજૂરી નથી"</string>
diff --git a/tests/CarDeveloperOptions/res/values-hi/config.xml b/tests/CarDeveloperOptions/res/values-hi/config.xml
index b05e073..04c8bf8 100644
--- a/tests/CarDeveloperOptions/res/values-hi/config.xml
+++ b/tests/CarDeveloperOptions/res/values-hi/config.xml
@@ -17,5 +17,4 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="config_backup_settings_label" msgid="5230713622702899641"></string>
-    <string name="config_grayscale_settings_intent" msgid="7661066612238245852"></string>
 </resources>
diff --git a/tests/CarDeveloperOptions/res/values-hi/strings.xml b/tests/CarDeveloperOptions/res/values-hi/strings.xml
index de795ad..6a560cd 100644
--- a/tests/CarDeveloperOptions/res/values-hi/strings.xml
+++ b/tests/CarDeveloperOptions/res/values-hi/strings.xml
@@ -668,7 +668,6 @@
       <item quantity="one">पिन में <xliff:g id="NUMBER_1">%d</xliff:g> से कम अंक होने चाहिए</item>
       <item quantity="other">पिन में <xliff:g id="NUMBER_1">%d</xliff:g> से कम अंक होने चाहिए</item>
     </plurals>
-    <string name="lockpassword_pin_contains_non_digits" msgid="1501729742023195556">"इसमें सिर्फ़ 0 से 9 तक के अंक होने चाहिए"</string>
     <string name="lockpassword_pin_recently_used" msgid="5556148774911741103">"डिवाइस व्यवस्थापक हाल ही के पिन का उपयोग करने की अनुमति नहीं देता"</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="2752037810571200697">"आपके आईटी एडमिन ने आम तौर पर इस्तेमाल होने वाले पिन पर रोक लगा रखी है. दूसरा पिन बनाएं."</string>
     <string name="lockpassword_illegal_character" msgid="3577712947676261351">"इसमें अमान्य वर्ण शामिल नहीं हो सकता"</string>
@@ -699,6 +698,10 @@
       <item quantity="one">इसमें कम से कम <xliff:g id="COUNT">%d</xliff:g> वर्ण ऐसे होने चाहिए जो अक्षर ना हों</item>
       <item quantity="other">इसमें कम से कम <xliff:g id="COUNT">%d</xliff:g> वर्ण ऐसे होने चाहिए जो अक्षर ना हों</item>
     </plurals>
+    <plurals name="lockpassword_password_requires_nonnumerical" formatted="false" msgid="6943707011152180150">
+      <item quantity="one">पासवर्ड में कम से कम <xliff:g id="COUNT">%d</xliff:g> वर्ण ऐसे होने चाहिए जो संख्या न हो</item>
+      <item quantity="other">पासवर्ड में कम से कम <xliff:g id="COUNT">%d</xliff:g> वर्ण ऐसे होने चाहिए जो संख्या न हो</item>
+    </plurals>
     <string name="lockpassword_password_recently_used" msgid="6944729699375087431">"डिवाइस व्यवस्थापक हाल ही के पासवर्ड का उपयोग करने की अनुमति नहीं देता"</string>
     <string name="lockpassword_password_blacklisted_by_admin" msgid="4988166770148440755">"आपके आईटी एडमिन ने आम तौर पर इस्तेमाल होने वाले पासवर्ड पर रोक लगा रखी है. कोई दूसरा पासवर्ड बनाएं."</string>
     <string name="lockpassword_pin_no_sequential_digits" msgid="3902387296149848324">"अंकों के बढ़ते, घटते या दोहराए जाने वाले क्रम की अनुमति नहीं है"</string>
diff --git a/tests/CarDeveloperOptions/res/values-hr/config.xml b/tests/CarDeveloperOptions/res/values-hr/config.xml
index b05e073..04c8bf8 100644
--- a/tests/CarDeveloperOptions/res/values-hr/config.xml
+++ b/tests/CarDeveloperOptions/res/values-hr/config.xml
@@ -17,5 +17,4 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="config_backup_settings_label" msgid="5230713622702899641"></string>
-    <string name="config_grayscale_settings_intent" msgid="7661066612238245852"></string>
 </resources>
diff --git a/tests/CarDeveloperOptions/res/values-hr/strings.xml b/tests/CarDeveloperOptions/res/values-hr/strings.xml
index a7eefb7..3ae9864 100644
--- a/tests/CarDeveloperOptions/res/values-hr/strings.xml
+++ b/tests/CarDeveloperOptions/res/values-hr/strings.xml
@@ -676,7 +676,6 @@
       <item quantity="few">Mora sadržavati manje od <xliff:g id="NUMBER_1">%d</xliff:g> znamenke</item>
       <item quantity="other">Mora sadržavati manje od <xliff:g id="NUMBER_1">%d</xliff:g> znamenki</item>
     </plurals>
-    <string name="lockpassword_pin_contains_non_digits" msgid="1501729742023195556">"Smije sadržavati samo znamenke od 0 do 9"</string>
     <string name="lockpassword_pin_recently_used" msgid="5556148774911741103">"Administrator uređaja ne dopušta upotrebu nedavnog PIN-a"</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="2752037810571200697">"Vaš je IT administrator blokirao uobičajene PIN-ove. Pokušajte s nekom drugim PIN-om."</string>
     <string name="lockpassword_illegal_character" msgid="3577712947676261351">"Unos ne smije sadržavati nevažeće znakove"</string>
@@ -713,6 +712,11 @@
       <item quantity="few">Mora sadržavati najmanje <xliff:g id="COUNT">%d</xliff:g> znaka koji nisu slova</item>
       <item quantity="other">Mora sadržavati najmanje <xliff:g id="COUNT">%d</xliff:g> znakova koji nisu slova</item>
     </plurals>
+    <plurals name="lockpassword_password_requires_nonnumerical" formatted="false" msgid="6943707011152180150">
+      <item quantity="one">Mora sadržavati najmanje <xliff:g id="COUNT">%d</xliff:g> znak koji nije znamenka</item>
+      <item quantity="few">Mora sadržavati najmanje <xliff:g id="COUNT">%d</xliff:g> znaka koji nisu znamenke</item>
+      <item quantity="other">Mora sadržavati najmanje <xliff:g id="COUNT">%d</xliff:g> znakova koji nisu znamenke</item>
+    </plurals>
     <string name="lockpassword_password_recently_used" msgid="6944729699375087431">"Administrator uređaja ne dopušta upotrebu nedavne zaporke"</string>
     <string name="lockpassword_password_blacklisted_by_admin" msgid="4988166770148440755">"Vaš je IT administrator blokirao uobičajene zaporke. Pokušajte s nekom drugom zaporkom."</string>
     <string name="lockpassword_pin_no_sequential_digits" msgid="3902387296149848324">"Uzastopno rastući ili padajući slijed brojeva ili ponavljanje brojeva nije dopušteno"</string>
diff --git a/tests/CarDeveloperOptions/res/values-hu/config.xml b/tests/CarDeveloperOptions/res/values-hu/config.xml
index b05e073..04c8bf8 100644
--- a/tests/CarDeveloperOptions/res/values-hu/config.xml
+++ b/tests/CarDeveloperOptions/res/values-hu/config.xml
@@ -17,5 +17,4 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="config_backup_settings_label" msgid="5230713622702899641"></string>
-    <string name="config_grayscale_settings_intent" msgid="7661066612238245852"></string>
 </resources>
diff --git a/tests/CarDeveloperOptions/res/values-hu/strings.xml b/tests/CarDeveloperOptions/res/values-hu/strings.xml
index 2e2b8de..d211ad9 100644
--- a/tests/CarDeveloperOptions/res/values-hu/strings.xml
+++ b/tests/CarDeveloperOptions/res/values-hu/strings.xml
@@ -668,7 +668,6 @@
       <item quantity="other"><xliff:g id="NUMBER_1">%d</xliff:g> számjegynél rövidebbnek kell lennie</item>
       <item quantity="one"><xliff:g id="NUMBER_0">%d</xliff:g> számjegynél rövidebbnek kell lennie</item>
     </plurals>
-    <string name="lockpassword_pin_contains_non_digits" msgid="1501729742023195556">"Csak számokat tartalmazhat, 0-tól 9-ig"</string>
     <string name="lockpassword_pin_recently_used" msgid="5556148774911741103">"Az eszközrendszergazda nem engedélyezi a legutóbbi PIN-kódok használatát"</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="2752037810571200697">"Rendszergazdája letiltotta a gyakran használt PIN-kódokat. Próbálkozzon másik PIN-kóddal."</string>
     <string name="lockpassword_illegal_character" msgid="3577712947676261351">"Ez nem tartalmazhat érvénytelen karaktert"</string>
@@ -699,6 +698,10 @@
       <item quantity="other">Tartalmaznia kell legalább <xliff:g id="COUNT">%d</xliff:g> olyan karaktert, amely nem betű</item>
       <item quantity="one">Tartalmaznia kell legalább 1 olyan karaktert, amely nem betű</item>
     </plurals>
+    <plurals name="lockpassword_password_requires_nonnumerical" formatted="false" msgid="6943707011152180150">
+      <item quantity="other">Tartalmaznia kell legalább <xliff:g id="COUNT">%d</xliff:g> olyan karaktert, amely nem szám</item>
+      <item quantity="one">Tartalmaznia kell legalább 1 olyan karaktert, amely nem szám</item>
+    </plurals>
     <string name="lockpassword_password_recently_used" msgid="6944729699375087431">"Az eszközrendszergazda nem engedélyezi a legutóbbi jelszavak használatát"</string>
     <string name="lockpassword_password_blacklisted_by_admin" msgid="4988166770148440755">"Rendszergazdája letiltotta a gyakran használt jelszavakat. Próbálkozzon másik jelszóval."</string>
     <string name="lockpassword_pin_no_sequential_digits" msgid="3902387296149848324">"Növekvő, csökkenő vagy ismétlődő számsor megadása nem engedélyezett"</string>
diff --git a/tests/CarDeveloperOptions/res/values-hy/config.xml b/tests/CarDeveloperOptions/res/values-hy/config.xml
index b05e073..04c8bf8 100644
--- a/tests/CarDeveloperOptions/res/values-hy/config.xml
+++ b/tests/CarDeveloperOptions/res/values-hy/config.xml
@@ -17,5 +17,4 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="config_backup_settings_label" msgid="5230713622702899641"></string>
-    <string name="config_grayscale_settings_intent" msgid="7661066612238245852"></string>
 </resources>
diff --git a/tests/CarDeveloperOptions/res/values-hy/strings.xml b/tests/CarDeveloperOptions/res/values-hy/strings.xml
index f66a674..e076190 100644
--- a/tests/CarDeveloperOptions/res/values-hy/strings.xml
+++ b/tests/CarDeveloperOptions/res/values-hy/strings.xml
@@ -668,7 +668,6 @@
       <item quantity="one">Պետք է <xliff:g id="NUMBER_1">%d</xliff:g>-ից քիչ թվանշան պարունակի</item>
       <item quantity="other">Պետք է <xliff:g id="NUMBER_1">%d</xliff:g>-ից քիչ թվանշան պարունակի</item>
     </plurals>
-    <string name="lockpassword_pin_contains_non_digits" msgid="1501729742023195556">"Պետք է պարունակի միայն 0-9 թվանշաններ"</string>
     <string name="lockpassword_pin_recently_used" msgid="5556148774911741103">"Սարքի ադմինիստրատորը չի թույլատրում օգտագործել վերջին PIN կոդերը"</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="2752037810571200697">"ՏՏ ադմինիստրատորն արգելափակել է պարզ PIN կոդերը: Փորձեք մեկ այլ PIN:"</string>
     <string name="lockpassword_illegal_character" msgid="3577712947676261351">"Այն չի կարող պարունակել անվավեր գրանշան"</string>
@@ -699,6 +698,10 @@
       <item quantity="one">Must contain at least <xliff:g id="COUNT">%d</xliff:g> non-letter characters</item>
       <item quantity="other">Պետք է պարունակի առնվազն <xliff:g id="COUNT">%d</xliff:g> ոչ-տառային գրանշան</item>
     </plurals>
+    <plurals name="lockpassword_password_requires_nonnumerical" formatted="false" msgid="6943707011152180150">
+      <item quantity="one">Պետք է պարունակի առնվազն <xliff:g id="COUNT">%d</xliff:g> ոչ թվային նիշ</item>
+      <item quantity="other">Պետք է պարունակի առնվազն <xliff:g id="COUNT">%d</xliff:g> ոչ թվային նիշ</item>
+    </plurals>
     <string name="lockpassword_password_recently_used" msgid="6944729699375087431">"Սարքի ադմինիստրատորը թույլ չի տալիս օգտագործել վերջին գաղտնաբառը"</string>
     <string name="lockpassword_password_blacklisted_by_admin" msgid="4988166770148440755">"ՏՏ ադմինիստրատորն արգելափակել է պարզ գաղտնաբառերը: Փորձեք մեկ այլ գաղտնաբառ:"</string>
     <string name="lockpassword_pin_no_sequential_digits" msgid="3902387296149848324">"Թվանշանների աճող, նվազող կամ կրկնվող հաջորդականությունն արգելված է"</string>
diff --git a/tests/CarDeveloperOptions/res/values-in/config.xml b/tests/CarDeveloperOptions/res/values-in/config.xml
index b05e073..04c8bf8 100644
--- a/tests/CarDeveloperOptions/res/values-in/config.xml
+++ b/tests/CarDeveloperOptions/res/values-in/config.xml
@@ -17,5 +17,4 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="config_backup_settings_label" msgid="5230713622702899641"></string>
-    <string name="config_grayscale_settings_intent" msgid="7661066612238245852"></string>
 </resources>
diff --git a/tests/CarDeveloperOptions/res/values-in/strings.xml b/tests/CarDeveloperOptions/res/values-in/strings.xml
index 40150f65..a855c38 100644
--- a/tests/CarDeveloperOptions/res/values-in/strings.xml
+++ b/tests/CarDeveloperOptions/res/values-in/strings.xml
@@ -668,7 +668,6 @@
       <item quantity="other">Harus kurang dari <xliff:g id="NUMBER_1">%d</xliff:g> digit</item>
       <item quantity="one">Harus kurang dari <xliff:g id="NUMBER_0">%d</xliff:g> digit</item>
     </plurals>
-    <string name="lockpassword_pin_contains_non_digits" msgid="1501729742023195556">"Hanya boleh berisi angka 0-9"</string>
     <string name="lockpassword_pin_recently_used" msgid="5556148774911741103">"Admin perangkat tidak mengizinkan penggunaan PIN terbaru."</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="2752037810571200697">"PIN umum diblokir oleh admin IT. Coba PIN lain."</string>
     <string name="lockpassword_illegal_character" msgid="3577712947676261351">"Tidak boleh berisi karakter yang tidak valid"</string>
@@ -699,6 +698,10 @@
       <item quantity="other">Minimal berisi <xliff:g id="COUNT">%d</xliff:g> karakter bukan huruf</item>
       <item quantity="one">Minimal berisi 1 karakter bukan huruf</item>
     </plurals>
+    <plurals name="lockpassword_password_requires_nonnumerical" formatted="false" msgid="6943707011152180150">
+      <item quantity="other">Minimal berisi <xliff:g id="COUNT">%d</xliff:g> karakter bukan numerik</item>
+      <item quantity="one">Minimal berisi 1 karakter bukan numerik</item>
+    </plurals>
     <string name="lockpassword_password_recently_used" msgid="6944729699375087431">"Admin perangkat tidak mengizinkan penggunaan sandi terbaru."</string>
     <string name="lockpassword_password_blacklisted_by_admin" msgid="4988166770148440755">"Sandi umum diblokir oleh admin IT. Coba sandi lain."</string>
     <string name="lockpassword_pin_no_sequential_digits" msgid="3902387296149848324">"Urutan digit naik, turun, atau berulang tidak diizinkan"</string>
diff --git a/tests/CarDeveloperOptions/res/values-is/config.xml b/tests/CarDeveloperOptions/res/values-is/config.xml
index b05e073..04c8bf8 100644
--- a/tests/CarDeveloperOptions/res/values-is/config.xml
+++ b/tests/CarDeveloperOptions/res/values-is/config.xml
@@ -17,5 +17,4 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="config_backup_settings_label" msgid="5230713622702899641"></string>
-    <string name="config_grayscale_settings_intent" msgid="7661066612238245852"></string>
 </resources>
diff --git a/tests/CarDeveloperOptions/res/values-is/strings.xml b/tests/CarDeveloperOptions/res/values-is/strings.xml
index 5abef77..81ca330 100644
--- a/tests/CarDeveloperOptions/res/values-is/strings.xml
+++ b/tests/CarDeveloperOptions/res/values-is/strings.xml
@@ -668,7 +668,6 @@
       <item quantity="one">Má ekki vera lengra en <xliff:g id="NUMBER_1">%d</xliff:g> tölustafur</item>
       <item quantity="other">Má ekki vera lengra en <xliff:g id="NUMBER_1">%d</xliff:g> tölustafir</item>
     </plurals>
-    <string name="lockpassword_pin_contains_non_digits" msgid="1501729742023195556">"Má eingöngu innihalda tölustafi, 0–9"</string>
     <string name="lockpassword_pin_recently_used" msgid="5556148774911741103">"Tækjastjóri leyfir ekki notkun nýlegs PIN-númers"</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="2752037810571200697">"Kerfisstjórinn þinn hefur lokað á algeng PIN-númer. Prófaðu annað PIN-númer."</string>
     <string name="lockpassword_illegal_character" msgid="3577712947676261351">"Þetta má ekki innihalda ógildan staf"</string>
@@ -699,6 +698,10 @@
       <item quantity="one">Verður að innihalda að minnsta kosti <xliff:g id="COUNT">%d</xliff:g> staftákn sem ekki er bókstafur</item>
       <item quantity="other">Verður að innihalda að minsta kosti <xliff:g id="COUNT">%d</xliff:g> staftákn sem ekki eru bókstafir</item>
     </plurals>
+    <plurals name="lockpassword_password_requires_nonnumerical" formatted="false" msgid="6943707011152180150">
+      <item quantity="one">Verður að innihalda að minnsta kosti <xliff:g id="COUNT">%d</xliff:g> staftákn sem er ekki tölustafur</item>
+      <item quantity="other">Verður að innihalda að minnsta kosti <xliff:g id="COUNT">%d</xliff:g> staftákn sem eru ekki tölustafir</item>
+    </plurals>
     <string name="lockpassword_password_recently_used" msgid="6944729699375087431">"Tækjastjóri leyfir ekki notkun nýlegs aðgangsorðs"</string>
     <string name="lockpassword_password_blacklisted_by_admin" msgid="4988166770148440755">"Kerfisstjórinn þinn hefur lokað á algeng aðgangsorð. Prófaðu annað aðgangsorð."</string>
     <string name="lockpassword_pin_no_sequential_digits" msgid="3902387296149848324">"Hækkandi, lækkandi eða endurtekin röð tölustafa er óheimil"</string>
diff --git a/tests/CarDeveloperOptions/res/values-it/config.xml b/tests/CarDeveloperOptions/res/values-it/config.xml
index b05e073..04c8bf8 100644
--- a/tests/CarDeveloperOptions/res/values-it/config.xml
+++ b/tests/CarDeveloperOptions/res/values-it/config.xml
@@ -17,5 +17,4 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="config_backup_settings_label" msgid="5230713622702899641"></string>
-    <string name="config_grayscale_settings_intent" msgid="7661066612238245852"></string>
 </resources>
diff --git a/tests/CarDeveloperOptions/res/values-it/strings.xml b/tests/CarDeveloperOptions/res/values-it/strings.xml
index 89d101a..d22c825 100644
--- a/tests/CarDeveloperOptions/res/values-it/strings.xml
+++ b/tests/CarDeveloperOptions/res/values-it/strings.xml
@@ -668,7 +668,6 @@
       <item quantity="other">Deve contenere meno di <xliff:g id="NUMBER_1">%d</xliff:g> cifre</item>
       <item quantity="one">Deve contenere meno di <xliff:g id="NUMBER_0">%d</xliff:g> cifra</item>
     </plurals>
-    <string name="lockpassword_pin_contains_non_digits" msgid="1501729742023195556">"Deve contenere solo cifre da 0 a 9"</string>
     <string name="lockpassword_pin_recently_used" msgid="5556148774911741103">"L\'amministratore del dispositivo non consente l\'utilizzo di un PIN recente"</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="2752037810571200697">"I PIN comuni sono stati bloccati dall\'amministratore IT. Prova a usare un altro PIN."</string>
     <string name="lockpassword_illegal_character" msgid="3577712947676261351">"Non può contenere un carattere non valido"</string>
@@ -699,6 +698,10 @@
       <item quantity="other">Deve contenere almeno <xliff:g id="COUNT">%d</xliff:g> caratteri non costituiti da una lettera</item>
       <item quantity="one">Deve contenere almeno 1 carattere non costituito da una lettera</item>
     </plurals>
+    <plurals name="lockpassword_password_requires_nonnumerical" formatted="false" msgid="6943707011152180150">
+      <item quantity="other">Deve contenere almeno <xliff:g id="COUNT">%d</xliff:g> caratteri non numerici</item>
+      <item quantity="one">Deve contenere almeno 1 carattere non numerico</item>
+    </plurals>
     <string name="lockpassword_password_recently_used" msgid="6944729699375087431">"L\'amministratore del dispositivo non consente l\'utilizzo di una password recente."</string>
     <string name="lockpassword_password_blacklisted_by_admin" msgid="4988166770148440755">"Le password comuni sono state bloccate dall\'amministratore IT. Prova a usare un\'altra password."</string>
     <string name="lockpassword_pin_no_sequential_digits" msgid="3902387296149848324">"Non sono consentite sequenze di cifre in ordine ascendente o discendente oppure ripetute"</string>
diff --git a/tests/CarDeveloperOptions/res/values-iw/config.xml b/tests/CarDeveloperOptions/res/values-iw/config.xml
index b05e073..04c8bf8 100644
--- a/tests/CarDeveloperOptions/res/values-iw/config.xml
+++ b/tests/CarDeveloperOptions/res/values-iw/config.xml
@@ -17,5 +17,4 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="config_backup_settings_label" msgid="5230713622702899641"></string>
-    <string name="config_grayscale_settings_intent" msgid="7661066612238245852"></string>
 </resources>
diff --git a/tests/CarDeveloperOptions/res/values-iw/strings.xml b/tests/CarDeveloperOptions/res/values-iw/strings.xml
index 3b30e25..b03afdc 100644
--- a/tests/CarDeveloperOptions/res/values-iw/strings.xml
+++ b/tests/CarDeveloperOptions/res/values-iw/strings.xml
@@ -684,7 +684,6 @@
       <item quantity="other">צריכה להכיל פחות מ-<xliff:g id="NUMBER_1">%d</xliff:g> ספרות</item>
       <item quantity="one">צריכה להכיל פחות מספרה אחת (<xliff:g id="NUMBER_0">%d</xliff:g>)</item>
     </plurals>
-    <string name="lockpassword_pin_contains_non_digits" msgid="1501729742023195556">"צריך להכיל רק את הספרות 0-9"</string>
     <string name="lockpassword_pin_recently_used" msgid="5556148774911741103">"מנהל המכשיר לא מאפשר להשתמש בקוד גישה שנעשה בו שימוש לאחרונה"</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="2752037810571200697">"קודי גישה נפוצים חסומים בידי מנהל ה-IT. יש לנסות קוד גישה אחר."</string>
     <string name="lockpassword_illegal_character" msgid="3577712947676261351">"לא ניתן לכלול תו לא חוקי"</string>
@@ -727,6 +726,12 @@
       <item quantity="other">צריכה להכיל לפחות <xliff:g id="COUNT">%d</xliff:g> תווים שאינם אותיות</item>
       <item quantity="one">צריכה להכיל לפחות תו אחד שאינו אות</item>
     </plurals>
+    <plurals name="lockpassword_password_requires_nonnumerical" formatted="false" msgid="6943707011152180150">
+      <item quantity="two">חייבת להכיל לפחות <xliff:g id="COUNT">%d</xliff:g> תווים שאינם מספרים</item>
+      <item quantity="many">חייבת להכיל לפחות <xliff:g id="COUNT">%d</xliff:g> תווים שאינם מספרים</item>
+      <item quantity="other">חייבת להכיל לפחות <xliff:g id="COUNT">%d</xliff:g> תווים שאינם מספרים</item>
+      <item quantity="one">חייבת להכיל לפחות תו אחד שאינו מספר</item>
+    </plurals>
     <string name="lockpassword_password_recently_used" msgid="6944729699375087431">"מנהל המכשיר לא מאפשר להשתמש בסיסמה שנעשה בה שימוש לאחרונה"</string>
     <string name="lockpassword_password_blacklisted_by_admin" msgid="4988166770148440755">"סיסמאות נפוצות חסומות בידי מנהל ה-IT. יש לנסות סיסמה אחרת."</string>
     <string name="lockpassword_pin_no_sequential_digits" msgid="3902387296149848324">"אין להגדיר רצף ספרות עולה, יורד או חוזר"</string>
diff --git a/tests/CarDeveloperOptions/res/values-ja/config.xml b/tests/CarDeveloperOptions/res/values-ja/config.xml
index b05e073..04c8bf8 100644
--- a/tests/CarDeveloperOptions/res/values-ja/config.xml
+++ b/tests/CarDeveloperOptions/res/values-ja/config.xml
@@ -17,5 +17,4 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="config_backup_settings_label" msgid="5230713622702899641"></string>
-    <string name="config_grayscale_settings_intent" msgid="7661066612238245852"></string>
 </resources>
diff --git a/tests/CarDeveloperOptions/res/values-ja/strings.xml b/tests/CarDeveloperOptions/res/values-ja/strings.xml
index ff4be74..b0b238f 100644
--- a/tests/CarDeveloperOptions/res/values-ja/strings.xml
+++ b/tests/CarDeveloperOptions/res/values-ja/strings.xml
@@ -668,7 +668,6 @@
       <item quantity="other"><xliff:g id="NUMBER_1">%d</xliff:g> 桁未満にしてください</item>
       <item quantity="one"><xliff:g id="NUMBER_0">%d</xliff:g> 桁未満にしてください</item>
     </plurals>
-    <string name="lockpassword_pin_contains_non_digits" msgid="1501729742023195556">"使用できるのは 0~9 の数字のみです"</string>
     <string name="lockpassword_pin_recently_used" msgid="5556148774911741103">"デバイス管理により、最近使用した PIN は使用できません"</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="2752037810571200697">"一般的な PIN は IT 管理者によってブロックされています。別の PIN をお試しください。"</string>
     <string name="lockpassword_illegal_character" msgid="3577712947676261351">"無効な文字があります"</string>
@@ -699,6 +698,10 @@
       <item quantity="other">記号または数字が <xliff:g id="COUNT">%d</xliff:g> つ以上必要です</item>
       <item quantity="one">記号または数字が 1 つ以上必要です</item>
     </plurals>
+    <plurals name="lockpassword_password_requires_nonnumerical" formatted="false" msgid="6943707011152180150">
+      <item quantity="other">数字以外の文字を <xliff:g id="COUNT">%d</xliff:g> 個以上使用してください</item>
+      <item quantity="one">数字以外の文字を 1 個以上使用してください</item>
+    </plurals>
     <string name="lockpassword_password_recently_used" msgid="6944729699375087431">"デバイス管理により、最近使用したパスワードは使用できません"</string>
     <string name="lockpassword_password_blacklisted_by_admin" msgid="4988166770148440755">"一般的なパスワードは IT 管理者によってブロックされています。別のパスワードをお試しください。"</string>
     <string name="lockpassword_pin_no_sequential_digits" msgid="3902387296149848324">"一連の数字を昇順や降順にしたり、繰り返したりすることはできません"</string>
diff --git a/tests/CarDeveloperOptions/res/values-ka/config.xml b/tests/CarDeveloperOptions/res/values-ka/config.xml
index b05e073..04c8bf8 100644
--- a/tests/CarDeveloperOptions/res/values-ka/config.xml
+++ b/tests/CarDeveloperOptions/res/values-ka/config.xml
@@ -17,5 +17,4 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="config_backup_settings_label" msgid="5230713622702899641"></string>
-    <string name="config_grayscale_settings_intent" msgid="7661066612238245852"></string>
 </resources>
diff --git a/tests/CarDeveloperOptions/res/values-ka/strings.xml b/tests/CarDeveloperOptions/res/values-ka/strings.xml
index 9536e8f..1392a22 100644
--- a/tests/CarDeveloperOptions/res/values-ka/strings.xml
+++ b/tests/CarDeveloperOptions/res/values-ka/strings.xml
@@ -668,7 +668,6 @@
       <item quantity="other">უნდა შეიცავდეს <xliff:g id="NUMBER_1">%d</xliff:g> ციფრზე ნაკლებს</item>
       <item quantity="one">უნდა შეიცავდეს <xliff:g id="NUMBER_0">%d</xliff:g> ციფრზე ნაკლებს</item>
     </plurals>
-    <string name="lockpassword_pin_contains_non_digits" msgid="1501729742023195556">"უნდა შეიცავდეს მხოლოდ ციფრებს 0-დან 9-მდე"</string>
     <string name="lockpassword_pin_recently_used" msgid="5556148774911741103">"ბოლოდროინდელი PIN-კოდის ხელახლა გამოყენება მოწყობილობის ადმინისტრატორის მიერ ნებადართული არ არის"</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="2752037810571200697">"ხშირად გამოყენებული PIN-კოდები თქვენი IT ადმინისტრატორის მიერ დაბლოკილია. ცადეთ სხვა PIN-კოდი."</string>
     <string name="lockpassword_illegal_character" msgid="3577712947676261351">"არ უნდა შეიცავდეს არასწორ სიმბოლოს"</string>
@@ -699,6 +698,10 @@
       <item quantity="other">უნდა შეიცავდეს მინიმუმ <xliff:g id="COUNT">%d</xliff:g> არაანბანურ სიმბოლოს</item>
       <item quantity="one">უნდა შეიცავდეს მინიმუმ 1 არაანბანურ სიმბოლოს</item>
     </plurals>
+    <plurals name="lockpassword_password_requires_nonnumerical" formatted="false" msgid="6943707011152180150">
+      <item quantity="other">უნდა შეიცავდეს, სულ მცირე, <xliff:g id="COUNT">%d</xliff:g> არარიცხვით სიმბოლოს</item>
+      <item quantity="one">უნდა შეიცავდეს, სულ მცირე, 1 არარიცხვით სიმბოლოს</item>
+    </plurals>
     <string name="lockpassword_password_recently_used" msgid="6944729699375087431">"ბოლოდროინდელი პაროლის ხელახლა გამოყენება მოწყობილობის ადმინისტრატორის მიერ ნებადართული არ არის"</string>
     <string name="lockpassword_password_blacklisted_by_admin" msgid="4988166770148440755">"ხშირად გამოყენებული პაროლები თქვენი IT ადმინისტრატორის მიერ დაბლოკილია. ცადეთ სხვა პაროლი."</string>
     <string name="lockpassword_pin_no_sequential_digits" msgid="3902387296149848324">"ციფრების ზრდადი, კლებადი ან გამეორებადი მიმდევრობის გამოყენება ნებადართული არ არის"</string>
diff --git a/tests/CarDeveloperOptions/res/values-kk/config.xml b/tests/CarDeveloperOptions/res/values-kk/config.xml
index b05e073..04c8bf8 100644
--- a/tests/CarDeveloperOptions/res/values-kk/config.xml
+++ b/tests/CarDeveloperOptions/res/values-kk/config.xml
@@ -17,5 +17,4 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="config_backup_settings_label" msgid="5230713622702899641"></string>
-    <string name="config_grayscale_settings_intent" msgid="7661066612238245852"></string>
 </resources>
diff --git a/tests/CarDeveloperOptions/res/values-kk/strings.xml b/tests/CarDeveloperOptions/res/values-kk/strings.xml
index efe8411..83a5afd 100644
--- a/tests/CarDeveloperOptions/res/values-kk/strings.xml
+++ b/tests/CarDeveloperOptions/res/values-kk/strings.xml
@@ -668,7 +668,6 @@
       <item quantity="other"><xliff:g id="NUMBER_1">%d</xliff:g> таңбадан аз болуы керек</item>
       <item quantity="one"><xliff:g id="NUMBER_0">%d</xliff:g> таңбадан аз болуы керек</item>
     </plurals>
-    <string name="lockpassword_pin_contains_non_digits" msgid="1501729742023195556">"Тек 0-9 арасындағы сандар болуы керек"</string>
     <string name="lockpassword_pin_recently_used" msgid="5556148774911741103">"Құрылғы әкімшісі жақында пайдаланылған PIN кодын қолдануға рұқсат бермейді"</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="2752037810571200697">"АТ әкімшісі оңай PIN кодтарына тыйым салды. Басқа PIN кодын енгізіп көріңіз."</string>
     <string name="lockpassword_illegal_character" msgid="3577712947676261351">"Жарамсыз таңба болмауы керек"</string>
@@ -699,6 +698,10 @@
       <item quantity="other">Кемінде <xliff:g id="COUNT">%d</xliff:g> әріптік емес таңба болуы керек</item>
       <item quantity="one">Кемінде 1 әріптік емес таңба болуы керек</item>
     </plurals>
+    <plurals name="lockpassword_password_requires_nonnumerical" formatted="false" msgid="6943707011152180150">
+      <item quantity="other">Кемінде <xliff:g id="COUNT">%d</xliff:g> сандық емес таңба болуы керек.</item>
+      <item quantity="one">Кемінде 1 сандық емес таңба болуы керек.</item>
+    </plurals>
     <string name="lockpassword_password_recently_used" msgid="6944729699375087431">"Құрылғы әкімшісі жуықтағы құпиясөзді қолдануға рұқсат бермейді"</string>
     <string name="lockpassword_password_blacklisted_by_admin" msgid="4988166770148440755">"Оңай құпия сөздерге АТ әкімшісі тыйым салды. Басқа құпия сөз енгізіп көріңіз."</string>
     <string name="lockpassword_pin_no_sequential_digits" msgid="3902387296149848324">"Сандардың артатын, кемитін немесе қайталанатын ретіне рұқсат берілмейді"</string>
diff --git a/tests/CarDeveloperOptions/res/values-km/config.xml b/tests/CarDeveloperOptions/res/values-km/config.xml
index b05e073..04c8bf8 100644
--- a/tests/CarDeveloperOptions/res/values-km/config.xml
+++ b/tests/CarDeveloperOptions/res/values-km/config.xml
@@ -17,5 +17,4 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="config_backup_settings_label" msgid="5230713622702899641"></string>
-    <string name="config_grayscale_settings_intent" msgid="7661066612238245852"></string>
 </resources>
diff --git a/tests/CarDeveloperOptions/res/values-km/strings.xml b/tests/CarDeveloperOptions/res/values-km/strings.xml
index c54c6a9..af6f4bd 100644
--- a/tests/CarDeveloperOptions/res/values-km/strings.xml
+++ b/tests/CarDeveloperOptions/res/values-km/strings.xml
@@ -668,7 +668,6 @@
       <item quantity="other">ត្រូវ​តែ​តិច​ជាង <xliff:g id="NUMBER_1">%d</xliff:g> ខ្ទង់</item>
       <item quantity="one">ត្រូវ​តែ​តិច​ជាង <xliff:g id="NUMBER_0">%d</xliff:g> ខ្ទង់</item>
     </plurals>
-    <string name="lockpassword_pin_contains_non_digits" msgid="1501729742023195556">"ត្រូវមានលេខពី 0-9 តែប៉ុណ្ណោះ"</string>
     <string name="lockpassword_pin_recently_used" msgid="5556148774911741103">"អ្នក​គ្រប់គ្រង​ឧបករណ៍​មិនអនុញ្ញាត​កូដ PIN ​ទើបប្រើហើយទេ"</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="2752037810571200697">"កូដ PIN លក្ខណៈ​សាមញ្ញ​ត្រូវ​បាន​ទប់ស្កាត់​ដោយ​អ្នក​គ្រប់គ្រង​ព័ត៌មាន​វិទ្យា​របស់អ្នក។ សាកល្បង​ប្រើ​កូដ PIN ផ្សេង​ពី​នេះ។"</string>
     <string name="lockpassword_illegal_character" msgid="3577712947676261351">"វាមិនអាចប្រើតួអក្សរដែលគ្មានសុពលភាពទេ"</string>
@@ -699,6 +698,10 @@
       <item quantity="other">ត្រូវ​មាន​តួ​ដែល​មិន​មែន​ជា​អក្សរ​យ៉ាង​ហោច​ណាស់ <xliff:g id="COUNT">%d</xliff:g></item>
       <item quantity="one">ត្រូវមានតួដែលមិនមែនជាអក្សរយ៉ាងហោចណាស់ 1</item>
     </plurals>
+    <plurals name="lockpassword_password_requires_nonnumerical" formatted="false" msgid="6943707011152180150">
+      <item quantity="other">ត្រូវតែមាន​តួដែលមិនមែន​ជាលេខ​យ៉ាងហោចណាស់ <xliff:g id="COUNT">%d</xliff:g></item>
+      <item quantity="one">ត្រូវតែមាន​តួដែលមិនមែន​ជាលេខ​យ៉ាងហោចណាស់ 1</item>
+    </plurals>
     <string name="lockpassword_password_recently_used" msgid="6944729699375087431">"អ្នក​គ្រប់គ្រង​ឧបករណ៍​មិន​អនុញ្ញាត​ឲ្យ​ប្រើ​ពាក្យ​សម្ងាត់​បច្ចុប្បន្ន​ទេ"</string>
     <string name="lockpassword_password_blacklisted_by_admin" msgid="4988166770148440755">"ពាក្យសម្ងាត់​លក្ខណៈ​សាមញ្ញ​ត្រូវ​បាន​ទប់ស្កាត់​ដោយ​អ្នក​គ្រប់គ្រង​ព័ត៌មាន​វិទ្យា​របស់អ្នក។ សាកល្បង​ប្រើ​ពាក្យ​សម្ងាត់​ផ្សេង​ពី​នេះ។"</string>
     <string name="lockpassword_pin_no_sequential_digits" msgid="3902387296149848324">"មិន​អនុញ្ញាត​ឲ្យ​មានលំដាប់ឡើង ចុះ ឬច្រំដែលទេ"</string>
diff --git a/tests/CarDeveloperOptions/res/values-kn/config.xml b/tests/CarDeveloperOptions/res/values-kn/config.xml
index b05e073..04c8bf8 100644
--- a/tests/CarDeveloperOptions/res/values-kn/config.xml
+++ b/tests/CarDeveloperOptions/res/values-kn/config.xml
@@ -17,5 +17,4 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="config_backup_settings_label" msgid="5230713622702899641"></string>
-    <string name="config_grayscale_settings_intent" msgid="7661066612238245852"></string>
 </resources>
diff --git a/tests/CarDeveloperOptions/res/values-kn/strings.xml b/tests/CarDeveloperOptions/res/values-kn/strings.xml
index d300d0c..2974f74 100644
--- a/tests/CarDeveloperOptions/res/values-kn/strings.xml
+++ b/tests/CarDeveloperOptions/res/values-kn/strings.xml
@@ -668,7 +668,6 @@
       <item quantity="one"> <xliff:g id="NUMBER_1">%d</xliff:g> ಗಿಂತ ಕಡಿಮೆ ಅಂಕಿಗಳನ್ನು ಹೊಂದಿರಬೇಕು</item>
       <item quantity="other"> <xliff:g id="NUMBER_1">%d</xliff:g> ಗಿಂತ ಕಡಿಮೆ ಅಂಕಿಗಳನ್ನು ಹೊಂದಿರಬೇಕು</item>
     </plurals>
-    <string name="lockpassword_pin_contains_non_digits" msgid="1501729742023195556">"ಕೇವಲ 0-9 ಅಂಕಿಗಳನ್ನು ಮಾತ್ರ ಹೊಂದಿರಬೇಕು"</string>
     <string name="lockpassword_pin_recently_used" msgid="5556148774911741103">"ಇತ್ತೀಚಿನ ಪಿನ್ ಬಳಸಲು ಸಾಧನದ ನಿರ್ವಾಹಕರು ಅನುಮತಿಸುವುದಿಲ್ಲ"</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="2752037810571200697">"ಸರಳವಾದ ಪಿನ್‌ಗಳನ್ನು ನಿಮ್ಮ IT ನಿರ್ವಾಹಕರಿಂದ ನಿರ್ಬಂಧಿಸಲಾಗಿದೆ. ಬೇರೆಯ ಪಿನ್ ಪ್ರಯತ್ನಿಸಿ."</string>
     <string name="lockpassword_illegal_character" msgid="3577712947676261351">"ಇದು ಅಮಾನ್ಯ ಅಕ್ಷರವನ್ನು ಒಳಗೊಂಡಿರಬಾರದು"</string>
@@ -699,6 +698,10 @@
       <item quantity="one">ಕನಿಷ್ಠ <xliff:g id="COUNT">%d</xliff:g> ಅಕ್ಷರೇತರ ಕ್ಯಾರೆಕ್ಟರ್‌ಗಳನ್ನು ಹೊಂದಿರಬೇಕು</item>
       <item quantity="other">ಕನಿಷ್ಠ <xliff:g id="COUNT">%d</xliff:g> ಅಕ್ಷರೇತರ ಕ್ಯಾರೆಕ್ಟರ್‌ಗಳನ್ನು ಹೊಂದಿರಬೇಕು</item>
     </plurals>
+    <plurals name="lockpassword_password_requires_nonnumerical" formatted="false" msgid="6943707011152180150">
+      <item quantity="one">ಕನಿಷ್ಠ <xliff:g id="COUNT">%d</xliff:g> ಸಂಖ್ಯೆಯಲ್ಲದ ಅಕ್ಷರಗಳನ್ನು ಹೊಂದಿರಬೇಕು</item>
+      <item quantity="other">ಕನಿಷ್ಠ <xliff:g id="COUNT">%d</xliff:g> ಸಂಖ್ಯೆಯಲ್ಲದ ಅಕ್ಷರಗಳನ್ನು ಹೊಂದಿರಬೇಕು</item>
+    </plurals>
     <string name="lockpassword_password_recently_used" msgid="6944729699375087431">"ಇತ್ತೀಚಿನ ಪಾಸ್‌ವರ್ಡ್ ಬಳಸಲು ಸಾಧನದ ನಿರ್ವಾಹಕರು ಅನುಮತಿಸುವುದಿಲ್ಲ"</string>
     <string name="lockpassword_password_blacklisted_by_admin" msgid="4988166770148440755">"ಸರಳವಾದ ಪಾಸ್‌ವರ್ಡ್‌ಗಳನ್ನು ನಿಮ್ಮ IT ನಿರ್ವಾಹಕರಿಂದ ನಿರ್ಬಂಧಿಸಲಾಗಿದೆ. ಬೇರೆಯ ಪಾಸ್‌ವರ್ಡ್‌ ಪ್ರಯತ್ನಿಸಿ."</string>
     <string name="lockpassword_pin_no_sequential_digits" msgid="3902387296149848324">"ಅಂಕಿಗಳ ಆರೋಹಣ, ಅವರೋಹಣ ಅಥವಾ ಪುನರಾವರ್ತಿತ ಅನುಕ್ರಮವನ್ನು ನಿಷೇಧಿಸಲಾಗಿದೆ"</string>
diff --git a/tests/CarDeveloperOptions/res/values-ko/config.xml b/tests/CarDeveloperOptions/res/values-ko/config.xml
index b05e073..04c8bf8 100644
--- a/tests/CarDeveloperOptions/res/values-ko/config.xml
+++ b/tests/CarDeveloperOptions/res/values-ko/config.xml
@@ -17,5 +17,4 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="config_backup_settings_label" msgid="5230713622702899641"></string>
-    <string name="config_grayscale_settings_intent" msgid="7661066612238245852"></string>
 </resources>
diff --git a/tests/CarDeveloperOptions/res/values-ko/strings.xml b/tests/CarDeveloperOptions/res/values-ko/strings.xml
index b8657ba..f8f93ce 100644
--- a/tests/CarDeveloperOptions/res/values-ko/strings.xml
+++ b/tests/CarDeveloperOptions/res/values-ko/strings.xml
@@ -668,7 +668,6 @@
       <item quantity="other"><xliff:g id="NUMBER_1">%d</xliff:g>자리 미만이어야 합니다.</item>
       <item quantity="one"><xliff:g id="NUMBER_0">%d</xliff:g>자리 미만이어야 합니다.</item>
     </plurals>
-    <string name="lockpassword_pin_contains_non_digits" msgid="1501729742023195556">"0에서 9까지의 숫자만 포함되어야 합니다."</string>
     <string name="lockpassword_pin_recently_used" msgid="5556148774911741103">"기기 관리자가 최근 PIN 사용을 허용하지 않습니다."</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="2752037810571200697">"IT 관리자가 일반 PIN을 차단했습니다. 다른 PIN을 시도해 보세요."</string>
     <string name="lockpassword_illegal_character" msgid="3577712947676261351">"잘못된 문자를 포함할 수 없습니다."</string>
@@ -699,6 +698,10 @@
       <item quantity="other">글자가 아닌 문자를 <xliff:g id="COUNT">%d</xliff:g>개 이상 포함해야 합니다.</item>
       <item quantity="one">글자가 아닌 문자를 1개 이상 포함해야 합니다.</item>
     </plurals>
+    <plurals name="lockpassword_password_requires_nonnumerical" formatted="false" msgid="6943707011152180150">
+      <item quantity="other">숫자가 아닌 문자를 <xliff:g id="COUNT">%d</xliff:g>개 이상 포함해야 합니다.</item>
+      <item quantity="one">숫자가 아닌 문자를 1개 이상 포함해야 합니다.</item>
+    </plurals>
     <string name="lockpassword_password_recently_used" msgid="6944729699375087431">"기기 관리자가 최근 비밀번호 사용을 허용하지 않습니다."</string>
     <string name="lockpassword_password_blacklisted_by_admin" msgid="4988166770148440755">"IT 관리자가 일반적인 단어로 된 비밀번호를 허용하지 않습니다. 다른 비밀번호를 시도해 보세요."</string>
     <string name="lockpassword_pin_no_sequential_digits" msgid="3902387296149848324">"연속으로 올라가거나 내려가는 숫자 또는 반복되는 숫자의 배열은 허용되지 않습니다."</string>
diff --git a/tests/CarDeveloperOptions/res/values-ky/config.xml b/tests/CarDeveloperOptions/res/values-ky/config.xml
index b05e073..04c8bf8 100644
--- a/tests/CarDeveloperOptions/res/values-ky/config.xml
+++ b/tests/CarDeveloperOptions/res/values-ky/config.xml
@@ -17,5 +17,4 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="config_backup_settings_label" msgid="5230713622702899641"></string>
-    <string name="config_grayscale_settings_intent" msgid="7661066612238245852"></string>
 </resources>
diff --git a/tests/CarDeveloperOptions/res/values-ky/strings.xml b/tests/CarDeveloperOptions/res/values-ky/strings.xml
index 20dd214..b1c53cd 100644
--- a/tests/CarDeveloperOptions/res/values-ky/strings.xml
+++ b/tests/CarDeveloperOptions/res/values-ky/strings.xml
@@ -667,7 +667,6 @@
       <item quantity="other"><xliff:g id="NUMBER_1">%d</xliff:g> сандан ашпашы керек</item>
       <item quantity="one"><xliff:g id="NUMBER_0">%d</xliff:g> сандан ашпашы керек</item>
     </plurals>
-    <string name="lockpassword_pin_contains_non_digits" msgid="1501729742023195556">"0-9 сандарынан гана турушу керек"</string>
     <string name="lockpassword_pin_recently_used" msgid="5556148774911741103">"Түзмөктүн администратору акыркы PIN кодду колдонууга тыюу салган"</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="2752037810571200697">"Жөнөкөй PIN-коддорду коюу IT администраторуңуз тарабынан бөгөттөлгөн. Татаалыраак PIN-кодду коюп көрүңүз."</string>
     <string name="lockpassword_illegal_character" msgid="3577712947676261351">"Бул жерде жараксыз белги камтылбашы керек"</string>
@@ -698,6 +697,10 @@
       <item quantity="other">Тамгадан башка кеминде <xliff:g id="COUNT">%d</xliff:g> белги болушу керек</item>
       <item quantity="one">Тамгадан башка кеминде 1 белги болушу керек</item>
     </plurals>
+    <plurals name="lockpassword_password_requires_nonnumerical" formatted="false" msgid="6943707011152180150">
+      <item quantity="other">Тамгадан башка кеминде <xliff:g id="COUNT">%d</xliff:g> сандык эмес символ болушу керек</item>
+      <item quantity="one">Тамгадан башка кеминде 1 сандык эмес символ болушу керек</item>
+    </plurals>
     <string name="lockpassword_password_recently_used" msgid="6944729699375087431">"Түзмөктүн администратору акыркы сырсөздү колдонууга тыюу салган"</string>
     <string name="lockpassword_password_blacklisted_by_admin" msgid="4988166770148440755">"Жөнөкөй сырсөздөрү коюу IT администраторуңуз тарабынан бөгөттөлгөн. Татаалыраак сырсөздү коюп көрүңүз."</string>
     <string name="lockpassword_pin_no_sequential_digits" msgid="3902387296149848324">"Сандар чоңойгон, кичирейген же кайталанган ыраатта болбошу керек"</string>
diff --git a/tests/CarDeveloperOptions/res/values-lo/config.xml b/tests/CarDeveloperOptions/res/values-lo/config.xml
index b05e073..04c8bf8 100644
--- a/tests/CarDeveloperOptions/res/values-lo/config.xml
+++ b/tests/CarDeveloperOptions/res/values-lo/config.xml
@@ -17,5 +17,4 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="config_backup_settings_label" msgid="5230713622702899641"></string>
-    <string name="config_grayscale_settings_intent" msgid="7661066612238245852"></string>
 </resources>
diff --git a/tests/CarDeveloperOptions/res/values-lo/strings.xml b/tests/CarDeveloperOptions/res/values-lo/strings.xml
index 380f9ea..4a8e1a2 100644
--- a/tests/CarDeveloperOptions/res/values-lo/strings.xml
+++ b/tests/CarDeveloperOptions/res/values-lo/strings.xml
@@ -668,7 +668,6 @@
       <item quantity="other">ຕ້ອງມີໜ້ອຍກວ່າ <xliff:g id="NUMBER_1">%d</xliff:g> ຕົວເລກ</item>
       <item quantity="one">ຕ້ອງມີໜ້ອຍກວ່າ <xliff:g id="NUMBER_0">%d</xliff:g> ຕົວເລກ</item>
     </plurals>
-    <string name="lockpassword_pin_contains_non_digits" msgid="1501729742023195556">"ຕ້ອງມີຕົວເລກ 0 ຫາ 9 ເທົ່ານັ້ນ"</string>
     <string name="lockpassword_pin_recently_used" msgid="5556148774911741103">"ຜູ້ເບິ່ງແຍງລະບົບອຸປະກອນບໍ່ອະນຸຍາດໃຫ້ໃຊ້ລະຫັດ PIN ເມື່ອບໍ່ດົນມານີ້"</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="2752037810571200697">"ລະຫັດ PIN ທົ່ວໄປແມ່ນຖືກບລັອກໂດຍຜູ້ເບິ່ງແຍງລະບົບໄອທີຂອງທ່ານ. ໃຫ້ລອງໃຊ້ລະຫັດ PIN ອື່ນແທນ."</string>
     <string name="lockpassword_illegal_character" msgid="3577712947676261351">"ນີ້ບໍ່ສາມາດຮວມມີຕົວອັກສອນທີ່ບໍ່ຖືກຕ້ອງນຳໄດ້"</string>
@@ -699,6 +698,10 @@
       <item quantity="other">ຕ້ອງມີຕົວອັກຂະລະທີ່ບໍ່ແມ່ນຕົວອັກສອນຢ່າງໜ້ອຍ <xliff:g id="COUNT">%d</xliff:g> ຕົວ</item>
       <item quantity="one">ຕ້ອງມີຕົວອັກຂະລະທີ່ບໍ່ແມ່ນຕົວອັກສອນຢ່າງໜ້ອຍ 1 ຕົວ</item>
     </plurals>
+    <plurals name="lockpassword_password_requires_nonnumerical" formatted="false" msgid="6943707011152180150">
+      <item quantity="other">ຕ້ອງມີຕົວອັກສອນທີ່ບໍ່ແມ່ນຕົວເລກຢ່າງໜ້ອຍ <xliff:g id="COUNT">%d</xliff:g> ຕົວ</item>
+      <item quantity="one">ຕ້ອງມີຕົວອັກສອນທີ່ບໍ່ແມ່ນຕົວເລກຢ່າງໜ້ອຍ 1 ຕົວ</item>
+    </plurals>
     <string name="lockpassword_password_recently_used" msgid="6944729699375087431">"ຜູ້ເບິ່ງແຍງລະບົບບໍ່ອະນຸຍາດໃຫ້ໃຊ້ລະຫັດຜ່ານເມື່ອບໍ່ດົນມານີ້ໄດ້"</string>
     <string name="lockpassword_password_blacklisted_by_admin" msgid="4988166770148440755">"ລະຫັດຜ່ານທົ່ວໄປແມ່ນຖືກບລັອກໂດຍຜູ້ເບິ່ງແຍງລະບົບໄອທີຂອງທ່ານ. ໃຫ້ລອງໃຊ້ລະຫັດອື່ນແທນ."</string>
     <string name="lockpassword_pin_no_sequential_digits" msgid="3902387296149848324">"ນ້ອຍຫາໃຫຍ່, ໃຫຍ່ຫານ້ອຍ ຫຼື ຊຸດຕົວເລກຊ້ຳໆແມ່ນບໍ່ສາມາດໃຊ້ໄດ້"</string>
diff --git a/tests/CarDeveloperOptions/res/values-lt/config.xml b/tests/CarDeveloperOptions/res/values-lt/config.xml
index b05e073..04c8bf8 100644
--- a/tests/CarDeveloperOptions/res/values-lt/config.xml
+++ b/tests/CarDeveloperOptions/res/values-lt/config.xml
@@ -17,5 +17,4 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="config_backup_settings_label" msgid="5230713622702899641"></string>
-    <string name="config_grayscale_settings_intent" msgid="7661066612238245852"></string>
 </resources>
diff --git a/tests/CarDeveloperOptions/res/values-lt/strings.xml b/tests/CarDeveloperOptions/res/values-lt/strings.xml
index 4d5754b..7fbf042 100644
--- a/tests/CarDeveloperOptions/res/values-lt/strings.xml
+++ b/tests/CarDeveloperOptions/res/values-lt/strings.xml
@@ -684,7 +684,6 @@
       <item quantity="many">Turi būti mažiau nei <xliff:g id="NUMBER_1">%d</xliff:g> skaitmens</item>
       <item quantity="other">Turi būti mažiau nei <xliff:g id="NUMBER_1">%d</xliff:g> skaitmenų</item>
     </plurals>
-    <string name="lockpassword_pin_contains_non_digits" msgid="1501729742023195556">"Turi būti sudarytas tik iš skaitmenų (0–9)"</string>
     <string name="lockpassword_pin_recently_used" msgid="5556148774911741103">"Įrenginio administratorius neleidžia naudoti pastarojo PIN kodo"</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="2752037810571200697">"Dažnai naudojamus PIN kodus užblokavo IT administratorius. Bandykite naudoti kitą PIN kodą."</string>
     <string name="lockpassword_illegal_character" msgid="3577712947676261351">"Negali būti netinkamų simbolių"</string>
@@ -727,6 +726,12 @@
       <item quantity="many">Turi būti bent <xliff:g id="COUNT">%d</xliff:g> neraidinio simbolio</item>
       <item quantity="other">Turi būti bent <xliff:g id="COUNT">%d</xliff:g> neraidinių simbolių</item>
     </plurals>
+    <plurals name="lockpassword_password_requires_nonnumerical" formatted="false" msgid="6943707011152180150">
+      <item quantity="one">Turi būti bent <xliff:g id="COUNT">%d</xliff:g> neskaitinis simbolis</item>
+      <item quantity="few">Turi būti bent <xliff:g id="COUNT">%d</xliff:g> neskaitiniai simboliai</item>
+      <item quantity="many">Turi būti bent <xliff:g id="COUNT">%d</xliff:g> neskaitinio simbolio</item>
+      <item quantity="other">Turi būti bent <xliff:g id="COUNT">%d</xliff:g> neskaitinių simbolių</item>
+    </plurals>
     <string name="lockpassword_password_recently_used" msgid="6944729699375087431">"Įrenginio administratorius neleidžia naudoti pastarojo slaptažodžio"</string>
     <string name="lockpassword_password_blacklisted_by_admin" msgid="4988166770148440755">"Dažnai naudojamus slaptažodžius užblokavo IT administratorius. Bandykite naudoti kitą slaptažodį."</string>
     <string name="lockpassword_pin_no_sequential_digits" msgid="3902387296149848324">"Didėjanti, mažėjanti ar pasikartojanti skaitmenų seka neleidžiama"</string>
diff --git a/tests/CarDeveloperOptions/res/values-lv/config.xml b/tests/CarDeveloperOptions/res/values-lv/config.xml
index b05e073..04c8bf8 100644
--- a/tests/CarDeveloperOptions/res/values-lv/config.xml
+++ b/tests/CarDeveloperOptions/res/values-lv/config.xml
@@ -17,5 +17,4 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="config_backup_settings_label" msgid="5230713622702899641"></string>
-    <string name="config_grayscale_settings_intent" msgid="7661066612238245852"></string>
 </resources>
diff --git a/tests/CarDeveloperOptions/res/values-lv/strings.xml b/tests/CarDeveloperOptions/res/values-lv/strings.xml
index 9eef33e..b429375 100644
--- a/tests/CarDeveloperOptions/res/values-lv/strings.xml
+++ b/tests/CarDeveloperOptions/res/values-lv/strings.xml
@@ -676,7 +676,6 @@
       <item quantity="one">Ir jābūt mazāk par <xliff:g id="NUMBER_1">%d</xliff:g> ciparu.</item>
       <item quantity="other">Ir jābūt mazāk par <xliff:g id="NUMBER_1">%d</xliff:g> cipariem.</item>
     </plurals>
-    <string name="lockpassword_pin_contains_non_digits" msgid="1501729742023195556">"Ir jāietver tikai cipari no 0 līdz 9."</string>
     <string name="lockpassword_pin_recently_used" msgid="5556148774911741103">"Ierīces administrators neļauj izmantot nesen izveidotu PIN."</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="2752037810571200697">"Jūsu IT administrators ir bloķējis pārāk vienkāršus PIN kodus. Izmēģiniet sarežģītāku PIN kodu."</string>
     <string name="lockpassword_illegal_character" msgid="3577712947676261351">"Nedrīkst ietvert nederīgu rakstzīmi."</string>
@@ -713,6 +712,11 @@
       <item quantity="one">Ir jāietver vismaz <xliff:g id="COUNT">%d</xliff:g> rakstzīme, kas nav burts.</item>
       <item quantity="other">Ir jāietver vismaz <xliff:g id="COUNT">%d</xliff:g> rakstzīmes, kas nav burti.</item>
     </plurals>
+    <plurals name="lockpassword_password_requires_nonnumerical" formatted="false" msgid="6943707011152180150">
+      <item quantity="zero">Ir jāietver vismaz <xliff:g id="COUNT">%d</xliff:g> rakstzīmes, kas nav cipari.</item>
+      <item quantity="one">Ir jāietver vismaz <xliff:g id="COUNT">%d</xliff:g> rakstzīme, kas nav cipari.</item>
+      <item quantity="other">Ir jāietver vismaz <xliff:g id="COUNT">%d</xliff:g> rakstzīmes, kas nav cipari.</item>
+    </plurals>
     <string name="lockpassword_password_recently_used" msgid="6944729699375087431">"Ierīces administrators neļauj izmantot nesen izveidotu paroli."</string>
     <string name="lockpassword_password_blacklisted_by_admin" msgid="4988166770148440755">"Jūsu IT administrators ir bloķējis pārāk vienkāršas paroles. Izmēģiniet sarežģītāku paroli."</string>
     <string name="lockpassword_pin_no_sequential_digits" msgid="3902387296149848324">"Augoša, dilstoša vai atkārtota ciparu secība nav atļauta."</string>
diff --git a/tests/CarDeveloperOptions/res/values-mk/config.xml b/tests/CarDeveloperOptions/res/values-mk/config.xml
index b05e073..04c8bf8 100644
--- a/tests/CarDeveloperOptions/res/values-mk/config.xml
+++ b/tests/CarDeveloperOptions/res/values-mk/config.xml
@@ -17,5 +17,4 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="config_backup_settings_label" msgid="5230713622702899641"></string>
-    <string name="config_grayscale_settings_intent" msgid="7661066612238245852"></string>
 </resources>
diff --git a/tests/CarDeveloperOptions/res/values-mk/strings.xml b/tests/CarDeveloperOptions/res/values-mk/strings.xml
index 072add6..c69807b 100644
--- a/tests/CarDeveloperOptions/res/values-mk/strings.xml
+++ b/tests/CarDeveloperOptions/res/values-mk/strings.xml
@@ -668,7 +668,6 @@
       <item quantity="one">Мора да има помалку од <xliff:g id="NUMBER_1">%d</xliff:g> цифра</item>
       <item quantity="other">Мора да има помалку од <xliff:g id="NUMBER_1">%d</xliff:g> цифри</item>
     </plurals>
-    <string name="lockpassword_pin_contains_non_digits" msgid="1501729742023195556">"Мора да содржи само цифри од 0 до 9"</string>
     <string name="lockpassword_pin_recently_used" msgid="5556148774911741103">"Администраторот на уредот не дозволува користење на неодамнешен PIN"</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="2752037810571200697">"Обичните PIN-кодови се блокирани од вашиот IT-администратор. Обидете се со друг PIN."</string>
     <string name="lockpassword_illegal_character" msgid="3577712947676261351">"Ова не може да вклучува неважечки знак"</string>
@@ -699,6 +698,10 @@
       <item quantity="one">Мора да содржи најмалку <xliff:g id="COUNT">%d</xliff:g> знак што не е буква</item>
       <item quantity="other">Мора да содржи најмалку <xliff:g id="COUNT">%d</xliff:g> знаци што не се букви</item>
     </plurals>
+    <plurals name="lockpassword_password_requires_nonnumerical" formatted="false" msgid="6943707011152180150">
+      <item quantity="one">Мора да содржи најмалку <xliff:g id="COUNT">%d</xliff:g> знак што не е буква</item>
+      <item quantity="other">Мора да содржи најмалку <xliff:g id="COUNT">%d</xliff:g> знаци што не се букви</item>
+    </plurals>
     <string name="lockpassword_password_recently_used" msgid="6944729699375087431">"Администраторот на уредот не дозволува користење на неодамнешна лозинка"</string>
     <string name="lockpassword_password_blacklisted_by_admin" msgid="4988166770148440755">"Обичните лозинки се блокирани од вашиот IT-администратор. Обидете се со друга лозинка."</string>
     <string name="lockpassword_pin_no_sequential_digits" msgid="3902387296149848324">"Не е дозволена нагорна, надолна или повторлива секвенца на цифри"</string>
@@ -1687,7 +1690,7 @@
     <string name="safety_and_regulatory_info" msgid="7113766428000920132">"Упатство за безбедност и регулатива"</string>
     <string name="copyright_title" msgid="3847703367689932190">"Авторски права"</string>
     <string name="license_title" msgid="7582145947873528540">"Лиценца"</string>
-    <string name="terms_title" msgid="1804549588198223771">"Одредби и услови"</string>
+    <string name="terms_title" msgid="1804549588198223771">"Правила и услови"</string>
     <string name="webview_license_title" msgid="8244960025549725051">"Системска лиценца за WebView"</string>
     <string name="wallpaper_attributions" msgid="2941987966332943253">"Тапети"</string>
     <string name="wallpaper_attributions_values" msgid="4461979853894606323">"Извор на сателитски снимки:\n©2014 CNES/Astrium, DigitalGlobe, Bluesky"</string>
diff --git a/tests/CarDeveloperOptions/res/values-ml/config.xml b/tests/CarDeveloperOptions/res/values-ml/config.xml
index b05e073..04c8bf8 100644
--- a/tests/CarDeveloperOptions/res/values-ml/config.xml
+++ b/tests/CarDeveloperOptions/res/values-ml/config.xml
@@ -17,5 +17,4 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="config_backup_settings_label" msgid="5230713622702899641"></string>
-    <string name="config_grayscale_settings_intent" msgid="7661066612238245852"></string>
 </resources>
diff --git a/tests/CarDeveloperOptions/res/values-ml/strings.xml b/tests/CarDeveloperOptions/res/values-ml/strings.xml
index c0539aa..585800c 100644
--- a/tests/CarDeveloperOptions/res/values-ml/strings.xml
+++ b/tests/CarDeveloperOptions/res/values-ml/strings.xml
@@ -668,7 +668,6 @@
       <item quantity="other"><xliff:g id="NUMBER_1">%d</xliff:g> അക്കങ്ങളേക്കാൾ കുറവായിരിക്കണം</item>
       <item quantity="one"><xliff:g id="NUMBER_0">%d</xliff:g> അക്കത്തിനേക്കാൾ കുറവായിരിക്കണം</item>
     </plurals>
-    <string name="lockpassword_pin_contains_non_digits" msgid="1501729742023195556">"0 മുതൽ 9 വരെയുള്ള അക്കങ്ങൾ മാത്രം അടങ്ങിയിരിക്കണം"</string>
     <string name="lockpassword_pin_recently_used" msgid="5556148774911741103">"ഉപകരണ അഡ്‌മിൻ സമീപകാലത്തുള്ള പിൻ ഉപയോഗിക്കുന്നത് അനുവദിക്കുന്നില്ല"</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="2752037810571200697">"നിങ്ങളുടെ ഐടി അഡ്‌മിൻ സാധാരണ പിന്നുകൾ ബ്ലോക്ക് ചെയ്‌തിട്ടുണ്ട്. മറ്റൊരു പിൻ പരീക്ഷിക്കുക."</string>
     <string name="lockpassword_illegal_character" msgid="3577712947676261351">"ഇതിൽ അസാധുവായൊരു പ്രതീകം ഉണ്ടായിരിക്കാൻ പാടില്ല"</string>
@@ -699,6 +698,10 @@
       <item quantity="other">കുറഞ്ഞത് അക്ഷരമല്ലാത്ത <xliff:g id="COUNT">%d</xliff:g> പ്രതീകങ്ങളെങ്കിലും അടങ്ങിയിരിക്കണം</item>
       <item quantity="one">കുറഞ്ഞത് അക്ഷരമല്ലാത്ത ഒരു പ്രതീകമെങ്കിലും അടങ്ങിയിരിക്കണം</item>
     </plurals>
+    <plurals name="lockpassword_password_requires_nonnumerical" formatted="false" msgid="6943707011152180150">
+      <item quantity="other">കുറഞ്ഞത് സംഖ്യയല്ലാത്ത <xliff:g id="COUNT">%d</xliff:g> പ്രതീകങ്ങളെങ്കിലും അടങ്ങിയിരിക്കണം</item>
+      <item quantity="one">കുറഞ്ഞത് സംഖ്യയല്ലാത്ത ഒരു പ്രതീകമെങ്കിലും അടങ്ങിയിരിക്കണം</item>
+    </plurals>
     <string name="lockpassword_password_recently_used" msgid="6944729699375087431">"ഒരു സമീപകാല പാസ്‌വേഡ് ഉപയോഗിക്കാൻ ഉപകരണ അഡ്‌മിൻ അനുവദിക്കുന്നില്ല"</string>
     <string name="lockpassword_password_blacklisted_by_admin" msgid="4988166770148440755">"നിങ്ങളുടെ IT അഡ്‌മിൻ സാധാരണ പാസ്‍വേഡുകൾ ബ്ലോക്ക് ചെയ്‌തിട്ടുണ്ട്. മറ്റൊരു പാസ്‍വേഡ് പരീക്ഷിക്കുക."</string>
     <string name="lockpassword_pin_no_sequential_digits" msgid="3902387296149848324">"അക്കങ്ങൾ ആരോഹണ ക്രമത്തിലോ അവരോഹണ ക്രമത്തിലോ അനുക്രമമായോ നൽകുന്നത് അനുവദനീയമല്ല"</string>
diff --git a/tests/CarDeveloperOptions/res/values-mn/config.xml b/tests/CarDeveloperOptions/res/values-mn/config.xml
index b05e073..04c8bf8 100644
--- a/tests/CarDeveloperOptions/res/values-mn/config.xml
+++ b/tests/CarDeveloperOptions/res/values-mn/config.xml
@@ -17,5 +17,4 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="config_backup_settings_label" msgid="5230713622702899641"></string>
-    <string name="config_grayscale_settings_intent" msgid="7661066612238245852"></string>
 </resources>
diff --git a/tests/CarDeveloperOptions/res/values-mn/strings.xml b/tests/CarDeveloperOptions/res/values-mn/strings.xml
index 6fa3996..c941ea4 100644
--- a/tests/CarDeveloperOptions/res/values-mn/strings.xml
+++ b/tests/CarDeveloperOptions/res/values-mn/strings.xml
@@ -668,7 +668,6 @@
       <item quantity="other"><xliff:g id="NUMBER_1">%d</xliff:g>-с цөөн цифртэй байх ёстой</item>
       <item quantity="one"><xliff:g id="NUMBER_0">%d</xliff:g>-с цөөн цифртэй байх ёстой</item>
     </plurals>
-    <string name="lockpassword_pin_contains_non_digits" msgid="1501729742023195556">"Зөвхөн 0-9 хүртэлх цифр агуулах шаардлагатай"</string>
     <string name="lockpassword_pin_recently_used" msgid="5556148774911741103">"Төхөөрөмжийн админ саяхны ПИН-г ашиглахыг зөвшөөрдөггүй"</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="2752037810571200697">"Таны IT админ түгээмэл ПИН-г блоклосон байна. Өөр ПИН оруулна уу."</string>
     <string name="lockpassword_illegal_character" msgid="3577712947676261351">"Үүнд хүчингүй тэмдэгт агуулах боломжгүй"</string>
@@ -699,6 +698,10 @@
       <item quantity="other">Хамгийн багадаа <xliff:g id="COUNT">%d</xliff:g> тэмдэгт агуулах шаардлагатай</item>
       <item quantity="one">Хамгийн багадаа 1 тэмдэгт агуулах шаардлагатай</item>
     </plurals>
+    <plurals name="lockpassword_password_requires_nonnumerical" formatted="false" msgid="6943707011152180150">
+      <item quantity="other">Хамгийн багадаа тоон бус <xliff:g id="COUNT">%d</xliff:g> тэмдэгт агуулах ёстой</item>
+      <item quantity="one">Хамгийн багадаа тоон бус 1 тэмдэгт агуулах ёстой</item>
+    </plurals>
     <string name="lockpassword_password_recently_used" msgid="6944729699375087431">"Төхөөрөмжийн админ саяхны нууц үгийг ашиглахыг зөвшөөрдөггүй"</string>
     <string name="lockpassword_password_blacklisted_by_admin" msgid="4988166770148440755">"Таны IT админ түгээмэл нууц үгийг блоклосон байна. Өөр нууц үг оруулна уу."</string>
     <string name="lockpassword_pin_no_sequential_digits" msgid="3902387296149848324">"Цифр өсөх, буурах, давхцахыг зөвшөөрдөггүй"</string>
diff --git a/tests/CarDeveloperOptions/res/values-mr/config.xml b/tests/CarDeveloperOptions/res/values-mr/config.xml
index b05e073..04c8bf8 100644
--- a/tests/CarDeveloperOptions/res/values-mr/config.xml
+++ b/tests/CarDeveloperOptions/res/values-mr/config.xml
@@ -17,5 +17,4 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="config_backup_settings_label" msgid="5230713622702899641"></string>
-    <string name="config_grayscale_settings_intent" msgid="7661066612238245852"></string>
 </resources>
diff --git a/tests/CarDeveloperOptions/res/values-mr/strings.xml b/tests/CarDeveloperOptions/res/values-mr/strings.xml
index 49aa4be..cbf29cb 100644
--- a/tests/CarDeveloperOptions/res/values-mr/strings.xml
+++ b/tests/CarDeveloperOptions/res/values-mr/strings.xml
@@ -668,7 +668,6 @@
       <item quantity="other"><xliff:g id="NUMBER_1">%d</xliff:g> अंकांपेक्षा कमी असणे आवश्यक आहे</item>
       <item quantity="one"><xliff:g id="NUMBER_0">%d</xliff:g> अंकापेक्षा कमी असणे आवश्यक आहे</item>
     </plurals>
-    <string name="lockpassword_pin_contains_non_digits" msgid="1501729742023195556">"केवळ 0-9 अंक असणे आवश्यक आहे"</string>
     <string name="lockpassword_pin_recently_used" msgid="5556148774911741103">"डिव्हाइस प्रशासक अलीकडील पिन वापरण्याची अनुमती देत नाही"</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="2752037810571200697">"सामान्यत: वापरले जाणारे पिन तुमच्या IT प्रशासनाने ब्लॉक केलेले आहेत. दुसरा एखादा पिन वापरून पाहा."</string>
     <string name="lockpassword_illegal_character" msgid="3577712947676261351">"यामध्ये अवैध वर्ण समाविष्ट असू शकत नाही"</string>
@@ -699,6 +698,10 @@
       <item quantity="other">किमान <xliff:g id="COUNT">%d</xliff:g> अक्षर नसलेले वर्ण असणे आवश्यक आहे</item>
       <item quantity="one">किमान 1 अक्षर नसलेला वर्ण असणे आवश्यक आहे</item>
     </plurals>
+    <plurals name="lockpassword_password_requires_nonnumerical" formatted="false" msgid="6943707011152180150">
+      <item quantity="other">किमान <xliff:g id="COUNT">%d</xliff:g> अंक नसलेले वर्ण असणे आवश्यक आहे</item>
+      <item quantity="one">किमान एक अंक नसलेला वर्ण असणे आवश्यक आहे</item>
+    </plurals>
     <string name="lockpassword_password_recently_used" msgid="6944729699375087431">"डिव्हाइस प्रशासक अलीकडील पासवर्ड वापरण्याची अनुमती देत नाही"</string>
     <string name="lockpassword_password_blacklisted_by_admin" msgid="4988166770148440755">"सामान्यत: वापरले जाणारे पासवर्ड तुमच्या IT प्रशासनाने ब्लॉक केलेले आहेत. दुसरा एखादा पासवर्ड वापरून पाहा."</string>
     <string name="lockpassword_pin_no_sequential_digits" msgid="3902387296149848324">"अंकांच्या चढत्या, उतरत्या किंवा पुनरावृत्त क्रमाला अनुमती नाही"</string>
diff --git a/tests/CarDeveloperOptions/res/values-ms/config.xml b/tests/CarDeveloperOptions/res/values-ms/config.xml
index b05e073..04c8bf8 100644
--- a/tests/CarDeveloperOptions/res/values-ms/config.xml
+++ b/tests/CarDeveloperOptions/res/values-ms/config.xml
@@ -17,5 +17,4 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="config_backup_settings_label" msgid="5230713622702899641"></string>
-    <string name="config_grayscale_settings_intent" msgid="7661066612238245852"></string>
 </resources>
diff --git a/tests/CarDeveloperOptions/res/values-ms/strings.xml b/tests/CarDeveloperOptions/res/values-ms/strings.xml
index f21e428..bf141c2 100644
--- a/tests/CarDeveloperOptions/res/values-ms/strings.xml
+++ b/tests/CarDeveloperOptions/res/values-ms/strings.xml
@@ -668,7 +668,6 @@
       <item quantity="other">Mesti kurang daripada <xliff:g id="NUMBER_1">%d</xliff:g> digit</item>
       <item quantity="one">Mesti kurang daripada <xliff:g id="NUMBER_0">%d</xliff:g> digit</item>
     </plurals>
-    <string name="lockpassword_pin_contains_non_digits" msgid="1501729742023195556">"Mesti mengandungi angka 0-9 sahaja"</string>
     <string name="lockpassword_pin_recently_used" msgid="5556148774911741103">"Pentadbir peranti tidak membenarkan penggunaan PIN terbaharu"</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="2752037810571200697">"PIN lazim disekat oleh pentadbir IT anda. Cuba PIN yang lain."</string>
     <string name="lockpassword_illegal_character" msgid="3577712947676261351">"Tidak boleh menyertakan aksara yang tidak sah"</string>
@@ -699,6 +698,10 @@
       <item quantity="other">Mesti mengandungi sekurang-kurangnya <xliff:g id="COUNT">%d</xliff:g> aksara bukan huruf</item>
       <item quantity="one">Mesti mengandungi sekurang-kurangnya 1 aksara bukan huruf</item>
     </plurals>
+    <plurals name="lockpassword_password_requires_nonnumerical" formatted="false" msgid="6943707011152180150">
+      <item quantity="other">Mesti mengandungi sekurang-kurangnya <xliff:g id="COUNT">%d</xliff:g> aksara bukan angka</item>
+      <item quantity="one">Mesti mengandungi sekurang-kurangnya 1 aksara bukan angka</item>
+    </plurals>
     <string name="lockpassword_password_recently_used" msgid="6944729699375087431">"Pentadbir peranti tidak membenarkan penggunaan kata laluan terbaharu"</string>
     <string name="lockpassword_password_blacklisted_by_admin" msgid="4988166770148440755">"Kata laluan lazim disekat oleh pentadbir IT anda. Cuba kata laluan yang lain."</string>
     <string name="lockpassword_pin_no_sequential_digits" msgid="3902387296149848324">"Angka menaik, menurun atau jujukan berulang tidak dibenarkan"</string>
diff --git a/tests/CarDeveloperOptions/res/values-my/config.xml b/tests/CarDeveloperOptions/res/values-my/config.xml
index b05e073..04c8bf8 100644
--- a/tests/CarDeveloperOptions/res/values-my/config.xml
+++ b/tests/CarDeveloperOptions/res/values-my/config.xml
@@ -17,5 +17,4 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="config_backup_settings_label" msgid="5230713622702899641"></string>
-    <string name="config_grayscale_settings_intent" msgid="7661066612238245852"></string>
 </resources>
diff --git a/tests/CarDeveloperOptions/res/values-my/strings.xml b/tests/CarDeveloperOptions/res/values-my/strings.xml
index 359f8dc..7cb218b 100644
--- a/tests/CarDeveloperOptions/res/values-my/strings.xml
+++ b/tests/CarDeveloperOptions/res/values-my/strings.xml
@@ -668,7 +668,6 @@
       <item quantity="other">ဂဏန်း <xliff:g id="NUMBER_1">%d</xliff:g> လုံးထက် နည်းရမည်</item>
       <item quantity="one">ဂဏန်း <xliff:g id="NUMBER_0">%d</xliff:g> လုံးထက် နည်းရမည်</item>
     </plurals>
-    <string name="lockpassword_pin_contains_non_digits" msgid="1501729742023195556">"ဂဏန်း ၀−၉ အထိသာ ပါဝင်ရပါမည်"</string>
     <string name="lockpassword_pin_recently_used" msgid="5556148774911741103">"စက်ပစ္စည်း၏ စီမံခန့်ခွဲသူသည် မကြာသေးမီက အသုံးပြုခဲ့သည့် ပင်နံပါတ်ကို သုံးခွင့်မပြုပါ"</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="2752037810571200697">"အသုံးပြုလေ့ရှိသော PIN များကို သင်၏ IT စီမံခန့်ခွဲသူက ပိတ်ထားသည်။ အခြား PIN တစ်ခုဖြင့် စမ်းကြည့်ပါ။"</string>
     <string name="lockpassword_illegal_character" msgid="3577712947676261351">"၎င်းတွင် မမှန်ကန်သည့်စကားလုံးများ ပါဝင်၍မရပါ"</string>
@@ -699,6 +698,10 @@
       <item quantity="other">အနည်းဆုံး စာလုံးငယ် <xliff:g id="COUNT">%d</xliff:g> လုံးပါဝင်ရမည်</item>
       <item quantity="one">အနည်းဆုံး စာလုံးငယ် ၁ လုံးပါဝင်ရမည်</item>
     </plurals>
+    <plurals name="lockpassword_password_requires_nonnumerical" formatted="false" msgid="6943707011152180150">
+      <item quantity="other">ဂဏန်းမဟုတ်သော အက္ခရာ အနည်းဆုံး <xliff:g id="COUNT">%d</xliff:g> လုံးပါရမည်</item>
+      <item quantity="one">ဂဏန်းမဟုတ်သော အက္ခရာ အနည်းဆုံး ၁ လုံးပါရမည်</item>
+    </plurals>
     <string name="lockpassword_password_recently_used" msgid="6944729699375087431">"စက်ပစ္စည်းစီမံခန့်ခွဲသူသည် မကြာသေးမီက အသုံးပြုခဲ့သည့် စကားဝှက်ကို သုံးခွင့်မပြုပါ"</string>
     <string name="lockpassword_password_blacklisted_by_admin" msgid="4988166770148440755">"အသုံးပြုလေ့ရှိသော စကားဝှက်များကို သင်၏ IT စီမံခန့်ခွဲသူက ပိတ်ထားသည်။ အခြား စကားဝှက်တစ်ခုဖြင့် စမ်းကြည့်ပါ။"</string>
     <string name="lockpassword_pin_no_sequential_digits" msgid="3902387296149848324">"အစဉ်လိုက်ဖြစ်နေသော ဂဏန်း အငယ်မှအကြီး၊ အကြီးမှအငယ် သို့မဟုတ် ထပ်နေသည့် နံပါတ်စဉ်များကို ခွင့်မပြုပါ"</string>
diff --git a/tests/CarDeveloperOptions/res/values-nb/config.xml b/tests/CarDeveloperOptions/res/values-nb/config.xml
index b05e073..04c8bf8 100644
--- a/tests/CarDeveloperOptions/res/values-nb/config.xml
+++ b/tests/CarDeveloperOptions/res/values-nb/config.xml
@@ -17,5 +17,4 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="config_backup_settings_label" msgid="5230713622702899641"></string>
-    <string name="config_grayscale_settings_intent" msgid="7661066612238245852"></string>
 </resources>
diff --git a/tests/CarDeveloperOptions/res/values-nb/strings.xml b/tests/CarDeveloperOptions/res/values-nb/strings.xml
index 2e57216..98d8827 100644
--- a/tests/CarDeveloperOptions/res/values-nb/strings.xml
+++ b/tests/CarDeveloperOptions/res/values-nb/strings.xml
@@ -668,7 +668,6 @@
       <item quantity="other">Må inneholde færre enn <xliff:g id="NUMBER_1">%d</xliff:g> sifre</item>
       <item quantity="one">Må inneholde færre enn <xliff:g id="NUMBER_0">%d</xliff:g> siffer</item>
     </plurals>
-    <string name="lockpassword_pin_contains_non_digits" msgid="1501729742023195556">"Kan bare inneholde tall fra 0 til 9"</string>
     <string name="lockpassword_pin_recently_used" msgid="5556148774911741103">"Enhetsadministratoren forbyr nylig brukt PIN-kode"</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="2752037810571200697">"Vanlige PIN-koder er blokkert av IT-administratoren din. Prøv en annen PIN-kode."</string>
     <string name="lockpassword_illegal_character" msgid="3577712947676261351">"Dette kan ikke inkludere et ugyldig tegn"</string>
@@ -699,6 +698,10 @@
       <item quantity="other">Må inneholde minst <xliff:g id="COUNT">%d</xliff:g> andre tegn enn bokstaver</item>
       <item quantity="one">Må inneholde minst ett annet tegn enn bokstaver</item>
     </plurals>
+    <plurals name="lockpassword_password_requires_nonnumerical" formatted="false" msgid="6943707011152180150">
+      <item quantity="other">Må inneholde minst <xliff:g id="COUNT">%d</xliff:g> tegn som ikke er tall</item>
+      <item quantity="one">Må inneholde minst 1 tegn som ikke er et tall</item>
+    </plurals>
     <string name="lockpassword_password_recently_used" msgid="6944729699375087431">"Enhetsadministratoren forbyr nylig brukte passord"</string>
     <string name="lockpassword_password_blacklisted_by_admin" msgid="4988166770148440755">"Vanlige passord er blokkert av IT-administratoren din. Prøv et annet passord."</string>
     <string name="lockpassword_pin_no_sequential_digits" msgid="3902387296149848324">"En sekvens av stigende, synkende eller like sifre er ikke tillatt"</string>
diff --git a/tests/CarDeveloperOptions/res/values-ne/config.xml b/tests/CarDeveloperOptions/res/values-ne/config.xml
index b05e073..04c8bf8 100644
--- a/tests/CarDeveloperOptions/res/values-ne/config.xml
+++ b/tests/CarDeveloperOptions/res/values-ne/config.xml
@@ -17,5 +17,4 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="config_backup_settings_label" msgid="5230713622702899641"></string>
-    <string name="config_grayscale_settings_intent" msgid="7661066612238245852"></string>
 </resources>
diff --git a/tests/CarDeveloperOptions/res/values-ne/strings.xml b/tests/CarDeveloperOptions/res/values-ne/strings.xml
index e7fccc0..5764cb0 100644
--- a/tests/CarDeveloperOptions/res/values-ne/strings.xml
+++ b/tests/CarDeveloperOptions/res/values-ne/strings.xml
@@ -668,7 +668,6 @@
       <item quantity="other">अनिवार्य रूपमा <xliff:g id="NUMBER_1">%d</xliff:g> अङ्कहरू भन्दा कम हुनु पर्छ</item>
       <item quantity="one">अनिवार्य रूपमा <xliff:g id="NUMBER_0">%d</xliff:g> अङ्क भन्दा कम हुनु पर्छ</item>
     </plurals>
-    <string name="lockpassword_pin_contains_non_digits" msgid="1501729742023195556">"अनिवार्य रूपमा ० देखि ९ सम्मका अङ्कहरू मात्र समावेश हुनु पर्छ"</string>
     <string name="lockpassword_pin_recently_used" msgid="5556148774911741103">"यन्त्र प्रशासकले पछिल्लो PIN प्रयोग गर्न अनुमति दिँदैन"</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="2752037810571200697">"तपाईंकसे IT व्यवस्थापकले धेरै प्रयोग हुने PIN हरूमाथि रोक लगाउनु भएको छ। कुनै फरक PIN प्रयोग गरी हेर्नुहोस्।"</string>
     <string name="lockpassword_illegal_character" msgid="3577712947676261351">"यसमा अमान्य वर्ण समावेश गर्न सकिँदैन"</string>
@@ -699,6 +698,10 @@
       <item quantity="other">कम्तीमा पनि <xliff:g id="COUNT">%d</xliff:g> वटा गैर-अक्षर वर्ण हुनु अनिवार्य छ</item>
       <item quantity="one">कम्तीमा पनि एउटा गैर-अक्षर वर्ण हुनु अनिवार्य छ</item>
     </plurals>
+    <plurals name="lockpassword_password_requires_nonnumerical" formatted="false" msgid="6943707011152180150">
+      <item quantity="other">कम्तीमा पनि <xliff:g id="COUNT">%d</xliff:g> गैर-सङ्ख्यात्मक वर्ण हुनु अनिवार्य छ</item>
+      <item quantity="one">कम्तीमा पनि १ गैर-सङ्ख्यात्मक वर्ण हुनु अनिवार्य छ</item>
+    </plurals>
     <string name="lockpassword_password_recently_used" msgid="6944729699375087431">"यन्त्रको प्रशासकले पछिल्लो पासवर्ड प्रयोग गर्ने अनुमति दिँदैन"</string>
     <string name="lockpassword_password_blacklisted_by_admin" msgid="4988166770148440755">"तपाईंकसे IT व्यवस्थापकले धेरै प्रयोग हुने पासवर्डहरूमाथि रोक लगाउनु भएको छ। कुनै फरक पासवर्ड प्रयोग गरी हेर्नुहोस्।"</string>
     <string name="lockpassword_pin_no_sequential_digits" msgid="3902387296149848324">"बढ्दो, घट्दो वा दोहोरिएका अङ्कहरूको अनुक्रमलाई निषेध गरिएको छ"</string>
diff --git a/tests/CarDeveloperOptions/res/values-nl/config.xml b/tests/CarDeveloperOptions/res/values-nl/config.xml
index b05e073..04c8bf8 100644
--- a/tests/CarDeveloperOptions/res/values-nl/config.xml
+++ b/tests/CarDeveloperOptions/res/values-nl/config.xml
@@ -17,5 +17,4 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="config_backup_settings_label" msgid="5230713622702899641"></string>
-    <string name="config_grayscale_settings_intent" msgid="7661066612238245852"></string>
 </resources>
diff --git a/tests/CarDeveloperOptions/res/values-nl/strings.xml b/tests/CarDeveloperOptions/res/values-nl/strings.xml
index 3fb34dd..a8d8d6f 100644
--- a/tests/CarDeveloperOptions/res/values-nl/strings.xml
+++ b/tests/CarDeveloperOptions/res/values-nl/strings.xml
@@ -668,7 +668,6 @@
       <item quantity="other">Moet minder dan <xliff:g id="NUMBER_1">%d</xliff:g> cijfers bevatten</item>
       <item quantity="one">Moet minder dan <xliff:g id="NUMBER_0">%d</xliff:g> cijfer bevatten</item>
     </plurals>
-    <string name="lockpassword_pin_contains_non_digits" msgid="1501729742023195556">"Mag alleen de cijfers 0-9 bevatten"</string>
     <string name="lockpassword_pin_recently_used" msgid="5556148774911741103">"Apparaatbeheer staat het gebruik van een recente pincode niet toe"</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="2752037810571200697">"Veelvoorkomende pincodes worden geblokkeerd door je IT-beheerder. Probeer een andere pincode."</string>
     <string name="lockpassword_illegal_character" msgid="3577712947676261351">"Mag geen ongeldig teken bevatten"</string>
@@ -699,6 +698,10 @@
       <item quantity="other">Moet ten minste <xliff:g id="COUNT">%d</xliff:g> tekens bevatten die geen letters zijn</item>
       <item quantity="one">Moet ten minste één teken bevatten dat geen letter is</item>
     </plurals>
+    <plurals name="lockpassword_password_requires_nonnumerical" formatted="false" msgid="6943707011152180150">
+      <item quantity="other">Moet ten minste <xliff:g id="COUNT">%d</xliff:g> niet-numerieke tekens bevatten</item>
+      <item quantity="one">Moet ten minste 1 niet-numeriek teken bevatten</item>
+    </plurals>
     <string name="lockpassword_password_recently_used" msgid="6944729699375087431">"Apparaatbeheer staat het gebruik van een recent wachtwoord niet toe"</string>
     <string name="lockpassword_password_blacklisted_by_admin" msgid="4988166770148440755">"Veelvoorkomende wachtwoorden worden geblokkeerd door je IT-beheerder. Probeer een ander wachtwoord."</string>
     <string name="lockpassword_pin_no_sequential_digits" msgid="3902387296149848324">"Een oplopende, aflopende of herhaalde reeks cijfers is niet toegestaan"</string>
diff --git a/tests/CarDeveloperOptions/res/values-or/config.xml b/tests/CarDeveloperOptions/res/values-or/config.xml
index b05e073..04c8bf8 100644
--- a/tests/CarDeveloperOptions/res/values-or/config.xml
+++ b/tests/CarDeveloperOptions/res/values-or/config.xml
@@ -17,5 +17,4 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="config_backup_settings_label" msgid="5230713622702899641"></string>
-    <string name="config_grayscale_settings_intent" msgid="7661066612238245852"></string>
 </resources>
diff --git a/tests/CarDeveloperOptions/res/values-or/strings.xml b/tests/CarDeveloperOptions/res/values-or/strings.xml
index 5c65e80..ca13d8f 100644
--- a/tests/CarDeveloperOptions/res/values-or/strings.xml
+++ b/tests/CarDeveloperOptions/res/values-or/strings.xml
@@ -667,7 +667,6 @@
       <item quantity="other"><xliff:g id="NUMBER_1">%d</xliff:g> ଅଙ୍କରୁ କମ୍‌ ହେବା ଦରକାର</item>
       <item quantity="one"><xliff:g id="NUMBER_0">%d</xliff:g> ଅଙ୍କରୁ କମ୍‌ ହେବା ଦରକାର</item>
     </plurals>
-    <string name="lockpassword_pin_contains_non_digits" msgid="1501729742023195556">"କେବଳ 0-9 ସଂଖ୍ୟା ହିଁ ରହିବା ଦରକାର"</string>
     <string name="lockpassword_pin_recently_used" msgid="5556148774911741103">"ଏକ ସାମ୍ପ୍ରତିକ ପିନ୍‌ର ବ୍ୟବହାରକୁ ଡିଭାଇସ୍‌ ଆଡ୍‌ମିନ୍‌ ଅନୁମତି ଦେବନାହିଁ"</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="2752037810571200697">"ସାଧାରଣ PINଗୁଡିକ ଆପଣଙ୍କ IT ଆଡମିନ୍‌ଙ୍କ ଦ୍ୱାରା ଅବରୋଧ କରିଦିଆଯାଇଛି। ଭିନ୍ନ PIN ଚେଷ୍ଟା କରନ୍ତୁ।"</string>
     <string name="lockpassword_illegal_character" msgid="3577712947676261351">"ଏକ ଭୁଲ ଅକ୍ଷରକୁ ଏହା ମିଶାଇ ପାରିବନାହିଁ"</string>
@@ -698,6 +697,10 @@
       <item quantity="other">ଅତିକମ୍‌ରେ <xliff:g id="COUNT">%d</xliff:g>ଟି ବିଶେଷ ଅକ୍ଷର କିମ୍ୱା ସଂଖ୍ୟା ଆବଶ୍ୟକ</item>
       <item quantity="one">ଅତିକମ୍‌ରେ 1ଟି ବିଶେଷ ଅକ୍ଷର କିମ୍ୱା ସଂଖ୍ୟା ଆବଶ୍ୟକ</item>
     </plurals>
+    <plurals name="lockpassword_password_requires_nonnumerical" formatted="false" msgid="6943707011152180150">
+      <item quantity="other">ଅତିକମ୍‌ରେ <xliff:g id="COUNT">%d</xliff:g>ଟି ଅକ୍ଷର ହେବା ନିହାତି ଆବଶ୍ୟକ ଯାହା ସଂଖ୍ୟା ହୋଇନଥିବା</item>
+      <item quantity="one">ଅତିକମ୍‌ରେ 1ଟି ଅକ୍ଷର ହେବା ନିହାତି ଆବଶ୍ୟକ ଯାହା ସଂଖ୍ୟା ହୋଇନଥିବା</item>
+    </plurals>
     <string name="lockpassword_password_recently_used" msgid="6944729699375087431">"ଏକ ସମ୍ପ୍ରତି ପାସ୍‌ୱର୍ଡ ବ୍ୟବହାର କରିବାକୁ ଡିଭାଇସ୍‌ ଆଡମିନ୍‌ ଅନୁମତି ଦିଅନ୍ତି ନାହିଁ"</string>
     <string name="lockpassword_password_blacklisted_by_admin" msgid="4988166770148440755">"ଆପଣଙ୍କର ଆଇଟି ଆଡ୍‌ମିନ୍‌ ଦ୍ୱାରା ସାଧାରଣ ପାସ୍‌ୱର୍ଡଗୁଡ଼ିକୁ ରୋକିଦିଆଯାଇଛି। ଏକ ଭିନ୍ନ ପାସ୍‌ୱର୍ଡ ଦିଅନ୍ତୁ।"</string>
     <string name="lockpassword_pin_no_sequential_digits" msgid="3902387296149848324">"ସଂଖ୍ୟାର କ୍ରମବୃଦ୍ଧି, କ୍ରମହ୍ରାସ, କିମ୍ବା ପୁନରାବୃତ୍ତ କ୍ରମ ଅନୁମୋଦିତ ନୁହେଁ"</string>
diff --git a/tests/CarDeveloperOptions/res/values-pa/config.xml b/tests/CarDeveloperOptions/res/values-pa/config.xml
index b05e073..04c8bf8 100644
--- a/tests/CarDeveloperOptions/res/values-pa/config.xml
+++ b/tests/CarDeveloperOptions/res/values-pa/config.xml
@@ -17,5 +17,4 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="config_backup_settings_label" msgid="5230713622702899641"></string>
-    <string name="config_grayscale_settings_intent" msgid="7661066612238245852"></string>
 </resources>
diff --git a/tests/CarDeveloperOptions/res/values-pa/strings.xml b/tests/CarDeveloperOptions/res/values-pa/strings.xml
index 8b9a021..19e5809 100644
--- a/tests/CarDeveloperOptions/res/values-pa/strings.xml
+++ b/tests/CarDeveloperOptions/res/values-pa/strings.xml
@@ -668,7 +668,6 @@
       <item quantity="one"><xliff:g id="NUMBER_1">%d</xliff:g> ਤੋਂ ਘੱਟ ਅੰਕ ਹੋਣਾ ਚਾਹੀਦਾ ਹੈ</item>
       <item quantity="other"><xliff:g id="NUMBER_1">%d</xliff:g> ਤੋਂ ਵਧੇਰੇ ਘੱਟ ਅੰਕ ਹੋਣੇ ਚਾਹੀਦੇ ਹਨ</item>
     </plurals>
-    <string name="lockpassword_pin_contains_non_digits" msgid="1501729742023195556">"ਸਿਰਫ਼ 0-9 ਤੱਕ ਅੰਕ ਸ਼ਾਮਲ ਹੋਣੇ ਲਾਜ਼ਮੀ ਹਨ"</string>
     <string name="lockpassword_pin_recently_used" msgid="5556148774911741103">"ਡੀਵਾਈਸ ਪ੍ਰਸ਼ਾਸਕ ਇੱਕ ਹਾਲੀਆ ਪਿੰਨ ਵਰਤਣ ਦੀ ਇਜਾਜ਼ਤ ਨਹੀਂ ਦਿੰਦਾ ਹੈ"</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="2752037810571200697">"ਆਮ ਪਿੰਨ ਤੁਹਾਡੇ IT ਪ੍ਰਸ਼ਾਸਕ ਵੱਲੋਂ ਬਲਾਕ ਕੀਤੇ ਗਏ ਹਨ। ਕੋਈ ਵੱਖਰਾ ਪਿੰਨ ਵਰਤੋ।"</string>
     <string name="lockpassword_illegal_character" msgid="3577712947676261351">"ਇਸ ਵਿੱਚ ਇੱਕ ਅਵੈਧ ਅੱਖਰ-ਚਿੰਨ੍ਹ ਸ਼ਾਮਲ ਨਹੀਂ ਹੋ ਸਕਦਾ"</string>
@@ -699,6 +698,10 @@
       <item quantity="one">ਘੱਟੋ-ਘੱਟ <xliff:g id="COUNT">%d</xliff:g> ਗੈਰ-ਅੱਖਰੀ ਅੱਖਰ-ਚਿੰਨ੍ਹ ਸ਼ਾਮਲ ਕਰਨਾ ਲਾਜ਼ਮੀ ਹੈ</item>
       <item quantity="other">ਘੱਟੋ-ਘੱਟ <xliff:g id="COUNT">%d</xliff:g> ਗੈਰ-ਅੱਖਰੀ ਅੱਖਰ-ਚਿੰਨ੍ਹ ਸ਼ਾਮਲ ਕਰਨਾ ਲਾਜ਼ਮੀ ਹੈ</item>
     </plurals>
+    <plurals name="lockpassword_password_requires_nonnumerical" formatted="false" msgid="6943707011152180150">
+      <item quantity="one">ਘੱਟੋ-ਘੱਟ <xliff:g id="COUNT">%d</xliff:g> ਗੈਰ-ਸੰਖਿਆਵਾਚੀ ਅੱਖਰ-ਚਿੰਨ੍ਹ ਸ਼ਾਮਲ ਕਰਨਾ ਲਾਜ਼ਮੀ ਹੈ</item>
+      <item quantity="other">ਘੱਟੋ-ਘੱਟ <xliff:g id="COUNT">%d</xliff:g> ਗੈਰ-ਸੰਖਿਆਵਾਚੀ ਅੱਖਰ-ਚਿੰਨ੍ਹ ਸ਼ਾਮਲ ਕਰਨੇ ਲਾਜ਼ਮੀ ਹਨ</item>
+    </plurals>
     <string name="lockpassword_password_recently_used" msgid="6944729699375087431">"ਡੀਵਾਈਸ ਪ੍ਰਸ਼ਾਸਕ ਇੱਕ ਹਾਲੀਆ ਪਾਸਵਰਡ ਵਰਤਣ ਦੀ ਇਜਾਜ਼ਤ ਨਹੀਂ ਦਿੰਦਾ ਹੈ।"</string>
     <string name="lockpassword_password_blacklisted_by_admin" msgid="4988166770148440755">"ਆਮ ਪਾਸਵਰਡ ਤੁਹਾਡੇ IT ਪ੍ਰਸ਼ਾਸਕ ਵੱਲੋਂ ਬਲਾਕ ਕੀਤੇ ਗਏ ਹਨ। ਕੋਈ ਵੱਖਰਾ ਪਾਸਵਰਡ ਵਰਤੋ।"</string>
     <string name="lockpassword_pin_no_sequential_digits" msgid="3902387296149848324">"ਅੰਕਾਂ ਦਾ ਵਧਦਾ ਕ੍ਰਮ, ਘਟਦਾ ਕ੍ਰਮ, ਜਾਂ ਦੁਹਰਾਈ ਗਈ ਲੜੀ ਦੀ ਇਜਾਜ਼ਤ ਨਹੀਂ ਹੈ"</string>
diff --git a/tests/CarDeveloperOptions/res/values-pl/config.xml b/tests/CarDeveloperOptions/res/values-pl/config.xml
index b05e073..04c8bf8 100644
--- a/tests/CarDeveloperOptions/res/values-pl/config.xml
+++ b/tests/CarDeveloperOptions/res/values-pl/config.xml
@@ -17,5 +17,4 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="config_backup_settings_label" msgid="5230713622702899641"></string>
-    <string name="config_grayscale_settings_intent" msgid="7661066612238245852"></string>
 </resources>
diff --git a/tests/CarDeveloperOptions/res/values-pl/strings.xml b/tests/CarDeveloperOptions/res/values-pl/strings.xml
index 947a908..b009c48 100644
--- a/tests/CarDeveloperOptions/res/values-pl/strings.xml
+++ b/tests/CarDeveloperOptions/res/values-pl/strings.xml
@@ -684,7 +684,6 @@
       <item quantity="other">Musi mieć mniej niż <xliff:g id="NUMBER_1">%d</xliff:g> cyfry</item>
       <item quantity="one">Musi mieć mniej niż <xliff:g id="NUMBER_0">%d</xliff:g> cyfrę</item>
     </plurals>
-    <string name="lockpassword_pin_contains_non_digits" msgid="1501729742023195556">"Musi zawierać tylko cyfry 0-9"</string>
     <string name="lockpassword_pin_recently_used" msgid="5556148774911741103">"Administrator urządzenia nie zezwala na ustawianie niedawno używanego kodu PIN"</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="2752037810571200697">"Popularne kody PIN zostały zablokowane przez administratora. Spróbuj użyć innego kodu PIN."</string>
     <string name="lockpassword_illegal_character" msgid="3577712947676261351">"Nie może zawierać nieprawidłowego znaku"</string>
@@ -727,6 +726,12 @@
       <item quantity="other">Musi zawierać co najmniej <xliff:g id="COUNT">%d</xliff:g> znaku niebędącego literą</item>
       <item quantity="one">Musi zawierać co najmniej 1 znak niebędący literą</item>
     </plurals>
+    <plurals name="lockpassword_password_requires_nonnumerical" formatted="false" msgid="6943707011152180150">
+      <item quantity="few">Musi zawierać co najmniej <xliff:g id="COUNT">%d</xliff:g> znaki niebędące cyframi</item>
+      <item quantity="many">Musi zawierać co najmniej <xliff:g id="COUNT">%d</xliff:g> znaków niebędących cyframi</item>
+      <item quantity="other">Musi zawierać co najmniej <xliff:g id="COUNT">%d</xliff:g> znaku niebędącego cyfrą</item>
+      <item quantity="one">Musi zawierać co najmniej 1 znak niebędący cyfrą</item>
+    </plurals>
     <string name="lockpassword_password_recently_used" msgid="6944729699375087431">"Administrator urządzenia nie zezwala na ustawianie niedawno używanego hasła"</string>
     <string name="lockpassword_password_blacklisted_by_admin" msgid="4988166770148440755">"Popularne hasła zostały zablokowane przez administratora. Spróbuj użyć innego hasła."</string>
     <string name="lockpassword_pin_no_sequential_digits" msgid="3902387296149848324">"Ciąg cyfr rosnących, malejących lub powtarzających się jest niedozwolony"</string>
diff --git a/tests/CarDeveloperOptions/res/values-pt-rPT/config.xml b/tests/CarDeveloperOptions/res/values-pt-rPT/config.xml
index b05e073..04c8bf8 100644
--- a/tests/CarDeveloperOptions/res/values-pt-rPT/config.xml
+++ b/tests/CarDeveloperOptions/res/values-pt-rPT/config.xml
@@ -17,5 +17,4 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="config_backup_settings_label" msgid="5230713622702899641"></string>
-    <string name="config_grayscale_settings_intent" msgid="7661066612238245852"></string>
 </resources>
diff --git a/tests/CarDeveloperOptions/res/values-pt-rPT/strings.xml b/tests/CarDeveloperOptions/res/values-pt-rPT/strings.xml
index ab46a31..4804f72 100644
--- a/tests/CarDeveloperOptions/res/values-pt-rPT/strings.xml
+++ b/tests/CarDeveloperOptions/res/values-pt-rPT/strings.xml
@@ -668,7 +668,6 @@
       <item quantity="other">Tem de ter menos de <xliff:g id="NUMBER_1">%d</xliff:g> dígitos.</item>
       <item quantity="one">Tem de ter menos de <xliff:g id="NUMBER_0">%d</xliff:g> dígito.</item>
     </plurals>
-    <string name="lockpassword_pin_contains_non_digits" msgid="1501729742023195556">"Tem de incluir apenas dígitos de 0 a 9"</string>
     <string name="lockpassword_pin_recently_used" msgid="5556148774911741103">"O gestor do dispositivo não permite utilizar um PIN recente"</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="2752037810571200697">"Os PINs comuns estão bloqueados pelo seu gestor de TI. Experimente outro PIN."</string>
     <string name="lockpassword_illegal_character" msgid="3577712947676261351">"Não pode incluir um caráter inválido"</string>
@@ -699,6 +698,10 @@
       <item quantity="other">Tem de incluir, pelo menos, <xliff:g id="COUNT">%d</xliff:g> carateres que não sejam letras</item>
       <item quantity="one">Tem de incluir, pelo menos, 1 caráter que não seja uma letra</item>
     </plurals>
+    <plurals name="lockpassword_password_requires_nonnumerical" formatted="false" msgid="6943707011152180150">
+      <item quantity="other">Tem de incluir, pelo menos, <xliff:g id="COUNT">%d</xliff:g> carateres que não sejam números.</item>
+      <item quantity="one">Tem de incluir, pelo menos, 1 caráter que não seja um número.</item>
+    </plurals>
     <string name="lockpassword_password_recently_used" msgid="6944729699375087431">"O admin. do disp. não permite a utilização de uma palavra-passe recente"</string>
     <string name="lockpassword_password_blacklisted_by_admin" msgid="4988166770148440755">"As palavras-passe comuns estão bloqueadas pelo seu gestor de TI. Experimente outra palavra-passe."</string>
     <string name="lockpassword_pin_no_sequential_digits" msgid="3902387296149848324">"Não é permitida uma sequência de dígitos ascendente, descendente ou repetida"</string>
diff --git a/tests/CarDeveloperOptions/res/values-pt/config.xml b/tests/CarDeveloperOptions/res/values-pt/config.xml
index b05e073..04c8bf8 100644
--- a/tests/CarDeveloperOptions/res/values-pt/config.xml
+++ b/tests/CarDeveloperOptions/res/values-pt/config.xml
@@ -17,5 +17,4 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="config_backup_settings_label" msgid="5230713622702899641"></string>
-    <string name="config_grayscale_settings_intent" msgid="7661066612238245852"></string>
 </resources>
diff --git a/tests/CarDeveloperOptions/res/values-pt/strings.xml b/tests/CarDeveloperOptions/res/values-pt/strings.xml
index 0de3591..55fe26b 100644
--- a/tests/CarDeveloperOptions/res/values-pt/strings.xml
+++ b/tests/CarDeveloperOptions/res/values-pt/strings.xml
@@ -668,7 +668,6 @@
       <item quantity="one">Precisa ter menos de <xliff:g id="NUMBER_1">%d</xliff:g> dígito</item>
       <item quantity="other">Precisa ter menos de <xliff:g id="NUMBER_1">%d</xliff:g> dígitos</item>
     </plurals>
-    <string name="lockpassword_pin_contains_non_digits" msgid="1501729742023195556">"Precisa ter apenas dígitos de 0 a 9"</string>
     <string name="lockpassword_pin_recently_used" msgid="5556148774911741103">"O administrador do dispositivo não permite o uso de um PIN recente"</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="2752037810571200697">"PINs comuns foram bloqueados pelo seu administrador de TI. Tente um PIN diferente."</string>
     <string name="lockpassword_illegal_character" msgid="3577712947676261351">"Não pode incluir um caractere inválido"</string>
@@ -699,6 +698,10 @@
       <item quantity="one">Precisa ter pelo menos <xliff:g id="COUNT">%d</xliff:g> caractere que não seja letra</item>
       <item quantity="other">Precisa ter pelo menos <xliff:g id="COUNT">%d</xliff:g> caracteres que não sejam letras</item>
     </plurals>
+    <plurals name="lockpassword_password_requires_nonnumerical" formatted="false" msgid="6943707011152180150">
+      <item quantity="one">Precisa ter pelo menos <xliff:g id="COUNT">%d</xliff:g> caractere não numérico</item>
+      <item quantity="other">Precisa ter pelo menos <xliff:g id="COUNT">%d</xliff:g> caracteres não numéricos</item>
+    </plurals>
     <string name="lockpassword_password_recently_used" msgid="6944729699375087431">"O administrador do dispositivo não permite usar uma senha recente"</string>
     <string name="lockpassword_password_blacklisted_by_admin" msgid="4988166770148440755">"Senhas comuns foram bloqueadas pelo seu administrador de TI. Tente uma senha diferente."</string>
     <string name="lockpassword_pin_no_sequential_digits" msgid="3902387296149848324">"Não é permitido usar uma sequência de dígitos em ordem crescente, decrescente ou repetidos"</string>
diff --git a/tests/CarDeveloperOptions/res/values-ro/config.xml b/tests/CarDeveloperOptions/res/values-ro/config.xml
index b05e073..04c8bf8 100644
--- a/tests/CarDeveloperOptions/res/values-ro/config.xml
+++ b/tests/CarDeveloperOptions/res/values-ro/config.xml
@@ -17,5 +17,4 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="config_backup_settings_label" msgid="5230713622702899641"></string>
-    <string name="config_grayscale_settings_intent" msgid="7661066612238245852"></string>
 </resources>
diff --git a/tests/CarDeveloperOptions/res/values-ro/strings.xml b/tests/CarDeveloperOptions/res/values-ro/strings.xml
index 721a81a..f279356 100644
--- a/tests/CarDeveloperOptions/res/values-ro/strings.xml
+++ b/tests/CarDeveloperOptions/res/values-ro/strings.xml
@@ -676,7 +676,6 @@
       <item quantity="other">Trebuie să conțină mai puțin de <xliff:g id="NUMBER_1">%d</xliff:g> de cifre</item>
       <item quantity="one">Trebuie să conțină mai puțin de <xliff:g id="NUMBER_0">%d</xliff:g> cifră</item>
     </plurals>
-    <string name="lockpassword_pin_contains_non_digits" msgid="1501729742023195556">"Trebuie să conțină numai cifre de la 0 la 9"</string>
     <string name="lockpassword_pin_recently_used" msgid="5556148774911741103">"Administratorul dispozitivului nu permite utilizarea unui PIN recent"</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="2752037810571200697">"Codurile PIN sunt blocate de administratorul dvs. IT. Încercați alt cod PIN."</string>
     <string name="lockpassword_illegal_character" msgid="3577712947676261351">"Aceasta nu poate include un caracter nevalid"</string>
@@ -713,6 +712,11 @@
       <item quantity="other">Trebuie să conțină cel puțin <xliff:g id="COUNT">%d</xliff:g> de caractere care să nu fie litere</item>
       <item quantity="one">Trebuie să conțină cel puțin un caracter care să nu fie literă</item>
     </plurals>
+    <plurals name="lockpassword_password_requires_nonnumerical" formatted="false" msgid="6943707011152180150">
+      <item quantity="few">Trebuie să conțină cel puțin <xliff:g id="COUNT">%d</xliff:g> caractere care să nu fie cifre</item>
+      <item quantity="other">Trebuie să conțină cel puțin <xliff:g id="COUNT">%d</xliff:g> de caractere care să nu fie cifre</item>
+      <item quantity="one">Trebuie să conțină cel puțin un caracter care să nu fie cifră</item>
+    </plurals>
     <string name="lockpassword_password_recently_used" msgid="6944729699375087431">"Administratorul dispozitivului nu permite utilizarea unei parole recente"</string>
     <string name="lockpassword_password_blacklisted_by_admin" msgid="4988166770148440755">"Parolele obișnuite sunt blocate de administratorul dvs. IT. Încercați altă parolă."</string>
     <string name="lockpassword_pin_no_sequential_digits" msgid="3902387296149848324">"Șirurile de cifre ascendente, descendente sau repetate nu sunt permise"</string>
diff --git a/tests/CarDeveloperOptions/res/values-ru/config.xml b/tests/CarDeveloperOptions/res/values-ru/config.xml
index b05e073..04c8bf8 100644
--- a/tests/CarDeveloperOptions/res/values-ru/config.xml
+++ b/tests/CarDeveloperOptions/res/values-ru/config.xml
@@ -17,5 +17,4 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="config_backup_settings_label" msgid="5230713622702899641"></string>
-    <string name="config_grayscale_settings_intent" msgid="7661066612238245852"></string>
 </resources>
diff --git a/tests/CarDeveloperOptions/res/values-ru/strings.xml b/tests/CarDeveloperOptions/res/values-ru/strings.xml
index b675160..e8720d0 100644
--- a/tests/CarDeveloperOptions/res/values-ru/strings.xml
+++ b/tests/CarDeveloperOptions/res/values-ru/strings.xml
@@ -684,7 +684,6 @@
       <item quantity="many">PIN-код должен содержать менее <xliff:g id="NUMBER_1">%d</xliff:g> цифр</item>
       <item quantity="other">PIN-код должен содержать менее <xliff:g id="NUMBER_1">%d</xliff:g> цифры</item>
     </plurals>
-    <string name="lockpassword_pin_contains_non_digits" msgid="1501729742023195556">"PIN-код должен содержать только цифры"</string>
     <string name="lockpassword_pin_recently_used" msgid="5556148774911741103">"Использовать недавний PIN-код запрещено"</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="2752037810571200697">"ИТ-администратор заблокировал простые PIN-коды. Выберите более сложную комбинацию."</string>
     <string name="lockpassword_illegal_character" msgid="3577712947676261351">"Недопустимые символы"</string>
@@ -727,6 +726,12 @@
       <item quantity="many">Используйте как минимум <xliff:g id="COUNT">%d</xliff:g> небуквенных символов</item>
       <item quantity="other">Используйте как минимум <xliff:g id="COUNT">%d</xliff:g> небуквенного символа</item>
     </plurals>
+    <plurals name="lockpassword_password_requires_nonnumerical" formatted="false" msgid="6943707011152180150">
+      <item quantity="one">Пароль должен содержать как минимум <xliff:g id="COUNT">%d</xliff:g> нечисловой символ.</item>
+      <item quantity="few">Пароль должен содержать как минимум <xliff:g id="COUNT">%d</xliff:g> нечисловых символа.</item>
+      <item quantity="many">Пароль должен содержать как минимум <xliff:g id="COUNT">%d</xliff:g> нечисловых символов.</item>
+      <item quantity="other">Пароль должен содержать как минимум <xliff:g id="COUNT">%d</xliff:g> нечислового символа.</item>
+    </plurals>
     <string name="lockpassword_password_recently_used" msgid="6944729699375087431">"Использовать недавний пароль запрещено"</string>
     <string name="lockpassword_password_blacklisted_by_admin" msgid="4988166770148440755">"ИТ-администратор заблокировал простые пароли. Выберите более сложную комбинацию."</string>
     <string name="lockpassword_pin_no_sequential_digits" msgid="3902387296149848324">"Нельзя использовать последовательности из идущих подряд или повторяющихся цифр"</string>
diff --git a/tests/CarDeveloperOptions/res/values-si/config.xml b/tests/CarDeveloperOptions/res/values-si/config.xml
index b05e073..04c8bf8 100644
--- a/tests/CarDeveloperOptions/res/values-si/config.xml
+++ b/tests/CarDeveloperOptions/res/values-si/config.xml
@@ -17,5 +17,4 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="config_backup_settings_label" msgid="5230713622702899641"></string>
-    <string name="config_grayscale_settings_intent" msgid="7661066612238245852"></string>
 </resources>
diff --git a/tests/CarDeveloperOptions/res/values-si/strings.xml b/tests/CarDeveloperOptions/res/values-si/strings.xml
index 21e056c..19db2d6 100644
--- a/tests/CarDeveloperOptions/res/values-si/strings.xml
+++ b/tests/CarDeveloperOptions/res/values-si/strings.xml
@@ -668,7 +668,6 @@
       <item quantity="one">ඉලක්කම් <xliff:g id="NUMBER_1">%d</xliff:g>කට වඩා අඩු විය යුතුය</item>
       <item quantity="other">ඉලක්කම් <xliff:g id="NUMBER_1">%d</xliff:g>කට වඩා අඩු විය යුතුය</item>
     </plurals>
-    <string name="lockpassword_pin_contains_non_digits" msgid="1501729742023195556">"0-9 දක්වා ඉලක්කම් පමණක් අඩංගු විය යුතුය"</string>
     <string name="lockpassword_pin_recently_used" msgid="5556148774911741103">"උපාංග පරිපාලක මෑත PIN එකක් භාවිතා කිරීමට අවසර නොදේ"</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="2752037810571200697">"ඔබගේ IT පරිපාලක විසින් සුලබ PIN අවහිර කරනු ලැබේ. වෙනත් PIN එකක් උත්සාහ කරන්න."</string>
     <string name="lockpassword_illegal_character" msgid="3577712947676261351">"මෙහි වලංගු නොවන අනුලකුණක් ඇතුළත් විය නොහැකිය"</string>
@@ -699,6 +698,10 @@
       <item quantity="one">අඩු තරමින් අකුරු නොවන අනුලකුණු <xliff:g id="COUNT">%d</xliff:g>ක් අඩංගු විය යුතුය</item>
       <item quantity="other">අඩු තරමින් අකුරු නොවන අනුලකුණු <xliff:g id="COUNT">%d</xliff:g>ක් අඩංගු විය යුතුය</item>
     </plurals>
+    <plurals name="lockpassword_password_requires_nonnumerical" formatted="false" msgid="6943707011152180150">
+      <item quantity="one">අඩු තරමින් සංඛ්‍යාත්මක-නොවන අනුලකුණු <xliff:g id="COUNT">%d</xliff:g>ක් අඩංගු විය යුතුය</item>
+      <item quantity="other">අඩු තරමින් සංඛ්‍යාත්මක-නොවන අනුලකුණු <xliff:g id="COUNT">%d</xliff:g>ක් අඩංගු විය යුතුය</item>
+    </plurals>
     <string name="lockpassword_password_recently_used" msgid="6944729699375087431">"උපාංග පරිපාලක මෑත මුරපදයක් භාවිතා කිරීමට අවසර නොදේ."</string>
     <string name="lockpassword_password_blacklisted_by_admin" msgid="4988166770148440755">"ඔබගේ IT පරිපාලක විසින් සුලබ මුරපද අවහිර කරනු ලැබේ. වෙනත් මුරපදයක් උත්සාහ කරන්න."</string>
     <string name="lockpassword_pin_no_sequential_digits" msgid="3902387296149848324">"ආරෝහණ, අවරෝහණ හෝ ප්‍රනරාවර්ත අනුක්‍රමයේ අංක වෙත අවසර නැත"</string>
diff --git a/tests/CarDeveloperOptions/res/values-sk/config.xml b/tests/CarDeveloperOptions/res/values-sk/config.xml
index b05e073..04c8bf8 100644
--- a/tests/CarDeveloperOptions/res/values-sk/config.xml
+++ b/tests/CarDeveloperOptions/res/values-sk/config.xml
@@ -17,5 +17,4 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="config_backup_settings_label" msgid="5230713622702899641"></string>
-    <string name="config_grayscale_settings_intent" msgid="7661066612238245852"></string>
 </resources>
diff --git a/tests/CarDeveloperOptions/res/values-sk/strings.xml b/tests/CarDeveloperOptions/res/values-sk/strings.xml
index 8b68658..e64d4f8 100644
--- a/tests/CarDeveloperOptions/res/values-sk/strings.xml
+++ b/tests/CarDeveloperOptions/res/values-sk/strings.xml
@@ -684,7 +684,6 @@
       <item quantity="other">Musí mať menej ako <xliff:g id="NUMBER_1">%d</xliff:g> číslic</item>
       <item quantity="one">Musí mať menej ako <xliff:g id="NUMBER_0">%d</xliff:g> číslicu</item>
     </plurals>
-    <string name="lockpassword_pin_contains_non_digits" msgid="1501729742023195556">"Musí obsahovať len číslice 0 až 9"</string>
     <string name="lockpassword_pin_recently_used" msgid="5556148774911741103">"Správca zariadenia neumožňuje používať nedávny kód PIN"</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="2752037810571200697">"Bežné kódy PIN zablokoval váš správca IT. Skúste iný PIN."</string>
     <string name="lockpassword_illegal_character" msgid="3577712947676261351">"Musí obsahovať iba platné znaky"</string>
@@ -727,6 +726,12 @@
       <item quantity="other">Musí obsahovať aspoň <xliff:g id="COUNT">%d</xliff:g> znakov, ktoré nie sú písmená</item>
       <item quantity="one">Musí obsahovať aspoň 1 znak, ktorý nie je písmeno</item>
     </plurals>
+    <plurals name="lockpassword_password_requires_nonnumerical" formatted="false" msgid="6943707011152180150">
+      <item quantity="few">Musí obsahovať aspoň <xliff:g id="COUNT">%d</xliff:g> znaky, ktoré nie sú písmená</item>
+      <item quantity="many">Must contain at least <xliff:g id="COUNT">%d</xliff:g> non-numerical characters</item>
+      <item quantity="other">Musí obsahovať aspoň <xliff:g id="COUNT">%d</xliff:g> znakov, ktoré nie sú písmená</item>
+      <item quantity="one">Musí obsahovať aspoň jeden znak, ktorý nie je písmeno</item>
+    </plurals>
     <string name="lockpassword_password_recently_used" msgid="6944729699375087431">"Správca zariadenia neumožňuje používať nedávne heslo"</string>
     <string name="lockpassword_password_blacklisted_by_admin" msgid="4988166770148440755">"Bežné heslá zablokoval váš správca IT. Skúste iné heslo."</string>
     <string name="lockpassword_pin_no_sequential_digits" msgid="3902387296149848324">"Vzostupná, zostupná alebo opakovaná sekvencia čísiel nie je povolená"</string>
diff --git a/tests/CarDeveloperOptions/res/values-sl/config.xml b/tests/CarDeveloperOptions/res/values-sl/config.xml
index b05e073..04c8bf8 100644
--- a/tests/CarDeveloperOptions/res/values-sl/config.xml
+++ b/tests/CarDeveloperOptions/res/values-sl/config.xml
@@ -17,5 +17,4 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="config_backup_settings_label" msgid="5230713622702899641"></string>
-    <string name="config_grayscale_settings_intent" msgid="7661066612238245852"></string>
 </resources>
diff --git a/tests/CarDeveloperOptions/res/values-sl/strings.xml b/tests/CarDeveloperOptions/res/values-sl/strings.xml
index 7151979..004d9e2 100644
--- a/tests/CarDeveloperOptions/res/values-sl/strings.xml
+++ b/tests/CarDeveloperOptions/res/values-sl/strings.xml
@@ -684,7 +684,6 @@
       <item quantity="few">Vsebovati mora manj kot <xliff:g id="NUMBER_1">%d</xliff:g> števke</item>
       <item quantity="other">Vsebovati mora manj kot <xliff:g id="NUMBER_1">%d</xliff:g> števk</item>
     </plurals>
-    <string name="lockpassword_pin_contains_non_digits" msgid="1501729742023195556">"Vsebovati sme samo števke od 0 do 9."</string>
     <string name="lockpassword_pin_recently_used" msgid="5556148774911741103">"Skrbnik naprave ne dovoli uporabe nedavne kode PIN."</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="2752037810571200697">"Skrbnik za IT je blokiral pogoste kode PIN. Poskusite z drugo kodo PIN."</string>
     <string name="lockpassword_illegal_character" msgid="3577712947676261351">"Ne sme vsebovati neveljavnih znakov."</string>
@@ -727,6 +726,12 @@
       <item quantity="few">Vsebovati mora vsaj <xliff:g id="COUNT">%d</xliff:g> znake, ki niso črka</item>
       <item quantity="other">Vsebovati mora vsaj <xliff:g id="COUNT">%d</xliff:g> znakov, ki niso črka</item>
     </plurals>
+    <plurals name="lockpassword_password_requires_nonnumerical" formatted="false" msgid="6943707011152180150">
+      <item quantity="one">Vsebovati mora vsaj <xliff:g id="COUNT">%d</xliff:g> znak, ki ni števka</item>
+      <item quantity="two">Vsebovati mora vsaj <xliff:g id="COUNT">%d</xliff:g> znaka, ki nista števki</item>
+      <item quantity="few">Vsebovati mora vsaj <xliff:g id="COUNT">%d</xliff:g> znake, ki niso števke</item>
+      <item quantity="other">Vsebovati mora vsaj <xliff:g id="COUNT">%d</xliff:g> znakov, ki niso števke</item>
+    </plurals>
     <string name="lockpassword_password_recently_used" msgid="6944729699375087431">"Skrbnik naprave ne dovoli uporabe nedavnega gesla."</string>
     <string name="lockpassword_password_blacklisted_by_admin" msgid="4988166770148440755">"Skrbnik za IT je blokiral pogosta gesla. Poskusite z drugim geslom."</string>
     <string name="lockpassword_pin_no_sequential_digits" msgid="3902387296149848324">"Naraščajoč, padajoč ali ponavljajoč se niz števk je prepovedan."</string>
@@ -1861,7 +1866,7 @@
     <string name="disable_text" msgid="5065834603951474397">"Onemogoči"</string>
     <string name="enable_text" msgid="7179141636849225884">"Omogoči"</string>
     <string name="clear_user_data_text" msgid="8894073247302821764">"Počisti shrambo"</string>
-    <string name="app_factory_reset" msgid="8718986000278776272">"Odmesti posodobitve"</string>
+    <string name="app_factory_reset" msgid="8718986000278776272">"Odstrani posodobitve"</string>
     <string name="auto_launch_enable_text" msgid="3372898942144027341">"Izbrali ste, da se bo pri nekaterih dejanjih ta aplikacija samodejno zagnala."</string>
     <string name="always_allow_bind_appwidgets_text" msgid="2286211654774611037">"Aplikaciji dovolite, da ustvari pripomočke in dostopa do njihovih podatkov."</string>
     <string name="auto_launch_disable_text" msgid="8560921288036801416">"Ni privzetih nastavitev."</string>
diff --git a/tests/CarDeveloperOptions/res/values-sq/config.xml b/tests/CarDeveloperOptions/res/values-sq/config.xml
index b05e073..04c8bf8 100644
--- a/tests/CarDeveloperOptions/res/values-sq/config.xml
+++ b/tests/CarDeveloperOptions/res/values-sq/config.xml
@@ -17,5 +17,4 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="config_backup_settings_label" msgid="5230713622702899641"></string>
-    <string name="config_grayscale_settings_intent" msgid="7661066612238245852"></string>
 </resources>
diff --git a/tests/CarDeveloperOptions/res/values-sq/strings.xml b/tests/CarDeveloperOptions/res/values-sq/strings.xml
index 630513c..8583d20 100644
--- a/tests/CarDeveloperOptions/res/values-sq/strings.xml
+++ b/tests/CarDeveloperOptions/res/values-sq/strings.xml
@@ -668,7 +668,6 @@
       <item quantity="other">Duhet të ketë më pak se <xliff:g id="NUMBER_1">%d</xliff:g> shifra</item>
       <item quantity="one">Duhet të ketë më pak se <xliff:g id="NUMBER_0">%d</xliff:g> shifër</item>
     </plurals>
-    <string name="lockpassword_pin_contains_non_digits" msgid="1501729742023195556">"Duhet të përmbajë vetëm shifrat 0-9"</string>
     <string name="lockpassword_pin_recently_used" msgid="5556148774911741103">"Administratori i pajisjes nuk e lejon përdorimin e një kodi PIN të përdorur së fundi"</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="2752037810571200697">"Kodet e zakonshme PIN janë bllokuar nga administratori i TI-së. Provo një kod tjetër PIN."</string>
     <string name="lockpassword_illegal_character" msgid="3577712947676261351">"Kjo nuk mund të përfshijë një karakter të pavlefshëm"</string>
@@ -699,6 +698,10 @@
       <item quantity="other">Duhet të përmbajë të paktën <xliff:g id="COUNT">%d</xliff:g> karaktere që nuk janë shkronja</item>
       <item quantity="one">Duhet të përmbajë të paktën 1 karakter që nuk është shkronjë</item>
     </plurals>
+    <plurals name="lockpassword_password_requires_nonnumerical" formatted="false" msgid="6943707011152180150">
+      <item quantity="other">Duhet të përmbajë të paktën <xliff:g id="COUNT">%d</xliff:g> karaktere jonumerike</item>
+      <item quantity="one">Duhet të përmbajë të paktën 1 karakter jonumerik</item>
+    </plurals>
     <string name="lockpassword_password_recently_used" msgid="6944729699375087431">"Administratori i pajisjes nuk e lejon përdorimin e një fjalëkalimi të përdorur së fundi"</string>
     <string name="lockpassword_password_blacklisted_by_admin" msgid="4988166770148440755">"Fjalëkalimet e zakonshme janë bllokuar nga administratori i TI-së. Provo një fjalëkalim tjetër."</string>
     <string name="lockpassword_pin_no_sequential_digits" msgid="3902387296149848324">"Nuk lejohet një sekuencë shifrash në rritje, në zbritje ose me përsëritje"</string>
diff --git a/tests/CarDeveloperOptions/res/values-sr/config.xml b/tests/CarDeveloperOptions/res/values-sr/config.xml
index b05e073..04c8bf8 100644
--- a/tests/CarDeveloperOptions/res/values-sr/config.xml
+++ b/tests/CarDeveloperOptions/res/values-sr/config.xml
@@ -17,5 +17,4 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="config_backup_settings_label" msgid="5230713622702899641"></string>
-    <string name="config_grayscale_settings_intent" msgid="7661066612238245852"></string>
 </resources>
diff --git a/tests/CarDeveloperOptions/res/values-sr/strings.xml b/tests/CarDeveloperOptions/res/values-sr/strings.xml
index 699cf07..be9bc3e 100644
--- a/tests/CarDeveloperOptions/res/values-sr/strings.xml
+++ b/tests/CarDeveloperOptions/res/values-sr/strings.xml
@@ -676,7 +676,6 @@
       <item quantity="few">Мора да садржи мање од <xliff:g id="NUMBER_1">%d</xliff:g> цифре</item>
       <item quantity="other">Мора да садржи мање од <xliff:g id="NUMBER_1">%d</xliff:g> цифара</item>
     </plurals>
-    <string name="lockpassword_pin_contains_non_digits" msgid="1501729742023195556">"Мора да садржи само цифре 0–9"</string>
     <string name="lockpassword_pin_recently_used" msgid="5556148774911741103">"Администратор уређаја не дозвољава употребу недавно коришћеног PIN-а"</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="2752037810571200697">"ИТ администратор блокира честе PIN-ове. Изаберите други PIN."</string>
     <string name="lockpassword_illegal_character" msgid="3577712947676261351">"Не сме да обухвата неважећи знак"</string>
@@ -713,6 +712,11 @@
       <item quantity="few">Мора да садржи најмање <xliff:g id="COUNT">%d</xliff:g> знака који нису словa</item>
       <item quantity="other">Мора да садржи најмање <xliff:g id="COUNT">%d</xliff:g> знакова који нису словa</item>
     </plurals>
+    <plurals name="lockpassword_password_requires_nonnumerical" formatted="false" msgid="6943707011152180150">
+      <item quantity="one">Мора да садржи најмање <xliff:g id="COUNT">%d</xliff:g> знак који није број</item>
+      <item quantity="few">Мора да садржи најмање <xliff:g id="COUNT">%d</xliff:g> знака који нису бројеви</item>
+      <item quantity="other">Мора да садржи најмање <xliff:g id="COUNT">%d</xliff:g> знакова који нису бројеви</item>
+    </plurals>
     <string name="lockpassword_password_recently_used" msgid="6944729699375087431">"Администратор уређаја не дозвољава употребу недавно коришћене лозинке"</string>
     <string name="lockpassword_password_blacklisted_by_admin" msgid="4988166770148440755">"ИТ администратор блокира честе лозинке. Изаберите другу лозинку."</string>
     <string name="lockpassword_pin_no_sequential_digits" msgid="3902387296149848324">"Растући, опадајући или поновљени низ цифара није дозвољен"</string>
diff --git a/tests/CarDeveloperOptions/res/values-sv/config.xml b/tests/CarDeveloperOptions/res/values-sv/config.xml
index b05e073..04c8bf8 100644
--- a/tests/CarDeveloperOptions/res/values-sv/config.xml
+++ b/tests/CarDeveloperOptions/res/values-sv/config.xml
@@ -17,5 +17,4 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="config_backup_settings_label" msgid="5230713622702899641"></string>
-    <string name="config_grayscale_settings_intent" msgid="7661066612238245852"></string>
 </resources>
diff --git a/tests/CarDeveloperOptions/res/values-sv/strings.xml b/tests/CarDeveloperOptions/res/values-sv/strings.xml
index df5d7ac..b22c73f 100644
--- a/tests/CarDeveloperOptions/res/values-sv/strings.xml
+++ b/tests/CarDeveloperOptions/res/values-sv/strings.xml
@@ -668,7 +668,6 @@
       <item quantity="other">Får inte innehålla fler än <xliff:g id="NUMBER_1">%d</xliff:g> siffror</item>
       <item quantity="one">Får inte innehålla fler än <xliff:g id="NUMBER_0">%d</xliff:g> siffra</item>
     </plurals>
-    <string name="lockpassword_pin_contains_non_digits" msgid="1501729742023195556">"Får endast innehålla siffrorna 0–9"</string>
     <string name="lockpassword_pin_recently_used" msgid="5556148774911741103">"Enhetsadministratören tillåter inte att en pinkod som använts nyligen används igen"</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="2752037810571200697">"IT-adminstratören har blockerat de vanligaste pinkoderna. Testa en annan pinkod."</string>
     <string name="lockpassword_illegal_character" msgid="3577712947676261351">"Får inte innehålla ogiltiga tecken"</string>
@@ -699,6 +698,10 @@
       <item quantity="other">Lösenordet måste innehålla minst <xliff:g id="COUNT">%d</xliff:g> tecken som inte är bokstäver</item>
       <item quantity="one">Lösenordet måste innehålla minst ett tecken som inte är en bokstav</item>
     </plurals>
+    <plurals name="lockpassword_password_requires_nonnumerical" formatted="false" msgid="6943707011152180150">
+      <item quantity="other">Lösenordet måste innehålla minst <xliff:g id="COUNT">%d</xliff:g> tecken som inte är en siffra</item>
+      <item quantity="one">Lösenordet måste innehålla minst ett tecken som inte är en siffra</item>
+    </plurals>
     <string name="lockpassword_password_recently_used" msgid="6944729699375087431">"Enhetsadministratören tillåter inte att ett lösenord som använts nyligen används igen"</string>
     <string name="lockpassword_password_blacklisted_by_admin" msgid="4988166770148440755">"IT-adminstratören har blockerat de vanligaste lösenorden. Testa ett annat lösenord."</string>
     <string name="lockpassword_pin_no_sequential_digits" msgid="3902387296149848324">"En stigande, fallande eller upprepad siffersekvens är inte tillåten"</string>
diff --git a/tests/CarDeveloperOptions/res/values-sw/config.xml b/tests/CarDeveloperOptions/res/values-sw/config.xml
index b05e073..04c8bf8 100644
--- a/tests/CarDeveloperOptions/res/values-sw/config.xml
+++ b/tests/CarDeveloperOptions/res/values-sw/config.xml
@@ -17,5 +17,4 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="config_backup_settings_label" msgid="5230713622702899641"></string>
-    <string name="config_grayscale_settings_intent" msgid="7661066612238245852"></string>
 </resources>
diff --git a/tests/CarDeveloperOptions/res/values-sw/strings.xml b/tests/CarDeveloperOptions/res/values-sw/strings.xml
index 3b4a247..c33fe8d 100644
--- a/tests/CarDeveloperOptions/res/values-sw/strings.xml
+++ b/tests/CarDeveloperOptions/res/values-sw/strings.xml
@@ -574,7 +574,7 @@
     <string name="unlock_set_unlock_pin_summary" msgid="8076921768675948228">"Wastani hadi usalama wa juu"</string>
     <string name="unlock_set_unlock_password_title" msgid="2092949379238466560">"Nenosiri"</string>
     <string name="unlock_set_unlock_password_summary" msgid="7042787631866059147">"Usalama wa juu"</string>
-    <string name="unlock_set_do_later_title" msgid="2939110070503695956">"Si sasa"</string>
+    <string name="unlock_set_do_later_title" msgid="2939110070503695956">"Siyo sasa"</string>
     <string name="current_screen_lock" msgid="398328543694154510">"Mbinu inayotumika kufunga skrini"</string>
     <string name="fingerprint_unlock_set_unlock_pattern" msgid="132337696546315927">"Alama ya kidole + Mchoro"</string>
     <string name="fingerprint_unlock_set_unlock_pin" msgid="886426673328906002">"Alama ya kidole + PIN"</string>
@@ -668,7 +668,6 @@
       <item quantity="other">Lazima iwe na chini ya tarakimu <xliff:g id="NUMBER_1">%d</xliff:g></item>
       <item quantity="one">Lazima iwe na chini ya tarakimu <xliff:g id="NUMBER_0">%d</xliff:g></item>
     </plurals>
-    <string name="lockpassword_pin_contains_non_digits" msgid="1501729742023195556">"Ni lazima iwe na tarakimu 0-9 pekee"</string>
     <string name="lockpassword_pin_recently_used" msgid="5556148774911741103">"Msimamizi wa kifaa haruhusu utumie PIN uliyotumia hivi majuzi"</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="2752037810571200697">"PIN zinazotumika zaidi zimezuiwa na msimamizi wako wa Tehama. Jaribu PIN tofauti."</string>
     <string name="lockpassword_illegal_character" msgid="3577712947676261351">"Hali hii haiwezi kujumuisha kibambo kisichoruhusiwa"</string>
@@ -699,6 +698,10 @@
       <item quantity="other">Ni lazima liwe na angalau vibambo <xliff:g id="COUNT">%d</xliff:g> ambavyo si herufi</item>
       <item quantity="one">Ni lazima liwe na angalau kibambo 1 ambacho si herufi</item>
     </plurals>
+    <plurals name="lockpassword_password_requires_nonnumerical" formatted="false" msgid="6943707011152180150">
+      <item quantity="other">Ni lazima liwe na angalau herufi <xliff:g id="COUNT">%d</xliff:g> ambazo si tarakimu</item>
+      <item quantity="one">Ni lazima liwe na angalau herufi moja ambayo si tarakimu</item>
+    </plurals>
     <string name="lockpassword_password_recently_used" msgid="6944729699375087431">"Msimamizi wa kifaa haruhusu kutumia nenosiri ulilotumia hivi majuzi"</string>
     <string name="lockpassword_password_blacklisted_by_admin" msgid="4988166770148440755">"Manenosiri yanayotumika sana yamezuiwa na msimamizi wako wa Tehama. Jaribu nenosiri tofauti."</string>
     <string name="lockpassword_pin_no_sequential_digits" msgid="3902387296149848324">"Haturuhusi mpangilio wa kupanda, kushuka au kujirudia kwa tarakimu"</string>
@@ -2871,7 +2874,7 @@
     <string name="user_setup_dialog_message" msgid="2988559933258353919">"Hakikisha kuwa mtu huyu anaweza kuchukua kifaa na kuweka mapendeleo yake"</string>
     <string name="user_setup_profile_dialog_message" msgid="7611900802824048526">"Ungependa kuweka wasifu sasa?"</string>
     <string name="user_setup_button_setup_now" msgid="4941459406266856176">"Mwongeze sasa"</string>
-    <string name="user_setup_button_setup_later" msgid="6596031428556518752">"Si sasa"</string>
+    <string name="user_setup_button_setup_later" msgid="6596031428556518752">"Siyo sasa"</string>
     <string name="user_cannot_manage_message" product="tablet" msgid="7108992906553210763">"Mmiliki wa kompyuta kibao pekee ndiye anayeweza kudhibiti watumiaji."</string>
     <string name="user_cannot_manage_message" product="default" msgid="915260531390608092">"Mmiliki wa simu pekee ndiye anayeweza kudhibiti watumiaji."</string>
     <string name="user_cannot_add_accounts_message" msgid="5993561303748749097">"Wasifu zilizodhibitiwa haziwezi kuongeza akaunti"</string>
diff --git a/tests/CarDeveloperOptions/res/values-ta/config.xml b/tests/CarDeveloperOptions/res/values-ta/config.xml
index b05e073..04c8bf8 100644
--- a/tests/CarDeveloperOptions/res/values-ta/config.xml
+++ b/tests/CarDeveloperOptions/res/values-ta/config.xml
@@ -17,5 +17,4 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="config_backup_settings_label" msgid="5230713622702899641"></string>
-    <string name="config_grayscale_settings_intent" msgid="7661066612238245852"></string>
 </resources>
diff --git a/tests/CarDeveloperOptions/res/values-ta/strings.xml b/tests/CarDeveloperOptions/res/values-ta/strings.xml
index 299793c..e24bd36 100644
--- a/tests/CarDeveloperOptions/res/values-ta/strings.xml
+++ b/tests/CarDeveloperOptions/res/values-ta/strings.xml
@@ -31,8 +31,8 @@
     <string name="dev_settings_disabled_warning" msgid="3198732189395396721">"முதலில் டெவெலப்பர் விருப்பங்களை இயக்கவும்."</string>
     <string name="header_category_wireless_networks" msgid="8968405993937795898">"வயர்லெஸ் &amp; நெட்வொர்க்குகள்"</string>
     <string name="header_category_system" msgid="4045988717359334410">"சிஸ்டம்"</string>
-    <string name="radio_info_data_connection_enable" msgid="2554249462719717119">"டேட்டா இணைப்பை இயக்கு"</string>
-    <string name="radio_info_data_connection_disable" msgid="2430609627397999371">"டேட்டா இணைப்பை முடக்கு"</string>
+    <string name="radio_info_data_connection_enable" msgid="2554249462719717119">"தரவு இணைப்பை இயக்கு"</string>
+    <string name="radio_info_data_connection_disable" msgid="2430609627397999371">"தரவு இணைப்பை முடக்கு"</string>
     <string name="volte_provisioned_switch_string" msgid="6326756678226686704">"VoLTE ஒதுக்கீட்டுக் கொடி இயக்கத்தில்"</string>
     <string name="vt_provisioned_switch_string" msgid="7458479879009293613">"வீடியோ அழைப்பு அமைக்கப்பட்டது"</string>
     <string name="wfc_provisioned_switch_string" msgid="5446697646596639516">"வைஃபை அழைப்பு அமைக்கப்பட்டது"</string>
@@ -162,7 +162,7 @@
     <string name="bluetooth_map_request" msgid="34345631620551756">"செய்திக்கான அணுகல் கோரிக்கை"</string>
     <string name="bluetooth_map_acceptance_dialog_text" msgid="736507842082640410">"உங்கள் செய்திகளை %1$s அணுக விரும்புகிறது. %2$s க்கு அணுகலை வழங்கவா?"</string>
     <string name="bluetooth_sap_request" msgid="6318039677671263261">"SIM அணுகல் கோரிக்கை"</string>
-    <string name="bluetooth_sap_acceptance_dialog_text" msgid="1909352413109340355">"<xliff:g id="DEVICE_NAME_0">%1$s</xliff:g> உங்கள் சிம் கார்டை அணுக விரும்புகிறது. சிம் கார்டிற்கு அணுகல் வழங்குவது இணைப்பின் போது, உங்கள் சாதனத்தின் டேட்டா இணைப்பை முடக்கும். <xliff:g id="DEVICE_NAME_1">%2$s?</xliff:g>க்கு அணுகல் வழங்கவும்"</string>
+    <string name="bluetooth_sap_acceptance_dialog_text" msgid="1909352413109340355">"<xliff:g id="DEVICE_NAME_0">%1$s</xliff:g> உங்கள் சிம் கார்டை அணுக விரும்புகிறது. சிம் கார்டிற்கு அணுகல் வழங்குவது இணைப்பின் போது, உங்கள் சாதனத்தின் தரவு இணைப்பை முடக்கும். <xliff:g id="DEVICE_NAME_1">%2$s?</xliff:g>க்கு அணுகல் வழங்கவும்"</string>
     <string name="bluetooth_device_name_summary" msgid="8661066392056595005">"பிற சாதனங்களில் “<xliff:g id="DEVICE_NAME">^1</xliff:g>” எனத் தெரியும்"</string>
     <string name="bluetooth_off_footer" msgid="7658444560543730571">"பிற சாதனங்களுடன் இணைக்க, புளூடூத்தை ஆன் செய்யவும்."</string>
     <string name="bluetooth_paired_device_title" msgid="8361860197780425286">"உங்கள் சாதனங்கள்"</string>
@@ -312,7 +312,7 @@
     <string name="roaming" msgid="8860308342135146004">"ரோமிங்"</string>
     <string name="roaming_enable" msgid="2108142024297441116">"ரோமிங்கின் போது டேட்டா சேவைகளுடன் இணை"</string>
     <string name="roaming_disable" msgid="1915440242079953809">"ரோமிங்கின் போது டேட்டா சேவைகளுடன் இணை"</string>
-    <string name="roaming_reenable_message" msgid="8388505868655113258">"உங்களுடைய உள்ளூர் நெட்வொர்க்கில் தரவு ரோமிங்கை முடக்கியுள்ளதால் உங்கள் டேட்டா இணைப்பை இழந்துவிட்டீர்கள்."</string>
+    <string name="roaming_reenable_message" msgid="8388505868655113258">"உங்களுடைய உள்ளூர் நெட்வொர்க்கில் தரவு ரோமிங்கை முடக்கியுள்ளதால் உங்கள் தரவு இணைப்பை இழந்துவிட்டீர்கள்."</string>
     <string name="roaming_turn_it_on_button" msgid="4370846458830537578">"இதை இயக்கவும்"</string>
     <string name="roaming_warning" msgid="5488050911277592868">"கட்டணம் விதிக்கப்படலாம்."</string>
     <string name="roaming_warning_multiuser" product="tablet" msgid="7090388691615686893">"நீங்கள் தரவு ரோமிங்கை அனுமதிக்கும்போது, குறிப்பிட்ட ரோமிங் பேமெண்ட்கள் உங்களுக்கு விதிக்கப்படலாம்!\n\nஅமைப்பானது, டேப்லெட்டில் உள்ள அனைவரையும் பாதிக்கும்."</string>
@@ -668,7 +668,6 @@
       <item quantity="other"><xliff:g id="NUMBER_1">%d</xliff:g> இலக்கங்களை விடக் குறைவாக இருக்க வேண்டும்</item>
       <item quantity="one"><xliff:g id="NUMBER_0">%d</xliff:g> இலக்கத்தை விடக் குறைவாக இருக்க வேண்டும்</item>
     </plurals>
-    <string name="lockpassword_pin_contains_non_digits" msgid="1501729742023195556">"0-9 இலக்கங்கள் மட்டுமே இருக்க வேண்டும்"</string>
     <string name="lockpassword_pin_recently_used" msgid="5556148774911741103">"சாதன நிர்வாகி சமீபத்திய பின்னை பயன்படுத்துவதை அனுமதிக்கவில்லை"</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="2752037810571200697">"IT நிர்வாகியால், பொதுவான பின்கள் தடை செய்யப்பட்டுள்ளன. வேறொரு பின்னை முயலவும்."</string>
     <string name="lockpassword_illegal_character" msgid="3577712947676261351">"இதில் தவறான எழுத்துக்குறி இருக்கக்கூடாது"</string>
@@ -699,6 +698,10 @@
       <item quantity="other">குறைந்தது எழுத்து அல்லாத <xliff:g id="COUNT">%d</xliff:g> குறிகள் இருக்க வேண்டும்</item>
       <item quantity="one">குறைந்தது எழுத்து அல்லாத 1 குறி இருக்க வேண்டும்</item>
     </plurals>
+    <plurals name="lockpassword_password_requires_nonnumerical" formatted="false" msgid="6943707011152180150">
+      <item quantity="other">குறைந்தது எண் அல்லாத <xliff:g id="COUNT">%d</xliff:g> எழுத்துகளாவது இருக்க வேண்டும்</item>
+      <item quantity="one">குறைந்தது எண் அல்லாத 1 எழுத்தாவது இருக்க வேண்டும்</item>
+    </plurals>
     <string name="lockpassword_password_recently_used" msgid="6944729699375087431">"சாதன நிர்வாகி சமீபத்திய கடவுச்சொல்லைப் பயன்படுத்துவதை அனுமதிக்கவில்லை"</string>
     <string name="lockpassword_password_blacklisted_by_admin" msgid="4988166770148440755">"IT நிர்வாகியால், பொதுவான கடவுச்சொற்கள் தடை செய்யப்பட்டுள்ளன. வேறொரு கடவுச்சொல்லை முயலவும்."</string>
     <string name="lockpassword_pin_no_sequential_digits" msgid="3902387296149848324">"இலக்கங்கள் ஏறுவரிசையில், இறங்குவரிசையில் அல்லது ஒரே இலக்கத்தைப் பயன்படுத்துவது அனுமதிக்கப்படவில்லை"</string>
@@ -1236,7 +1239,7 @@
     <string name="wallpaper_settings_title" msgid="347390905813529607">"வால்பேப்பர்"</string>
     <string name="style_and_wallpaper_settings_title" msgid="8898539141152705754">"ஸ்டைல்கள் &amp; வால்பேப்பர்கள்"</string>
     <string name="wallpaper_settings_summary_default" msgid="2626880032742784599">"இயல்பு"</string>
-    <string name="wallpaper_settings_summary_custom" msgid="8950504698015331202">"பிரத்தியேகம்"</string>
+    <string name="wallpaper_settings_summary_custom" msgid="8950504698015331202">"தனிப்பயன்"</string>
     <string name="wallpaper_suggestion_title" msgid="3012130414886743201">"வால்பேப்பரை மாற்று"</string>
     <string name="wallpaper_suggestion_summary" msgid="4247262938988875842">"திரையைப் பிரத்தியேகமாக்கு"</string>
     <string name="wallpaper_settings_fragment_title" msgid="1503701065297188901">"வால்பேப்பர் தேர்வு"</string>
@@ -2040,7 +2043,7 @@
     <string name="display_category_title" msgid="545168481672250195">"திரை அமைப்பு"</string>
     <string name="interaction_control_category_title" msgid="8775039211811947683">"ஊடாடல் கட்டுப்பாடுகள்"</string>
     <string name="user_installed_services_category_title" msgid="4288689493753221319">"பதிவிறக்கிய சேவைகள்"</string>
-    <string name="experimental_category_title" msgid="3797000069740110717">"பரிசோதனை முயற்சி"</string>
+    <string name="experimental_category_title" msgid="3797000069740110717">"சோதனை முயற்சி"</string>
     <string name="feature_flags_dashboard_title" msgid="3153034144122754381">"அம்சங்களை மாற்றுதல்"</string>
     <string name="talkback_title" msgid="3717960404234260050">"Talkback"</string>
     <string name="talkback_summary" msgid="6602857105831641574">"திரைப் படிப்பான் முக்கியமாக பார்வையற்றோர் மற்றும் பார்வைக் குறைபாடு உள்ளவர்களுக்காக வடிவமைக்கப்பட்டது"</string>
@@ -2147,7 +2150,7 @@
     <string name="captioning_locale" msgid="4734464353806207943">"மொழி"</string>
     <string name="captioning_text_size" msgid="1707122517246408084">"உரையின் அளவு"</string>
     <string name="captioning_preset" msgid="7429888317480872337">"தலைப்பின் நடை"</string>
-    <string name="captioning_custom_options_title" msgid="4530479671071326732">"பிரத்தியேக விருப்பங்கள்"</string>
+    <string name="captioning_custom_options_title" msgid="4530479671071326732">"தனிப்பயன் விருப்பங்கள்"</string>
     <string name="captioning_background_color" msgid="2434458880326292180">"பின்புல வண்ணம்"</string>
     <string name="captioning_background_opacity" msgid="8178926599201811936">"பின்னணி ஒளிபுகாத்தன்மை"</string>
     <string name="captioning_window_color" msgid="8696903405657599896">"தலைப்பு சாளரத்தின் வண்ணம்"</string>
@@ -2616,7 +2619,7 @@
     <string name="sync_gmail" msgid="4457967084840001296">"Gmail"</string>
     <string name="sync_calendar" msgid="6573708019827519372">"கேலெண்டர்"</string>
     <string name="sync_contacts" msgid="5687434785723746534">"தொடர்புகள்"</string>
-    <string name="sync_plug" msgid="6703804441408427257"><font fgcolor="#ffffffff">"Google ஒத்திசைவுக்கு வரவேற்கிறோம்!"</font>" \nநீங்கள் எங்கிருந்தாலும் உங்கள் தொடர்புகள், அப்பாயிண்ட்மெண்ட்டுகள் மற்றும் பலவற்றுக்கான அணுகலை அனுமதிப்பதற்காக Google தரவை ஒத்திசைக்கிறது."</string>
+    <string name="sync_plug" msgid="6703804441408427257"><font fgcolor="#ffffffff">"Google ஒத்திசைவுக்கு வரவேற்கிறோம்!"</font>" \nநீங்கள் எங்கிருந்தாலும் உங்கள் தொடர்புகள், சந்திப்புகள் மற்றும் பலவற்றுக்கான அணுகலை அனுமதிப்பதற்காக Google தரவை ஒத்திசைக்கிறது."</string>
     <string name="header_application_sync_settings" msgid="4581847153669774489">"ஆப்ஸ் ஒத்திசைவு அமைப்பு"</string>
     <string name="header_data_and_synchronization" msgid="400831816068697286">"தரவு &amp; ஒத்திசைத்தல்"</string>
     <string name="preference_change_password_title" msgid="7243527448378789274">"கடவுச்சொல்லை மாற்று"</string>
@@ -3198,8 +3201,8 @@
     <string name="zen_mode_restrict_notifications_hide_summary" msgid="1449301153755270168">"அறிவிப்புகளைப் பார்க்கவோ கேட்கவோ மாட்டீர்கள்"</string>
     <string name="zen_mode_restrict_notifications_hide_footer" msgid="7617688597593946765">"புதிய அல்லது ஏற்கனவே இருக்கும் அறிவிப்புகளை உங்கள் மொபைல் காட்டாது, அதிர்வுறாது அல்லது ஒலி எழுப்பாது. உங்கள் மொபைலின் செயல்பாடு மற்றும் நிலை தொடர்பான முக்கிய அறிவிப்புகள் தொடர்ந்து தோன்றும் என்பதை நினைவில்கொள்ளவும்.\n\nதொந்தரவு செய்ய வேண்டாம் என்பதை நீங்கள் முடக்கும் போது, உங்கள் திரையின் மேலிருந்து கீழாக ஸ்வைப் செய்வதன் மூலம் தவறிய அழைப்புகள் எவை என்பதைப் பார்க்கலாம்."</string>
     <string name="zen_mode_restrict_notifications_custom" msgid="3167252482570424133">"பிரத்தியேகமானது"</string>
-    <string name="zen_mode_restrict_notifications_enable_custom" msgid="6376983315529894440">"பிரத்தியேக அமைப்பை இயக்கு"</string>
-    <string name="zen_mode_restrict_notifications_disable_custom" msgid="8004212081465043044">"பிரத்தியேக அமைப்பை அகற்று"</string>
+    <string name="zen_mode_restrict_notifications_enable_custom" msgid="6376983315529894440">"தனிப்பயன் அமைப்பை இயக்கு"</string>
+    <string name="zen_mode_restrict_notifications_disable_custom" msgid="8004212081465043044">"தனிப்பயன் அமைப்பை அகற்று"</string>
     <string name="zen_mode_restrict_notifications_summary_muted" msgid="1075196788469381282">"அறிவிப்புகள் வரும்போது ஒலியெழுப்ப வேண்டாம்"</string>
     <string name="zen_mode_restrict_notifications_summary_custom" msgid="4982187708274505748">"பகுதியளவு மறைக்கப்பட்டவை"</string>
     <string name="zen_mode_restrict_notifications_summary_hidden" msgid="7637206880685474111">"அறிவிப்புகள் வரும்போது காட்டவோ ஒலியெழுப்பவோ வேண்டாம்"</string>
@@ -3209,7 +3212,7 @@
     <string name="zen_mode_block_effect_sound" msgid="1499243540186357631">"ஒலியையும் அதிர்வையும் முடக்கு"</string>
     <string name="zen_mode_block_effect_intent" msgid="5666951244667422668">"திரையை ஆன் செய்யாதே"</string>
     <string name="zen_mode_block_effect_light" msgid="8679333758037487644">"ஒளியை மிளிரச் செய்யாதே"</string>
-    <string name="zen_mode_block_effect_peek" msgid="6075662813575910221">"திரையில் அறிவிப்புகளைப் பாப்-அப் செய்யாதே"</string>
+    <string name="zen_mode_block_effect_peek" msgid="6075662813575910221">"திரையில் அறிவிப்புகளைப் பாப் அப் செய்யாதே"</string>
     <string name="zen_mode_block_effect_status" msgid="6516614225115681068">"திரையின் மேற்பகுதியில் \'நிலைப் பட்டி\' ஐகான்களை மறை"</string>
     <string name="zen_mode_block_effect_badge" msgid="3891743027347075136">"ஆப்ஸ் ஐகான்களில் அறிவிப்புப் புள்ளிகளை மறை"</string>
     <string name="zen_mode_block_effect_ambient" msgid="6382013125863616197">"அறிவிப்புகள் வரும்போது சாதனத்தை எழுப்ப வேண்டாம்"</string>
@@ -3232,7 +3235,7 @@
     <string name="zen_mode_settings_dnd_automatic_rule" msgid="2843297614114625408">"திட்ட அட்டவணையின் (<xliff:g id="RULE_NAME">%s</xliff:g>) மூலம் ‘தொந்தரவு செய்ய வேண்டாம்’ தானாக ஆன் செய்யப்பட்டது"</string>
     <string name="zen_mode_settings_dnd_automatic_rule_app" msgid="5103454923160912313">"(<xliff:g id="APP_NAME">%s</xliff:g>) ஆப்ஸின் மூலம், ‘தொந்தரவு செய்ய வேண்டாம்’ தானாக ஆன் செய்யப்பட்டது"</string>
     <string name="zen_mode_settings_dnd_custom_settings_footer" msgid="6335108298640066560">"பிரத்தியேக அமைப்புகளுடன் <xliff:g id="RULE_NAMES">%s</xliff:g> என்பதற்குத் \'தொந்தரவு செய்ய வேண்டாம்’ ஆன் நிலையில் உள்ளது."</string>
-    <string name="zen_mode_settings_dnd_custom_settings_footer_link" msgid="4007974052885089379"><annotation id="link">" பிரத்தியேக அமைப்புகளைக் காட்டு"</annotation></string>
+    <string name="zen_mode_settings_dnd_custom_settings_footer_link" msgid="4007974052885089379"><annotation id="link">" தனிப்பயன் அமைப்புகளைக் காட்டு"</annotation></string>
     <string name="zen_interruption_level_priority" msgid="9178419297408319234">"முக்கியமானவை மட்டும்"</string>
     <string name="zen_mode_and_condition" msgid="4123722186007123567">"<xliff:g id="ZEN_MODE">%1$s</xliff:g>. <xliff:g id="EXIT_CONDITION">%2$s</xliff:g>"</string>
     <string name="zen_mode_sound_summary_on_with_info" msgid="2539952366467518398">"ஆன் / <xliff:g id="ID_1">%1$s</xliff:g>"</string>
@@ -3261,7 +3264,7 @@
     <string name="zen_sound_one_allowed" msgid="2417988417080579980">"ஒலியடக்கப்பட்டது, ஆனால் <xliff:g id="SOUND_TYPE">%1$s</xliff:g> அனுமதிக்கப்பட்டுள்ளது"</string>
     <string name="zen_sound_two_allowed" msgid="299344481401823614">"ஒலியடக்கப்பட்டது, ஆனால் <xliff:g id="SOUND_TYPE_0">%1$s</xliff:g> மற்றும் <xliff:g id="SOUND_TYPE_1">%2$s</xliff:g> அனுமதிக்கப்பட்டுள்ளன"</string>
     <string name="zen_sound_three_allowed" msgid="8374564453060696012">"ஒலியடக்கப்பட்டது, ஆனால் <xliff:g id="SOUND_TYPE_0">%1$s</xliff:g>, <xliff:g id="SOUND_TYPE_1">%2$s</xliff:g> மற்றும் <xliff:g id="SOUND_TYPE_2">%3$s</xliff:g> அனுமதிக்கப்பட்டுள்ளன"</string>
-    <string name="zen_custom_settings_dialog_title" msgid="908049494676219236">"பிரத்தியேக அமைப்புகள்"</string>
+    <string name="zen_custom_settings_dialog_title" msgid="908049494676219236">"தனிப்பயன் அமைப்புகள்"</string>
     <string name="zen_custom_settings_dialog_review_schedule" msgid="2247761749333893513">"திட்ட அட்டவணையைச் சரிபார்"</string>
     <string name="zen_custom_settings_dialog_ok" msgid="3572754922025853427">"புரிந்தது"</string>
     <string name="zen_custom_settings_notifications_header" msgid="7469592764589354302">"அறிவிப்புகள்"</string>
@@ -3285,7 +3288,7 @@
     <string name="work_sync_dialog_title" msgid="4799120971202956837">"ஒலிகளை மாற்றியமைக்கவா?"</string>
     <string name="work_sync_dialog_yes" msgid="2110726233746476066">"மாற்று"</string>
     <string name="work_sync_dialog_message" msgid="944233463059129156">"உங்கள் தனிப்பட்ட சுயவிவர ஒலிகள், உங்கள் பணி விவரத்திற்குப் பயன்படுத்தப்படும்"</string>
-    <string name="ringtones_install_custom_sound_title" msgid="210551218424553671">"பிரத்தியேக ஒலியைச் சேர்க்கவா?"</string>
+    <string name="ringtones_install_custom_sound_title" msgid="210551218424553671">"தனிப்பயன் ஒலியைச் சேர்க்கவா?"</string>
     <string name="ringtones_install_custom_sound_content" msgid="6683649115132255452">"இந்தக் கோப்பு, <xliff:g id="FOLDER_NAME">%s</xliff:g> கோப்புறைக்கு நகலெடுக்கப்படும்"</string>
     <string name="ringtones_category_preference_title" msgid="4491932700769815470">"ரிங்டோன்கள்"</string>
     <string name="other_sound_category_preference_title" msgid="2045757472469840859">"பிற ஒலிகள் மற்றும் அதிர்வுகள்"</string>
@@ -3336,7 +3339,7 @@
     <string name="notification_importance_low" msgid="7609797151662295364">"ஒலிக்காமல் காட்டும்"</string>
     <string name="notification_importance_default" msgid="4091563759103917166">"ஒலியெழுப்பும்"</string>
     <string name="notification_importance_high" msgid="7973764540402436656">"ஒலியெழுப்பி, திரையில் காட்டும்"</string>
-    <string name="notification_importance_high_silent" msgid="3177662759865661155">"திரையில் பாப்-அப் செய்யும்"</string>
+    <string name="notification_importance_high_silent" msgid="3177662759865661155">"திரையில் பாப் அப் செய்யும்"</string>
     <string name="notification_importance_min_title" msgid="705872537330744154">"சிறிதாக்கு"</string>
     <string name="notification_importance_low_title" msgid="2956199021781786232">"நடுத்தர முக்கியத்துவம்"</string>
     <string name="notification_importance_default_title" msgid="7985549807203332482">"அதிக முக்கியத்துவம்"</string>
@@ -3441,9 +3444,9 @@
     <string name="zen_mode_delete_rule_confirmation" msgid="2646596466259025978">"\"<xliff:g id="RULE">%1$s</xliff:g>\" விதியை நீக்கவா?"</string>
     <string name="zen_mode_delete_rule_button" msgid="611058106279881991">"நீக்கு"</string>
     <string name="zen_mode_rule_type_unknown" msgid="2819480113355191421">"தெரியாதது"</string>
-    <string name="zen_mode_app_set_behavior" msgid="8597398780262575571">"இந்த அமைப்புகளை இப்போது மாற்ற முடியாது. பிரத்தியேக செயல்பாட்டின் அடிப்படையில், ஆப்ஸின் (<xliff:g id="APP_NAME">%1$s</xliff:g>) மூலம் ’தொந்தரவு செய்ய வேண்டாம்’ தானாக ஆன் செய்யப்பட்டது."</string>
-    <string name="zen_mode_unknown_app_set_behavior" msgid="5666462954329932302">"இந்த அமைப்புகளை இப்போது மாற்ற முடியாது. பிரத்தியேக செயல்பாட்டின் அடிப்படையில், ஆப்ஸின் மூலம் ’தொந்தரவு செய்ய வேண்டாம்’ தானாக ஆன் செய்யப்பட்டது."</string>
-    <string name="zen_mode_qs_set_behavior" msgid="788646569296973998">"இந்த அமைப்புகளை இப்போது மாற்ற முடியாது. பிரத்தியேக செயல்பாட்டின் அடிப்படையில் ’தொந்தரவு செய்ய வேண்டாம்’ கைமுறையாக ஆன் செய்யப்பட்டது."</string>
+    <string name="zen_mode_app_set_behavior" msgid="8597398780262575571">"இந்த அமைப்புகளை இப்போது மாற்ற முடியாது. தனிப்பயன் செயல்பாட்டின் அடிப்படையில், ஆப்ஸின் (<xliff:g id="APP_NAME">%1$s</xliff:g>) மூலம் ’தொந்தரவு செய்ய வேண்டாம்’ தானாக ஆன் செய்யப்பட்டது."</string>
+    <string name="zen_mode_unknown_app_set_behavior" msgid="5666462954329932302">"இந்த அமைப்புகளை இப்போது மாற்ற முடியாது. தனிப்பயன் செயல்பாட்டின் அடிப்படையில், ஆப்ஸின் மூலம் ’தொந்தரவு செய்ய வேண்டாம்’ தானாக ஆன் செய்யப்பட்டது."</string>
+    <string name="zen_mode_qs_set_behavior" msgid="788646569296973998">"இந்த அமைப்புகளை இப்போது மாற்ற முடியாது. தனிப்பயன் செயல்பாட்டின் அடிப்படையில் ’தொந்தரவு செய்ய வேண்டாம்’ கைமுறையாக ஆன் செய்யப்பட்டது."</string>
     <string name="zen_schedule_rule_type_name" msgid="4516851728113801329">"நேரம்"</string>
     <string name="zen_schedule_rule_enabled_toast" msgid="1742354493045049048">"குறிப்பிட்ட நேரங்களில் தொந்தரவு செய்ய வேண்டாம் என்பதை இயக்கும் தானியங்கு விதி"</string>
     <string name="zen_event_rule_type_name" msgid="7467729997336583342">"நிகழ்வு"</string>
@@ -3503,7 +3506,7 @@
     <string name="zen_mode_reminders" msgid="7560664194610054038">"நினைவூட்டல்களை அனுமதி"</string>
     <string name="zen_mode_reminders_list" msgid="7347061314032326677">"நினைவூட்டல்கள்"</string>
     <string name="zen_mode_events" msgid="5784076928339534984">"நிகழ்வுகளை அனுமதி"</string>
-    <string name="zen_mode_bypassing_apps" msgid="3080739479028713449">"ஆப்ஸை மீறிச் செயல்பட அனுமதிக்கின்றன"</string>
+    <string name="zen_mode_bypassing_apps" msgid="3080739479028713449">"ஆப்ஸை மேலெழுத அனுமதிக்கின்றன"</string>
     <string name="zen_mode_bypassing_apps_title" msgid="2115024664615538847">"ஆப்ஸ் விதிவிலக்குகள்"</string>
     <plurals name="zen_mode_bypassing_apps_subtext" formatted="false" msgid="8723144434730871572">
       <item quantity="other">‘தொந்தரவு செய்ய வேண்டாம்’ அம்சம் ஆன் செய்யப்பட்டிருந்தாலும் <xliff:g id="NUMBER">%1$d</xliff:g> ஆப்ஸில் இருந்து அறிவிப்புகள் வரும்</item>
@@ -3539,7 +3542,7 @@
     <string name="zen_mode_summary_alarms_only_by_time" msgid="2462898862757904560">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> வரை அலாரங்கள் மட்டும் என மாற்று"</string>
     <string name="zen_mode_summary_always" msgid="2703276042913200837">"எப்போதும் குறுக்கிடு என மாற்று"</string>
     <string name="zen_mode_screen_on" msgid="7098470659072167219">"திரை ஆன் செய்யப்பட்டிருக்கும்போது"</string>
-    <string name="zen_mode_screen_on_summary" msgid="8275416649295357524">"\'தொந்தரவு செய்ய வேண்டாம்\' மூலம் ஒலியடக்கப்பட்ட அறிவிப்புகள் வரும்போது, திரையில் பாப்-அப் ஆவதுடன், நிலைப்பட்டி ஐகானைக் காட்டும்"</string>
+    <string name="zen_mode_screen_on_summary" msgid="8275416649295357524">"\'தொந்தரவு செய்ய வேண்டாம்\' மூலம் ஒலியடக்கப்பட்ட அறிவிப்புகள் வரும்போது, திரையில் பாப் அப் ஆவதுடன், நிலைப்பட்டி ஐகானைக் காட்டும்"</string>
     <string name="zen_mode_screen_off" msgid="84211490206459038">"திரை ஆஃப் செய்யப்பட்டிருக்கும்போது"</string>
     <string name="zen_mode_screen_off_summary" msgid="8592179073243001267">"’தொந்தரவு செய்ய வேண்டாம்’ அம்சத்தின் மூலம் ஒலியடக்கப்பட்ட அறிவிப்புகள் வரும்போது, திரை ஆன் செய்யப்பட்டு, ஒளியை மிளிரச் செய்யட்டும்"</string>
     <string name="zen_mode_screen_off_summary_no_led" msgid="7255874108150630145">"’தொந்தரவு செய்ய வேண்டாம்’ அம்சத்தின் மூலம் ஒலியடக்கப்பட்ட அறிவிப்புகள் வரும்போது, திரையை ஆன் செய்யட்டும்."</string>
@@ -3557,7 +3560,7 @@
     <string name="screen_pinning_unlock_none" msgid="9183040569733226911">"திரையை விலக்கும்போது சாதனத்தைப் பூட்டு"</string>
     <string name="opening_paragraph_delete_profile_unknown_company" msgid="2350380017136403670">"இந்தப் பணிக் கணக்கை நிர்வகிப்பது:"</string>
     <string name="managing_admin" msgid="3212584016377581608">"நிர்வகிப்பது: <xliff:g id="ADMIN_APP_LABEL">%s</xliff:g>"</string>
-    <string name="experimental_preference" msgid="5903223408406906322">"(பரிசோதனை முயற்சி)"</string>
+    <string name="experimental_preference" msgid="5903223408406906322">"(சோதனை முயற்சி)"</string>
     <string name="encryption_interstitial_header" msgid="3298397268731647519">"பாதுகாப்பான தொடக்கம்"</string>
     <string name="encryption_continue_button" msgid="2808797091460167842">"தொடர்"</string>
     <string name="encryption_interstitial_message_pin" msgid="6592265582286340307">"இந்தச் சாதனத்தைத் துவக்கும் முன், பின் தேவைப்படுமாறு அமைத்து, மேலும் பாதுகாக்கலாம். சாதனம் துவங்கும் வரை, அழைப்புகள், செய்திகள் அல்லது அலாரங்கள் உள்ளிட்ட அறிவிப்புகளை இதில் பெற முடியாது. \n\nஉங்கள் சாதனம் தொலைந்து போனாலோ திருடப்பட்டாலோ, அதில் உள்ள உங்கள் டேட்டாவைப் பாதுகாக்க இதைக் கடைபிடிக்கலாம். சாதனத்தைத் தொடங்கும் போது, பின்னைக் கேட்பதை அமைக்கவா?"</string>
@@ -4463,7 +4466,7 @@
     <string name="mobile_data_ap_mode_disabled" msgid="2452716524753472885">"விமானப் பயன்முறையின்போது கிடைக்காது"</string>
     <string name="force_desktop_mode" msgid="6973100177551040740">"கட்டாய டெஸ்க்டாப் பயன்முறை"</string>
     <string name="force_desktop_mode_summary" msgid="8865007610266954719">"இரண்டாம்நிலை திரைகளில் \'கட்டாயப் பரிசோதனை டெஸ்க்டாப்\' பயன்முறை"</string>
-    <string name="hwui_force_dark_title" msgid="3744825212652331461">"force-dark அம்சத்தை மீறிச் செயல்படுதல்"</string>
+    <string name="hwui_force_dark_title" msgid="3744825212652331461">"force-dark அம்சத்தை மேலெழுதுதல்"</string>
     <string name="hwui_force_dark_summary" msgid="2051891908674765817">"எப்போதும் இயக்கத்தில் இருக்குமாறு force-dark அம்சத்தை மேலெழுதுகிறது"</string>
     <string name="privacy_dashboard_title" msgid="8764930992456607513">"தனியுரிமை"</string>
     <string name="privacy_dashboard_summary" msgid="7916431309860824945">"அனுமதிகள், கணக்குச் செயல்பாடு, தனிப்பட்ட தரவு"</string>
diff --git a/tests/CarDeveloperOptions/res/values-te/config.xml b/tests/CarDeveloperOptions/res/values-te/config.xml
index b05e073..04c8bf8 100644
--- a/tests/CarDeveloperOptions/res/values-te/config.xml
+++ b/tests/CarDeveloperOptions/res/values-te/config.xml
@@ -17,5 +17,4 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="config_backup_settings_label" msgid="5230713622702899641"></string>
-    <string name="config_grayscale_settings_intent" msgid="7661066612238245852"></string>
 </resources>
diff --git a/tests/CarDeveloperOptions/res/values-te/strings.xml b/tests/CarDeveloperOptions/res/values-te/strings.xml
index 1fcf271..3414c85 100644
--- a/tests/CarDeveloperOptions/res/values-te/strings.xml
+++ b/tests/CarDeveloperOptions/res/values-te/strings.xml
@@ -668,7 +668,6 @@
       <item quantity="other"><xliff:g id="NUMBER_1">%d</xliff:g> కంటే తక్కువ అంకెలు తప్పక ఉండాలి</item>
       <item quantity="one"><xliff:g id="NUMBER_0">%d</xliff:g> కంటే తక్కువ అంకె తప్పక ఉండాలి</item>
     </plurals>
-    <string name="lockpassword_pin_contains_non_digits" msgid="1501729742023195556">"తప్పనిసరిగా 0-9 అంకెలను మాత్రమే కలిగి ఉండాలి"</string>
     <string name="lockpassword_pin_recently_used" msgid="5556148774911741103">"ఇటీవలి పిన్‌ని ఉపయోగించడానికి డివైజ్ నిర్వాహకులు అనుమతించరు"</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="2752037810571200697">"సాధారణ PINలను మా IT నిర్వాహకుడు బ్లాక్ చేసారు. వేరే PINని ప్రయత్నించండి."</string>
     <string name="lockpassword_illegal_character" msgid="3577712947676261351">"ఇందులో చెల్లని అక్షరం ఉండకూడదు"</string>
@@ -699,6 +698,10 @@
       <item quantity="other">తప్పనిసరిగా కనీసం <xliff:g id="COUNT">%d</xliff:g> వర్ణమాల యేతర అక్షరాలను కలిగి ఉండాలి</item>
       <item quantity="one">తప్పనిసరిగా కనీసం 1 వర్ణమాల యేతర అక్షరాన్ని కలిగి ఉండాలి</item>
     </plurals>
+    <plurals name="lockpassword_password_requires_nonnumerical" formatted="false" msgid="6943707011152180150">
+      <item quantity="other">తప్పనిసరిగా కనీసం <xliff:g id="COUNT">%d</xliff:g> సంఖ్యేతర అక్షరాలను కలిగి ఉండాలి</item>
+      <item quantity="one">తప్పనిసరిగా కనీసం 1 సంఖ్యేతర అక్షరాన్ని కలిగి ఉండాలి</item>
+    </plurals>
     <string name="lockpassword_password_recently_used" msgid="6944729699375087431">"ఇటీవలి పాస్‌వర్డ్‌ను ఉపయోగించడానికి పరికర నిర్వాహకులు అనుమతించరు"</string>
     <string name="lockpassword_password_blacklisted_by_admin" msgid="4988166770148440755">"సాధారణ పాస్‌వర్డ్‌లను మా IT నిర్వాహకుడు బ్లాక్ చేసారు. వేరే పాస్‌వర్డ్‌ని ప్రయత్నించండి."</string>
     <string name="lockpassword_pin_no_sequential_digits" msgid="3902387296149848324">"అంకెలను ఆరోహణ, అవరోహణ క్రమంలో లేదా ఒకే అంకెను వరుసగా పునరావృతంగా ఉపయోగించకూడదు"</string>
diff --git a/tests/CarDeveloperOptions/res/values-th/config.xml b/tests/CarDeveloperOptions/res/values-th/config.xml
index b05e073..04c8bf8 100644
--- a/tests/CarDeveloperOptions/res/values-th/config.xml
+++ b/tests/CarDeveloperOptions/res/values-th/config.xml
@@ -17,5 +17,4 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="config_backup_settings_label" msgid="5230713622702899641"></string>
-    <string name="config_grayscale_settings_intent" msgid="7661066612238245852"></string>
 </resources>
diff --git a/tests/CarDeveloperOptions/res/values-th/strings.xml b/tests/CarDeveloperOptions/res/values-th/strings.xml
index eb3a613..fdf64aa 100644
--- a/tests/CarDeveloperOptions/res/values-th/strings.xml
+++ b/tests/CarDeveloperOptions/res/values-th/strings.xml
@@ -668,7 +668,6 @@
       <item quantity="other">ต้องมีตัวเลขไม่เกิน <xliff:g id="NUMBER_1">%d</xliff:g> ตัว</item>
       <item quantity="one">ต้องมีตัวเลขไม่เกิน <xliff:g id="NUMBER_0">%d</xliff:g> ตัว</item>
     </plurals>
-    <string name="lockpassword_pin_contains_non_digits" msgid="1501729742023195556">"ต้องมีตัวเลข 0-9 เท่านั้น"</string>
     <string name="lockpassword_pin_recently_used" msgid="5556148774911741103">"ผู้ดูแลระบบอุปกรณ์ไม่อนุญาตให้ใช้ PIN ที่เพิ่งใช้ไป"</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="2752037810571200697">"ผู้ดูแลระบบไอทีบล็อก PIN ที่ไม่รัดกุม ลอง PIN อื่น"</string>
     <string name="lockpassword_illegal_character" msgid="3577712947676261351">"ต้องใช้อักขระที่ใช้ได้ทั้งหมด"</string>
@@ -699,6 +698,10 @@
       <item quantity="other">ต้องมีอักขระที่ไม่ใช่ตัวอักษรอย่างน้อย <xliff:g id="COUNT">%d</xliff:g> ตัว</item>
       <item quantity="one">ต้องมีอักขระที่ไม่ใช่ตัวอักษรอย่างน้อย 1 ตัว</item>
     </plurals>
+    <plurals name="lockpassword_password_requires_nonnumerical" formatted="false" msgid="6943707011152180150">
+      <item quantity="other">ต้องมีอักขระที่ไม่ใช่ตัวเลขอย่างน้อย <xliff:g id="COUNT">%d</xliff:g> ตัว</item>
+      <item quantity="one">ต้องมีอักขระที่ไม่ใช่ตัวเลขอย่างน้อย 1 ตัว</item>
+    </plurals>
     <string name="lockpassword_password_recently_used" msgid="6944729699375087431">"ผู้ดูแลระบบอุปกรณ์ไม่อนุญาตให้ใช้รหัสผ่านที่เพิ่งใช้ไป"</string>
     <string name="lockpassword_password_blacklisted_by_admin" msgid="4988166770148440755">"ผู้ดูแลระบบไอทีบล็อกรหัสผ่านที่ไม่รัดกุม ลองรหัสผ่านอื่น"</string>
     <string name="lockpassword_pin_no_sequential_digits" msgid="3902387296149848324">"ไม่อนุญาตให้เรียงจากน้อยไปมาก จากมากไปน้อย หรือเรียงลำดับตัวเลขที่ซ้ำกัน"</string>
diff --git a/tests/CarDeveloperOptions/res/values-tl/config.xml b/tests/CarDeveloperOptions/res/values-tl/config.xml
index b05e073..04c8bf8 100644
--- a/tests/CarDeveloperOptions/res/values-tl/config.xml
+++ b/tests/CarDeveloperOptions/res/values-tl/config.xml
@@ -17,5 +17,4 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="config_backup_settings_label" msgid="5230713622702899641"></string>
-    <string name="config_grayscale_settings_intent" msgid="7661066612238245852"></string>
 </resources>
diff --git a/tests/CarDeveloperOptions/res/values-tl/strings.xml b/tests/CarDeveloperOptions/res/values-tl/strings.xml
index 0a405bc..440bf79 100644
--- a/tests/CarDeveloperOptions/res/values-tl/strings.xml
+++ b/tests/CarDeveloperOptions/res/values-tl/strings.xml
@@ -668,7 +668,6 @@
       <item quantity="one">Dapat ay wala pang <xliff:g id="NUMBER_1">%d</xliff:g> digit</item>
       <item quantity="other">Dapat ay wala pang <xliff:g id="NUMBER_1">%d</xliff:g> na digit</item>
     </plurals>
-    <string name="lockpassword_pin_contains_non_digits" msgid="1501729742023195556">"Maglaman lang dapat ng mga digit na 0-9"</string>
     <string name="lockpassword_pin_recently_used" msgid="5556148774911741103">"Hindi pinapayagan ng admin ng device ang paggamit ng kamakailang PIN"</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="2752037810571200697">"Bina-block ng iyong IT admin ang mga pangkaraniwang PIN. Sumubok ng ibang PIN."</string>
     <string name="lockpassword_illegal_character" msgid="3577712947676261351">"Hindi ito maaaring magsama ng di-wastong character"</string>
@@ -699,6 +698,10 @@
       <item quantity="one">Dapat na maglaman ng kahit <xliff:g id="COUNT">%d</xliff:g> hindi titik na character lang</item>
       <item quantity="other">Dapat na maglaman ng kahit <xliff:g id="COUNT">%d</xliff:g> na hindi titik na character lang</item>
     </plurals>
+    <plurals name="lockpassword_password_requires_nonnumerical" formatted="false" msgid="6943707011152180150">
+      <item quantity="one">Dapat ay may kahit <xliff:g id="COUNT">%d</xliff:g> hindi numerong character</item>
+      <item quantity="other">Dapat ay may kahit <xliff:g id="COUNT">%d</xliff:g> na hindi numerong character</item>
+    </plurals>
     <string name="lockpassword_password_recently_used" msgid="6944729699375087431">"Hindi pinapayagan ng admin ng device ang paggamit ng kamakailang password"</string>
     <string name="lockpassword_password_blacklisted_by_admin" msgid="4988166770148440755">"Bina-block ng iyong IT admin ang mga pangkaraniwang password. Sumubok ng ibang password."</string>
     <string name="lockpassword_pin_no_sequential_digits" msgid="3902387296149848324">"Hindi pinapayagan ang pataas, pababa, o paulit-ulit na pagkakasunod-sunod ng mga digit"</string>
diff --git a/tests/CarDeveloperOptions/res/values-tr/config.xml b/tests/CarDeveloperOptions/res/values-tr/config.xml
index b05e073..04c8bf8 100644
--- a/tests/CarDeveloperOptions/res/values-tr/config.xml
+++ b/tests/CarDeveloperOptions/res/values-tr/config.xml
@@ -17,5 +17,4 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="config_backup_settings_label" msgid="5230713622702899641"></string>
-    <string name="config_grayscale_settings_intent" msgid="7661066612238245852"></string>
 </resources>
diff --git a/tests/CarDeveloperOptions/res/values-tr/strings.xml b/tests/CarDeveloperOptions/res/values-tr/strings.xml
index 7e4ef2c..89e58f7 100644
--- a/tests/CarDeveloperOptions/res/values-tr/strings.xml
+++ b/tests/CarDeveloperOptions/res/values-tr/strings.xml
@@ -668,7 +668,6 @@
       <item quantity="other"><xliff:g id="NUMBER_1">%d</xliff:g> basamaktan az olmalıdır</item>
       <item quantity="one"><xliff:g id="NUMBER_0">%d</xliff:g> basamaktan az olmalıdır</item>
     </plurals>
-    <string name="lockpassword_pin_contains_non_digits" msgid="1501729742023195556">"Yalnızca 0-9 arasındaki rakamları içermelidir"</string>
     <string name="lockpassword_pin_recently_used" msgid="5556148774911741103">"Cihaz yöneticisi yakın zamanda kullanılan bir PIN\'e izin vermiyor"</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="2752037810571200697">"Yaygın olarak kullanılan PIN\'ler BT yöneticiniz tarafından engellendi. Farklı bir PIN deneyin."</string>
     <string name="lockpassword_illegal_character" msgid="3577712947676261351">"Burada geçersiz bir karakter kullanılamaz"</string>
@@ -699,6 +698,10 @@
       <item quantity="other">Harf olmayan en az <xliff:g id="COUNT">%d</xliff:g> karakter içermelidir</item>
       <item quantity="one">Harf olmayan en az 1 karakter içermelidir</item>
     </plurals>
+    <plurals name="lockpassword_password_requires_nonnumerical" formatted="false" msgid="6943707011152180150">
+      <item quantity="other">Rakam olmayan en az <xliff:g id="COUNT">%d</xliff:g> karakter içermelidir</item>
+      <item quantity="one">Rakam olmayan en az 1 karakter içermelidir</item>
+    </plurals>
     <string name="lockpassword_password_recently_used" msgid="6944729699375087431">"Cihaz yöneticisi yakın zamanda kullanılan bir şifreye izin vermiyor"</string>
     <string name="lockpassword_password_blacklisted_by_admin" msgid="4988166770148440755">"Yaygın olarak kullanılan şifreler BT yöneticiniz tarafından engellendi. Farklı bir şifre deneyin."</string>
     <string name="lockpassword_pin_no_sequential_digits" msgid="3902387296149848324">"Artan, azalan veya tekrar eden rakam dizisine izin verilmiyor"</string>
diff --git a/tests/CarDeveloperOptions/res/values-uk/config.xml b/tests/CarDeveloperOptions/res/values-uk/config.xml
index b05e073..04c8bf8 100644
--- a/tests/CarDeveloperOptions/res/values-uk/config.xml
+++ b/tests/CarDeveloperOptions/res/values-uk/config.xml
@@ -17,5 +17,4 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="config_backup_settings_label" msgid="5230713622702899641"></string>
-    <string name="config_grayscale_settings_intent" msgid="7661066612238245852"></string>
 </resources>
diff --git a/tests/CarDeveloperOptions/res/values-uk/strings.xml b/tests/CarDeveloperOptions/res/values-uk/strings.xml
index b166589..38b465d 100644
--- a/tests/CarDeveloperOptions/res/values-uk/strings.xml
+++ b/tests/CarDeveloperOptions/res/values-uk/strings.xml
@@ -684,7 +684,6 @@
       <item quantity="many">Може містити до <xliff:g id="NUMBER_1">%d</xliff:g> цифр</item>
       <item quantity="other">Може містити до <xliff:g id="NUMBER_1">%d</xliff:g> цифри</item>
     </plurals>
-    <string name="lockpassword_pin_contains_non_digits" msgid="1501729742023195556">"Має містити лише цифри від 0 до 9"</string>
     <string name="lockpassword_pin_recently_used" msgid="5556148774911741103">"Адміністратор пристрою не дозволяє використовувати останній PIN-код"</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="2752037810571200697">"IT-адміністратор заблокував загальні PIN-коди. Введіть інший PIN-код."</string>
     <string name="lockpassword_illegal_character" msgid="3577712947676261351">"Не може містити недійсних символів"</string>
@@ -727,6 +726,12 @@
       <item quantity="many">Має містити принаймні <xliff:g id="COUNT">%d</xliff:g> символів, які не є літерами</item>
       <item quantity="other">Має містити принаймні <xliff:g id="COUNT">%d</xliff:g> символу, які не є літерами</item>
     </plurals>
+    <plurals name="lockpassword_password_requires_nonnumerical" formatted="false" msgid="6943707011152180150">
+      <item quantity="one">Має містити, окрім цифр, принаймні <xliff:g id="COUNT">%d</xliff:g> інший символ</item>
+      <item quantity="few">Має містити, окрім цифр, принаймні <xliff:g id="COUNT">%d</xliff:g> інші символи</item>
+      <item quantity="many">Має містити, окрім цифр, принаймні <xliff:g id="COUNT">%d</xliff:g> інших символів</item>
+      <item quantity="other">Має містити, окрім цифр, принаймні <xliff:g id="COUNT">%d</xliff:g> іншого символу</item>
+    </plurals>
     <string name="lockpassword_password_recently_used" msgid="6944729699375087431">"Адміністратор пристрою не дозволяє використовувати останній пароль"</string>
     <string name="lockpassword_password_blacklisted_by_admin" msgid="4988166770148440755">"IT-адміністратор заблокував загальні паролі. Введіть інший пароль."</string>
     <string name="lockpassword_pin_no_sequential_digits" msgid="3902387296149848324">"Цифри в порядку зростання чи спадання та повторювані цифри заборонені"</string>
diff --git a/tests/CarDeveloperOptions/res/values-ur/config.xml b/tests/CarDeveloperOptions/res/values-ur/config.xml
index b05e073..04c8bf8 100644
--- a/tests/CarDeveloperOptions/res/values-ur/config.xml
+++ b/tests/CarDeveloperOptions/res/values-ur/config.xml
@@ -17,5 +17,4 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="config_backup_settings_label" msgid="5230713622702899641"></string>
-    <string name="config_grayscale_settings_intent" msgid="7661066612238245852"></string>
 </resources>
diff --git a/tests/CarDeveloperOptions/res/values-ur/strings.xml b/tests/CarDeveloperOptions/res/values-ur/strings.xml
index fb8ff24..89383c8 100644
--- a/tests/CarDeveloperOptions/res/values-ur/strings.xml
+++ b/tests/CarDeveloperOptions/res/values-ur/strings.xml
@@ -668,7 +668,6 @@
       <item quantity="other"><xliff:g id="NUMBER_1">%d</xliff:g>ہندسوں سے کم ہونے چاہئیں</item>
       <item quantity="one"><xliff:g id="NUMBER_0">%d</xliff:g> ہندسے سے کم ہونا چاہیے</item>
     </plurals>
-    <string name="lockpassword_pin_contains_non_digits" msgid="1501729742023195556">"صرف ‎0-9 ہندسے شامل ہونے چاہئیں"</string>
     <string name="lockpassword_pin_recently_used" msgid="5556148774911741103">"آلہ کا منتظم ایک حالیہ PIN استعمال کرنے کی اجازت نہیں دیتا ہے"</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="2752037810571200697">"آپ کے IT منتظم نے عمومی PINs کو مسدود کر دیا ہے۔ کوئی دوسرا PIN آزمائیں۔"</string>
     <string name="lockpassword_illegal_character" msgid="3577712947676261351">"اس میں غلط کریکٹر شامل نہیں ہو سکتا"</string>
@@ -699,6 +698,10 @@
       <item quantity="other">کم از کم <xliff:g id="COUNT">%d</xliff:g> غیر حرفی علامتوں پر مشتمل ہونا چاہیے</item>
       <item quantity="one">کم از کم ایک ایسے کریکٹر پر مشتمل ہونا چاہیئے جو حرف نہ ہو</item>
     </plurals>
+    <plurals name="lockpassword_password_requires_nonnumerical" formatted="false" msgid="6943707011152180150">
+      <item quantity="other">کم از کم <xliff:g id="COUNT">%d</xliff:g> غیر عددی حروف پر مشتمل ہونا چاہیے</item>
+      <item quantity="one">کم از کم 1 غیر عددی حرف ہونا چاہیے</item>
+    </plurals>
     <string name="lockpassword_password_recently_used" msgid="6944729699375087431">"آلے کا منتظم ایک حالیہ پاس ورڈ کا استعمال کرنے کی اجازت نہیں دیتا ہے"</string>
     <string name="lockpassword_password_blacklisted_by_admin" msgid="4988166770148440755">"آپ کے IT منتظم نے عمومی پاس ورڈز کو مسدود کر دیا ہے۔ کوئی دوسرا پاس ورڈ آزمائیں۔"</string>
     <string name="lockpassword_pin_no_sequential_digits" msgid="3902387296149848324">"ہندسوں کی صعودی، نزولی یا مکرر ترتیب کی اجازت نہیں ہے"</string>
diff --git a/tests/CarDeveloperOptions/res/values-uz/config.xml b/tests/CarDeveloperOptions/res/values-uz/config.xml
index b05e073..04c8bf8 100644
--- a/tests/CarDeveloperOptions/res/values-uz/config.xml
+++ b/tests/CarDeveloperOptions/res/values-uz/config.xml
@@ -17,5 +17,4 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="config_backup_settings_label" msgid="5230713622702899641"></string>
-    <string name="config_grayscale_settings_intent" msgid="7661066612238245852"></string>
 </resources>
diff --git a/tests/CarDeveloperOptions/res/values-uz/strings.xml b/tests/CarDeveloperOptions/res/values-uz/strings.xml
index 6b347a5..b71c9e4 100644
--- a/tests/CarDeveloperOptions/res/values-uz/strings.xml
+++ b/tests/CarDeveloperOptions/res/values-uz/strings.xml
@@ -668,7 +668,6 @@
       <item quantity="other">Maksimum <xliff:g id="NUMBER_1">%d</xliff:g> ta raqam</item>
       <item quantity="one">Maksimum <xliff:g id="NUMBER_0">%d</xliff:g> ta raqam</item>
     </plurals>
-    <string name="lockpassword_pin_contains_non_digits" msgid="1501729742023195556">"PIN kod faqat 0-9 raqamlaridan iborat bo‘lishi lozim"</string>
     <string name="lockpassword_pin_recently_used" msgid="5556148774911741103">"Qurilma administratori oxirgi PIN koddan yana foydalanishga ruxsat bermaydi"</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="2752037810571200697">"Oddiy PIN kodlar AT admini tomonidan bloklangan. Murakkabroq PIN kod tanlang."</string>
     <string name="lockpassword_illegal_character" msgid="3577712947676261351">"Parol yaroqsiz belgidan iborat bo‘lmasligi lozim"</string>
@@ -699,6 +698,10 @@
       <item quantity="other">Parol tarkibida kamida <xliff:g id="COUNT">%d</xliff:g> ta harf bo‘lmagan belgi bo‘lishi lozim</item>
       <item quantity="one">Parol tarkibida kamida 1 ta harf bo‘lmagan belgi bo‘lishi lozim</item>
     </plurals>
+    <plurals name="lockpassword_password_requires_nonnumerical" formatted="false" msgid="6943707011152180150">
+      <item quantity="other">Kamida <xliff:g id="COUNT">%d</xliff:g> ta raqam boʻlmagan belgi boʻlishi lozim</item>
+      <item quantity="one">Kamida 1 ta raqam boʻlmagan belgi boʻlishi lozim</item>
+    </plurals>
     <string name="lockpassword_password_recently_used" msgid="6944729699375087431">"Qurilma administratori yaqinda foydalanilgan paroldan yana foydalanishga ruxsat bermaydi"</string>
     <string name="lockpassword_password_blacklisted_by_admin" msgid="4988166770148440755">"Oddiy parollar AT admini tomonidan bloklangan. Murakkabroq parol tanlang."</string>
     <string name="lockpassword_pin_no_sequential_digits" msgid="3902387296149848324">"Oshib boruvchi, kamayib boruvchi yoki qaytarilgan ketma-ket raqamlarga ruxsat berilmaydi"</string>
diff --git a/tests/CarDeveloperOptions/res/values-vi/config.xml b/tests/CarDeveloperOptions/res/values-vi/config.xml
index b05e073..04c8bf8 100644
--- a/tests/CarDeveloperOptions/res/values-vi/config.xml
+++ b/tests/CarDeveloperOptions/res/values-vi/config.xml
@@ -17,5 +17,4 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="config_backup_settings_label" msgid="5230713622702899641"></string>
-    <string name="config_grayscale_settings_intent" msgid="7661066612238245852"></string>
 </resources>
diff --git a/tests/CarDeveloperOptions/res/values-vi/strings.xml b/tests/CarDeveloperOptions/res/values-vi/strings.xml
index 7024a6f..569bed6 100644
--- a/tests/CarDeveloperOptions/res/values-vi/strings.xml
+++ b/tests/CarDeveloperOptions/res/values-vi/strings.xml
@@ -668,7 +668,6 @@
       <item quantity="other">Phải có ít hơn <xliff:g id="NUMBER_1">%d</xliff:g> chữ số</item>
       <item quantity="one">Phải có ít hơn <xliff:g id="NUMBER_0">%d</xliff:g> chữ số</item>
     </plurals>
-    <string name="lockpassword_pin_contains_non_digits" msgid="1501729742023195556">"Chỉ được chứa các chữ số 0-9"</string>
     <string name="lockpassword_pin_recently_used" msgid="5556148774911741103">"Quản trị viên thiết bị không cho phép dùng mã PIN gần đây"</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="2752037810571200697">"Quản vị viên CNTT đã chặn những mã PIN phổ biến. Hãy thử mã PIN khác."</string>
     <string name="lockpassword_illegal_character" msgid="3577712947676261351">"Mật khẩu này không được bao gồm ký tự không hợp lệ"</string>
@@ -699,6 +698,10 @@
       <item quantity="other">Phải chứa ít nhất <xliff:g id="COUNT">%d</xliff:g> ký tự không phải chữ cái</item>
       <item quantity="one">Phải chứa ít nhất 1 ký tự không phải chữ cái</item>
     </plurals>
+    <plurals name="lockpassword_password_requires_nonnumerical" formatted="false" msgid="6943707011152180150">
+      <item quantity="other">Phải có ít nhất <xliff:g id="COUNT">%d</xliff:g> ký tự không phải là số</item>
+      <item quantity="one">Phải có ít nhất 1 ký tự không phải là số</item>
+    </plurals>
     <string name="lockpassword_password_recently_used" msgid="6944729699375087431">"Quản trị viên thiết bị không cho phép sử dụng mật khẩu gần đây"</string>
     <string name="lockpassword_password_blacklisted_by_admin" msgid="4988166770148440755">"Quản vị viên CNTT đã chặn những mật khẩu phổ biến. Hãy thử mật khẩu khác."</string>
     <string name="lockpassword_pin_no_sequential_digits" msgid="3902387296149848324">"Không cho phép thứ tự chữ số tăng dần, giảm dần hoặc lặp lại"</string>
diff --git a/tests/CarDeveloperOptions/res/values-zh-rCN/config.xml b/tests/CarDeveloperOptions/res/values-zh-rCN/config.xml
index b05e073..04c8bf8 100644
--- a/tests/CarDeveloperOptions/res/values-zh-rCN/config.xml
+++ b/tests/CarDeveloperOptions/res/values-zh-rCN/config.xml
@@ -17,5 +17,4 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="config_backup_settings_label" msgid="5230713622702899641"></string>
-    <string name="config_grayscale_settings_intent" msgid="7661066612238245852"></string>
 </resources>
diff --git a/tests/CarDeveloperOptions/res/values-zh-rCN/strings.xml b/tests/CarDeveloperOptions/res/values-zh-rCN/strings.xml
index a2c5369..c2532c4 100644
--- a/tests/CarDeveloperOptions/res/values-zh-rCN/strings.xml
+++ b/tests/CarDeveloperOptions/res/values-zh-rCN/strings.xml
@@ -668,7 +668,6 @@
       <item quantity="other">必须少于 <xliff:g id="NUMBER_1">%d</xliff:g> 位数</item>
       <item quantity="one">必须少于 <xliff:g id="NUMBER_0">%d</xliff:g> 位数</item>
     </plurals>
-    <string name="lockpassword_pin_contains_non_digits" msgid="1501729742023195556">"只能包含 0-9 的数字"</string>
     <string name="lockpassword_pin_recently_used" msgid="5556148774911741103">"设备管理员不允许使用最近用过的 PIN 码"</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="2752037810571200697">"常用 PIN 码已被您的 IT 管理员屏蔽。请尝试一个不同的 PIN 码。"</string>
     <string name="lockpassword_illegal_character" msgid="3577712947676261351">"密码不得包含无效字符"</string>
@@ -699,6 +698,10 @@
       <item quantity="other">必须包含至少 <xliff:g id="COUNT">%d</xliff:g> 个非字母字符</item>
       <item quantity="one">必须包含至少 1 个非字母字符</item>
     </plurals>
+    <plurals name="lockpassword_password_requires_nonnumerical" formatted="false" msgid="6943707011152180150">
+      <item quantity="other">必须包含至少 <xliff:g id="COUNT">%d</xliff:g> 个非数字字符</item>
+      <item quantity="one">必须包含至少 1 个非数字字符</item>
+    </plurals>
     <string name="lockpassword_password_recently_used" msgid="6944729699375087431">"设备管理员不允许使用最近用过的密码"</string>
     <string name="lockpassword_password_blacklisted_by_admin" msgid="4988166770148440755">"常用密码已被您的 IT 管理员屏蔽。请尝试一个不同的密码。"</string>
     <string name="lockpassword_pin_no_sequential_digits" msgid="3902387296149848324">"不允许使用以升序、降序或重复序列进行排列的一串数字"</string>
diff --git a/tests/CarDeveloperOptions/res/values-zh-rHK/config.xml b/tests/CarDeveloperOptions/res/values-zh-rHK/config.xml
index b05e073..04c8bf8 100644
--- a/tests/CarDeveloperOptions/res/values-zh-rHK/config.xml
+++ b/tests/CarDeveloperOptions/res/values-zh-rHK/config.xml
@@ -17,5 +17,4 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="config_backup_settings_label" msgid="5230713622702899641"></string>
-    <string name="config_grayscale_settings_intent" msgid="7661066612238245852"></string>
 </resources>
diff --git a/tests/CarDeveloperOptions/res/values-zh-rHK/strings.xml b/tests/CarDeveloperOptions/res/values-zh-rHK/strings.xml
index 96b8fef..e090f07 100644
--- a/tests/CarDeveloperOptions/res/values-zh-rHK/strings.xml
+++ b/tests/CarDeveloperOptions/res/values-zh-rHK/strings.xml
@@ -668,7 +668,6 @@
       <item quantity="other">必須少於 <xliff:g id="NUMBER_1">%d</xliff:g> 個數字</item>
       <item quantity="one">必須少於 <xliff:g id="NUMBER_0">%d</xliff:g> 個數字</item>
     </plurals>
-    <string name="lockpassword_pin_contains_non_digits" msgid="1501729742023195556">"只可包含數字 0-9"</string>
     <string name="lockpassword_pin_recently_used" msgid="5556148774911741103">"裝置管理員不允許使用最近用過的 PIN 碼"</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="2752037810571200697">"您的 IT 管理員已禁止使用常用的 PIN 碼,請嘗試輸入另一個 PIN 碼。"</string>
     <string name="lockpassword_illegal_character" msgid="3577712947676261351">"您不可使用無效字元"</string>
@@ -699,6 +698,10 @@
       <item quantity="other">必須包含最少 <xliff:g id="COUNT">%d</xliff:g> 個非字母字元</item>
       <item quantity="one">必須包含最少 1 個非字母字元</item>
     </plurals>
+    <plurals name="lockpassword_password_requires_nonnumerical" formatted="false" msgid="6943707011152180150">
+      <item quantity="other">必須包含至少 <xliff:g id="COUNT">%d</xliff:g> 個非數字字元</item>
+      <item quantity="one">必須包含至少 1 個非數字字元</item>
+    </plurals>
     <string name="lockpassword_password_recently_used" msgid="6944729699375087431">"裝置管理員不允許使用最近用過的密碼"</string>
     <string name="lockpassword_password_blacklisted_by_admin" msgid="4988166770148440755">"您的 IT 管理員已禁止使用常用的密碼,請嘗試輸入另一個密碼。"</string>
     <string name="lockpassword_pin_no_sequential_digits" msgid="3902387296149848324">"不可使用依遞增或遞減順序排列或重複的連續數字"</string>
diff --git a/tests/CarDeveloperOptions/res/values-zh-rTW/config.xml b/tests/CarDeveloperOptions/res/values-zh-rTW/config.xml
index b05e073..04c8bf8 100644
--- a/tests/CarDeveloperOptions/res/values-zh-rTW/config.xml
+++ b/tests/CarDeveloperOptions/res/values-zh-rTW/config.xml
@@ -17,5 +17,4 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="config_backup_settings_label" msgid="5230713622702899641"></string>
-    <string name="config_grayscale_settings_intent" msgid="7661066612238245852"></string>
 </resources>
diff --git a/tests/CarDeveloperOptions/res/values-zh-rTW/strings.xml b/tests/CarDeveloperOptions/res/values-zh-rTW/strings.xml
index b61af3b..e5e9792 100644
--- a/tests/CarDeveloperOptions/res/values-zh-rTW/strings.xml
+++ b/tests/CarDeveloperOptions/res/values-zh-rTW/strings.xml
@@ -668,7 +668,6 @@
       <item quantity="other">長度必須少於 <xliff:g id="NUMBER_1">%d</xliff:g> 個數字</item>
       <item quantity="one">長度必須少於 <xliff:g id="NUMBER_0">%d</xliff:g> 個數字</item>
     </plurals>
-    <string name="lockpassword_pin_contains_non_digits" msgid="1501729742023195556">"只能包含數字 0 到 9"</string>
     <string name="lockpassword_pin_recently_used" msgid="5556148774911741103">"裝置管理員不允許使用最近用過的 PIN"</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="2752037810571200697">"你的 IT 管理員已封鎖常見 PIN 碼,請改用其他 PIN 碼。"</string>
     <string name="lockpassword_illegal_character" msgid="3577712947676261351">"密碼不得包含無效字元"</string>
@@ -699,6 +698,10 @@
       <item quantity="other">必須包含至少 <xliff:g id="COUNT">%d</xliff:g> 個非字母的字元</item>
       <item quantity="one">必須包含至少 1 個非字母的字元</item>
     </plurals>
+    <plurals name="lockpassword_password_requires_nonnumerical" formatted="false" msgid="6943707011152180150">
+      <item quantity="other">至少必須包含 <xliff:g id="COUNT">%d</xliff:g> 個非數值字元</item>
+      <item quantity="one">至少必須包含 1 個非數值字元</item>
+    </plurals>
     <string name="lockpassword_password_recently_used" msgid="6944729699375087431">"裝置管理員不允許使用最近用過的密碼"</string>
     <string name="lockpassword_password_blacklisted_by_admin" msgid="4988166770148440755">"你的 IT 管理員已封鎖常見密碼,請改用其他密碼。"</string>
     <string name="lockpassword_pin_no_sequential_digits" msgid="3902387296149848324">"不允許使用依遞增或遞減順序排列或是重複的一串數字"</string>
diff --git a/tests/CarDeveloperOptions/res/values-zu/config.xml b/tests/CarDeveloperOptions/res/values-zu/config.xml
index b05e073..04c8bf8 100644
--- a/tests/CarDeveloperOptions/res/values-zu/config.xml
+++ b/tests/CarDeveloperOptions/res/values-zu/config.xml
@@ -17,5 +17,4 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="config_backup_settings_label" msgid="5230713622702899641"></string>
-    <string name="config_grayscale_settings_intent" msgid="7661066612238245852"></string>
 </resources>
diff --git a/tests/CarDeveloperOptions/res/values-zu/strings.xml b/tests/CarDeveloperOptions/res/values-zu/strings.xml
index 6aec398..d9b9221 100644
--- a/tests/CarDeveloperOptions/res/values-zu/strings.xml
+++ b/tests/CarDeveloperOptions/res/values-zu/strings.xml
@@ -668,7 +668,6 @@
       <item quantity="one">Kufanele kube amadijithi angaphansi kuka-<xliff:g id="NUMBER_1">%d</xliff:g></item>
       <item quantity="other">Kufanele kube amadijithi angaphansi kuka-<xliff:g id="NUMBER_1">%d</xliff:g></item>
     </plurals>
-    <string name="lockpassword_pin_contains_non_digits" msgid="1501729742023195556">"Kumele iqukathe amadijithi angu-0-9 kuphela"</string>
     <string name="lockpassword_pin_recently_used" msgid="5556148774911741103">"Umlawuli wedivayisi akavumeli ukusebenzisa iphinikhodi yakamuva"</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="2752037810571200697">"Ama-PIN avamile avinjelwe umlawuli wakho we-IT. Zama i-PIN ehlukile."</string>
     <string name="lockpassword_illegal_character" msgid="3577712947676261351">"Lokhu akukwazi ukufana uhlamvu olungavumelekile"</string>
@@ -699,6 +698,10 @@
       <item quantity="one">Kumele iqukathe okungenani okungu-<xliff:g id="COUNT">%d</xliff:g> okungezona izinhlamvu</item>
       <item quantity="other">Kumele iqukathe okungenani okungu-<xliff:g id="COUNT">%d</xliff:g> okungezona izinhlamvu</item>
     </plurals>
+    <plurals name="lockpassword_password_requires_nonnumerical" formatted="false" msgid="6943707011152180150">
+      <item quantity="one">Kumele iqukathe okungenani izinhlamvu ezingasizo izinombolo ezingu-<xliff:g id="COUNT">%d</xliff:g></item>
+      <item quantity="other">Kumele iqukathe okungenani izinhlamvu ezingasizo izinombolo ezingu-<xliff:g id="COUNT">%d</xliff:g></item>
+    </plurals>
     <string name="lockpassword_password_recently_used" msgid="6944729699375087431">"Umlawuli wedivayisi akavumeli ukusebenzisa iphasiwedi yakamuva"</string>
     <string name="lockpassword_password_blacklisted_by_admin" msgid="4988166770148440755">"Amaphasiwedi avamile avinjelwe umlawuli wakho we-IT. Zama iphasiwedi ehlukile."</string>
     <string name="lockpassword_pin_no_sequential_digits" msgid="3902387296149848324">"Ukwenyuka, ukwehla, noma uchungechunge olwehlayo lamadijithi alivunyewe"</string>
diff --git a/tests/CarDeveloperOptions/res/values/arrays.xml b/tests/CarDeveloperOptions/res/values/arrays.xml
index 1a36aa8..70c22a4 100644
--- a/tests/CarDeveloperOptions/res/values/arrays.xml
+++ b/tests/CarDeveloperOptions/res/values/arrays.xml
@@ -221,7 +221,7 @@
     <!-- Values for security type for wireless tether -->
     <string-array name="wifi_tether_security_values" translatable="false">
         <!-- Do not translate. -->
-        <item>4</item>
+        <item>1</item>
         <!-- Do not translate. -->
         <item>0</item>
     </string-array>
@@ -246,24 +246,15 @@
         <item>PWD</item>
     </string-array>
 
-   <!-- Wi-Fi AP band settings.  Either Auto, 2.4GHz or 5GHz. -->
+   <!-- Wi-Fi AP band settings.  Either 2.4GHz or (2.4G|5GHz). -->
    <!-- Note that adding/removing/moving the items will need wifi settings code change. -->
-    <string-array translatable="false" name="wifi_ap_band_config_full">
-        <item>0</item>
+
+    <string-array translatable="false" name="wifi_ap_band">
         <item>1</item>
+        <item>3</item>
     </string-array>
 
-    <string-array translatable="false" name="wifi_ap_band_summary_full">
-        <item>@string/wifi_ap_choose_2G</item>
-        <item>@string/wifi_ap_choose_5G</item>
-    </string-array>
-
-    <string-array translatable="false" name="wifi_ap_band_dual_mode">
-        <item>0</item>
-        <item>-1</item>
-    </string-array>
-
-    <string-array translatable="false" name="wifi_ap_band_dual_mode_summary">
+    <string-array translatable="false" name="wifi_ap_band_summary">
         <item>@string/wifi_ap_choose_2G</item>
         <item>@string/wifi_ap_prefer_5G</item>
     </string-array>
diff --git a/tests/CarDeveloperOptions/res/values/config.xml b/tests/CarDeveloperOptions/res/values/config.xml
index c54d50e..8e08012 100755
--- a/tests/CarDeveloperOptions/res/values/config.xml
+++ b/tests/CarDeveloperOptions/res/values/config.xml
@@ -362,5 +362,5 @@
     <string name="config_nearby_devices_slice_uri" translatable="false">content://com.google.android.gms.nearby.fastpair/device_status_list_item</string>
 
     <!-- Grayscale settings intent -->
-    <string name="config_grayscale_settings_intent" translate="false"></string>
+    <string name="config_grayscale_settings_intent" translatable="false"></string>
 </resources>
diff --git a/tests/CarDeveloperOptions/res/values/strings.xml b/tests/CarDeveloperOptions/res/values/strings.xml
index 130cfb9..a8c38f4 100644
--- a/tests/CarDeveloperOptions/res/values/strings.xml
+++ b/tests/CarDeveloperOptions/res/values/strings.xml
@@ -1529,9 +1529,6 @@
         <item quantity="other">Must be fewer than <xliff:g id="number" example="17">%d</xliff:g> digits</item>
     </plurals>
 
-    <!-- Error shown when in PIN mode and user enters a non-digit -->
-    <string name="lockpassword_pin_contains_non_digits">Must contain only digits 0-9</string>
-
     <!-- Error shown when in PIN mode and PIN has been used recently. Please keep this string short! -->
     <string name="lockpassword_pin_recently_used">Device admin doesn\'t allow using a recent PIN</string>
 
@@ -1586,6 +1583,12 @@
         <item quantity="other">Must contain at least <xliff:g id="count" example="3">%d</xliff:g> non-letter characters</item>
     </plurals>
 
+    <!-- Error shown when in PASSWORD mode and password doesn't contain the required number of non-numerical characters -->
+    <plurals name="lockpassword_password_requires_nonnumerical">
+        <item quantity="one">Must contain at least 1 non-numerical character</item>
+        <item quantity="other">Must contain at least <xliff:g id="count" example="3">%d</xliff:g> non-numerical characters</item>
+    </plurals>
+
     <!-- Error shown when in PASSWORD mode and password has been used recently. Please keep this string short! -->
     <string name="lockpassword_password_recently_used">Device admin doesn\'t allow using a recent
         password</string>
diff --git a/tests/CarDeveloperOptions/res/xml/accessibility_autoclick_settings.xml b/tests/CarDeveloperOptions/res/xml/accessibility_autoclick_settings.xml
index 7dd88cb..7ec174d 100644
--- a/tests/CarDeveloperOptions/res/xml/accessibility_autoclick_settings.xml
+++ b/tests/CarDeveloperOptions/res/xml/accessibility_autoclick_settings.xml
@@ -16,6 +16,7 @@
 
 <PreferenceScreen
     xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:settings="http://schemas.android.com/apk/res-auto"
     android:key="autoclick_preference_screen"
     android:title="@string/accessibility_autoclick_preference_title">
 
@@ -24,4 +25,9 @@
         android:key="autoclick_delay"
         android:title="@string/accessibility_autoclick_delay_preference_title" />
 
+    <com.android.settingslib.widget.FooterPreference
+        android:key="autoclick_footer"
+        android:title="@string/accessibility_autoclick_description"
+        android:selectable="false"
+        settings:searchable="false"/>
 </PreferenceScreen>
diff --git a/tests/CarDeveloperOptions/res/xml/accessibility_daltonizer_settings.xml b/tests/CarDeveloperOptions/res/xml/accessibility_daltonizer_settings.xml
index 38b830c..5b8811f 100644
--- a/tests/CarDeveloperOptions/res/xml/accessibility_daltonizer_settings.xml
+++ b/tests/CarDeveloperOptions/res/xml/accessibility_daltonizer_settings.xml
@@ -16,6 +16,7 @@
 
 <PreferenceScreen
     xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:settings="http://schemas.android.com/apk/res-auto"
     android:key="daltonizer_preference_screen"
     android:title="@string/accessibility_display_daltonizer_preference_title">
 
@@ -27,4 +28,9 @@
         android:title="@string/daltonizer_type"
         android:icon="@drawable/ic_accessibility_illustration_colorblind" />
 
+    <com.android.settingslib.widget.FooterPreference
+        android:key="daltonizer_footer"
+        android:title="@string/accessibility_display_daltonizer_preference_subtitle"
+        android:selectable="false"
+        settings:searchable="false"/>
 </PreferenceScreen>
diff --git a/tests/CarDeveloperOptions/res/xml/accessibility_settings.xml b/tests/CarDeveloperOptions/res/xml/accessibility_settings.xml
index b170ca0..3c98a00 100644
--- a/tests/CarDeveloperOptions/res/xml/accessibility_settings.xml
+++ b/tests/CarDeveloperOptions/res/xml/accessibility_settings.xml
@@ -21,11 +21,6 @@
         android:title="@string/accessibility_settings"
         android:persistent="true">
 
-    <Preference
-            android:key="accessibility_shortcut_preference"
-            android:fragment="com.android.car.developeroptions.accessibility.AccessibilityShortcutPreferenceFragment"
-            android:title="@string/accessibility_global_gesture_preference_title"/>
-
     <PreferenceCategory
             android:key="user_installed_services_category"
             android:title="@string/user_installed_services_category_title">
diff --git a/tests/CarDeveloperOptions/res/xml/accessibility_shortcut_settings.xml b/tests/CarDeveloperOptions/res/xml/accessibility_shortcut_settings.xml
index 66ae3e8..872c8ca 100644
--- a/tests/CarDeveloperOptions/res/xml/accessibility_shortcut_settings.xml
+++ b/tests/CarDeveloperOptions/res/xml/accessibility_shortcut_settings.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
+                  xmlns:settings="http://schemas.android.com/apk/res-auto"
                   android:title="@string/accessibility_global_gesture_preference_title" >
 
     <Preference
@@ -24,4 +25,10 @@
     <SwitchPreference
             android:key="accessibility_shortcut_on_lock_screen"
             android:title="@string/accessibility_shortcut_service_on_lock_screen_title"/>
-</PreferenceScreen>
\ No newline at end of file
+
+    <com.android.settingslib.widget.FooterPreference
+        android:key="accessibility_shortcut_footer"
+        android:title="@string/accessibility_shortcut_description"
+        android:selectable="false"
+        settings:searchable="false"/>
+</PreferenceScreen>
diff --git a/tests/CarDeveloperOptions/res/xml/adaptive_sleep_detail.xml b/tests/CarDeveloperOptions/res/xml/adaptive_sleep_detail.xml
index da9281c..24d8542 100644
--- a/tests/CarDeveloperOptions/res/xml/adaptive_sleep_detail.xml
+++ b/tests/CarDeveloperOptions/res/xml/adaptive_sleep_detail.xml
@@ -38,4 +38,11 @@
         settings:useAdminDisabledSummary="true"
         settings:allowDividerAbove="true" />
 
-</PreferenceScreen>
\ No newline at end of file
+    <com.android.settingslib.widget.FooterPreference
+        android:key="adaptive_sleep_footer"
+        android:title="@string/adaptive_sleep_privacy"
+        android:icon="@drawable/ic_privacy_shield_24dp"
+        android:selectable="false"
+        settings:searchable="false" />
+
+</PreferenceScreen>
diff --git a/tests/CarDeveloperOptions/res/xml/add_account_settings.xml b/tests/CarDeveloperOptions/res/xml/add_account_settings.xml
index 57e0eba..002c290 100644
--- a/tests/CarDeveloperOptions/res/xml/add_account_settings.xml
+++ b/tests/CarDeveloperOptions/res/xml/add_account_settings.xml
@@ -19,4 +19,11 @@
     xmlns:settings="http://schemas.android.com/apk/res-auto"
     android:key="add_account_screen"
     android:title="@string/header_add_an_account"
-    settings:controller="com.android.car.developeroptions.accounts.ChooseAccountPreferenceController" />
+    settings:controller="com.android.car.developeroptions.accounts.ChooseAccountPreferenceController" >
+
+    <com.android.settingslib.widget.FooterPreference
+        android:key="add_account_enterprise_disclosure_footer"
+        android:selectable="false"
+        settings:searchable="false"
+        settings:controller="com.android.car.developeroptions.accounts.EnterpriseDisclosurePreferenceController"/>
+</PreferenceScreen>
diff --git a/tests/CarDeveloperOptions/res/xml/auto_brightness_detail.xml b/tests/CarDeveloperOptions/res/xml/auto_brightness_detail.xml
index dfa4105..6232873 100644
--- a/tests/CarDeveloperOptions/res/xml/auto_brightness_detail.xml
+++ b/tests/CarDeveloperOptions/res/xml/auto_brightness_detail.xml
@@ -38,4 +38,10 @@
         settings:userRestriction="no_config_brightness"
         settings:allowDividerAbove="true" />
 
-</PreferenceScreen>
\ No newline at end of file
+    <com.android.settingslib.widget.FooterPreference
+        android:key="auto_brightness_footer"
+        android:title="@string/auto_brightness_description"
+        android:selectable="false"
+        settings:searchable="false" />
+
+</PreferenceScreen>
diff --git a/tests/CarDeveloperOptions/res/xml/billing_cycle.xml b/tests/CarDeveloperOptions/res/xml/billing_cycle.xml
index 365fb1d..4b44c65 100644
--- a/tests/CarDeveloperOptions/res/xml/billing_cycle.xml
+++ b/tests/CarDeveloperOptions/res/xml/billing_cycle.xml
@@ -16,6 +16,7 @@
 
 <PreferenceScreen
     xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:settings="http://schemas.android.com/apk/res-auto"
     android:key="billing_cycle_settings"
     android:title="@string/billing_cycle">
 
@@ -39,4 +40,10 @@
         android:key="data_limit"
         android:title="@string/data_limit" />
 
+    <com.android.settingslib.widget.FooterPreference
+        android:key="billing_cycle_footer"
+        android:title="@string/data_warning_footnote"
+        android:selectable="false"
+        settings:searchable="false"/>
+
 </PreferenceScreen>
diff --git a/tests/CarDeveloperOptions/res/xml/bluetooth_device_details_fragment.xml b/tests/CarDeveloperOptions/res/xml/bluetooth_device_details_fragment.xml
index e8eae7c..72d6d61 100644
--- a/tests/CarDeveloperOptions/res/xml/bluetooth_device_details_fragment.xml
+++ b/tests/CarDeveloperOptions/res/xml/bluetooth_device_details_fragment.xml
@@ -46,4 +46,10 @@
     <PreferenceCategory
         android:key="bluetooth_profiles"/>
 
-</PreferenceScreen>
\ No newline at end of file
+    <com.android.settingslib.widget.FooterPreference
+        android:key="device_details_footer"
+        android:selectable="false"
+        settings:searchable="false"
+        settings:controller="com.android.car.developeroptions.bluetooth.BluetoothDetailsMacAddressController"/>
+
+</PreferenceScreen>
diff --git a/tests/CarDeveloperOptions/res/xml/bluetooth_screen.xml b/tests/CarDeveloperOptions/res/xml/bluetooth_screen.xml
index a36704f..8821999 100644
--- a/tests/CarDeveloperOptions/res/xml/bluetooth_screen.xml
+++ b/tests/CarDeveloperOptions/res/xml/bluetooth_screen.xml
@@ -36,4 +36,8 @@
         settings:useAdminDisabledSummary="true"
         settings:controller="com.android.car.developeroptions.connecteddevice.AddDevicePreferenceController"/>
 
-</PreferenceScreen>
\ No newline at end of file
+    <com.android.settingslib.widget.FooterPreference
+        android:key="bluetooth_screen_footer"
+        android:selectable="false"
+        settings:searchable="false"/>
+</PreferenceScreen>
diff --git a/tests/CarDeveloperOptions/res/xml/connected_devices.xml b/tests/CarDeveloperOptions/res/xml/connected_devices.xml
index 56b9139..88421be 100644
--- a/tests/CarDeveloperOptions/res/xml/connected_devices.xml
+++ b/tests/CarDeveloperOptions/res/xml/connected_devices.xml
@@ -67,4 +67,11 @@
         settings:allowDividerAbove="true"
         settings:controller="com.android.car.developeroptions.connecteddevice.AdvancedConnectedDeviceController"/>
 
+    <com.android.settingslib.widget.FooterPreference
+        android:key="discoverable_footer"
+        android:title="@string/bluetooth_off_footer"
+        android:selectable="false"
+        settings:controller="com.android.car.developeroptions.connecteddevice.DiscoverableFooterPreferenceController">
+    </com.android.settingslib.widget.FooterPreference>
+
 </PreferenceScreen>
diff --git a/tests/CarDeveloperOptions/res/xml/data_saver.xml b/tests/CarDeveloperOptions/res/xml/data_saver.xml
index 614b079..6903642 100644
--- a/tests/CarDeveloperOptions/res/xml/data_saver.xml
+++ b/tests/CarDeveloperOptions/res/xml/data_saver.xml
@@ -27,4 +27,10 @@
         android:fragment="com.android.car.developeroptions.datausage.UnrestrictedDataAccess"
         settings:controller="com.android.car.developeroptions.applications.specialaccess.DataSaverController" />
 
+    <com.android.settingslib.widget.FooterPreference
+        android:key="data_saver_footer"
+        android:title="@*android:string/data_saver_description"
+        android:selectable="false"
+        settings:searchable="false"/>
+
 </PreferenceScreen>
diff --git a/tests/CarDeveloperOptions/res/xml/device_admin_settings.xml b/tests/CarDeveloperOptions/res/xml/device_admin_settings.xml
index b77ed81..fc54c90 100644
--- a/tests/CarDeveloperOptions/res/xml/device_admin_settings.xml
+++ b/tests/CarDeveloperOptions/res/xml/device_admin_settings.xml
@@ -20,4 +20,11 @@
     xmlns:settings="http://schemas.android.com/apk/res-auto"
     android:title="@string/manage_device_admin"
     android:key="device_admin_settings"
-    settings:controller="com.android.car.developeroptions.applications.specialaccess.deviceadmin.DeviceAdminListPreferenceController" />
\ No newline at end of file
+    settings:controller="com.android.car.developeroptions.applications.specialaccess.deviceadmin.DeviceAdminListPreferenceController">
+
+    <com.android.settingslib.widget.FooterPreference
+        android:key="device_admin_footer"
+        android:title="@string/no_device_admins"
+        android:selectable="false"
+        settings:searchable="false"/>
+</PreferenceScreen>
diff --git a/tests/CarDeveloperOptions/res/xml/feature_flags_settings.xml b/tests/CarDeveloperOptions/res/xml/feature_flags_settings.xml
index e194795..6b90bee 100644
--- a/tests/CarDeveloperOptions/res/xml/feature_flags_settings.xml
+++ b/tests/CarDeveloperOptions/res/xml/feature_flags_settings.xml
@@ -25,4 +25,10 @@
         android:layout="@layout/preference_category_no_label"
         android:title="@string/summary_placeholder"
         settings:controller="com.android.car.developeroptions.development.featureflags.FeatureFlagsPreferenceController" />
+
+    <com.android.settingslib.widget.FooterPreference
+        android:key="feature_flag_footer"
+        android:title="@string/experimental_category_title"
+        android:selectable="false"
+        settings:searchable="false"/>
 </PreferenceScreen>
diff --git a/tests/CarDeveloperOptions/res/xml/game_driver_settings.xml b/tests/CarDeveloperOptions/res/xml/game_driver_settings.xml
index bb847da..52d0ab2 100644
--- a/tests/CarDeveloperOptions/res/xml/game_driver_settings.xml
+++ b/tests/CarDeveloperOptions/res/xml/game_driver_settings.xml
@@ -34,7 +34,7 @@
     </PreferenceCategory>
 
     <com.android.settingslib.widget.FooterPreference
-        android:key="footer_preference"
+        android:key="game_driver_footer"
         android:title="@string/game_driver_footer_text"
         android:selectable="false"
         settings:controller="com.android.car.developeroptions.development.gamedriver.GameDriverFooterPreferenceController">
diff --git a/tests/CarDeveloperOptions/res/xml/manage_assist.xml b/tests/CarDeveloperOptions/res/xml/manage_assist.xml
index 46917cb..56837b5 100644
--- a/tests/CarDeveloperOptions/res/xml/manage_assist.xml
+++ b/tests/CarDeveloperOptions/res/xml/manage_assist.xml
@@ -53,4 +53,10 @@
         android:title="@string/voice_input_settings_title"
         android:fragment="com.android.car.developeroptions.applications.assist.DefaultVoiceInputPicker" />
 
+    <com.android.settingslib.widget.FooterPreference
+        android:key="manage_assist_footer"
+        android:title="@string/assist_footer"
+        android:selectable="false"
+        settings:searchable="false"/>
+
 </PreferenceScreen>
diff --git a/tests/CarDeveloperOptions/res/xml/night_display_settings.xml b/tests/CarDeveloperOptions/res/xml/night_display_settings.xml
index f1d8d9e..4b7855d 100644
--- a/tests/CarDeveloperOptions/res/xml/night_display_settings.xml
+++ b/tests/CarDeveloperOptions/res/xml/night_display_settings.xml
@@ -49,11 +49,15 @@
         android:title="@string/night_display_title"
         android:selectable="false"
         android:layout="@layout/night_display_activation_button"
+        settings:allowDividerBelow="true"
         settings:keywords="@string/keywords_display_night_display"
         settings:controller="com.android.car.developeroptions.display.NightDisplayActivationPreferenceController" />
 
-    <PreferenceCategory android:key="night_display_footer_category">
-        <com.android.settingslib.widget.FooterPreference />
-    </PreferenceCategory>
+    <com.android.settingslib.widget.FooterPreference
+        android:key="night_display_footer"
+        android:title="@string/night_display_text"
+        android:selectable="false"
+        settings:allowDividerAbove="true"
+        settings:controller="com.android.car.developeroptions.display.NightDisplayFooterPreferenceController"/>
 
-</PreferenceScreen>
\ No newline at end of file
+</PreferenceScreen>
diff --git a/tests/CarDeveloperOptions/res/xml/power_usage_summary.xml b/tests/CarDeveloperOptions/res/xml/power_usage_summary.xml
index 5f67488..9dd54db 100644
--- a/tests/CarDeveloperOptions/res/xml/power_usage_summary.xml
+++ b/tests/CarDeveloperOptions/res/xml/power_usage_summary.xml
@@ -64,4 +64,9 @@
         android:title="@string/device_screen_usage"
         android:selectable="false" />
 
+    <com.android.settingslib.widget.FooterPreference
+        android:key="power_usage_footer"
+        android:title="@string/battery_footer_summary"
+        android:selectable="false"
+        settings:searchable="false"/>
 </PreferenceScreen>
diff --git a/tests/CarDeveloperOptions/res/xml/restricted_apps_detail.xml b/tests/CarDeveloperOptions/res/xml/restricted_apps_detail.xml
index eea9de8..6b6404c 100644
--- a/tests/CarDeveloperOptions/res/xml/restricted_apps_detail.xml
+++ b/tests/CarDeveloperOptions/res/xml/restricted_apps_detail.xml
@@ -16,9 +16,15 @@
 
 <PreferenceScreen
     xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:settings="http://schemas.android.com/apk/res-auto"
     android:title="@string/restricted_app_title">
 
     <PreferenceCategory
         android:key="restrict_app_list"/>
 
+    <com.android.settingslib.widget.FooterPreference
+        android:key="restricted_app_footer"
+        android:title="@string/restricted_app_detail_footer"
+        android:selectable="false"
+        settings:searchable="false"/>
 </PreferenceScreen>
diff --git a/tests/CarDeveloperOptions/res/xml/security_settings_picker.xml b/tests/CarDeveloperOptions/res/xml/security_settings_picker.xml
index 070b657..af4cfe7 100644
--- a/tests/CarDeveloperOptions/res/xml/security_settings_picker.xml
+++ b/tests/CarDeveloperOptions/res/xml/security_settings_picker.xml
@@ -15,6 +15,7 @@
 -->
 
 <PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
+                  xmlns:settings="http://schemas.android.com/apk/res-auto"
         android:title="@string/lock_settings_picker_title"
         android:key="lock_settings_picker">
 
@@ -57,4 +58,9 @@
             android:title="@string/face_unlock_skip_face"
             android:persistent="false"/>
 
+    <com.android.settingslib.widget.FooterPreference
+            android:key="lock_settings_footer"
+            android:selectable="false"
+            settings:searchable="false"/>
+
 </PreferenceScreen>
diff --git a/tests/CarDeveloperOptions/res/xml/smart_battery_detail.xml b/tests/CarDeveloperOptions/res/xml/smart_battery_detail.xml
index fa71e1a..8c2ea1f 100644
--- a/tests/CarDeveloperOptions/res/xml/smart_battery_detail.xml
+++ b/tests/CarDeveloperOptions/res/xml/smart_battery_detail.xml
@@ -45,4 +45,9 @@
         android:key="restricted_app"
         android:title="@string/restricted_app_title"/>
 
-</PreferenceScreen>
\ No newline at end of file
+    <com.android.settingslib.widget.FooterPreference
+        android:key="smart_battery_detail_footer"
+        android:title="@string/smart_battery_footer"
+        android:selectable="false"
+        settings:searchable="false"/>
+</PreferenceScreen>
diff --git a/tests/CarDeveloperOptions/res/xml/tether_prefs.xml b/tests/CarDeveloperOptions/res/xml/tether_prefs.xml
index 3267115..aa85314 100644
--- a/tests/CarDeveloperOptions/res/xml/tether_prefs.xml
+++ b/tests/CarDeveloperOptions/res/xml/tether_prefs.xml
@@ -44,4 +44,10 @@
         android:summary="@string/tether_settings_disabled_on_data_saver"
         android:selectable="false"
         settings:allowDividerAbove="true" />
+
+    <com.android.settingslib.widget.FooterPreference
+        android:key="tether_prefs_footer"
+        android:title="@string/tethering_footer_info"
+        android:selectable="false"
+        settings:searchable="false"/>
 </PreferenceScreen>
diff --git a/tests/CarDeveloperOptions/res/xml/time_zone_prefs.xml b/tests/CarDeveloperOptions/res/xml/time_zone_prefs.xml
index dba42bf..30e52ce 100644
--- a/tests/CarDeveloperOptions/res/xml/time_zone_prefs.xml
+++ b/tests/CarDeveloperOptions/res/xml/time_zone_prefs.xml
@@ -31,7 +31,7 @@
             android:title="@string/date_time_set_timezone_title"
             android:summary="@string/summary_placeholder" />
         <com.android.settingslib.widget.FooterPreference
-            android:key="footer_preference"
+            android:key="timezone_footer"
             settings:controller="com.android.car.developeroptions.datetime.timezone.TimeZoneInfoPreferenceController" />
     </PreferenceCategory>
 
diff --git a/tests/CarDeveloperOptions/res/xml/user_settings.xml b/tests/CarDeveloperOptions/res/xml/user_settings.xml
index d2b7d46..bdc8b4d 100644
--- a/tests/CarDeveloperOptions/res/xml/user_settings.xml
+++ b/tests/CarDeveloperOptions/res/xml/user_settings.xml
@@ -40,4 +40,11 @@
         android:order="105"
         settings:allowDividerAbove="true"/>
 
+    <com.android.settingslib.widget.FooterPreference
+        android:key="multiuser_footer"
+        android:title="@string/user_settings_footer_text"
+        android:selectable="false"
+        settings:searchable="false"
+        settings:controller="com.android.car.developeroptions.users.MultiUserFooterPreferenceController"/>
+
 </PreferenceScreen>
diff --git a/tests/CarDeveloperOptions/res/xml/wifi_configure_settings.xml b/tests/CarDeveloperOptions/res/xml/wifi_configure_settings.xml
index c4e4485..2f02f7d 100644
--- a/tests/CarDeveloperOptions/res/xml/wifi_configure_settings.xml
+++ b/tests/CarDeveloperOptions/res/xml/wifi_configure_settings.xml
@@ -48,10 +48,12 @@
     <Preference
         android:key="install_credentials"
         android:title="@string/wifi_install_credentials">
-        <intent android:action="android.credentials.INSTALL_AS_USER"
-                android:targetPackage="com.android.certinstaller"
-                android:targetClass="com.android.certinstaller.CertInstallerMain">
-            <extra android:name="install_as_uid" android:value="1010" />
+        <intent
+            android:action="android.credentials.INSTALL"
+            android:targetPackage="com.android.certinstaller"
+            android:targetClass="com.android.certinstaller.CertInstallerMain">
+            <!-- Same value as CERTIFICATE_USAGE_WIFI in keystore/java/android/security/Credentials.java -->
+            <extra android:name="certificate_install_usage" android:value="wifi"/>
         </intent>
     </Preference>
 
diff --git a/tests/CarDeveloperOptions/res/xml/zen_mode_block_settings.xml b/tests/CarDeveloperOptions/res/xml/zen_mode_block_settings.xml
index e290842..2aa43f0 100644
--- a/tests/CarDeveloperOptions/res/xml/zen_mode_block_settings.xml
+++ b/tests/CarDeveloperOptions/res/xml/zen_mode_block_settings.xml
@@ -17,6 +17,7 @@
 
 <PreferenceScreen
     xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:settings="http://schemas.android.com/apk/res-auto"
     android:key="zen_mode_block_settings_page"
     android:title="@string/zen_mode_what_to_block_title">
 
@@ -57,4 +58,10 @@
            android:title="@string/zen_mode_block_effect_list" />
    </PreferenceCategory>
 
+    <com.android.settingslib.widget.FooterPreference
+        android:key="zen_mode_block_footer"
+        android:title="@string/zen_mode_blocked_effects_footer"
+        android:selectable="false"
+        settings:searchable="false"/>
+
 </PreferenceScreen>
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/AirplaneModeEnabler.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/AirplaneModeEnabler.java
index 6b7e912..1e82aa9 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/AirplaneModeEnabler.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/AirplaneModeEnabler.java
@@ -17,29 +17,27 @@
 package com.android.car.developeroptions;
 
 import android.app.settings.SettingsEnums;
+import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
+import android.content.IntentFilter;
 import android.database.ContentObserver;
 import android.os.Handler;
 import android.os.Looper;
-import android.os.Message;
-import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.provider.Settings;
+import android.sysprop.TelephonyProperties;
 
-import com.android.internal.telephony.PhoneStateIntentReceiver;
-import com.android.internal.telephony.TelephonyProperties;
 import com.android.settingslib.WirelessUtils;
 import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
 
-public class AirplaneModeEnabler {
+public class AirplaneModeEnabler  extends BroadcastReceiver {
 
     private static final int EVENT_SERVICE_STATE_CHANGED = 3;
 
     private final Context mContext;
     private final MetricsFeatureProvider mMetricsFeatureProvider;
-
-    private PhoneStateIntentReceiver mPhoneStateReceiver;
+    private IntentFilter mFilter;
 
     private OnAirplaneModeChangedListener mOnAirplaneModeChangedListener;
 
@@ -52,17 +50,6 @@
         void onAirplaneModeChanged(boolean isAirplaneModeOn);
     }
 
-    private Handler mHandler = new Handler(Looper.getMainLooper()) {
-        @Override
-        public void handleMessage(Message msg) {
-            switch (msg.what) {
-                case EVENT_SERVICE_STATE_CHANGED:
-                    onAirplaneModeChanged();
-                    break;
-            }
-        }
-    };
-
     private ContentObserver mAirplaneModeObserver = new ContentObserver(
             new Handler(Looper.getMainLooper())) {
         @Override
@@ -78,19 +65,18 @@
         mMetricsFeatureProvider = metricsFeatureProvider;
         mOnAirplaneModeChangedListener = listener;
 
-        mPhoneStateReceiver = new PhoneStateIntentReceiver(mContext, mHandler);
-        mPhoneStateReceiver.notifyServiceState(EVENT_SERVICE_STATE_CHANGED);
+        mFilter = new IntentFilter(Intent.ACTION_SERVICE_STATE);
     }
 
     public void resume() {
-        mPhoneStateReceiver.registerIntent();
+        mContext.registerReceiver(this, mFilter);
         mContext.getContentResolver().registerContentObserver(
                 Settings.Global.getUriFor(Settings.Global.AIRPLANE_MODE_ON), true,
                 mAirplaneModeObserver);
     }
 
     public void pause() {
-        mPhoneStateReceiver.unregisterIntent();
+        mContext.unregisterReceiver(this);
         mContext.getContentResolver().unregisterContentObserver(mAirplaneModeObserver);
     }
 
@@ -125,8 +111,7 @@
     }
 
     public void setAirplaneMode(boolean isAirplaneModeOn) {
-        if (Boolean.parseBoolean(
-                SystemProperties.get(TelephonyProperties.PROPERTY_INECM_MODE))) {
+        if (TelephonyProperties.in_ecm_mode().orElse(false)) {
             // In ECM mode, do not update database at this point
         } else {
             mMetricsFeatureProvider.action(mContext, SettingsEnums.ACTION_AIRPLANE_TOGGLE,
@@ -148,4 +133,13 @@
     public boolean isAirplaneModeOn() {
         return WirelessUtils.isAirplaneModeOn(mContext);
     }
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        String action = intent.getAction();
+
+        if (Intent.ACTION_SERVICE_STATE.equals(action)) {
+            onAirplaneModeChanged();
+        }
+    }
 }
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/CryptKeeper.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/CryptKeeper.java
index bafe467..c7b372e 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/CryptKeeper.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/CryptKeeper.java
@@ -60,12 +60,11 @@
 import android.widget.ProgressBar;
 import android.widget.TextView;
 
-import com.android.internal.telephony.PhoneConstants;
+import com.android.car.developeroptions.widget.ImeAwareEditText;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.internal.widget.LockPatternView;
 import com.android.internal.widget.LockPatternView.Cell;
 import com.android.internal.widget.LockPatternView.DisplayMode;
-import com.android.car.developeroptions.widget.ImeAwareEditText;
 
 import java.util.List;
 
@@ -727,7 +726,7 @@
         public void onPatternDetected(List<LockPatternView.Cell> pattern) {
             mLockPatternView.setEnabled(false);
             if (pattern.size() >= MIN_LENGTH_BEFORE_REPORT) {
-                new DecryptTask().execute(LockPatternUtils.patternToString(pattern));
+                new DecryptTask().execute(new String(LockPatternUtils.patternToByteArray(pattern)));
             } else {
                 // Allow user to make as many of these as they want.
                 fakeUnlockAttempt(mLockPatternView);
@@ -916,9 +915,7 @@
      *    phone that has no encryption.
      */
     private final void setAirplaneModeIfNecessary() {
-        final boolean isLteDevice =
-                getTelephonyManager().getLteOnCdmaMode() == PhoneConstants.LTE_ON_CDMA_TRUE;
-        if (!isLteDevice) {
+        if (!getTelephonyManager().isGlobalModeEnabled()) {
             Log.d(TAG, "Going into airplane mode.");
             Settings.Global.putInt(getContentResolver(), Settings.Global.AIRPLANE_MODE_ON, 1);
             final Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
@@ -964,7 +961,7 @@
     }
 
     private boolean isEmergencyCallCapable() {
-        return getResources().getBoolean(com.android.internal.R.bool.config_voice_capable);
+        return getTelephonyManager().isVoiceCapable();
     }
 
     private void takeEmergencyCallAction() {
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/CryptKeeperConfirm.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/CryptKeeperConfirm.java
index fcdbd1b..e0ff5c6 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/CryptKeeperConfirm.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/CryptKeeperConfirm.java
@@ -35,8 +35,8 @@
 import android.view.ViewGroup;
 import android.widget.Button;
 
-import com.android.internal.widget.LockPatternUtils;
 import com.android.car.developeroptions.core.InstrumentedFragment;
+import com.android.internal.widget.LockPatternUtils;
 
 import java.util.Arrays;
 import java.util.Locale;
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/DateTimeSettings.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/DateTimeSettings.java
index f64debd..035a8d3 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/DateTimeSettings.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/DateTimeSettings.java
@@ -34,9 +34,9 @@
 import com.android.car.developeroptions.datetime.TimePreferenceController;
 import com.android.car.developeroptions.datetime.TimeZonePreferenceController;
 import com.android.car.developeroptions.search.BaseSearchIndexProvider;
-import com.android.car.developeroptions.search.Indexable;
 import com.android.settingslib.core.AbstractPreferenceController;
 import com.android.settingslib.datetime.ZoneGetter;
+import com.android.settingslib.search.Indexable;
 import com.android.settingslib.search.SearchIndexable;
 
 import java.util.ArrayList;
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/DisplaySettings.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/DisplaySettings.java
index a449e8a..35e9d71 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/DisplaySettings.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/DisplaySettings.java
@@ -33,9 +33,9 @@
 import com.android.car.developeroptions.display.TimeoutPreferenceController;
 import com.android.car.developeroptions.display.VrDisplayPreferenceController;
 import com.android.car.developeroptions.search.BaseSearchIndexProvider;
-import com.android.car.developeroptions.search.Indexable;
 import com.android.settingslib.core.AbstractPreferenceController;
 import com.android.settingslib.core.lifecycle.Lifecycle;
+import com.android.settingslib.search.Indexable;
 import com.android.settingslib.search.SearchIndexable;
 
 import java.util.ArrayList;
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/IccLockSettings.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/IccLockSettings.java
index 443fb85..0eff1c0 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/IccLockSettings.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/IccLockSettings.java
@@ -37,6 +37,7 @@
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
+import android.view.WindowInsets.Type;
 import android.view.WindowManager;
 import android.widget.EditText;
 import android.widget.ListView;
@@ -516,6 +517,7 @@
         params.format = PixelFormat.TRANSLUCENT;
         params.windowAnimations = com.android.internal.R.style.Animation_Toast;
         params.type = WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL;
+        params.setFitInsetsTypes(params.getFitInsetsTypes() & ~Type.statusBars());
         params.setTitle(errorMessage);
         params.flags = WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
                 | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/LegalSettings.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/LegalSettings.java
index 0c1c150..e0cf6d7 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/LegalSettings.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/LegalSettings.java
@@ -22,7 +22,7 @@
 
 import com.android.car.developeroptions.dashboard.DashboardFragment;
 import com.android.car.developeroptions.search.BaseSearchIndexProvider;
-import com.android.car.developeroptions.search.Indexable;
+import com.android.settingslib.search.Indexable;
 import com.android.settingslib.search.SearchIndexable;
 
 import java.util.Arrays;
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/RadioInfo.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/RadioInfo.java
index 5187c91..73f12f7 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/RadioInfo.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/RadioInfo.java
@@ -170,6 +170,7 @@
     private static final int EVENT_SET_PREFERRED_TYPE_DONE = 1001;
     private static final int EVENT_QUERY_SMSC_DONE = 1005;
     private static final int EVENT_UPDATE_SMSC_DONE = 1006;
+    private static final int EVENT_PHYSICAL_CHANNEL_CONFIG_CHANGED = 1007;
 
     private static final int MENU_ITEM_SELECT_BAND  = 0;
     private static final int MENU_ITEM_VIEW_ADN     = 1;
@@ -313,12 +314,6 @@
             updateImsProvisionedState();
         }
 
-        @Override
-        public void onPhysicalChannelConfigurationChanged(
-                List<PhysicalChannelConfig> configs) {
-            updatePhysicalChannelConfiguration(configs);
-        }
-
     };
 
     private void updatePhysicalChannelConfiguration(List<PhysicalChannelConfig> configs) {
@@ -381,6 +376,13 @@
                         smsc.setText("update error");
                     }
                     break;
+                case EVENT_PHYSICAL_CHANNEL_CONFIG_CHANGED:
+                    ar = (AsyncResult) msg.obj;
+                    if (ar.exception != null) {
+                        mPhyChanConfig.setText(("update error"));
+                    }
+                    updatePhysicalChannelConfiguration((List<PhysicalChannelConfig>) ar.result);
+                    break;
                 default:
                     super.handleMessage(msg);
                     break;
@@ -574,8 +576,9 @@
                 | PhoneStateListener.LISTEN_CALL_FORWARDING_INDICATOR
                 | PhoneStateListener.LISTEN_CELL_INFO
                 | PhoneStateListener.LISTEN_SERVICE_STATE
-                | PhoneStateListener.LISTEN_SIGNAL_STRENGTHS
-                | PhoneStateListener.LISTEN_PHYSICAL_CHANNEL_CONFIGURATION);
+                | PhoneStateListener.LISTEN_SIGNAL_STRENGTHS);
+        phone.registerForPhysicalChannelConfig(mHandler,
+            EVENT_PHYSICAL_CHANNEL_CONFIG_CHANGED, null);
 
         mConnectivityManager.registerNetworkCallback(
                 mDefaultNetworkRequest, mNetworkCallback, mHandler);
@@ -592,7 +595,7 @@
         mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_NONE);
         mTelephonyManager.setCellInfoListRate(CELL_INFO_LIST_RATE_DISABLED);
         mConnectivityManager.unregisterNetworkCallback(mNetworkCallback);
-
+        phone.unregisterForPhysicalChannelConfig(mHandler);
     }
 
     private void restoreFromBundle(Bundle b) {
@@ -1526,8 +1529,10 @@
     };
 
     boolean isCbrsSupported() {
-        return getResources().getBoolean(
-              com.android.internal.R.bool.config_cbrs_supported);
+        // No longer possible to access com.android.internal.R.bool.config_cbrs_supported, so
+        // returning false for now.
+        // TODO: This needs to be cleaned up in future CL.
+        return false;
     }
 
     void updateCbrsDataState(boolean state) {
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/SettingsActivity.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/SettingsActivity.java
index 1471ebb..b84b749 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/SettingsActivity.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/SettingsActivity.java
@@ -28,6 +28,7 @@
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.res.Resources.Theme;
+import android.graphics.drawable.Icon;
 import android.os.AsyncTask;
 import android.os.Bundle;
 import android.os.UserHandle;
@@ -480,7 +481,7 @@
 
     @Override
     public void setTaskDescription(ActivityManager.TaskDescription taskDescription) {
-        taskDescription.setIcon(R.drawable.ic_launcher_settings);
+        taskDescription.setIcon(Icon.createWithResource(this, R.drawable.ic_launcher_settings));
         super.setTaskDescription(taskDescription);
     }
 
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/SettingsPreferenceFragment.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/SettingsPreferenceFragment.java
index 480800d..fd72461 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/SettingsPreferenceFragment.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/SettingsPreferenceFragment.java
@@ -44,7 +44,6 @@
 
 import com.android.car.developeroptions.core.InstrumentedPreferenceFragment;
 import com.android.car.developeroptions.core.instrumentation.InstrumentedDialogFragment;
-import com.android.car.developeroptions.search.Indexable;
 import com.android.car.developeroptions.search.actionbar.SearchMenuController;
 import com.android.car.developeroptions.support.actionbar.HelpMenuController;
 import com.android.car.developeroptions.support.actionbar.HelpResourceProvider;
@@ -53,7 +52,7 @@
 import com.android.settingslib.CustomDialogPreferenceCompat;
 import com.android.settingslib.CustomEditTextPreferenceCompat;
 import com.android.settingslib.core.instrumentation.Instrumentable;
-import com.android.settingslib.widget.FooterPreferenceMixinCompat;
+import com.android.settingslib.search.Indexable;
 import com.android.settingslib.widget.LayoutPreference;
 
 import java.util.UUID;
@@ -68,9 +67,6 @@
 
     private static final String SAVE_HIGHLIGHTED_KEY = "android:preference_highlighted";
 
-    protected final FooterPreferenceMixinCompat mFooterPreferenceMixin =
-            new FooterPreferenceMixinCompat(this, getSettingsLifecycle());
-
 
     private static final int ORDER_FIRST = -1;
 
@@ -310,8 +306,7 @@
         if (getPreferenceScreen() != null) {
             final View listContainer = getActivity().findViewById(android.R.id.list_container);
             boolean show = (getPreferenceScreen().getPreferenceCount()
-                    - (mHeader != null ? 1 : 0)
-                    - (mFooterPreferenceMixin.hasFooter() ? 1 : 0)) <= 0
+                    - (mHeader != null ? 1 : 0)) <= 0
                     || (listContainer != null && listContainer.getVisibility() != View.VISIBLE);
             mEmptyView.setVisibility(show ? View.VISIBLE : View.GONE);
         } else {
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/TetherSettings.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/TetherSettings.java
index 2a26947..489377e 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/TetherSettings.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/TetherSettings.java
@@ -43,9 +43,9 @@
 
 import com.android.car.developeroptions.datausage.DataSaverBackend;
 import com.android.car.developeroptions.search.BaseSearchIndexProvider;
-import com.android.car.developeroptions.search.Indexable;
 import com.android.car.developeroptions.wifi.tether.WifiTetherPreferenceController;
 import com.android.settingslib.TetherUtil;
+import com.android.settingslib.search.Indexable;
 import com.android.settingslib.search.SearchIndexable;
 
 import java.lang.ref.WeakReference;
@@ -120,9 +120,6 @@
         super.onCreate(icicle);
 
         addPreferencesFromResource(R.xml.tether_prefs);
-        mFooterPreferenceMixin.createFooterPreference()
-            .setTitle(R.string.tethering_footer_info);
-
         mDataSaverBackend = new DataSaverBackend(getContext());
         mDataSaverEnabled = mDataSaverBackend.isDataSaverEnabled();
         mDataSaverFooter = findPreference(KEY_DATA_SAVER_FOOTER);
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/accessibility/AccessibilitySettings.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/accessibility/AccessibilitySettings.java
index df096bd..0c8b62c 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/accessibility/AccessibilitySettings.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/accessibility/AccessibilitySettings.java
@@ -52,16 +52,16 @@
 import androidx.preference.PreferenceScreen;
 import androidx.preference.SwitchPreference;
 
-import com.android.internal.accessibility.AccessibilityShortcutController;
-import com.android.internal.content.PackageMonitor;
-import com.android.internal.view.RotationPolicy;
-import com.android.internal.view.RotationPolicy.RotationPolicyListener;
 import com.android.car.developeroptions.R;
 import com.android.car.developeroptions.SettingsPreferenceFragment;
 import com.android.car.developeroptions.Utils;
 import com.android.car.developeroptions.display.DarkUIPreferenceController;
 import com.android.car.developeroptions.display.ToggleFontSizePreferenceFragment;
 import com.android.car.developeroptions.search.BaseSearchIndexProvider;
+import com.android.internal.accessibility.AccessibilityShortcutController;
+import com.android.internal.content.PackageMonitor;
+import com.android.internal.view.RotationPolicy;
+import com.android.internal.view.RotationPolicy.RotationPolicyListener;
 import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
 import com.android.settingslib.RestrictedLockUtilsInternal;
 import com.android.settingslib.RestrictedPreference;
@@ -236,7 +236,6 @@
     private Preference mDisplayMagnificationPreferenceScreen;
     private Preference mFontSizePreferenceScreen;
     private Preference mAutoclickPreferenceScreen;
-    private Preference mAccessibilityShortcutPreferenceScreen;
     private Preference mDisplayDaltonizerPreferenceScreen;
     private Preference mHearingAidPreference;
     private Preference mVibrationPreferenceScreen;
@@ -514,9 +513,6 @@
         // Display color adjustments.
         mDisplayDaltonizerPreferenceScreen = findPreference(DISPLAY_DALTONIZER_PREFERENCE_SCREEN);
 
-        // Accessibility shortcut.
-        mAccessibilityShortcutPreferenceScreen = findPreference(ACCESSIBILITY_SHORTCUT_PREFERENCE);
-
         // Vibrations.
         mVibrationPreferenceScreen = findPreference(VIBRATION_PREFERENCE_SCREEN);
 
@@ -759,8 +755,6 @@
 
         updateAutoclickSummary(mAutoclickPreferenceScreen);
 
-        updateAccessibilityShortcut(mAccessibilityShortcutPreferenceScreen);
-
         updateAccessibilityTimeoutSummary(getContentResolver(),
                 findPreference(ACCESSIBILITY_CONTENT_TIMEOUT_PREFERENCE));
         updateAccessibilityTimeoutSummary(getContentResolver(),
@@ -936,23 +930,6 @@
         mToggleMasterMonoPreference.setChecked(masterMono);
     }
 
-    private void updateAccessibilityShortcut(Preference preference) {
-        if (AccessibilityManager.getInstance(getActivity())
-                .getInstalledAccessibilityServiceList().isEmpty()) {
-            mAccessibilityShortcutPreferenceScreen
-                    .setSummary(getString(R.string.accessibility_no_services_installed));
-            mAccessibilityShortcutPreferenceScreen.setEnabled(false);
-        } else {
-            mAccessibilityShortcutPreferenceScreen.setEnabled(true);
-            boolean shortcutEnabled =
-                    AccessibilityUtils.isShortcutEnabled(getContext(), UserHandle.myUserId());
-            CharSequence summary = shortcutEnabled
-                    ? AccessibilityShortcutPreferenceFragment.getServiceName(getContext())
-                    : getString(R.string.accessibility_feature_state_off);
-            mAccessibilityShortcutPreferenceScreen.setSummary(summary);
-        }
-    }
-
     private static void configureMagnificationPreferenceIfNeeded(Preference preference) {
         // Some devices support only a single magnification mode. In these cases, we redirect to
         // the magnification mode's UI directly, rather than showing a PreferenceScreen with a
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/accessibility/AccessibilityShortcutPreferenceFragment.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/accessibility/AccessibilityShortcutPreferenceFragment.java
index 22fdab7..16d005b 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/accessibility/AccessibilityShortcutPreferenceFragment.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/accessibility/AccessibilityShortcutPreferenceFragment.java
@@ -32,11 +32,11 @@
 import androidx.preference.Preference;
 import androidx.preference.SwitchPreference;
 
-import com.android.internal.accessibility.AccessibilityShortcutController;
 import com.android.car.developeroptions.R;
 import com.android.car.developeroptions.search.BaseSearchIndexProvider;
-import com.android.car.developeroptions.search.Indexable;
+import com.android.internal.accessibility.AccessibilityShortcutController;
 import com.android.settingslib.accessibility.AccessibilityUtils;
+import com.android.settingslib.search.Indexable;
 import com.android.settingslib.search.SearchIndexable;
 
 /**
@@ -80,8 +80,6 @@
                     ((Boolean) o) ? 1 : 0);
             return true;
         });
-        mFooterPreferenceMixin.createFooterPreference()
-                .setTitle(R.string.accessibility_shortcut_description);
     }
 
     @Override
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/accessibility/MagnificationPreferenceFragment.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/accessibility/MagnificationPreferenceFragment.java
index bbc5b68..a7ba6c6 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/accessibility/MagnificationPreferenceFragment.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/accessibility/MagnificationPreferenceFragment.java
@@ -34,9 +34,9 @@
 import com.android.car.developeroptions.R;
 import com.android.car.developeroptions.dashboard.DashboardFragment;
 import com.android.car.developeroptions.search.BaseSearchIndexProvider;
-import com.android.car.developeroptions.search.Indexable;
 import com.android.car.developeroptions.search.actionbar.SearchMenuController;
 import com.android.car.developeroptions.support.actionbar.HelpResourceProvider;
+import com.android.settingslib.search.Indexable;
 import com.android.settingslib.search.SearchIndexable;
 
 import java.util.Arrays;
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/accessibility/ToggleAccessibilityServicePreferenceFragment.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/accessibility/ToggleAccessibilityServicePreferenceFragment.java
index 217267a..1b8e5cd 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/accessibility/ToggleAccessibilityServicePreferenceFragment.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/accessibility/ToggleAccessibilityServicePreferenceFragment.java
@@ -38,11 +38,11 @@
 
 import androidx.appcompat.app.AlertDialog;
 
-import com.android.internal.widget.LockPatternUtils;
 import com.android.car.developeroptions.R;
 import com.android.car.developeroptions.password.ConfirmDeviceCredentialActivity;
 import com.android.car.developeroptions.widget.ToggleSwitch;
 import com.android.car.developeroptions.widget.ToggleSwitch.OnBeforeCheckedChangeListener;
+import com.android.internal.widget.LockPatternUtils;
 import com.android.settingslib.accessibility.AccessibilityUtils;
 
 import java.util.List;
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/accessibility/ToggleAutoclickPreferenceFragment.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/accessibility/ToggleAutoclickPreferenceFragment.java
index db0ddb2..1fa01e6 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/accessibility/ToggleAutoclickPreferenceFragment.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/accessibility/ToggleAutoclickPreferenceFragment.java
@@ -29,9 +29,9 @@
 
 import com.android.car.developeroptions.R;
 import com.android.car.developeroptions.search.BaseSearchIndexProvider;
-import com.android.car.developeroptions.search.Indexable;
 import com.android.car.developeroptions.widget.SeekBarPreference;
 import com.android.car.developeroptions.widget.SwitchBar;
+import com.android.settingslib.search.Indexable;
 import com.android.settingslib.search.SearchIndexable;
 
 import java.util.ArrayList;
@@ -136,8 +136,6 @@
         mDelay.setMax(delayToSeekBarProgress(MAX_AUTOCLICK_DELAY));
         mDelay.setProgress(delayToSeekBarProgress(delay));
         mDelay.setOnPreferenceChangeListener(this);
-        mFooterPreferenceMixin.createFooterPreference()
-                .setTitle(R.string.accessibility_autoclick_description);
     }
 
     @Override
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/accessibility/ToggleDaltonizerPreferenceFragment.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/accessibility/ToggleDaltonizerPreferenceFragment.java
index 3c7685f..71fb3c9 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/accessibility/ToggleDaltonizerPreferenceFragment.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/accessibility/ToggleDaltonizerPreferenceFragment.java
@@ -30,8 +30,8 @@
 
 import com.android.car.developeroptions.R;
 import com.android.car.developeroptions.search.BaseSearchIndexProvider;
-import com.android.car.developeroptions.search.Indexable;
 import com.android.car.developeroptions.widget.SwitchBar;
+import com.android.settingslib.search.Indexable;
 import com.android.settingslib.search.SearchIndexable;
 
 import java.util.ArrayList;
@@ -43,6 +43,7 @@
     private static final String ENABLED = Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED;
     private static final String TYPE = Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER;
     private static final int DEFAULT_TYPE = AccessibilityManager.DALTONIZER_CORRECT_DEUTERANOMALY;
+    private static final String KEY_DALTONIZER_FOOTER = "daltonizer_footer";
 
     private ListPreference mType;
 
@@ -62,10 +63,8 @@
 
         mType = (ListPreference) findPreference("type");
 
-        if (!ColorDisplayManager.isColorTransformAccelerated(getActivity())) {
-            mFooterPreferenceMixin.createFooterPreference().setTitle(
-                    R.string.accessibility_display_daltonizer_preference_subtitle);
-        }
+        final Preference footer = findPreference(KEY_DALTONIZER_FOOTER);
+        footer.setVisible(!ColorDisplayManager.isColorTransformAccelerated(getActivity()));
         initPreferences();
     }
 
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/accessibility/ToggleFeaturePreferenceFragment.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/accessibility/ToggleFeaturePreferenceFragment.java
index 04a9713..5bc26d7 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/accessibility/ToggleFeaturePreferenceFragment.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/accessibility/ToggleFeaturePreferenceFragment.java
@@ -29,6 +29,7 @@
 import com.android.car.developeroptions.SettingsPreferenceFragment;
 import com.android.car.developeroptions.widget.SwitchBar;
 import com.android.car.developeroptions.widget.ToggleSwitch;
+import com.android.settingslib.widget.FooterPreference;
 
 public abstract class ToggleFeaturePreferenceFragment extends SettingsPreferenceFragment {
 
@@ -138,11 +139,17 @@
         // Summary.
         if (arguments.containsKey(AccessibilitySettings.EXTRA_SUMMARY_RES)) {
             final int summary = arguments.getInt(AccessibilitySettings.EXTRA_SUMMARY_RES);
-            mFooterPreferenceMixin.createFooterPreference().setTitle(summary);
+            createFooterPreference(getText(summary));
         } else if (arguments.containsKey(AccessibilitySettings.EXTRA_SUMMARY)) {
             final CharSequence summary = arguments.getCharSequence(
                     AccessibilitySettings.EXTRA_SUMMARY);
-            mFooterPreferenceMixin.createFooterPreference().setTitle(summary);
+            createFooterPreference(summary);
         }
     }
+
+    private void createFooterPreference(CharSequence title) {
+        final PreferenceScreen preferenceScreen = getPreferenceScreen();
+        preferenceScreen.addPreference(new FooterPreference.Builder(getActivity()).setTitle(
+                title).build());
+    }
 }
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/accounts/AccountPreferenceController.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/accounts/AccountPreferenceController.java
index e2fbd75..9646cfc 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/accounts/AccountPreferenceController.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/accounts/AccountPreferenceController.java
@@ -54,7 +54,6 @@
 import com.android.car.developeroptions.core.PreferenceControllerMixin;
 import com.android.car.developeroptions.core.SubSettingLauncher;
 import com.android.car.developeroptions.overlay.FeatureFactory;
-import com.android.car.developeroptions.search.SearchIndexableRaw;
 import com.android.settingslib.RestrictedPreference;
 import com.android.settingslib.accounts.AuthenticatorHelper;
 import com.android.settingslib.core.AbstractPreferenceController;
@@ -62,6 +61,7 @@
 import com.android.settingslib.core.lifecycle.LifecycleObserver;
 import com.android.settingslib.core.lifecycle.events.OnPause;
 import com.android.settingslib.core.lifecycle.events.OnResume;
+import com.android.settingslib.search.SearchIndexableRaw;
 
 import java.util.ArrayList;
 import java.util.Collections;
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/accounts/AccountSyncSettings.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/accounts/AccountSyncSettings.java
index 5680f76..fc0f272 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/accounts/AccountSyncSettings.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/accounts/AccountSyncSettings.java
@@ -48,6 +48,7 @@
 import com.android.car.developeroptions.R;
 import com.android.car.developeroptions.Utils;
 import com.android.car.developeroptions.widget.EntityHeaderController;
+import com.android.settingslib.widget.FooterPreference;
 
 import com.google.android.collect.Lists;
 
@@ -458,8 +459,8 @@
             syncPref.setChecked(oneTimeSyncMode || syncEnabled);
         }
         if (syncIsFailing) {
-            mFooterPreferenceMixin.createFooterPreference()
-                    .setTitle(R.string.sync_is_failing);
+            getPreferenceScreen().addPreference(new FooterPreference.Builder(
+                    getActivity()).setTitle(R.string.sync_is_failing).build());
         }
     }
 
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/accounts/ChooseAccountFragment.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/accounts/ChooseAccountFragment.java
index 48c7aeb..3c055db 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/accounts/ChooseAccountFragment.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/accounts/ChooseAccountFragment.java
@@ -20,23 +20,14 @@
 import android.content.Context;
 import android.os.UserHandle;
 import android.os.UserManager;
-import android.provider.SearchIndexableResource;
 
 import com.android.car.developeroptions.R;
 import com.android.car.developeroptions.Utils;
 import com.android.car.developeroptions.dashboard.DashboardFragment;
-import com.android.car.developeroptions.search.BaseSearchIndexProvider;
-import com.android.car.developeroptions.search.Indexable;
-import com.android.settingslib.core.AbstractPreferenceController;
-import com.android.settingslib.search.SearchIndexable;
-
-import java.util.ArrayList;
-import java.util.List;
 
 /**
  * Activity asking a user to select an account to be set up.
  */
-@SearchIndexable
 public class ChooseAccountFragment extends DashboardFragment {
 
     private static final String TAG = "ChooseAccountFragment";
@@ -60,8 +51,6 @@
 
         use(ChooseAccountPreferenceController.class).initialize(authorities, accountTypesFilter,
                 userHandle, getActivity());
-        use(EnterpriseDisclosurePreferenceController.class).setFooterPreferenceMixin(
-                mFooterPreferenceMixin);
     }
 
     @Override
@@ -73,35 +62,4 @@
     protected String getLogTag() {
         return TAG;
     }
-
-    @Override
-    protected List<AbstractPreferenceController> createPreferenceControllers(Context context) {
-        return buildControllers(context);
-    }
-
-    private static List<AbstractPreferenceController> buildControllers(Context context) {
-        final List<AbstractPreferenceController> controllers = new ArrayList<>();
-        controllers.add(new EnterpriseDisclosurePreferenceController(context));
-        return controllers;
-    }
-
-    public static final Indexable.SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
-            new BaseSearchIndexProvider() {
-                @Override
-                public List<SearchIndexableResource> getXmlResourcesToIndex(Context context,
-                        boolean enabled) {
-                    final ArrayList<SearchIndexableResource> result = new ArrayList<>();
-
-                    final SearchIndexableResource sir = new SearchIndexableResource(context);
-                    sir.xmlResId = R.xml.add_account_settings;
-                    result.add(sir);
-                    return result;
-                }
-
-                @Override
-                public List<AbstractPreferenceController> createPreferenceControllers(
-                        Context context) {
-                    return buildControllers(context);
-                }
-            };
 }
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/accounts/EmergencyInfoPreferenceController.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/accounts/EmergencyInfoPreferenceController.java
index f4d1c71..ee0216f 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/accounts/EmergencyInfoPreferenceController.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/accounts/EmergencyInfoPreferenceController.java
@@ -28,7 +28,7 @@
 
 import com.android.car.developeroptions.R;
 import com.android.car.developeroptions.core.BasePreferenceController;
-import com.android.car.developeroptions.search.SearchIndexableRaw;
+import com.android.settingslib.search.SearchIndexableRaw;
 
 import java.util.List;
 
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/accounts/EnterpriseDisclosurePreferenceController.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/accounts/EnterpriseDisclosurePreferenceController.java
index 18d5a90..889baf0 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/accounts/EnterpriseDisclosurePreferenceController.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/accounts/EnterpriseDisclosurePreferenceController.java
@@ -19,32 +19,23 @@
 import android.content.Context;
 
 import androidx.annotation.VisibleForTesting;
-import androidx.preference.PreferenceScreen;
+import androidx.preference.Preference;
 
 import com.android.car.developeroptions.core.BasePreferenceController;
 import com.android.car.developeroptions.enterprise.EnterprisePrivacyFeatureProvider;
 import com.android.car.developeroptions.overlay.FeatureFactory;
-import com.android.settingslib.widget.FooterPreference;
-import com.android.settingslib.widget.FooterPreferenceMixinCompat;
 
 public class EnterpriseDisclosurePreferenceController extends BasePreferenceController {
 
     private final EnterprisePrivacyFeatureProvider mFeatureProvider;
-    private FooterPreferenceMixinCompat mFooterPreferenceMixin;
-    private PreferenceScreen mScreen;
 
-    public EnterpriseDisclosurePreferenceController(Context context) {
+    public EnterpriseDisclosurePreferenceController(Context context, String key) {
         // Preference key doesn't matter as we are creating the preference in code.
-        super(context, "add_account_enterprise_disclosure_footer");
-
+        super(context, key);
         mFeatureProvider = FeatureFactory.getFactory(mContext)
                 .getEnterprisePrivacyFeatureProvider(mContext);
     }
 
-    public void setFooterPreferenceMixin(FooterPreferenceMixinCompat footerPreferenceMixin) {
-        mFooterPreferenceMixin = footerPreferenceMixin;
-    }
-
     @Override
     public int getAvailabilityStatus() {
         if (getDisclosure() == null) {
@@ -53,27 +44,17 @@
         return AVAILABLE;
     }
 
-    @Override
-    public void displayPreference(PreferenceScreen screen) {
-        super.displayPreference(screen);
-        mScreen = screen;
-        addEnterpriseDisclosure();
-    }
-
     @VisibleForTesting
     CharSequence getDisclosure() {
         return mFeatureProvider.getDeviceOwnerDisclosure();
     }
 
-    private void addEnterpriseDisclosure() {
+    @Override
+    public void updateState(Preference preference) {
         final CharSequence disclosure = getDisclosure();
         if (disclosure == null) {
             return;
         }
-        final FooterPreference enterpriseDisclosurePreference =
-                mFooterPreferenceMixin.createFooterPreference();
-        enterpriseDisclosurePreference.setSelectable(false);
-        enterpriseDisclosurePreference.setTitle(disclosure);
-        mScreen.addPreference(enterpriseDisclosurePreference);
+        preference.setTitle(disclosure);
     }
 }
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/accounts/ManagedProfileSettings.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/accounts/ManagedProfileSettings.java
index 65d99a6..c9fbaf3 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/accounts/ManagedProfileSettings.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/accounts/ManagedProfileSettings.java
@@ -31,7 +31,7 @@
 import com.android.car.developeroptions.Utils;
 import com.android.car.developeroptions.dashboard.DashboardFragment;
 import com.android.car.developeroptions.search.BaseSearchIndexProvider;
-import com.android.car.developeroptions.search.Indexable;
+import com.android.settingslib.search.Indexable;
 import com.android.settingslib.search.SearchIndexable;
 
 import java.util.ArrayList;
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/applications/AppStorageSizesController.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/applications/AppStorageSizesController.java
index 5654239..5f3841d 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/applications/AppStorageSizesController.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/applications/AppStorageSizesController.java
@@ -23,9 +23,10 @@
 import androidx.annotation.StringRes;
 import androidx.preference.Preference;
 
-import com.android.internal.util.Preconditions;
 import com.android.settingslib.applications.StorageStatsSource;
 
+import java.util.Objects;
+
 /**
  * Handles setting the sizes for the app info screen.
  */
@@ -173,10 +174,10 @@
 
         public AppStorageSizesController build() {
             return new AppStorageSizesController(
-                    Preconditions.checkNotNull(mTotalSize),
-                    Preconditions.checkNotNull(mAppSize),
-                    Preconditions.checkNotNull(mDataSize),
-                    Preconditions.checkNotNull(mCacheSize),
+                    Objects.requireNonNull(mTotalSize),
+                    Objects.requireNonNull(mAppSize),
+                    Objects.requireNonNull(mDataSize),
+                    Objects.requireNonNull(mCacheSize),
                     mComputing,
                     mError);
         }
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/applications/FetchPackageStorageAsyncLoader.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/applications/FetchPackageStorageAsyncLoader.java
index a4713df..ca86b68 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/applications/FetchPackageStorageAsyncLoader.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/applications/FetchPackageStorageAsyncLoader.java
@@ -23,12 +23,12 @@
 import android.os.UserHandle;
 import android.util.Log;
 
-import com.android.internal.util.Preconditions;
 import com.android.settingslib.applications.StorageStatsSource;
 import com.android.settingslib.applications.StorageStatsSource.AppStorageStats;
 import com.android.settingslib.utils.AsyncLoaderCompat;
 
 import java.io.IOException;
+import java.util.Objects;
 
 /**
  * Fetches the storage stats using the StorageStatsManager for a given package and user tuple.
@@ -42,7 +42,7 @@
     public FetchPackageStorageAsyncLoader(Context context, @NonNull StorageStatsSource source,
             @NonNull ApplicationInfo info, @NonNull UserHandle user) {
         super(context);
-        mSource = Preconditions.checkNotNull(source);
+        mSource = Objects.requireNonNull(source);
         mInfo = info;
         mUser = user;
     }
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/applications/appops/AppOpsState.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/applications/appops/AppOpsState.java
index 45f2975..0573320 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/applications/appops/AppOpsState.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/applications/appops/AppOpsState.java
@@ -28,6 +28,7 @@
 import android.os.Parcelable;
 import android.text.format.DateUtils;
 import android.util.Log;
+import android.util.Pair;
 import android.util.SparseArray;
 
 import com.android.car.developeroptions.R;
@@ -618,7 +619,8 @@
 
                         }
                         AppOpsManager.OpEntry opEntry = new AppOpsManager.OpEntry(
-                                permOps.get(k), AppOpsManager.MODE_ALLOWED);
+                                permOps.get(k), AppOpsManager.MODE_ALLOWED,
+                                Collections.emptyMap());
                         dummyOps.add(opEntry);
                         addOp(entries, pkgOps, appEntry, opEntry, packageName == null,
                                 packageName == null ? 0 : opToOrder[opEntry.getOp()]);
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/applications/assist/ManageAssist.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/applications/assist/ManageAssist.java
index ece7643..e86f21f 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/applications/assist/ManageAssist.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/applications/assist/ManageAssist.java
@@ -24,9 +24,9 @@
 import com.android.car.developeroptions.dashboard.DashboardFragment;
 import com.android.car.developeroptions.gestures.AssistGestureSettingsPreferenceController;
 import com.android.car.developeroptions.search.BaseSearchIndexProvider;
-import com.android.car.developeroptions.search.Indexable;
 import com.android.settingslib.core.AbstractPreferenceController;
 import com.android.settingslib.core.lifecycle.Lifecycle;
+import com.android.settingslib.search.Indexable;
 import com.android.settingslib.search.SearchIndexable;
 
 import java.util.ArrayList;
@@ -68,14 +68,6 @@
         use(AssistGestureSettingsPreferenceController.class).setAssistOnly(true);
     }
 
-    @Override
-    public void onResume() {
-        super.onResume();
-
-        mFooterPreferenceMixin.createFooterPreference()
-                .setTitle(R.string.assist_footer);
-    }
-
     private static List<AbstractPreferenceController> buildPreferenceControllers(Context context,
             Lifecycle lifecycle) {
         final List<AbstractPreferenceController> controllers = new ArrayList<>();
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/applications/defaultapps/AutofillPicker.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/applications/defaultapps/AutofillPicker.java
index 8d63e1c..fc01ba6 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/applications/defaultapps/AutofillPicker.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/applications/defaultapps/AutofillPicker.java
@@ -23,8 +23,8 @@
 import com.android.car.developeroptions.R;
 import com.android.car.developeroptions.dashboard.DashboardFragment;
 import com.android.car.developeroptions.search.BaseSearchIndexProvider;
-import com.android.car.developeroptions.search.Indexable;
 import com.android.settingslib.core.AbstractPreferenceController;
+import com.android.settingslib.search.Indexable;
 import com.android.settingslib.search.SearchIndexable;
 
 import java.util.Arrays;
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/applications/managedomainurls/ManageDomainUrls.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/applications/managedomainurls/ManageDomainUrls.java
index 28314d7..7559b50 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/applications/managedomainurls/ManageDomainUrls.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/applications/managedomainurls/ManageDomainUrls.java
@@ -25,7 +25,7 @@
 import com.android.car.developeroptions.R;
 import com.android.car.developeroptions.dashboard.DashboardFragment;
 import com.android.car.developeroptions.search.BaseSearchIndexProvider;
-import com.android.car.developeroptions.search.Indexable;
+import com.android.settingslib.search.Indexable;
 import com.android.settingslib.search.SearchIndexable;
 
 import java.util.ArrayList;
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/applications/specialaccess/SpecialAccessSettings.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/applications/specialaccess/SpecialAccessSettings.java
index c1b35a7..6cc6bc9 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/applications/specialaccess/SpecialAccessSettings.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/applications/specialaccess/SpecialAccessSettings.java
@@ -23,7 +23,7 @@
 import com.android.car.developeroptions.R;
 import com.android.car.developeroptions.dashboard.DashboardFragment;
 import com.android.car.developeroptions.search.BaseSearchIndexProvider;
-import com.android.car.developeroptions.search.Indexable;
+import com.android.settingslib.search.Indexable;
 import com.android.settingslib.search.SearchIndexable;
 
 import java.util.ArrayList;
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/applications/specialaccess/deviceadmin/DeviceAdminListPreferenceController.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/applications/specialaccess/deviceadmin/DeviceAdminListPreferenceController.java
index 0d65f1b..8e559d0 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/applications/specialaccess/deviceadmin/DeviceAdminListPreferenceController.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/applications/specialaccess/deviceadmin/DeviceAdminListPreferenceController.java
@@ -45,13 +45,11 @@
 import androidx.preference.PreferenceScreen;
 import androidx.preference.SwitchPreference;
 
-import com.android.car.developeroptions.R;
 import com.android.car.developeroptions.core.BasePreferenceController;
 import com.android.settingslib.core.lifecycle.LifecycleObserver;
 import com.android.settingslib.core.lifecycle.events.OnStart;
 import com.android.settingslib.core.lifecycle.events.OnStop;
 import com.android.settingslib.widget.FooterPreference;
-import com.android.settingslib.widget.FooterPreferenceMixinCompat;
 
 import org.xmlpull.v1.XmlPullParserException;
 
@@ -67,6 +65,7 @@
 
     private static final IntentFilter FILTER = new IntentFilter();
     private static final String TAG = "DeviceAdminListPrefCtrl";
+    private static final String KEY_DEVICE_ADMIN_FOOTER = "device_admin_footer";
 
     private final DevicePolicyManager mDPM;
     private final UserManager mUm;
@@ -91,7 +90,7 @@
     };
 
     private PreferenceGroup mPreferenceGroup;
-    private FooterPreferenceMixinCompat mFooterPreferenceMixin;
+    private FooterPreference mFooterPreference;
 
     static {
         FILTER.addAction(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED);
@@ -105,12 +104,6 @@
         mIPackageManager = AppGlobals.getPackageManager();
     }
 
-    public DeviceAdminListPreferenceController setFooterPreferenceMixin(
-            FooterPreferenceMixinCompat mixin) {
-        mFooterPreferenceMixin = mixin;
-        return this;
-    }
-
     @Override
     public int getAvailabilityStatus() {
         return AVAILABLE;
@@ -120,6 +113,7 @@
     public void displayPreference(PreferenceScreen screen) {
         super.displayPreference(screen);
         mPreferenceGroup = screen.findPreference(getPreferenceKey());
+        mFooterPreference = mPreferenceGroup.findPreference(KEY_DEVICE_ADMIN_FOOTER);
     }
 
     @Override
@@ -167,10 +161,8 @@
         if (mPreferenceGroup == null) {
             return;
         }
-        if (mFooterPreferenceMixin != null) {
-            final FooterPreference footer = mFooterPreferenceMixin.createFooterPreference();
-            footer.setTitle(R.string.no_device_admins);
-            footer.setVisible(mAdmins.isEmpty());
+        if (mFooterPreference != null) {
+            mFooterPreference.setVisible(mAdmins.isEmpty());
         }
         final Map<String, SwitchPreference> preferenceCache = new ArrayMap<>();
         final Context prefContext = mPreferenceGroup.getContext();
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/applications/specialaccess/deviceadmin/DeviceAdminSettings.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/applications/specialaccess/deviceadmin/DeviceAdminSettings.java
index 5e5b704..2a1b37b 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/applications/specialaccess/deviceadmin/DeviceAdminSettings.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/applications/specialaccess/deviceadmin/DeviceAdminSettings.java
@@ -23,7 +23,7 @@
 import com.android.car.developeroptions.R;
 import com.android.car.developeroptions.dashboard.DashboardFragment;
 import com.android.car.developeroptions.search.BaseSearchIndexProvider;
-import com.android.car.developeroptions.search.Indexable;
+import com.android.settingslib.search.Indexable;
 import com.android.settingslib.search.SearchIndexable;
 
 import java.util.ArrayList;
@@ -38,13 +38,6 @@
     }
 
     @Override
-    public void onAttach(Context context) {
-        super.onAttach(context);
-        use(DeviceAdminListPreferenceController.class).setFooterPreferenceMixin(
-                mFooterPreferenceMixin);
-    }
-
-    @Override
     protected int getPreferenceScreenResId() {
         return R.xml.device_admin_settings;
     }
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/applications/specialaccess/financialapps/FinancialAppsSmsAccess.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/applications/specialaccess/financialapps/FinancialAppsSmsAccess.java
index dbc5cea..058c132 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/applications/specialaccess/financialapps/FinancialAppsSmsAccess.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/applications/specialaccess/financialapps/FinancialAppsSmsAccess.java
@@ -23,7 +23,7 @@
 import com.android.car.developeroptions.R;
 import com.android.car.developeroptions.dashboard.DashboardFragment;
 import com.android.car.developeroptions.search.BaseSearchIndexProvider;
-import com.android.car.developeroptions.search.Indexable;
+import com.android.settingslib.search.Indexable;
 import com.android.settingslib.search.SearchIndexable;
 
 import java.util.ArrayList;
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/applications/specialaccess/pictureinpicture/PictureInPictureSettings.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/applications/specialaccess/pictureinpicture/PictureInPictureSettings.java
index faf4841..9643354 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/applications/specialaccess/pictureinpicture/PictureInPictureSettings.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/applications/specialaccess/pictureinpicture/PictureInPictureSettings.java
@@ -41,8 +41,8 @@
 import com.android.car.developeroptions.R;
 import com.android.car.developeroptions.applications.AppInfoBase;
 import com.android.car.developeroptions.search.BaseSearchIndexProvider;
-import com.android.car.developeroptions.search.Indexable;
 import com.android.car.developeroptions.widget.EmptyTextSettings;
+import com.android.settingslib.search.Indexable;
 import com.android.settingslib.search.SearchIndexable;
 import com.android.settingslib.widget.apppreference.AppPreference;
 
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/applications/specialaccess/premiumsms/PremiumSmsAccess.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/applications/specialaccess/premiumsms/PremiumSmsAccess.java
index d44196e..0f042ca 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/applications/specialaccess/premiumsms/PremiumSmsAccess.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/applications/specialaccess/premiumsms/PremiumSmsAccess.java
@@ -31,20 +31,20 @@
 import androidx.preference.PreferenceScreen;
 import androidx.preference.PreferenceViewHolder;
 
-import com.android.internal.telephony.SmsUsageMonitor;
 import com.android.car.developeroptions.R;
 import com.android.car.developeroptions.applications.AppStateBaseBridge.Callback;
 import com.android.car.developeroptions.applications.AppStateSmsPremBridge;
 import com.android.car.developeroptions.applications.AppStateSmsPremBridge.SmsState;
 import com.android.car.developeroptions.overlay.FeatureFactory;
 import com.android.car.developeroptions.search.BaseSearchIndexProvider;
-import com.android.car.developeroptions.search.Indexable;
 import com.android.car.developeroptions.widget.EmptyTextSettings;
+import com.android.internal.telephony.SmsUsageMonitor;
 import com.android.settingslib.applications.ApplicationsState;
 import com.android.settingslib.applications.ApplicationsState.AppEntry;
 import com.android.settingslib.applications.ApplicationsState.Callbacks;
 import com.android.settingslib.applications.ApplicationsState.Session;
 import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
+import com.android.settingslib.search.Indexable;
 import com.android.settingslib.search.SearchIndexable;
 import com.android.settingslib.widget.FooterPreference;
 
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/applications/specialaccess/vrlistener/VrListenerSettings.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/applications/specialaccess/vrlistener/VrListenerSettings.java
index acf4885..bd946fa 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/applications/specialaccess/vrlistener/VrListenerSettings.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/applications/specialaccess/vrlistener/VrListenerSettings.java
@@ -27,9 +27,9 @@
 import com.android.car.developeroptions.R;
 import com.android.car.developeroptions.overlay.FeatureFactory;
 import com.android.car.developeroptions.search.BaseSearchIndexProvider;
-import com.android.car.developeroptions.search.Indexable;
 import com.android.car.developeroptions.utils.ManagedServiceSettings;
 import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
+import com.android.settingslib.search.Indexable;
 import com.android.settingslib.search.SearchIndexable;
 
 import java.util.ArrayList;
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/backup/BackupSettingsFragment.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/backup/BackupSettingsFragment.java
index ef377fa..304cf6f 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/backup/BackupSettingsFragment.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/backup/BackupSettingsFragment.java
@@ -23,8 +23,8 @@
 import com.android.car.developeroptions.R;
 import com.android.car.developeroptions.dashboard.DashboardFragment;
 import com.android.car.developeroptions.search.BaseSearchIndexProvider;
-import com.android.car.developeroptions.search.Indexable;
 import com.android.settingslib.core.AbstractPreferenceController;
+import com.android.settingslib.search.Indexable;
 import com.android.settingslib.search.SearchIndexable;
 
 import java.util.ArrayList;
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/backup/UserBackupSettingsActivity.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/backup/UserBackupSettingsActivity.java
index 9c72d48..b88362b 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/backup/UserBackupSettingsActivity.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/backup/UserBackupSettingsActivity.java
@@ -28,9 +28,9 @@
 
 import com.android.car.developeroptions.R;
 import com.android.car.developeroptions.search.BaseSearchIndexProvider;
-import com.android.car.developeroptions.search.Indexable;
-import com.android.car.developeroptions.search.SearchIndexableRaw;
+import com.android.settingslib.search.Indexable;
 import com.android.settingslib.search.SearchIndexable;
+import com.android.settingslib.search.SearchIndexableRaw;
 
 import java.util.ArrayList;
 import java.util.List;
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/biometrics/fingerprint/FingerprintSettings.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/biometrics/fingerprint/FingerprintSettings.java
index d3fc8de..4149228 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/biometrics/fingerprint/FingerprintSettings.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/biometrics/fingerprint/FingerprintSettings.java
@@ -132,6 +132,7 @@
         private boolean mLaunchedConfirm;
         private Drawable mHighlightDrawable;
         private int mUserId;
+        private CharSequence mFooterTitle;
 
         private static final String TAG_AUTHENTICATE_SIDECAR = "authenticate_sidecar";
         private static final String TAG_REMOVAL_SIDECAR = "removal_sidecar";
@@ -320,7 +321,6 @@
                 launchChooseOrConfirmLock();
             }
 
-            final FooterPreference pref = mFooterPreferenceMixin.createFooterPreference();
             final EnforcedAdmin admin = RestrictedLockUtilsInternal.checkIfKeyguardFeaturesDisabled(
                     activity, DevicePolicyManager.KEYGUARD_DISABLE_FINGERPRINT, mUserId);
             final AnnotationSpan.LinkInfo adminLinkInfo = new AnnotationSpan.LinkInfo(
@@ -331,11 +331,11 @@
                     activity, getString(getHelpResource()), activity.getClass().getName());
             final AnnotationSpan.LinkInfo linkInfo = new AnnotationSpan.LinkInfo(
                     activity, ANNOTATION_URL, helpIntent);
-            pref.setTitle(AnnotationSpan.linkify(getText(admin != null
+            mFooterTitle = AnnotationSpan.linkify(getText(admin != null
                             ? R.string
                             .security_settings_fingerprint_enroll_disclaimer_lockscreen_disabled
                             : R.string.security_settings_fingerprint_enroll_disclaimer),
-                    linkInfo, adminLinkInfo));
+                    linkInfo, adminLinkInfo);
         }
 
         protected void removeFingerprintPreference(int fingerprintId) {
@@ -397,6 +397,7 @@
             root.addPreference(addPreference);
             addPreference.setOnPreferenceChangeListener(this);
             updateAddPreference();
+            createFooterPreference(root);
         }
 
         private void updateAddPreference() {
@@ -416,6 +417,15 @@
             addPreference.setEnabled(!tooMany && !removalInProgress);
         }
 
+        private void createFooterPreference(PreferenceGroup root) {
+            final Context context = getActivity();
+            if (context == null) {
+                return;
+            }
+            root.addPreference(new FooterPreference.Builder(context).setTitle(
+                    mFooterTitle).build());
+        }
+
         private static String genKey(int id) {
             return KEY_FINGERPRINT_ITEM_PREFIX + "_" + id;
         }
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/bluetooth/BluetoothDetailsMacAddressController.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/bluetooth/BluetoothDetailsMacAddressController.java
new file mode 100644
index 0000000..5f47f1b
--- /dev/null
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/bluetooth/BluetoothDetailsMacAddressController.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.developeroptions.bluetooth;
+
+import android.content.Context;
+
+import androidx.preference.PreferenceFragmentCompat;
+import androidx.preference.PreferenceScreen;
+
+import com.android.car.developeroptions.R;
+import com.android.settingslib.bluetooth.CachedBluetoothDevice;
+import com.android.settingslib.core.lifecycle.Lifecycle;
+import com.android.settingslib.widget.FooterPreference;
+
+/**
+ * This class adds the device MAC address to a footer.
+ */
+public class BluetoothDetailsMacAddressController extends BluetoothDetailsController {
+    public static final String KEY_DEVICE_DETAILS_FOOTER = "device_details_footer";
+
+    private FooterPreference mFooterPreference;
+
+    public BluetoothDetailsMacAddressController(Context context,
+            PreferenceFragmentCompat fragment,
+            CachedBluetoothDevice device,
+            Lifecycle lifecycle) {
+        super(context, fragment, device, lifecycle);
+    }
+
+    @Override
+    protected void init(PreferenceScreen screen) {
+        mFooterPreference = screen.findPreference(KEY_DEVICE_DETAILS_FOOTER);
+        mFooterPreference.setTitle(mContext.getString(
+                R.string.bluetooth_device_mac_address, mCachedDevice.getAddress()));
+    }
+
+    @Override
+    protected void refresh() {
+        mFooterPreference.setTitle(mContext.getString(
+                R.string.bluetooth_device_mac_address, mCachedDevice.getAddress()));
+    }
+
+    @Override
+    public String getPreferenceKey() {
+        return KEY_DEVICE_DETAILS_FOOTER;
+    }
+}
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/bluetooth/BluetoothPairingController.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/bluetooth/BluetoothPairingController.java
index b016b8c..9073e4a 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/bluetooth/BluetoothPairingController.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/bluetooth/BluetoothPairingController.java
@@ -294,8 +294,7 @@
         if (mType == BluetoothDevice.PAIRING_VARIANT_DISPLAY_PASSKEY) {
             mDevice.setPairingConfirmation(true);
         } else if (mType == BluetoothDevice.PAIRING_VARIANT_DISPLAY_PIN) {
-            byte[] pinBytes = BluetoothDevice.convertPinToBytes(mPasskeyFormatted);
-            mDevice.setPin(pinBytes);
+            mDevice.setPin(mPasskeyFormatted);
         }
     }
 
@@ -391,17 +390,9 @@
         switch (mType) {
             case BluetoothDevice.PAIRING_VARIANT_PIN:
             case BluetoothDevice.PAIRING_VARIANT_PIN_16_DIGITS:
-                byte[] pinBytes = BluetoothDevice.convertPinToBytes(passkey);
-                if (pinBytes == null) {
-                    return;
-                }
-                mDevice.setPin(pinBytes);
+                mDevice.setPin(passkey);
                 break;
 
-            case BluetoothDevice.PAIRING_VARIANT_PASSKEY:
-                int pass = Integer.parseInt(passkey);
-                mDevice.setPasskey(pass);
-                break;
 
             case BluetoothDevice.PAIRING_VARIANT_PASSKEY_CONFIRMATION:
             case BluetoothDevice.PAIRING_VARIANT_CONSENT:
@@ -410,11 +401,9 @@
 
             case BluetoothDevice.PAIRING_VARIANT_DISPLAY_PASSKEY:
             case BluetoothDevice.PAIRING_VARIANT_DISPLAY_PIN:
-                // Do nothing.
-                break;
-
             case BluetoothDevice.PAIRING_VARIANT_OOB_CONSENT:
-                mDevice.setRemoteOutOfBandData();
+            case BluetoothDevice.PAIRING_VARIANT_PASSKEY:
+                // Do nothing.
                 break;
 
             default:
@@ -428,7 +417,7 @@
      */
     public void onCancel() {
         Log.d(TAG, "Pairing dialog canceled");
-        mDevice.cancelPairingUserInput();
+        mDevice.cancelPairing();
     }
 
     /**
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/bluetooth/BluetoothPairingDetail.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/bluetooth/BluetoothPairingDetail.java
index 4759453..ee66d4d 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/bluetooth/BluetoothPairingDetail.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/bluetooth/BluetoothPairingDetail.java
@@ -29,9 +29,9 @@
 import androidx.annotation.VisibleForTesting;
 
 import com.android.car.developeroptions.R;
-import com.android.car.developeroptions.search.Indexable;
 import com.android.settingslib.bluetooth.BluetoothDeviceFilter;
 import com.android.settingslib.bluetooth.CachedBluetoothDevice;
+import com.android.settingslib.search.Indexable;
 import com.android.settingslib.widget.FooterPreference;
 
 /**
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/bluetooth/BluetoothPairingService.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/bluetooth/BluetoothPairingService.java
index 7b2d4b0..5801e2c 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/bluetooth/BluetoothPairingService.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/bluetooth/BluetoothPairingService.java
@@ -85,7 +85,7 @@
             } else if (action.equals(ACTION_DISMISS_PAIRING)) {
                 Log.d(TAG, "Notification cancel " + mDevice.getAddress() + " (" +
                         mDevice.getName() + ")");
-                mDevice.cancelPairingUserInput();
+                mDevice.cancelPairing();
             } else {
                 int bondState = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE,
                         BluetoothDevice.ERROR);
@@ -141,7 +141,7 @@
         String name = intent.getStringExtra(BluetoothDevice.EXTRA_NAME);
         if (TextUtils.isEmpty(name)) {
             BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
-            name = device != null ? device.getAliasName() : res.getString(android.R.string.unknownName);
+            name = device != null ? device.getAlias() : res.getString(android.R.string.unknownName);
         }
 
         Log.d(TAG, "Show pairing notification for " + mDevice.getAddress() + " (" + name + ")");
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/bluetooth/Utils.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/bluetooth/Utils.java
index 49b2d88..de7e391 100755
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/bluetooth/Utils.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/bluetooth/Utils.java
@@ -95,7 +95,8 @@
     @VisibleForTesting
     static void showConnectingError(Context context, String name, LocalBluetoothManager manager) {
         FeatureFactory.getFactory(context).getMetricsFeatureProvider().visible(context,
-            SettingsEnums.PAGE_UNKNOWN, SettingsEnums.ACTION_SETTINGS_BLUETOOTH_CONNECT_ERROR);
+                SettingsEnums.PAGE_UNKNOWN, SettingsEnums.ACTION_SETTINGS_BLUETOOTH_CONNECT_ERROR,
+                0);
         showError(context, name, R.string.bluetooth_connecting_error_message, manager);
     }
 
@@ -127,7 +128,7 @@
     }
 
     public static String createRemoteName(Context context, BluetoothDevice device) {
-        String mRemoteName = device != null ? device.getAliasName() : null;
+        String mRemoteName = device != null ? device.getAlias() : null;
 
         if (mRemoteName == null) {
             mRemoteName = context.getString(R.string.unknown);
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/connecteddevice/BluetoothDashboardFragment.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/connecteddevice/BluetoothDashboardFragment.java
index ed2ec98..de09849 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/connecteddevice/BluetoothDashboardFragment.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/connecteddevice/BluetoothDashboardFragment.java
@@ -28,11 +28,11 @@
 import com.android.car.developeroptions.core.TogglePreferenceController;
 import com.android.car.developeroptions.dashboard.DashboardFragment;
 import com.android.car.developeroptions.search.BaseSearchIndexProvider;
-import com.android.car.developeroptions.search.SearchIndexableRaw;
 import com.android.car.developeroptions.widget.SwitchBar;
 import com.android.car.developeroptions.widget.SwitchBarController;
 import com.android.settingslib.core.lifecycle.Lifecycle;
 import com.android.settingslib.search.SearchIndexable;
+import com.android.settingslib.search.SearchIndexableRaw;
 import com.android.settingslib.widget.FooterPreference;
 
 import java.util.ArrayList;
@@ -46,6 +46,7 @@
 public class BluetoothDashboardFragment extends DashboardFragment {
 
     private static final String TAG = "BluetoothDashboardFrag";
+    private static final String KEY_BLUETOOTH_SCREEN_FOOTER = "bluetooth_screen_footer";
     public static final String KEY_BLUETOOTH_SCREEN = "bluetooth_switchbar_screen";
 
     private FooterPreference mFooterPreference;
@@ -75,7 +76,7 @@
     @Override
     public void onCreate(Bundle icicle) {
         super.onCreate(icicle);
-        mFooterPreference = mFooterPreferenceMixin.createFooterPreference();
+        mFooterPreference = findPreference(KEY_BLUETOOTH_SCREEN_FOOTER);
     }
 
     @Override
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/connecteddevice/ConnectedDeviceDashboardFragment.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/connecteddevice/ConnectedDeviceDashboardFragment.java
index 1f0985c..279224f 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/connecteddevice/ConnectedDeviceDashboardFragment.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/connecteddevice/ConnectedDeviceDashboardFragment.java
@@ -26,11 +26,8 @@
 import com.android.car.developeroptions.dashboard.DashboardFragment;
 import com.android.car.developeroptions.search.BaseSearchIndexProvider;
 import com.android.car.developeroptions.slices.SlicePreferenceController;
-import com.android.settingslib.core.AbstractPreferenceController;
-import com.android.settingslib.core.lifecycle.Lifecycle;
 import com.android.settingslib.search.SearchIndexable;
 
-import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
 
@@ -65,31 +62,11 @@
     }
 
     @Override
-    protected List<AbstractPreferenceController> createPreferenceControllers(Context context) {
-        return buildPreferenceControllers(context, getSettingsLifecycle());
-    }
-
-    private static List<AbstractPreferenceController> buildPreferenceControllers(Context context,
-            Lifecycle lifecycle) {
-        final List<AbstractPreferenceController> controllers = new ArrayList<>();
-        final DiscoverableFooterPreferenceController discoverableFooterPreferenceController =
-                new DiscoverableFooterPreferenceController(context);
-        controllers.add(discoverableFooterPreferenceController);
-
-        if (lifecycle != null) {
-            lifecycle.addObserver(discoverableFooterPreferenceController);
-        }
-
-        return controllers;
-    }
-
-    @Override
     public void onAttach(Context context) {
         super.onAttach(context);
         use(AvailableMediaDeviceGroupController.class).init(this);
         use(ConnectedDeviceGroupController.class).init(this);
         use(PreviouslyConnectedDevicePreferenceController.class).init(this);
-        use(DiscoverableFooterPreferenceController.class).init(this);
         use(SlicePreferenceController.class).setSliceUri(
                 Uri.parse(getString(R.string.config_nearby_devices_slice_uri)));
     }
@@ -106,11 +83,5 @@
                     sir.xmlResId = R.xml.connected_devices;
                     return Arrays.asList(sir);
                 }
-
-                @Override
-                public List<AbstractPreferenceController> createPreferenceControllers(Context
-                        context) {
-                    return buildPreferenceControllers(context, null /* lifecycle */);
-                }
             };
 }
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/connecteddevice/DiscoverableFooterPreferenceController.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/connecteddevice/DiscoverableFooterPreferenceController.java
index 088ab38..0f8f38e 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/connecteddevice/DiscoverableFooterPreferenceController.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/connecteddevice/DiscoverableFooterPreferenceController.java
@@ -32,32 +32,29 @@
 import com.android.car.developeroptions.bluetooth.AlwaysDiscoverable;
 import com.android.car.developeroptions.bluetooth.Utils;
 import com.android.car.developeroptions.core.BasePreferenceController;
-import com.android.car.developeroptions.dashboard.DashboardFragment;
 import com.android.settingslib.bluetooth.LocalBluetoothManager;
 import com.android.settingslib.core.lifecycle.LifecycleObserver;
-import com.android.settingslib.core.lifecycle.events.OnPause;
-import com.android.settingslib.core.lifecycle.events.OnResume;
+import com.android.settingslib.core.lifecycle.events.OnStart;
+import com.android.settingslib.core.lifecycle.events.OnStop;
 import com.android.settingslib.widget.FooterPreference;
-import com.android.settingslib.widget.FooterPreferenceMixinCompat;
 
 /**
  * Controller that shows and updates the bluetooth device name
  */
 public class DiscoverableFooterPreferenceController extends BasePreferenceController
-        implements LifecycleObserver, OnResume, OnPause {
+        implements LifecycleObserver, OnStart, OnStop {
     private static final String KEY = "discoverable_footer_preference";
 
     @VisibleForTesting
     BroadcastReceiver mBluetoothChangedReceiver;
     @VisibleForTesting
     LocalBluetoothManager mLocalManager;
-    private FooterPreferenceMixinCompat mFooterPreferenceMixin;
-    private FooterPreference mPreference;
     private BluetoothAdapter mBluetoothAdapter;
     private AlwaysDiscoverable mAlwaysDiscoverable;
+    private FooterPreference mPreference;
 
-    public DiscoverableFooterPreferenceController(Context context) {
-        super(context, KEY);
+    public DiscoverableFooterPreferenceController(Context context, String key) {
+        super(context, key);
         mLocalManager = Utils.getLocalBtManager(context);
         if (mLocalManager == null) {
             return;
@@ -67,53 +64,21 @@
         initReceiver();
     }
 
-    private void initReceiver() {
-        mBluetoothChangedReceiver = new BroadcastReceiver() {
-            @Override
-            public void onReceive(Context context, Intent intent) {
-                if (intent.getAction().equals(BluetoothAdapter.ACTION_STATE_CHANGED)) {
-                    final int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE,
-                            BluetoothAdapter.ERROR);
-                    updateFooterPreferenceTitle(state);
-                }
-            }
-        };
-    }
-
-    public void init(DashboardFragment fragment) {
-        mFooterPreferenceMixin = new FooterPreferenceMixinCompat(fragment,
-                fragment.getSettingsLifecycle());
-    }
-
-    @VisibleForTesting
-    void init(FooterPreferenceMixinCompat footerPreferenceMixin, FooterPreference preference,
-            AlwaysDiscoverable alwaysDiscoverable) {
-        mFooterPreferenceMixin = footerPreferenceMixin;
-        mPreference = preference;
-        mAlwaysDiscoverable = alwaysDiscoverable;
-    }
-
     @Override
     public void displayPreference(PreferenceScreen screen) {
         super.displayPreference(screen);
-        addFooterPreference(screen);
+        mPreference = screen.findPreference(getPreferenceKey());
     }
 
     @Override
     public int getAvailabilityStatus() {
         return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH)
-                ? AVAILABLE
+                ? AVAILABLE_UNSEARCHABLE
                 : UNSUPPORTED_ON_DEVICE;
     }
 
-    private void addFooterPreference(PreferenceScreen screen) {
-        mPreference = mFooterPreferenceMixin.createFooterPreference();
-        mPreference.setKey(KEY);
-        screen.addPreference(mPreference);
-    }
-
     @Override
-    public void onResume() {
+    public void onStart() {
         if (mLocalManager == null) {
             return;
         }
@@ -124,7 +89,7 @@
     }
 
     @Override
-    public void onPause() {
+    public void onStop() {
         if (mLocalManager == null) {
             return;
         }
@@ -132,7 +97,7 @@
         mAlwaysDiscoverable.stop();
     }
 
-    private void updateFooterPreferenceTitle (int bluetoothState) {
+    private void updateFooterPreferenceTitle(int bluetoothState) {
         if (bluetoothState == BluetoothAdapter.STATE_ON) {
             mPreference.setTitle(getPreferenceTitle());
         } else {
@@ -150,4 +115,17 @@
                 mContext.getText(R.string.bluetooth_device_name_summary),
                 BidiFormatter.getInstance().unicodeWrap(deviceName));
     }
-}
\ No newline at end of file
+
+    private void initReceiver() {
+        mBluetoothChangedReceiver = new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                if (intent.getAction().equals(BluetoothAdapter.ACTION_STATE_CHANGED)) {
+                    final int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE,
+                            BluetoothAdapter.ERROR);
+                    updateFooterPreferenceTitle(state);
+                }
+            }
+        };
+    }
+}
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/connecteddevice/PreviouslyConnectedDeviceDashboardFragment.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/connecteddevice/PreviouslyConnectedDeviceDashboardFragment.java
index 924c9b2..a776476 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/connecteddevice/PreviouslyConnectedDeviceDashboardFragment.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/connecteddevice/PreviouslyConnectedDeviceDashboardFragment.java
@@ -22,8 +22,8 @@
 import com.android.car.developeroptions.R;
 import com.android.car.developeroptions.dashboard.DashboardFragment;
 import com.android.car.developeroptions.search.BaseSearchIndexProvider;
-import com.android.car.developeroptions.search.SearchIndexableRaw;
 import com.android.settingslib.search.SearchIndexable;
+import com.android.settingslib.search.SearchIndexableRaw;
 
 import java.util.ArrayList;
 import java.util.List;
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/connecteddevice/usb/UsbDefaultFragment.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/connecteddevice/usb/UsbDefaultFragment.java
index 411153c..2b823ce 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/connecteddevice/usb/UsbDefaultFragment.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/connecteddevice/usb/UsbDefaultFragment.java
@@ -32,7 +32,6 @@
 import com.android.car.developeroptions.widget.RadioButtonPickerFragment;
 import com.android.settingslib.widget.CandidateInfo;
 import com.android.settingslib.widget.FooterPreference;
-import com.android.settingslib.widget.FooterPreferenceMixinCompat;
 
 import com.google.android.collect.Lists;
 
@@ -61,10 +60,8 @@
     @Override
     public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
         super.onCreatePreferences(savedInstanceState, rootKey);
-        FooterPreferenceMixinCompat footer = new FooterPreferenceMixinCompat(this,
-                this.getSettingsLifecycle());
-        FooterPreference pref = footer.createFooterPreference();
-        pref.setTitle(R.string.usb_default_info);
+        getPreferenceScreen().addPreference(new FooterPreference.Builder(getActivity()).setTitle(
+                R.string.usb_default_info).build());
     }
 
     @Override
@@ -149,4 +146,4 @@
             updateCandidates();
         }
     }
-}
\ No newline at end of file
+}
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/connecteddevice/usb/UsbDetailsFragment.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/connecteddevice/usb/UsbDetailsFragment.java
index ff58f89..1c90cf0 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/connecteddevice/usb/UsbDetailsFragment.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/connecteddevice/usb/UsbDetailsFragment.java
@@ -25,8 +25,8 @@
 import com.android.car.developeroptions.R;
 import com.android.car.developeroptions.dashboard.DashboardFragment;
 import com.android.car.developeroptions.search.BaseSearchIndexProvider;
-import com.android.car.developeroptions.search.Indexable;
 import com.android.settingslib.core.AbstractPreferenceController;
+import com.android.settingslib.search.Indexable;
 import com.android.settingslib.search.SearchIndexable;
 
 import com.google.android.collect.Lists;
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/core/BasePreferenceController.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/core/BasePreferenceController.java
index 73f735c..fc51221 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/core/BasePreferenceController.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/core/BasePreferenceController.java
@@ -21,10 +21,10 @@
 import androidx.preference.Preference;
 import androidx.preference.PreferenceScreen;
 
-import com.android.car.developeroptions.search.SearchIndexableRaw;
 import com.android.car.developeroptions.slices.SliceData;
 import com.android.car.developeroptions.slices.Sliceable;
 import com.android.settingslib.core.AbstractPreferenceController;
+import com.android.settingslib.search.SearchIndexableRaw;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/core/PreferenceControllerMixin.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/core/PreferenceControllerMixin.java
index bc1b7a6..30df488 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/core/PreferenceControllerMixin.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/core/PreferenceControllerMixin.java
@@ -18,8 +18,8 @@
 import android.text.TextUtils;
 import android.util.Log;
 
-import com.android.car.developeroptions.search.SearchIndexableRaw;
 import com.android.settingslib.core.AbstractPreferenceController;
+import com.android.settingslib.search.SearchIndexableRaw;
 
 import java.util.List;
 
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/core/instrumentation/SettingsIntelligenceLogWriter.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/core/instrumentation/SettingsIntelligenceLogWriter.java
index 6112713..ae28c96 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/core/instrumentation/SettingsIntelligenceLogWriter.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/core/instrumentation/SettingsIntelligenceLogWriter.java
@@ -61,21 +61,21 @@
     }
 
     @Override
-    public void visible(Context context, int attribution, int pageId) {
-        action(attribution /* attribution */,
+    public void visible(Context context, int attribution, int pageId, int latency) {
+        action(attribution /* from pageId */,
                 SettingsEnums.PAGE_VISIBLE /* action */,
-                pageId /* pageId */,
+                pageId /* target pageId */,
                 "" /* changedPreferenceKey */,
-                0 /* changedPreferenceIntValue */);
+                latency /* changedPreferenceIntValue */);
     }
 
     @Override
-    public void hidden(Context context, int pageId) {
+    public void hidden(Context context, int pageId, int visibleTime) {
         action(SettingsEnums.PAGE_UNKNOWN /* attribution */,
                 SettingsEnums.PAGE_HIDE /* action */,
                 pageId /* pageId */,
                 "" /* changedPreferenceKey */,
-                0 /* changedPreferenceIntValue */);
+                visibleTime /* changedPreferenceIntValue */);
     }
 
     @Override
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/core/instrumentation/StatsLogWriter.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/core/instrumentation/StatsLogWriter.java
index ae0dbeb..fa08b63 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/core/instrumentation/StatsLogWriter.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/core/instrumentation/StatsLogWriter.java
@@ -19,30 +19,30 @@
 import android.app.settings.SettingsEnums;
 import android.content.Context;
 import android.util.Pair;
-import android.util.StatsLog;
 
+import com.android.settings.core.instrumentation.SettingsStatsLog;
 import com.android.settingslib.core.instrumentation.LogWriter;
 
 public class StatsLogWriter implements LogWriter {
 
     @Override
-    public void visible(Context context, int attribution, int pageId) {
-        StatsLog.write(StatsLog.SETTINGS_UI_CHANGED /* Atom name */,
-                attribution,
+    public void visible(Context context, int attribution, int pageId, int latency) {
+        SettingsStatsLog.write(SettingsStatsLog.SETTINGS_UI_CHANGED /* Atom name */,
+                attribution, /* from pageId */
                 SettingsEnums.PAGE_VISIBLE /* action */,
-                pageId,
+                pageId, /* target pageId */
                 null /* changedPreferenceKey */,
-                0 /* changedPreferenceIntValue */);
+                latency /* changedPreferenceIntValue */);
     }
 
     @Override
-    public void hidden(Context context, int pageId) {
-        StatsLog.write(StatsLog.SETTINGS_UI_CHANGED /* Atom name */,
+    public void hidden(Context context, int pageId, int visibleTime) {
+        SettingsStatsLog.write(SettingsStatsLog.SETTINGS_UI_CHANGED /* Atom name */,
                 SettingsEnums.PAGE_UNKNOWN /* attribution */,
                 SettingsEnums.PAGE_HIDE /* action */,
                 pageId,
                 null /* changedPreferenceKey */,
-                0 /* changedPreferenceIntValue */);
+                visibleTime /* changedPreferenceIntValue */);
     }
 
     @Override
@@ -83,7 +83,7 @@
 
     @Override
     public void action(int attribution, int action, int pageId, String key, int value) {
-        StatsLog.write(StatsLog.SETTINGS_UI_CHANGED /* atomName */,
+        SettingsStatsLog.write(SettingsStatsLog.SETTINGS_UI_CHANGED /* atomName */,
                 attribution,
                 action,
                 pageId,
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/dashboard/DashboardFeatureProviderImpl.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/dashboard/DashboardFeatureProviderImpl.java
index db6efbf..de184cf 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/dashboard/DashboardFeatureProviderImpl.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/dashboard/DashboardFeatureProviderImpl.java
@@ -18,6 +18,8 @@
 
 import static android.content.Intent.EXTRA_USER;
 
+import static com.android.settingslib.drawer.SwitchesProvider.METHOD_GET_DYNAMIC_SUMMARY;
+import static com.android.settingslib.drawer.SwitchesProvider.METHOD_GET_PROVIDER_ICON;
 import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_ICON_URI;
 import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_SUMMARY;
 import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_SUMMARY_URI;
@@ -30,6 +32,7 @@
 import android.content.pm.PackageManager;
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.Icon;
+import android.net.Uri;
 import android.os.Bundle;
 import android.os.UserHandle;
 import android.provider.Settings;
@@ -180,7 +183,8 @@
 
             ThreadUtils.postOnBackgroundThread(() -> {
                 final Map<String, IContentProvider> providerMap = new ArrayMap<>();
-                final String uri = tile.getMetaData().getString(META_DATA_PREFERENCE_SUMMARY_URI);
+                final Uri uri = TileUtils.getCompleteUri(tile, META_DATA_PREFERENCE_SUMMARY_URI,
+                        METHOD_GET_DYNAMIC_SUMMARY);
                 final String summaryFromUri = TileUtils.getTextFromUri(
                         mContext, uri, providerMap, META_DATA_PREFERENCE_SUMMARY);
                 ThreadUtils.postOnMainThread(() -> preference.setSummary(summaryFromUri));
@@ -214,7 +218,8 @@
                     packageName = intent.getComponent().getPackageName();
                 }
                 final Map<String, IContentProvider> providerMap = new ArrayMap<>();
-                final String uri = tile.getMetaData().getString(META_DATA_PREFERENCE_ICON_URI);
+                final Uri uri = TileUtils.getCompleteUri(tile, META_DATA_PREFERENCE_ICON_URI,
+                        METHOD_GET_PROVIDER_ICON);
                 final Pair<String, Integer> iconInfo = TileUtils.getIconFromUri(
                         mContext, packageName, uri, providerMap);
                 if (iconInfo == null) {
@@ -238,16 +243,15 @@
         ProfileSelectDialog.updateUserHandlesIfNeeded(mContext, tile);
 
         if (tile.userHandle == null || tile.isPrimaryProfileOnly()) {
-            mMetricsFeatureProvider.logDashboardStartIntent(mContext, intent, sourceMetricCategory);
+            mMetricsFeatureProvider.logStartedIntent(intent, sourceMetricCategory);
             activity.startActivityForResult(intent, 0);
         } else if (tile.userHandle.size() == 1) {
-            mMetricsFeatureProvider.logDashboardStartIntent(mContext, intent, sourceMetricCategory);
+            mMetricsFeatureProvider.logStartedIntent(intent, sourceMetricCategory);
             activity.startActivityForResultAsUser(intent, 0, tile.userHandle.get(0));
         } else {
             final UserHandle userHandle = intent.getParcelableExtra(EXTRA_USER);
             if (userHandle != null && tile.userHandle.contains(userHandle)) {
-                mMetricsFeatureProvider.logDashboardStartIntent(
-                    mContext, intent, sourceMetricCategory);
+                mMetricsFeatureProvider.logStartedIntent(intent, sourceMetricCategory);
                 activity.startActivityForResultAsUser(intent, 0, userHandle);
             } else {
                 ProfileSelectDialog.show(activity.getSupportFragmentManager(), tile);
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/dashboard/DashboardFragment.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/dashboard/DashboardFragment.java
index e7323c6..a08ad0f 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/dashboard/DashboardFragment.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/dashboard/DashboardFragment.java
@@ -35,12 +35,12 @@
 import com.android.car.developeroptions.core.PreferenceControllerListHelper;
 import com.android.car.developeroptions.core.SettingsBaseActivity;
 import com.android.car.developeroptions.overlay.FeatureFactory;
-import com.android.car.developeroptions.search.Indexable;
 import com.android.settingslib.core.AbstractPreferenceController;
 import com.android.settingslib.core.lifecycle.Lifecycle;
 import com.android.settingslib.core.lifecycle.LifecycleObserver;
 import com.android.settingslib.drawer.DashboardCategory;
 import com.android.settingslib.drawer.Tile;
+import com.android.settingslib.search.Indexable;
 
 import java.util.ArrayList;
 import java.util.Collection;
@@ -197,8 +197,7 @@
         Collection<List<AbstractPreferenceController>> controllers =
                 mPreferenceControllers.values();
         // If preference contains intent, log it before handling.
-        mMetricsFeatureProvider.logDashboardStartIntent(
-                getContext(), preference.getIntent(), getMetricsCategory());
+        mMetricsFeatureProvider.logStartedIntent(preference.getIntent(), getMetricsCategory());
         // Give all controllers a chance to handle click.
         for (List<AbstractPreferenceController> controllerList : controllers) {
             for (AbstractPreferenceController controller : controllerList) {
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/datausage/BillingCycleSettings.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/datausage/BillingCycleSettings.java
index 1b648a9..51dbcb9 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/datausage/BillingCycleSettings.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/datausage/BillingCycleSettings.java
@@ -26,7 +26,6 @@
 import android.net.NetworkTemplate;
 import android.os.Bundle;
 import android.provider.SearchIndexableResource;
-import android.text.format.Time;
 import android.util.Log;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -43,13 +42,14 @@
 import com.android.car.developeroptions.R;
 import com.android.car.developeroptions.core.instrumentation.InstrumentedDialogFragment;
 import com.android.car.developeroptions.search.BaseSearchIndexProvider;
-import com.android.car.developeroptions.search.Indexable;
 import com.android.settingslib.NetworkPolicyEditor;
 import com.android.settingslib.net.DataUsageController;
+import com.android.settingslib.search.Indexable;
 import com.android.settingslib.search.SearchIndexable;
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.TimeZone;
 
 @SearchIndexable
 public class BillingCycleSettings extends DataUsageBaseFragment implements
@@ -118,8 +118,6 @@
         mEnableDataLimit = (SwitchPreference) findPreference(KEY_SET_DATA_LIMIT);
         mEnableDataLimit.setOnPreferenceChangeListener(this);
         mDataLimit = findPreference(KEY_DATA_LIMIT);
-
-        mFooterPreferenceMixin.createFooterPreference().setTitle(R.string.data_warning_footnote);
     }
 
     @Override
@@ -405,7 +403,7 @@
             mCycleDayPicker.clearFocus();
 
             final int cycleDay = mCycleDayPicker.getValue();
-            final String cycleTimezone = new Time().timezone;
+            final String cycleTimezone = TimeZone.getDefault().getID();
             editor.setPolicyCycleDay(template, cycleDay, cycleTimezone);
             target.updateDataUsage();
         }
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/datausage/DataSaverBackend.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/datausage/DataSaverBackend.java
index 78d55e8..c10b2b2 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/datausage/DataSaverBackend.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/datausage/DataSaverBackend.java
@@ -23,6 +23,7 @@
 import android.net.INetworkPolicyListener;
 import android.net.NetworkPolicyManager;
 import android.os.RemoteException;
+import android.telephony.SubscriptionPlan;
 import android.util.SparseIntArray;
 
 import com.android.car.developeroptions.overlay.FeatureFactory;
@@ -201,6 +202,10 @@
         @Override
         public void onSubscriptionOverride(int subId, int overrideMask, int overrideValue) {
         }
+
+        @Override
+        public void onSubscriptionPlansChanged(int subId, SubscriptionPlan[] plans) {
+        }
     };
 
     public interface Listener {
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/datausage/DataSaverSummary.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/datausage/DataSaverSummary.java
index 081347f..901b869 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/datausage/DataSaverSummary.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/datausage/DataSaverSummary.java
@@ -30,13 +30,13 @@
 import com.android.car.developeroptions.applications.AppStateBaseBridge.Callback;
 import com.android.car.developeroptions.datausage.DataSaverBackend.Listener;
 import com.android.car.developeroptions.search.BaseSearchIndexProvider;
-import com.android.car.developeroptions.search.Indexable;
 import com.android.car.developeroptions.widget.SwitchBar;
 import com.android.car.developeroptions.widget.SwitchBar.OnSwitchChangeListener;
 import com.android.settingslib.applications.ApplicationsState;
 import com.android.settingslib.applications.ApplicationsState.AppEntry;
 import com.android.settingslib.applications.ApplicationsState.Callbacks;
 import com.android.settingslib.applications.ApplicationsState.Session;
+import com.android.settingslib.search.Indexable;
 import com.android.settingslib.search.SearchIndexable;
 
 import java.util.ArrayList;
@@ -63,8 +63,6 @@
         super.onCreate(icicle);
 
         addPreferencesFromResource(R.xml.data_saver);
-        mFooterPreferenceMixin.createFooterPreference()
-                .setTitle(com.android.internal.R.string.data_saver_description);
         mUnrestrictedAccess = findPreference(KEY_UNRESTRICTED_ACCESS);
         mApplicationsState = ApplicationsState.getInstance(
                 (Application) getContext().getApplicationContext());
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/datausage/DataUsageSummary.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/datausage/DataUsageSummary.java
index 0e78bce..61aeab2 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/datausage/DataUsageSummary.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/datausage/DataUsageSummary.java
@@ -38,10 +38,10 @@
 import com.android.car.developeroptions.Utils;
 import com.android.car.developeroptions.dashboard.SummaryLoader;
 import com.android.car.developeroptions.search.BaseSearchIndexProvider;
-import com.android.car.developeroptions.search.Indexable;
 import com.android.settingslib.NetworkPolicyEditor;
 import com.android.settingslib.core.AbstractPreferenceController;
 import com.android.settingslib.net.DataUsageController;
+import com.android.settingslib.search.Indexable;
 import com.android.settingslib.search.SearchIndexable;
 
 import java.util.ArrayList;
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/datausage/DataUsageSummaryPreferenceController.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/datausage/DataUsageSummaryPreferenceController.java
index b2576f7..bccb280 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/datausage/DataUsageSummaryPreferenceController.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/datausage/DataUsageSummaryPreferenceController.java
@@ -19,8 +19,11 @@
 import android.app.Activity;
 import android.content.Context;
 import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.net.INetworkPolicyManager;
 import android.net.NetworkPolicyManager;
 import android.net.NetworkTemplate;
+import android.os.ServiceManager;
 import android.telephony.SubscriptionInfo;
 import android.telephony.SubscriptionManager;
 import android.telephony.SubscriptionPlan;
@@ -34,11 +37,11 @@
 import androidx.preference.PreferenceFragmentCompat;
 import androidx.recyclerview.widget.RecyclerView;
 
-import com.android.internal.util.CollectionUtils;
 import com.android.car.developeroptions.R;
 import com.android.car.developeroptions.core.BasePreferenceController;
 import com.android.car.developeroptions.core.PreferenceControllerMixin;
 import com.android.car.developeroptions.widget.EntityHeaderController;
+import com.android.internal.util.CollectionUtils;
 import com.android.settingslib.NetworkPolicyEditor;
 import com.android.settingslib.core.lifecycle.Lifecycle;
 import com.android.settingslib.core.lifecycle.LifecycleObserver;
@@ -297,12 +300,51 @@
                 mSnapshotTime = primaryPlan.getDataUsageTime();
             }
         }
-        mManageSubscriptionIntent =
-                mSubscriptionManager.createManageSubscriptionIntent(mSubscriptionId);
+        mManageSubscriptionIntent = createManageSubscriptionIntent(mSubscriptionId);
         Log.i(TAG, "Have " + mDataplanCount + " plans, dflt sub-id " + mSubscriptionId
                 + ", intent " + mManageSubscriptionIntent);
     }
 
+    /**
+     * Create an {@link Intent} that can be launched towards the carrier app
+     * that is currently defining the billing relationship plan through
+     * {@link INetworkPolicyManager#setSubscriptionPlans(int, SubscriptionPlan [], String)}.
+     *
+     * @return ready to launch Intent targeted towards the carrier app, or
+     *         {@code null} if no carrier app is defined, or if the defined
+     *         carrier app provides no management activity.
+     */
+    private Intent createManageSubscriptionIntent(int subId) {
+        INetworkPolicyManager networkPolicyManager = INetworkPolicyManager.Stub.asInterface(
+                ServiceManager.getService(Context.NETWORK_POLICY_SERVICE));
+        String owner = "";
+        try {
+            owner = networkPolicyManager.getSubscriptionPlansOwner(subId);
+        } catch (Exception ex) {
+            Log.w("Fail to get subscription plan owner for subId " + subId, ex);
+        }
+
+        if (TextUtils.isEmpty(owner)) {
+            return null;
+        }
+
+        List<SubscriptionPlan> plans = mSubscriptionManager.getSubscriptionPlans(subId);
+        if (plans.isEmpty()) {
+            return null;
+        }
+
+        Intent intent = new Intent(SubscriptionManager.ACTION_MANAGE_SUBSCRIPTION_PLANS);
+        intent.setPackage(owner);
+        intent.putExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, subId);
+
+        if (mContext.getPackageManager().queryIntentActivities(intent,
+                PackageManager.MATCH_DEFAULT_ONLY).isEmpty()) {
+            return null;
+        }
+
+        return intent;
+    }
+
     public static SubscriptionPlan getPrimaryPlan(SubscriptionManager subManager, int primaryId) {
         List<SubscriptionPlan> plans = subManager.getSubscriptionPlans(primaryId);
         if (CollectionUtils.isEmpty(plans)) {
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/datausage/UnrestrictedDataAccess.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/datausage/UnrestrictedDataAccess.java
index 9f1e7b7..f0f8c46 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/datausage/UnrestrictedDataAccess.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/datausage/UnrestrictedDataAccess.java
@@ -26,9 +26,9 @@
 import com.android.car.developeroptions.R;
 import com.android.car.developeroptions.dashboard.DashboardFragment;
 import com.android.car.developeroptions.search.BaseSearchIndexProvider;
-import com.android.car.developeroptions.search.Indexable;
 import com.android.settingslib.applications.ApplicationsState;
 import com.android.settingslib.applications.ApplicationsState.AppFilter;
+import com.android.settingslib.search.Indexable;
 import com.android.settingslib.search.SearchIndexable;
 
 import java.util.ArrayList;
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/datetime/AutoTimePreferenceController.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/datetime/AutoTimePreferenceController.java
index 6bf6d2d..cdac6ac 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/datetime/AutoTimePreferenceController.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/datetime/AutoTimePreferenceController.java
@@ -17,6 +17,8 @@
 package com.android.car.developeroptions.datetime;
 
 import android.content.Context;
+import android.os.UserHandle;
+import android.os.UserManager;
 import android.provider.Settings;
 
 import androidx.preference.Preference;
@@ -75,6 +77,7 @@
     }
 
     private RestrictedLockUtils.EnforcedAdmin getEnforcedAdminProperty() {
-        return RestrictedLockUtilsInternal.checkIfAutoTimeRequired(mContext);
+        return RestrictedLockUtilsInternal.checkIfRestrictionEnforced(
+                mContext, UserManager.DISALLOW_CONFIG_DATE_TIME, UserHandle.myUserId());
     }
 }
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/datetime/DatePreferenceController.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/datetime/DatePreferenceController.java
index 2434f1b..99625d7 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/datetime/DatePreferenceController.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/datetime/DatePreferenceController.java
@@ -17,8 +17,9 @@
 package com.android.car.developeroptions.datetime;
 
 import android.app.Activity;
-import android.app.AlarmManager;
 import android.app.DatePickerDialog;
+import android.app.timedetector.ManualTimeSuggestion;
+import android.app.timedetector.TimeDetector;
 import android.content.Context;
 import android.text.TextUtils;
 import android.text.format.DateFormat;
@@ -119,7 +120,10 @@
         long when = Math.max(c.getTimeInMillis(), DatePreferenceHost.MIN_DATE);
 
         if (when / 1000 < Integer.MAX_VALUE) {
-            ((AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE)).setTime(when);
+            TimeDetector timeDetector = mContext.getSystemService(TimeDetector.class);
+            ManualTimeSuggestion manualTimeSuggestion =
+                    TimeDetector.createManualTimeSuggestion(when, "Settings: Set date");
+            timeDetector.suggestManualTime(manualTimeSuggestion);
         }
     }
 }
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/datetime/TimePreferenceController.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/datetime/TimePreferenceController.java
index aeabc6e..ef0e7a9 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/datetime/TimePreferenceController.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/datetime/TimePreferenceController.java
@@ -17,8 +17,9 @@
 package com.android.car.developeroptions.datetime;
 
 import android.app.Activity;
-import android.app.AlarmManager;
 import android.app.TimePickerDialog;
+import android.app.timedetector.ManualTimeSuggestion;
+import android.app.timedetector.TimeDetector;
 import android.content.Context;
 import android.text.TextUtils;
 import android.text.format.DateFormat;
@@ -117,7 +118,10 @@
         long when = Math.max(c.getTimeInMillis(), TimePreferenceHost.MIN_DATE);
 
         if (when / 1000 < Integer.MAX_VALUE) {
-            ((AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE)).setTime(when);
+            TimeDetector timeDetector = mContext.getSystemService(TimeDetector.class);
+            ManualTimeSuggestion manualTimeSuggestion =
+                    TimeDetector.createManualTimeSuggestion(when, "Settings: Set time");
+            timeDetector.suggestManualTime(manualTimeSuggestion);
         }
     }
 }
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/datetime/timezone/TimeZoneInfoPreferenceController.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/datetime/timezone/TimeZoneInfoPreferenceController.java
index e31136a..abc2f0e 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/datetime/timezone/TimeZoneInfoPreferenceController.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/datetime/timezone/TimeZoneInfoPreferenceController.java
@@ -26,23 +26,21 @@
 import android.icu.util.TimeZoneTransition;
 
 import androidx.annotation.VisibleForTesting;
-import androidx.preference.Preference;
 
 import com.android.car.developeroptions.R;
-import com.android.settingslib.widget.FooterPreference;
+import com.android.car.developeroptions.core.BasePreferenceController;
 
 import java.util.Date;
 
-public class TimeZoneInfoPreferenceController extends BaseTimeZonePreferenceController {
+public class TimeZoneInfoPreferenceController extends BasePreferenceController {
 
-    private static final String PREFERENCE_KEY = FooterPreference.KEY_FOOTER;
     @VisibleForTesting
     Date mDate;
     private TimeZoneInfo mTimeZoneInfo;
     private final DateFormat mDateFormat;
 
-    public TimeZoneInfoPreferenceController(Context context) {
-        super(context, PREFERENCE_KEY);
+    public TimeZoneInfoPreferenceController(Context context, String key) {
+        super(context, key);
         mDateFormat = DateFormat.getDateInstance(SimpleDateFormat.LONG);
         mDateFormat.setContext(DisplayContext.CAPITALIZATION_NONE);
         mDate = new Date();
@@ -50,24 +48,18 @@
 
     @Override
     public int getAvailabilityStatus() {
-        return AVAILABLE;
+        return mTimeZoneInfo != null ? AVAILABLE_UNSEARCHABLE : UNSUPPORTED_ON_DEVICE;
     }
 
     @Override
-    public void updateState(Preference preference) {
-        CharSequence formattedTimeZone = mTimeZoneInfo == null ? "" : formatInfo(mTimeZoneInfo);
-        preference.setTitle(formattedTimeZone);
-        preference.setVisible(mTimeZoneInfo != null);
+    public CharSequence getSummary() {
+        return mTimeZoneInfo == null ? "" : formatInfo(mTimeZoneInfo);
     }
 
     public void setTimeZoneInfo(TimeZoneInfo timeZoneInfo) {
         mTimeZoneInfo = timeZoneInfo;
     }
 
-    public TimeZoneInfo getTimeZoneInfo() {
-        return mTimeZoneInfo;
-    }
-
     private CharSequence formatOffsetAndName(TimeZoneInfo item) {
         String name = item.getGenericName();
         if (name == null) {
@@ -130,5 +122,4 @@
         } while (transition != null);
         return transition;
     }
-
 }
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/datetime/timezone/TimeZoneSettings.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/datetime/timezone/TimeZoneSettings.java
index 4f43cd3..481e6ba 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/datetime/timezone/TimeZoneSettings.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/datetime/timezone/TimeZoneSettings.java
@@ -17,8 +17,9 @@
 package com.android.car.developeroptions.datetime.timezone;
 
 import android.app.Activity;
-import android.app.AlarmManager;
 import android.app.settings.SettingsEnums;
+import android.app.timezonedetector.ManualTimeZoneSuggestion;
+import android.app.timezonedetector.TimeZoneDetector;
 import android.content.Context;
 import android.content.Intent;
 import android.content.SharedPreferences;
@@ -270,7 +271,10 @@
             editor.putString(PREF_KEY_REGION, regionId);
         }
         editor.apply();
-        getActivity().getSystemService(AlarmManager.class).setTimeZone(tzId);
+        TimeZoneDetector timeZoneDetector = getActivity().getSystemService(TimeZoneDetector.class);
+        ManualTimeZoneSuggestion suggestion = TimeZoneDetector.createManualTimeZoneSuggestion(
+                tzId, "Settings: Set time zone");
+        timeZoneDetector.suggestManualTimeZone(suggestion);
     }
 
     @Override
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/datetime/timezone/model/FilteredCountryTimeZones.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/datetime/timezone/model/FilteredCountryTimeZones.java
index b8a56c6..48f7ab3 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/datetime/timezone/model/FilteredCountryTimeZones.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/datetime/timezone/model/FilteredCountryTimeZones.java
@@ -48,9 +48,10 @@
         mCountryTimeZones = countryTimeZones;
         List<String> timeZoneIds = countryTimeZones.getTimeZoneMappings().stream()
                 .filter(timeZoneMapping ->
-                        timeZoneMapping.showInPicker && (timeZoneMapping.notUsedAfter == null
-                                || timeZoneMapping.notUsedAfter >= MIN_USE_DATE_OF_TIMEZONE))
-                .map(timeZoneMapping -> timeZoneMapping.timeZoneId)
+                        timeZoneMapping.isShownInPicker()
+                                && (timeZoneMapping.getNotUsedAfter() == null
+                                || timeZoneMapping.getNotUsedAfter() >= MIN_USE_DATE_OF_TIMEZONE))
+                .map(timeZoneMapping -> timeZoneMapping.getTimeZoneId())
                 .collect(Collectors.toList());
         mTimeZoneIds = Collections.unmodifiableList(timeZoneIds);
     }
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/deletionhelper/AutomaticStorageManagerSettings.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/deletionhelper/AutomaticStorageManagerSettings.java
index 6ff0fb9..93edf9f 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/deletionhelper/AutomaticStorageManagerSettings.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/deletionhelper/AutomaticStorageManagerSettings.java
@@ -34,9 +34,9 @@
 import com.android.car.developeroptions.Utils;
 import com.android.car.developeroptions.dashboard.DashboardFragment;
 import com.android.car.developeroptions.search.BaseSearchIndexProvider;
-import com.android.car.developeroptions.search.Indexable;
 import com.android.car.developeroptions.widget.SwitchBar;
 import com.android.settingslib.core.AbstractPreferenceController;
+import com.android.settingslib.search.Indexable;
 import com.android.settingslib.search.SearchIndexable;
 
 import java.util.ArrayList;
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/deletionhelper/AutomaticStorageManagerSwitchBarController.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/deletionhelper/AutomaticStorageManagerSwitchBarController.java
index 342e44f..a46c7b1 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/deletionhelper/AutomaticStorageManagerSwitchBarController.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/deletionhelper/AutomaticStorageManagerSwitchBarController.java
@@ -25,11 +25,12 @@
 import androidx.fragment.app.FragmentManager;
 import androidx.preference.Preference;
 
-import com.android.internal.util.Preconditions;
 import com.android.car.developeroptions.widget.SwitchBar;
 import com.android.settingslib.Utils;
 import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
 
+import java.util.Objects;
+
 /** Handles the logic for flipping the storage management toggle on a {@link SwitchBar}. */
 public class AutomaticStorageManagerSwitchBarController
         implements SwitchBar.OnSwitchChangeListener {
@@ -48,11 +49,11 @@
             MetricsFeatureProvider metrics,
             Preference daysToRetainPreference,
             FragmentManager fragmentManager) {
-        mContext = Preconditions.checkNotNull(context);
-        mSwitchBar = Preconditions.checkNotNull(switchBar);
-        mMetrics = Preconditions.checkNotNull(metrics);
-        mDaysToRetainPreference = Preconditions.checkNotNull(daysToRetainPreference);
-        mFragmentManager = Preconditions.checkNotNull(fragmentManager);
+        mContext = Objects.requireNonNull(context);
+        mSwitchBar = Objects.requireNonNull(switchBar);
+        mMetrics = Objects.requireNonNull(metrics);
+        mDaysToRetainPreference = Objects.requireNonNull(daysToRetainPreference);
+        mFragmentManager = Objects.requireNonNull(fragmentManager);
 
         initializeCheckedStatus();
     }
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/development/DevelopmentSettingsDashboardFragment.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/development/DevelopmentSettingsDashboardFragment.java
index 33bd3ca..0f0c313 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/development/DevelopmentSettingsDashboardFragment.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/development/DevelopmentSettingsDashboardFragment.java
@@ -45,13 +45,13 @@
 import com.android.car.developeroptions.development.autofill.AutofillLoggingLevelPreferenceController;
 import com.android.car.developeroptions.development.autofill.AutofillResetOptionsPreferenceController;
 import com.android.car.developeroptions.search.BaseSearchIndexProvider;
-import com.android.car.developeroptions.search.Indexable;
 import com.android.car.developeroptions.widget.SwitchBar;
 import com.android.settingslib.core.AbstractPreferenceController;
 import com.android.settingslib.core.lifecycle.Lifecycle;
 import com.android.settingslib.development.DeveloperOptionsPreferenceController;
 import com.android.settingslib.development.DevelopmentSettingsEnabler;
 import com.android.settingslib.development.SystemPropPoker;
+import com.android.settingslib.search.Indexable;
 import com.android.settingslib.search.SearchIndexable;
 
 import java.util.ArrayList;
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/development/VerifyAppsOverUsbPreferenceController.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/development/VerifyAppsOverUsbPreferenceController.java
index 5f6e572..2554b2a 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/development/VerifyAppsOverUsbPreferenceController.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/development/VerifyAppsOverUsbPreferenceController.java
@@ -146,19 +146,11 @@
                 == AdbPreferenceController.ADB_SETTING_OFF) {
             return false;
         }
-        if (Settings.Global.getInt(cr, Settings.Global.PACKAGE_VERIFIER_ENABLE, SETTING_VALUE_ON)
-                == SETTING_VALUE_OFF) {
-            return false;
-        } else {
-            final Intent verification = new Intent(Intent.ACTION_PACKAGE_NEEDS_VERIFICATION);
-            verification.setType(PACKAGE_MIME_TYPE);
-            verification.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
-            final List<ResolveInfo> receivers = mPackageManager.queryBroadcastReceivers(
-                    verification, 0 /* flags */);
-            if (receivers.size() == 0) {
-                return false;
-            }
-        }
-        return true;
+        final Intent verification = new Intent(Intent.ACTION_PACKAGE_NEEDS_VERIFICATION);
+        verification.setType(PACKAGE_MIME_TYPE);
+        verification.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+        final List<ResolveInfo> receivers = mPackageManager.queryBroadcastReceivers(
+                verification, 0 /* flags */);
+        return receivers.size() != 0;
     }
 }
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/development/WifiVerboseLoggingPreferenceController.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/development/WifiVerboseLoggingPreferenceController.java
index b717bfb..101779b 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/development/WifiVerboseLoggingPreferenceController.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/development/WifiVerboseLoggingPreferenceController.java
@@ -52,13 +52,13 @@
     @Override
     public boolean onPreferenceChange(Preference preference, Object newValue) {
         final boolean isEnabled = (Boolean) newValue;
-        mWifiManager.enableVerboseLogging(isEnabled ? SETTING_VALUE_ON : SETTING_VALUE_OFF);
+        mWifiManager.setVerboseLoggingEnabled(isEnabled);
         return true;
     }
 
     @Override
     public void updateState(Preference preference) {
-        final boolean enabled = mWifiManager.getVerboseLoggingLevel() > 0;
+        final boolean enabled = mWifiManager.isVerboseLoggingEnabled();
         ((SwitchPreference) mPreference).setChecked(enabled);
 
     }
@@ -66,7 +66,7 @@
     @Override
     protected void onDeveloperOptionsSwitchDisabled() {
         super.onDeveloperOptionsSwitchDisabled();
-        mWifiManager.enableVerboseLogging(SETTING_VALUE_OFF);
+        mWifiManager.setVerboseLoggingEnabled(false);
         ((SwitchPreference) mPreference).setChecked(false);
     }
 }
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/development/featureflags/FeatureFlagFooterPreferenceController.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/development/featureflags/FeatureFlagFooterPreferenceController.java
deleted file mode 100644
index 36b8d9c..0000000
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/development/featureflags/FeatureFlagFooterPreferenceController.java
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.car.developeroptions.development.featureflags;
-
-import android.content.Context;
-
-import com.android.car.developeroptions.R;
-import com.android.car.developeroptions.core.BasePreferenceController;
-import com.android.settingslib.core.lifecycle.LifecycleObserver;
-import com.android.settingslib.core.lifecycle.events.OnStart;
-import com.android.settingslib.widget.FooterPreferenceMixinCompat;
-
-public class FeatureFlagFooterPreferenceController extends BasePreferenceController
-        implements LifecycleObserver, OnStart {
-
-    private FooterPreferenceMixinCompat mFooterMixin;
-
-    public FeatureFlagFooterPreferenceController(Context context) {
-        super(context, "feature_flag_footer_pref");
-    }
-
-    public void setFooterMixin(FooterPreferenceMixinCompat mixin) {
-        mFooterMixin = mixin;
-    }
-
-    @Override
-    public int getAvailabilityStatus() {
-        return AVAILABLE;
-    }
-
-    @Override
-    public void onStart() {
-        mFooterMixin.createFooterPreference()
-                .setTitle(R.string.experimental_category_title);
-    }
-}
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/development/featureflags/FeatureFlagsDashboard.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/development/featureflags/FeatureFlagsDashboard.java
index 933aaf3..021342b 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/development/featureflags/FeatureFlagsDashboard.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/development/featureflags/FeatureFlagsDashboard.java
@@ -23,10 +23,8 @@
 import com.android.car.developeroptions.R;
 import com.android.car.developeroptions.dashboard.DashboardFragment;
 import com.android.car.developeroptions.search.BaseSearchIndexProvider;
-import com.android.car.developeroptions.search.Indexable;
-import com.android.settingslib.core.AbstractPreferenceController;
-import com.android.settingslib.core.lifecycle.Lifecycle;
 import com.android.settingslib.development.DevelopmentSettingsEnabler;
+import com.android.settingslib.search.Indexable;
 import com.android.settingslib.search.SearchIndexable;
 
 import java.util.ArrayList;
@@ -55,7 +53,6 @@
     @Override
     public void onAttach(Context context) {
         super.onAttach(context);
-        use(FeatureFlagFooterPreferenceController.class).setFooterMixin(mFooterPreferenceMixin);
     }
 
     @Override
@@ -63,23 +60,6 @@
         return 0;
     }
 
-    @Override
-    protected List<AbstractPreferenceController> createPreferenceControllers(Context context) {
-        return buildPrefControllers(context, getSettingsLifecycle());
-    }
-
-    private static List<AbstractPreferenceController> buildPrefControllers(Context context,
-            Lifecycle lifecycle) {
-        final List<AbstractPreferenceController> controllers = new ArrayList<>();
-        final FeatureFlagFooterPreferenceController footerController =
-                new FeatureFlagFooterPreferenceController(context);
-        if (lifecycle != null) {
-            lifecycle.addObserver(footerController);
-        }
-        controllers.add(footerController);
-        return controllers;
-    }
-
     public static final Indexable.SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
             new BaseSearchIndexProvider() {
                 @Override
@@ -97,11 +77,5 @@
                 protected boolean isPageSearchEnabled(Context context) {
                     return DevelopmentSettingsEnabler.isDevelopmentSettingsEnabled(context);
                 }
-
-                @Override
-                public List<AbstractPreferenceController> createPreferenceControllers(
-                        Context context) {
-                    return buildPrefControllers(context, null /* lifecycle */);
-                }
             };
 }
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/development/gamedriver/GameDriverDashboard.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/development/gamedriver/GameDriverDashboard.java
index 8606b95..11b1486 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/development/gamedriver/GameDriverDashboard.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/development/gamedriver/GameDriverDashboard.java
@@ -25,10 +25,10 @@
 import com.android.car.developeroptions.SettingsActivity;
 import com.android.car.developeroptions.dashboard.DashboardFragment;
 import com.android.car.developeroptions.search.BaseSearchIndexProvider;
-import com.android.car.developeroptions.search.Indexable;
 import com.android.car.developeroptions.widget.SwitchBar;
 import com.android.car.developeroptions.widget.SwitchBarController;
 import com.android.settingslib.development.DevelopmentSettingsEnabler;
+import com.android.settingslib.search.Indexable;
 import com.android.settingslib.search.SearchIndexable;
 
 import java.util.ArrayList;
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/development/gamedriver/GameDriverFooterPreferenceController.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/development/gamedriver/GameDriverFooterPreferenceController.java
index 1469907..11b963e 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/development/gamedriver/GameDriverFooterPreferenceController.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/development/gamedriver/GameDriverFooterPreferenceController.java
@@ -26,7 +26,6 @@
 import android.provider.Settings;
 
 import androidx.annotation.VisibleForTesting;
-import androidx.preference.Preference;
 import androidx.preference.PreferenceScreen;
 
 import com.android.car.developeroptions.core.BasePreferenceController;
@@ -48,8 +47,8 @@
 
     private FooterPreference mPreference;
 
-    public GameDriverFooterPreferenceController(Context context) {
-        super(context, FooterPreference.KEY_FOOTER);
+    public GameDriverFooterPreferenceController(Context context, String key) {
+        super(context, key);
         mContentResolver = context.getContentResolver();
         mGameDriverContentObserver =
                 new GameDriverContentObserver(new Handler(Looper.getMainLooper()), this);
@@ -81,11 +80,6 @@
     }
 
     @Override
-    public void updateState(Preference preference) {
-        preference.setVisible(isAvailable());
-    }
-
-    @Override
     public void onGameDriverContentChanged() {
         updateState(mPreference);
     }
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/development/qstile/DevelopmentTileConfigFragment.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/development/qstile/DevelopmentTileConfigFragment.java
index ae492d0..e2aaeb9 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/development/qstile/DevelopmentTileConfigFragment.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/development/qstile/DevelopmentTileConfigFragment.java
@@ -23,8 +23,8 @@
 import com.android.car.developeroptions.R;
 import com.android.car.developeroptions.dashboard.DashboardFragment;
 import com.android.car.developeroptions.search.BaseSearchIndexProvider;
-import com.android.car.developeroptions.search.Indexable;
 import com.android.settingslib.development.DevelopmentSettingsEnabler;
+import com.android.settingslib.search.Indexable;
 import com.android.settingslib.search.SearchIndexable;
 
 import java.util.ArrayList;
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/deviceinfo/PrivateVolumeSettings.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/deviceinfo/PrivateVolumeSettings.java
index 31d13c3..3cd45c5 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/deviceinfo/PrivateVolumeSettings.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/deviceinfo/PrivateVolumeSettings.java
@@ -762,7 +762,7 @@
         public Dialog onCreateDialog(Bundle savedInstanceState) {
             return new AlertDialog.Builder(getActivity())
                     .setMessage(getContext().getString(R.string.storage_detail_dialog_system,
-                            Build.VERSION.RELEASE))
+                            Build.VERSION.RELEASE_OR_CODENAME))
                     .setPositiveButton(android.R.string.ok, null)
                     .create();
         }
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/deviceinfo/PublicVolumeSettings.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/deviceinfo/PublicVolumeSettings.java
index 7dda240..94176f0 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/deviceinfo/PublicVolumeSettings.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/deviceinfo/PublicVolumeSettings.java
@@ -39,7 +39,6 @@
 import androidx.preference.Preference;
 import androidx.preference.PreferenceScreen;
 
-import com.android.internal.util.Preconditions;
 import com.android.car.developeroptions.R;
 import com.android.car.developeroptions.SettingsPreferenceFragment;
 import com.android.car.developeroptions.deviceinfo.StorageSettings.MountTask;
@@ -109,7 +108,7 @@
         }
 
         mDisk = mStorageManager.findDiskById(mVolume.getDiskId());
-        Preconditions.checkNotNull(mDisk);
+        Objects.requireNonNull(mDisk);
 
         mVolumeId = mVolume.getId();
 
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/deviceinfo/StorageDashboardFragment.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/deviceinfo/StorageDashboardFragment.java
index 0f6c4a6..c6b740f 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/deviceinfo/StorageDashboardFragment.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/deviceinfo/StorageDashboardFragment.java
@@ -46,12 +46,12 @@
 import com.android.car.developeroptions.deviceinfo.storage.UserIconLoader;
 import com.android.car.developeroptions.deviceinfo.storage.VolumeSizesLoader;
 import com.android.car.developeroptions.search.BaseSearchIndexProvider;
-import com.android.car.developeroptions.search.Indexable;
 import com.android.car.developeroptions.widget.EntityHeaderController;
 import com.android.settingslib.applications.StorageStatsSource;
 import com.android.settingslib.core.AbstractPreferenceController;
 import com.android.settingslib.deviceinfo.PrivateStorageInfo;
 import com.android.settingslib.deviceinfo.StorageManagerVolumeProvider;
+import com.android.settingslib.search.Indexable;
 import com.android.settingslib.search.SearchIndexable;
 
 import java.util.ArrayList;
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/deviceinfo/StorageSettings.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/deviceinfo/StorageSettings.java
index d83af2f..c00fc35 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/deviceinfo/StorageSettings.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/deviceinfo/StorageSettings.java
@@ -50,13 +50,13 @@
 import com.android.car.developeroptions.core.SubSettingLauncher;
 import com.android.car.developeroptions.core.instrumentation.InstrumentedDialogFragment;
 import com.android.car.developeroptions.search.BaseSearchIndexProvider;
-import com.android.car.developeroptions.search.Indexable;
-import com.android.car.developeroptions.search.SearchIndexableRaw;
 import com.android.settingslib.RestrictedLockUtils;
 import com.android.settingslib.RestrictedLockUtilsInternal;
 import com.android.settingslib.deviceinfo.PrivateStorageInfo;
 import com.android.settingslib.deviceinfo.StorageManagerVolumeProvider;
+import com.android.settingslib.search.Indexable;
 import com.android.settingslib.search.SearchIndexable;
+import com.android.settingslib.search.SearchIndexableRaw;
 
 import java.util.ArrayList;
 import java.util.Collections;
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/deviceinfo/firmwareversion/FirmwareVersionDetailPreferenceController.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/deviceinfo/firmwareversion/FirmwareVersionDetailPreferenceController.java
index 24b1e29..4ee76b8 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/deviceinfo/firmwareversion/FirmwareVersionDetailPreferenceController.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/deviceinfo/firmwareversion/FirmwareVersionDetailPreferenceController.java
@@ -70,7 +70,7 @@
 
     @Override
     public CharSequence getSummary() {
-        return Build.VERSION.RELEASE;
+        return Build.VERSION.RELEASE_OR_CODENAME;
     }
 
     @Override
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/deviceinfo/firmwareversion/FirmwareVersionPreferenceController.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/deviceinfo/firmwareversion/FirmwareVersionPreferenceController.java
index e9f70e6..789d047 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/deviceinfo/firmwareversion/FirmwareVersionPreferenceController.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/deviceinfo/firmwareversion/FirmwareVersionPreferenceController.java
@@ -34,6 +34,6 @@
 
     @Override
     public CharSequence getSummary() {
-        return Build.VERSION.RELEASE;
+        return Build.VERSION.RELEASE_OR_CODENAME;
     }
 }
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/deviceinfo/firmwareversion/FirmwareVersionSettings.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/deviceinfo/firmwareversion/FirmwareVersionSettings.java
index da7339d..8200b53 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/deviceinfo/firmwareversion/FirmwareVersionSettings.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/deviceinfo/firmwareversion/FirmwareVersionSettings.java
@@ -23,7 +23,7 @@
 import com.android.car.developeroptions.R;
 import com.android.car.developeroptions.dashboard.DashboardFragment;
 import com.android.car.developeroptions.search.BaseSearchIndexProvider;
-import com.android.car.developeroptions.search.Indexable;
+import com.android.settingslib.search.Indexable;
 import com.android.settingslib.search.SearchIndexable;
 
 import java.util.ArrayList;
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/deviceinfo/hardwareinfo/HardwareInfoFragment.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/deviceinfo/hardwareinfo/HardwareInfoFragment.java
index 4455221..f9aaac9 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/deviceinfo/hardwareinfo/HardwareInfoFragment.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/deviceinfo/hardwareinfo/HardwareInfoFragment.java
@@ -23,7 +23,7 @@
 import com.android.car.developeroptions.R;
 import com.android.car.developeroptions.dashboard.DashboardFragment;
 import com.android.car.developeroptions.search.BaseSearchIndexProvider;
-import com.android.car.developeroptions.search.Indexable;
+import com.android.settingslib.search.Indexable;
 import com.android.settingslib.search.SearchIndexable;
 
 import java.util.ArrayList;
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/deviceinfo/imei/ImeiInfoDialogController.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/deviceinfo/imei/ImeiInfoDialogController.java
index 303eaa0..e1c041c 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/deviceinfo/imei/ImeiInfoDialogController.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/deviceinfo/imei/ImeiInfoDialogController.java
@@ -30,7 +30,6 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.VisibleForTesting;
 
-import com.android.internal.telephony.PhoneConstants;
 import com.android.car.developeroptions.R;
 
 import java.util.List;
@@ -142,8 +141,7 @@
 
     @VisibleForTesting
     boolean isCdmaLteEnabled() {
-        return mTelephonyManager.getLteOnCdmaMode(mSubscriptionInfo.getSubscriptionId())
-                == PhoneConstants.LTE_ON_CDMA_TRUE;
+        return mTelephonyManager.isGlobalModeEnabled();
     }
 
     @VisibleForTesting
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/deviceinfo/simstatus/SimStatusDialogController.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/deviceinfo/simstatus/SimStatusDialogController.java
index e0c47b5..946d986 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/deviceinfo/simstatus/SimStatusDialogController.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/deviceinfo/simstatus/SimStatusDialogController.java
@@ -19,6 +19,7 @@
 import static android.content.Context.CARRIER_CONFIG_SERVICE;
 import static android.content.Context.EUICC_SERVICE;
 import static android.content.Context.TELEPHONY_SERVICE;
+import static android.content.Context.TELEPHONY_SUBSCRIPTION_SERVICE;
 
 import android.Manifest;
 import android.content.BroadcastReceiver;
@@ -30,10 +31,10 @@
 import android.os.PersistableBundle;
 import android.os.UserHandle;
 import android.telephony.CarrierConfigManager;
-import android.telephony.CellBroadcastMessage;
 import android.telephony.PhoneStateListener;
 import android.telephony.ServiceState;
 import android.telephony.SignalStrength;
+import android.telephony.SmsCbMessage;
 import android.telephony.SubscriptionInfo;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
@@ -102,6 +103,7 @@
     private final SubscriptionInfo mSubscriptionInfo;
     private final TelephonyManager mTelephonyManager;
     private final CarrierConfigManager mCarrierConfigManager;
+    private final SubscriptionManager mSubscriptionManager;
     private final EuiccManager mEuiccManager;
     private final Resources mRes;
     private final Context mContext;
@@ -117,11 +119,16 @@
                 if (extras == null) {
                     return;
                 }
-                final CellBroadcastMessage cbMessage = (CellBroadcastMessage) extras.get("message");
-                if (cbMessage != null
-                        && mSubscriptionInfo.getSubscriptionId() == cbMessage.getSubId()) {
-                    final String latestAreaInfo = cbMessage.getMessageBody();
-                    mDialog.setText(OPERATOR_INFO_VALUE_ID, latestAreaInfo);
+                final SmsCbMessage cbMessage = (SmsCbMessage) extras.get("message");
+                if (cbMessage != null) {
+                    int[] subIds = mSubscriptionManager.getSubscriptionIds(
+                            cbMessage.getSlotIndex());
+                    int subId = (subIds == null || subIds.length == 0)
+                            ? SubscriptionManager.INVALID_SUBSCRIPTION_ID : subIds[0];
+                    if (mSubscriptionInfo.getSubscriptionId() == subId) {
+                        final String latestAreaInfo = cbMessage.getMessageBody();
+                        mDialog.setText(OPERATOR_INFO_VALUE_ID, latestAreaInfo);
+                    }
                 }
             }
         }
@@ -139,6 +146,8 @@
         mCarrierConfigManager = (CarrierConfigManager) mContext.getSystemService(
                 CARRIER_CONFIG_SERVICE);
         mEuiccManager = (EuiccManager) mContext.getSystemService(EUICC_SERVICE);
+        mSubscriptionManager = (SubscriptionManager) mContext.getSystemService(
+                TELEPHONY_SUBSCRIPTION_SERVICE);
 
         mRes = mContext.getResources();
 
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/deviceinfo/storage/UserIconLoader.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/deviceinfo/storage/UserIconLoader.java
index 4540824..c8a7606 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/deviceinfo/storage/UserIconLoader.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/deviceinfo/storage/UserIconLoader.java
@@ -22,10 +22,11 @@
 import android.os.UserManager;
 import android.util.SparseArray;
 
-import com.android.internal.util.Preconditions;
 import com.android.car.developeroptions.Utils;
 import com.android.settingslib.utils.AsyncLoaderCompat;
 
+import java.util.Objects;
+
 /**
  * Fetches a user icon as a loader using a given icon loading lambda.
  */
@@ -48,7 +49,7 @@
 
     public UserIconLoader(Context context, FetchUserIconTask task) {
         super(context);
-        mTask = Preconditions.checkNotNull(task);
+        mTask = Objects.requireNonNull(task);
     }
 
     @Override
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/deviceinfo/storage/UserProfileController.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/deviceinfo/storage/UserProfileController.java
index 9b19647..0ac9285 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/deviceinfo/storage/UserProfileController.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/deviceinfo/storage/UserProfileController.java
@@ -27,7 +27,6 @@
 import androidx.preference.Preference;
 import androidx.preference.PreferenceScreen;
 
-import com.android.internal.util.Preconditions;
 import com.android.car.developeroptions.Utils;
 import com.android.car.developeroptions.core.PreferenceControllerMixin;
 import com.android.car.developeroptions.core.SubSettingLauncher;
@@ -35,6 +34,8 @@
 import com.android.car.developeroptions.deviceinfo.StorageProfileFragment;
 import com.android.settingslib.core.AbstractPreferenceController;
 
+import java.util.Objects;
+
 /**
  * Defines a {@link AbstractPreferenceController} which handles a single profile of the primary
  * user.
@@ -50,7 +51,7 @@
 
     public UserProfileController(Context context, UserInfo info, int preferenceOrder) {
         super(context);
-        mUser = Preconditions.checkNotNull(info);
+        mUser = Objects.requireNonNull(info);
         mPreferenceOrder = preferenceOrder;
     }
 
@@ -94,7 +95,7 @@
 
     @Override
     public void handleResult(SparseArray<StorageAsyncLoader.AppsStorageResult> stats) {
-        Preconditions.checkNotNull(stats);
+        Objects.requireNonNull(stats);
 
         int userId = mUser.id;
         StorageAsyncLoader.AppsStorageResult result = stats.get(userId);
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/display/AdaptiveSleepSettings.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/display/AdaptiveSleepSettings.java
index 26cdab0..b80bea4 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/display/AdaptiveSleepSettings.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/display/AdaptiveSleepSettings.java
@@ -25,7 +25,6 @@
 import com.android.car.developeroptions.dashboard.DashboardFragment;
 import com.android.car.developeroptions.search.BaseSearchIndexProvider;
 import com.android.settingslib.search.SearchIndexable;
-import com.android.settingslib.widget.FooterPreference;
 
 import java.util.Arrays;
 import java.util.List;
@@ -38,10 +37,6 @@
     @Override
     public void onCreate(Bundle icicle) {
         super.onCreate(icicle);
-        final FooterPreference footerPreference =
-                mFooterPreferenceMixin.createFooterPreference();
-        footerPreference.setIcon(R.drawable.ic_privacy_shield_24dp);
-        footerPreference.setTitle(R.string.adaptive_sleep_privacy);
     }
 
     @Override
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/display/AutoBrightnessSettings.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/display/AutoBrightnessSettings.java
index 60095ef..d0fca24 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/display/AutoBrightnessSettings.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/display/AutoBrightnessSettings.java
@@ -37,8 +37,6 @@
     @Override
     public void onCreate(Bundle icicle) {
         super.onCreate(icicle);
-        mFooterPreferenceMixin.createFooterPreference()
-                .setTitle(R.string.auto_brightness_description);
     }
 
     @Override
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/display/ColorModePreferenceFragment.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/display/ColorModePreferenceFragment.java
index 2786974..e33fd25 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/display/ColorModePreferenceFragment.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/display/ColorModePreferenceFragment.java
@@ -23,15 +23,15 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.provider.SearchIndexableResource;
-
 import android.provider.Settings.Secure;
+
 import androidx.annotation.VisibleForTesting;
 import androidx.preference.PreferenceScreen;
 
 import com.android.car.developeroptions.R;
 import com.android.car.developeroptions.search.BaseSearchIndexProvider;
-import com.android.car.developeroptions.search.Indexable;
 import com.android.car.developeroptions.widget.RadioButtonPickerFragment;
+import com.android.settingslib.search.Indexable;
 import com.android.settingslib.search.SearchIndexable;
 import com.android.settingslib.widget.CandidateInfo;
 import com.android.settingslib.widget.LayoutPreference;
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/display/DarkUISettings.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/display/DarkUISettings.java
index f58928e..ed880c9 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/display/DarkUISettings.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/display/DarkUISettings.java
@@ -21,15 +21,18 @@
 import android.content.Context;
 import android.graphics.drawable.Drawable;
 import android.provider.SearchIndexableResource;
+
 import androidx.preference.Preference;
 import androidx.preference.PreferenceScreen;
+
 import com.android.car.developeroptions.R;
 import com.android.car.developeroptions.search.BaseSearchIndexProvider;
-import com.android.car.developeroptions.search.Indexable;
 import com.android.car.developeroptions.widget.RadioButtonPickerFragment;
+import com.android.settingslib.search.Indexable;
 import com.android.settingslib.search.SearchIndexable;
 import com.android.settingslib.widget.CandidateInfo;
 import com.android.settingslib.widget.FooterPreference;
+
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/display/NightDisplayFooterPreferenceController.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/display/NightDisplayFooterPreferenceController.java
index 3c75fd2..2e417d4 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/display/NightDisplayFooterPreferenceController.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/display/NightDisplayFooterPreferenceController.java
@@ -19,26 +19,17 @@
 import android.content.Context;
 import android.hardware.display.ColorDisplayManager;
 
-import androidx.preference.Preference;
-
-import com.android.car.developeroptions.R;
 import com.android.car.developeroptions.core.BasePreferenceController;
-import com.android.settingslib.widget.FooterPreference;
 
 public class NightDisplayFooterPreferenceController extends BasePreferenceController {
 
-    public NightDisplayFooterPreferenceController(Context context) {
-        super(context, FooterPreference.KEY_FOOTER);
+    public NightDisplayFooterPreferenceController(Context context, String key) {
+        super(context, key);
     }
 
     @Override
     public int getAvailabilityStatus() {
-        return ColorDisplayManager.isNightDisplayAvailable(mContext) ? AVAILABLE
-                : UNSUPPORTED_ON_DEVICE;
-    }
-
-    @Override
-    public void updateState(Preference preference) {
-        preference.setTitle(R.string.night_display_text);
+        return ColorDisplayManager.isNightDisplayAvailable(mContext)
+                ? AVAILABLE_UNSEARCHABLE : UNSUPPORTED_ON_DEVICE;
     }
 }
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/display/NightDisplaySettings.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/display/NightDisplaySettings.java
index 56cef42..8d2389c 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/display/NightDisplaySettings.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/display/NightDisplaySettings.java
@@ -30,8 +30,7 @@
 import com.android.car.developeroptions.R;
 import com.android.car.developeroptions.dashboard.DashboardFragment;
 import com.android.car.developeroptions.search.BaseSearchIndexProvider;
-import com.android.car.developeroptions.search.Indexable;
-import com.android.settingslib.core.AbstractPreferenceController;
+import com.android.settingslib.search.Indexable;
 import com.android.settingslib.search.SearchIndexable;
 
 import java.time.LocalTime;
@@ -176,17 +175,6 @@
         return TAG;
     }
 
-    @Override
-    protected List<AbstractPreferenceController> createPreferenceControllers(Context context) {
-        return buildPreferenceControllers(context);
-    }
-
-    private static List<AbstractPreferenceController> buildPreferenceControllers(Context context) {
-        final List<AbstractPreferenceController> controllers = new ArrayList<>(1);
-        controllers.add(new NightDisplayFooterPreferenceController(context));
-        return controllers;
-    }
-
     public static final Indexable.SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
             new BaseSearchIndexProvider() {
                 @Override
@@ -203,11 +191,5 @@
                 protected boolean isPageSearchEnabled(Context context) {
                     return ColorDisplayManager.isNightDisplayAvailable(context);
                 }
-
-                @Override
-                public List<AbstractPreferenceController> createPreferenceControllers(
-                        Context context) {
-                    return buildPreferenceControllers(context);
-                }
             };
 }
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/display/ScreenZoomSettings.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/display/ScreenZoomSettings.java
index b2eade8..df60fa8 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/display/ScreenZoomSettings.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/display/ScreenZoomSettings.java
@@ -26,10 +26,10 @@
 
 import com.android.car.developeroptions.R;
 import com.android.car.developeroptions.search.BaseSearchIndexProvider;
-import com.android.car.developeroptions.search.Indexable;
-import com.android.car.developeroptions.search.SearchIndexableRaw;
 import com.android.settingslib.display.DisplayDensityUtils;
+import com.android.settingslib.search.Indexable;
 import com.android.settingslib.search.SearchIndexable;
+import com.android.settingslib.search.SearchIndexableRaw;
 
 import java.util.ArrayList;
 import java.util.List;
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/display/ToggleFontSizePreferenceFragment.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/display/ToggleFontSizePreferenceFragment.java
index 894f2ce..c6221b1 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/display/ToggleFontSizePreferenceFragment.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/display/ToggleFontSizePreferenceFragment.java
@@ -27,9 +27,9 @@
 
 import com.android.car.developeroptions.R;
 import com.android.car.developeroptions.search.BaseSearchIndexProvider;
-import com.android.car.developeroptions.search.Indexable;
-import com.android.car.developeroptions.search.SearchIndexableRaw;
+import com.android.settingslib.search.Indexable;
 import com.android.settingslib.search.SearchIndexable;
+import com.android.settingslib.search.SearchIndexableRaw;
 
 import java.util.ArrayList;
 import java.util.List;
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/flashlight/FlashlightHandleActivity.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/flashlight/FlashlightHandleActivity.java
index d2a30b4..669a779 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/flashlight/FlashlightHandleActivity.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/flashlight/FlashlightHandleActivity.java
@@ -24,9 +24,9 @@
 
 import com.android.car.developeroptions.R;
 import com.android.car.developeroptions.search.BaseSearchIndexProvider;
-import com.android.car.developeroptions.search.Indexable;
-import com.android.car.developeroptions.search.SearchIndexableRaw;
+import com.android.settingslib.search.Indexable;
 import com.android.settingslib.search.SearchIndexable;
+import com.android.settingslib.search.SearchIndexableRaw;
 
 import java.util.ArrayList;
 import java.util.List;
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/fuelgauge/BatteryBroadcastReceiver.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/fuelgauge/BatteryBroadcastReceiver.java
index f3dfa23..bf1f3fd 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/fuelgauge/BatteryBroadcastReceiver.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/fuelgauge/BatteryBroadcastReceiver.java
@@ -102,7 +102,7 @@
             if (Intent.ACTION_BATTERY_CHANGED.equals(intent.getAction())) {
                 final String batteryLevel = Utils.getBatteryPercentage(intent);
                 final String batteryStatus = Utils.getBatteryStatus(
-                        mContext.getResources(), intent);
+                        mContext, intent);
                 if (forceUpdate) {
                     mBatteryListener.onBatteryChanged(BatteryUpdateType.MANUAL);
                 } else if(!batteryLevel.equals(mBatteryLevel)) {
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/fuelgauge/BatteryInfo.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/fuelgauge/BatteryInfo.java
index e87e539..1172e99 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/fuelgauge/BatteryInfo.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/fuelgauge/BatteryInfo.java
@@ -30,9 +30,9 @@
 import androidx.annotation.WorkerThread;
 
 import com.android.internal.os.BatteryStatsHelper;
-import com.android.car.developeroptions.Utils;
 import com.android.car.developeroptions.overlay.FeatureFactory;
 import com.android.car.developeroptions.widget.UsageView;
+import com.android.car.developeroptions.Utils;
 import com.android.settingslib.R;
 import com.android.settingslib.utils.PowerUtil;
 import com.android.settingslib.utils.StringUtil;
@@ -229,9 +229,8 @@
         info.batteryPercentString = Utils.formatPercentage(info.batteryLevel);
         info.mCharging = batteryBroadcast.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0) != 0;
         info.averageTimeToDischarge = estimate.averageDischargeTime;
-        final Resources resources = context.getResources();
 
-        info.statusLabel = Utils.getBatteryStatus(resources, batteryBroadcast);
+        info.statusLabel = Utils.getBatteryStatus(context, batteryBroadcast);
         if (!info.mCharging) {
             updateBatteryInfoDischarging(context, shortString, estimate, info);
         } else {
@@ -258,8 +257,7 @@
                     R.string.power_remaining_charging_duration_only, timeString);
             info.chargeLabel = context.getString(resId, info.batteryPercentString, timeString);
         } else {
-            final String chargeStatusLabel = resources.getString(
-                    R.string.battery_info_status_charging_lower);
+            final String chargeStatusLabel = Utils.getBatteryStatus(context, batteryBroadcast);
             info.remainingLabel = null;
             info.chargeLabel = info.batteryLevel == 100 ? info.batteryPercentString :
                     resources.getString(R.string.power_charging, info.batteryPercentString,
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/fuelgauge/BatteryWifiParser.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/fuelgauge/BatteryWifiParser.java
index d6677bf..c228254 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/fuelgauge/BatteryWifiParser.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/fuelgauge/BatteryWifiParser.java
@@ -14,8 +14,8 @@
 
 package com.android.car.developeroptions.fuelgauge;
 
-import android.os.BatteryStats;
 import android.os.BatteryStats.HistoryItem;
+import android.os.BatteryStatsManager;
 
 public class BatteryWifiParser extends BatteryFlagParser {
 
@@ -27,12 +27,12 @@
     protected boolean isSet(HistoryItem record) {
         switch ((record.states2 & HistoryItem.STATE2_WIFI_SUPPL_STATE_MASK)
                 >> HistoryItem.STATE2_WIFI_SUPPL_STATE_SHIFT) {
-            case BatteryStats.WIFI_SUPPL_STATE_DISCONNECTED:
-            case BatteryStats.WIFI_SUPPL_STATE_DORMANT:
-            case BatteryStats.WIFI_SUPPL_STATE_INACTIVE:
-            case BatteryStats.WIFI_SUPPL_STATE_INTERFACE_DISABLED:
-            case BatteryStats.WIFI_SUPPL_STATE_INVALID:
-            case BatteryStats.WIFI_SUPPL_STATE_UNINITIALIZED:
+            case BatteryStatsManager.WIFI_SUPPL_STATE_DISCONNECTED:
+            case BatteryStatsManager.WIFI_SUPPL_STATE_DORMANT:
+            case BatteryStatsManager.WIFI_SUPPL_STATE_INACTIVE:
+            case BatteryStatsManager.WIFI_SUPPL_STATE_INTERFACE_DISABLED:
+            case BatteryStatsManager.WIFI_SUPPL_STATE_INVALID:
+            case BatteryStatsManager.WIFI_SUPPL_STATE_UNINITIALIZED:
                 return false;
         }
         return true;
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/fuelgauge/PowerUsageSummary.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/fuelgauge/PowerUsageSummary.java
index 37c6aef..e2efb2c 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/fuelgauge/PowerUsageSummary.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/fuelgauge/PowerUsageSummary.java
@@ -215,7 +215,6 @@
         mScreenUsagePref = (PowerGaugePreference) findPreference(KEY_SCREEN_USAGE);
         mLastFullChargePref = (PowerGaugePreference) findPreference(
                 KEY_TIME_SINCE_LAST_FULL_CHARGE);
-        mFooterPreferenceMixin.createFooterPreference().setTitle(R.string.battery_footer_summary);
         mBatteryUtils = BatteryUtils.getInstance(getContext());
 
         restartBatteryInfoLoader();
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/fuelgauge/RestrictedAppDetails.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/fuelgauge/RestrictedAppDetails.java
index fb09ddc..d04c2c6 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/fuelgauge/RestrictedAppDetails.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/fuelgauge/RestrictedAppDetails.java
@@ -47,7 +47,6 @@
 import com.android.car.developeroptions.widget.AppCheckBoxPreference;
 import com.android.settingslib.core.AbstractPreferenceController;
 import com.android.settingslib.utils.StringUtil;
-import com.android.settingslib.widget.FooterPreferenceMixinCompat;
 
 import java.util.List;
 
@@ -76,8 +75,6 @@
     PackageManager mPackageManager;
     @VisibleForTesting
     BatteryDatabaseManager mBatteryDatabaseManager;
-    private final FooterPreferenceMixinCompat mFooterPreferenceMixin =
-            new FooterPreferenceMixinCompat(this, getSettingsLifecycle());
 
     public static void startRestrictedAppDetails(InstrumentedPreferenceFragment fragment,
             List<AppInfo> appInfos) {
@@ -97,8 +94,6 @@
         super.onCreate(icicle);
         final Context context = getContext();
 
-        mFooterPreferenceMixin.createFooterPreference().setTitle(
-                R.string.restricted_app_detail_footer);
         mRestrictedAppListGroup = (PreferenceGroup) findPreference(KEY_PREF_RESTRICTED_APP_LIST);
         mAppInfos = getArguments().getParcelableArrayList(EXTRA_APP_INFO_LIST);
         mPackageManager = context.getPackageManager();
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/fuelgauge/SmartBatterySettings.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/fuelgauge/SmartBatterySettings.java
index 0db35f4..2f0e0df 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/fuelgauge/SmartBatterySettings.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/fuelgauge/SmartBatterySettings.java
@@ -18,7 +18,6 @@
 
 import android.app.settings.SettingsEnums;
 import android.content.Context;
-import android.os.Bundle;
 import android.provider.SearchIndexableResource;
 
 import com.android.car.developeroptions.R;
@@ -41,12 +40,6 @@
     public static final String TAG = "SmartBatterySettings";
 
     @Override
-    public void onCreate(Bundle icicle) {
-        super.onCreate(icicle);
-        mFooterPreferenceMixin.createFooterPreference().setTitle(R.string.smart_battery_footer);
-    }
-
-    @Override
     public int getMetricsCategory() {
         return SettingsEnums.FUELGAUGE_SMART_BATTERY;
     }
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/fuelgauge/batterysaver/BatterySaverSettings.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/fuelgauge/batterysaver/BatterySaverSettings.java
index 4772142..d9f73e8 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/fuelgauge/batterysaver/BatterySaverSettings.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/fuelgauge/batterysaver/BatterySaverSettings.java
@@ -24,7 +24,7 @@
 import com.android.car.developeroptions.R;
 import com.android.car.developeroptions.dashboard.DashboardFragment;
 import com.android.car.developeroptions.search.BaseSearchIndexProvider;
-import com.android.car.developeroptions.search.Indexable;
+import com.android.settingslib.search.Indexable;
 import com.android.settingslib.search.SearchIndexable;
 
 import java.util.Arrays;
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/gestures/TapScreenGestureSettings.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/gestures/TapScreenGestureSettings.java
index c1c77d3..4cf2c3a 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/gestures/TapScreenGestureSettings.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/gestures/TapScreenGestureSettings.java
@@ -21,13 +21,13 @@
 import android.hardware.display.AmbientDisplayConfiguration;
 import android.provider.SearchIndexableResource;
 
-import com.android.internal.logging.nano.MetricsProto;
 import com.android.car.developeroptions.R;
 import com.android.car.developeroptions.dashboard.DashboardFragment;
 import com.android.car.developeroptions.dashboard.suggestions.SuggestionFeatureProvider;
 import com.android.car.developeroptions.overlay.FeatureFactory;
 import com.android.car.developeroptions.search.BaseSearchIndexProvider;
-import com.android.car.developeroptions.search.Indexable;
+import com.android.internal.logging.nano.MetricsProto;
+import com.android.settingslib.search.Indexable;
 import com.android.settingslib.search.SearchIndexable;
 
 import java.util.Arrays;
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/homepage/contextualcards/conditional/ConditionContextualCardRenderer.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/homepage/contextualcards/conditional/ConditionContextualCardRenderer.java
index 3f3c37d..ccbdb50 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/homepage/contextualcards/conditional/ConditionContextualCardRenderer.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/homepage/contextualcards/conditional/ConditionContextualCardRenderer.java
@@ -65,7 +65,7 @@
                 mContext).getMetricsFeatureProvider();
 
         metricsFeatureProvider.visible(mContext, SettingsEnums.SETTINGS_HOMEPAGE,
-                card.getMetricsConstant());
+                card.getMetricsConstant(), 0);
         initializePrimaryClick(view, card, metricsFeatureProvider);
         initializeView(view, card);
         initializeActionButton(view, card, metricsFeatureProvider);
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/homepage/contextualcards/conditional/HotspotConditionController.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/homepage/contextualcards/conditional/HotspotConditionController.java
index 69f7885..775a696 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/homepage/contextualcards/conditional/HotspotConditionController.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/homepage/contextualcards/conditional/HotspotConditionController.java
@@ -26,6 +26,7 @@
 import android.net.wifi.WifiManager;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.util.Log;
 
 import com.android.car.developeroptions.R;
 import com.android.car.developeroptions.TetherSettings;
@@ -37,6 +38,7 @@
 import java.util.Objects;
 
 public class HotspotConditionController implements ConditionalCardController {
+    static final String TAG = "HotspotConditionController";
     static final int ID = Objects.hash("HotspotConditionController");
 
     private static final IntentFilter WIFI_AP_STATE_FILTER =
@@ -116,8 +118,9 @@
     private CharSequence getSsid() {
         WifiConfiguration wifiConfig = mWifiManager.getWifiApConfiguration();
         if (wifiConfig == null) {
-            return mAppContext.getText(
-                    com.android.internal.R.string.wifi_tether_configure_ssid_default);
+            Log.e(TAG, "Ap config null unexpectedly");
+            // should never happen.
+            return "";
         } else {
             return wifiConfig.SSID;
         }
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/inputmethod/PhysicalKeyboardFragment.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/inputmethod/PhysicalKeyboardFragment.java
index 45b7404..6131fa4 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/inputmethod/PhysicalKeyboardFragment.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/inputmethod/PhysicalKeyboardFragment.java
@@ -40,12 +40,11 @@
 import androidx.preference.PreferenceScreen;
 import androidx.preference.SwitchPreference;
 
-import com.android.internal.util.Preconditions;
 import com.android.car.developeroptions.R;
 import com.android.car.developeroptions.Settings;
 import com.android.car.developeroptions.SettingsPreferenceFragment;
 import com.android.car.developeroptions.search.BaseSearchIndexProvider;
-import com.android.car.developeroptions.search.Indexable;
+import com.android.settingslib.search.Indexable;
 import com.android.settingslib.search.SearchIndexable;
 import com.android.settingslib.utils.ThreadUtils;
 
@@ -77,12 +76,12 @@
 
     @Override
     public void onCreatePreferences(Bundle bundle, String s) {
-        Activity activity = Preconditions.checkNotNull(getActivity());
+        Activity activity = Objects.requireNonNull(getActivity());
         addPreferencesFromResource(R.xml.physical_keyboard_settings);
-        mIm = Preconditions.checkNotNull(activity.getSystemService(InputManager.class));
-        mKeyboardAssistanceCategory = Preconditions.checkNotNull(
+        mIm = Objects.requireNonNull(activity.getSystemService(InputManager.class));
+        mKeyboardAssistanceCategory = Objects.requireNonNull(
                 (PreferenceCategory) findPreference(KEYBOARD_ASSISTANCE_CATEGORY));
-        mShowVirtualKeyboardSwitch = Preconditions.checkNotNull(
+        mShowVirtualKeyboardSwitch = Objects.requireNonNull(
                 (SwitchPreference) mKeyboardAssistanceCategory.findPreference(
                         SHOW_VIRTUAL_KEYBOARD_SWITCH));
         findPreference(KEYBOARD_SHORTCUTS_HELPER).setOnPreferenceClickListener(
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/inputmethod/SpellCheckerForWorkPreferenceController.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/inputmethod/SpellCheckerForWorkPreferenceController.java
index 9d4aac6..5418131 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/inputmethod/SpellCheckerForWorkPreferenceController.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/inputmethod/SpellCheckerForWorkPreferenceController.java
@@ -18,7 +18,6 @@
 
 import android.app.settings.SettingsEnums;
 import android.content.Context;
-import android.view.inputmethod.InputMethodSystemProperty;
 
 import com.android.car.developeroptions.R;
 import com.android.car.developeroptions.core.WorkProfilePreferenceController;
@@ -42,8 +41,7 @@
     @AvailabilityStatus
     @Override
     public int getAvailabilityStatus() {
-        if (!mContext.getResources().getBoolean(R.bool.config_show_spellcheckers_settings)
-                || !InputMethodSystemProperty.PER_PROFILE_IME_ENABLED) {
+        if (!mContext.getResources().getBoolean(R.bool.config_show_spellcheckers_settings)) {
             return UNSUPPORTED_ON_DEVICE;
         }
         return super.getAvailabilityStatus();
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/inputmethod/UserDictionaryList.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/inputmethod/UserDictionaryList.java
index 26926ab..2049aab 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/inputmethod/UserDictionaryList.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/inputmethod/UserDictionaryList.java
@@ -25,7 +25,7 @@
 import com.android.car.developeroptions.R;
 import com.android.car.developeroptions.dashboard.DashboardFragment;
 import com.android.car.developeroptions.search.BaseSearchIndexProvider;
-import com.android.car.developeroptions.search.Indexable;
+import com.android.settingslib.search.Indexable;
 import com.android.settingslib.search.SearchIndexable;
 
 import java.util.ArrayList;
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/inputmethod/VirtualKeyboardForWorkPreferenceController.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/inputmethod/VirtualKeyboardForWorkPreferenceController.java
index 78a5853..d6c2d40 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/inputmethod/VirtualKeyboardForWorkPreferenceController.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/inputmethod/VirtualKeyboardForWorkPreferenceController.java
@@ -18,7 +18,6 @@
 
 import android.app.settings.SettingsEnums;
 import android.content.Context;
-import android.view.inputmethod.InputMethodSystemProperty;
 
 import com.android.car.developeroptions.R;
 import com.android.car.developeroptions.core.WorkProfilePreferenceController;
@@ -39,8 +38,7 @@
     @AvailabilityStatus
     @Override
     public int getAvailabilityStatus() {
-        if (!mContext.getResources().getBoolean(R.bool.config_show_virtual_keyboard_pref)
-                || !InputMethodSystemProperty.PER_PROFILE_IME_ENABLED) {
+        if (!mContext.getResources().getBoolean(R.bool.config_show_virtual_keyboard_pref)) {
             return UNSUPPORTED_ON_DEVICE;
         }
         return super.getAvailabilityStatus();
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/inputmethod/VirtualKeyboardFragment.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/inputmethod/VirtualKeyboardFragment.java
index 2b186cf..0e048e9 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/inputmethod/VirtualKeyboardFragment.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/inputmethod/VirtualKeyboardFragment.java
@@ -30,19 +30,19 @@
 
 import androidx.preference.Preference;
 
-import com.android.internal.util.Preconditions;
 import com.android.car.developeroptions.R;
 import com.android.car.developeroptions.SettingsPreferenceFragment;
 import com.android.car.developeroptions.search.BaseSearchIndexProvider;
-import com.android.car.developeroptions.search.Indexable;
 import com.android.settingslib.inputmethod.InputMethodAndSubtypeUtilCompat;
 import com.android.settingslib.inputmethod.InputMethodPreference;
+import com.android.settingslib.search.Indexable;
 import com.android.settingslib.search.SearchIndexable;
 
 import java.text.Collator;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
+import java.util.Objects;
 
 @SearchIndexable
 public final class VirtualKeyboardFragment extends SettingsPreferenceFragment implements Indexable {
@@ -57,11 +57,11 @@
 
     @Override
     public void onCreatePreferences(Bundle bundle, String s) {
-        Activity activity = Preconditions.checkNotNull(getActivity());
+        Activity activity = Objects.requireNonNull(getActivity());
         addPreferencesFromResource(R.xml.virtual_keyboard_settings);
-        mImm = Preconditions.checkNotNull(activity.getSystemService(InputMethodManager.class));
-        mDpm = Preconditions.checkNotNull(activity.getSystemService(DevicePolicyManager.class));
-        mAddVirtualKeyboardScreen = Preconditions.checkNotNull(
+        mImm = Objects.requireNonNull(activity.getSystemService(InputMethodManager.class));
+        mDpm = Objects.requireNonNull(activity.getSystemService(DevicePolicyManager.class));
+        mAddVirtualKeyboardScreen = Objects.requireNonNull(
                 findPreference(ADD_VIRTUAL_KEYBOARD_SCREEN));
     }
 
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/language/UserDictionaryForWorkPreferenceController.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/language/UserDictionaryForWorkPreferenceController.java
index 68e89c1..c647561 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/language/UserDictionaryForWorkPreferenceController.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/language/UserDictionaryForWorkPreferenceController.java
@@ -18,7 +18,6 @@
 
 import android.app.settings.SettingsEnums;
 import android.content.Context;
-import android.view.inputmethod.InputMethodSystemProperty;
 
 import com.android.car.developeroptions.R;
 import com.android.car.developeroptions.core.WorkProfilePreferenceController;
@@ -39,13 +38,4 @@
     protected int getSourceMetricsCategory() {
         return SettingsEnums.SETTINGS_LANGUAGE_CATEGORY;
     }
-
-    @AvailabilityStatus
-    @Override
-    public int getAvailabilityStatus() {
-        if (!InputMethodSystemProperty.PER_PROFILE_IME_ENABLED) {
-            return UNSUPPORTED_ON_DEVICE;
-        }
-        return super.getAvailabilityStatus();
-    }
 }
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/location/LocationFooterPreferenceController.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/location/LocationFooterPreferenceController.java
index 424bf7d..4828cad 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/location/LocationFooterPreferenceController.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/location/LocationFooterPreferenceController.java
@@ -13,7 +13,6 @@
  */
 package com.android.car.developeroptions.location;
 
-import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
@@ -24,13 +23,9 @@
 import android.location.LocationManager;
 import android.util.Log;
 
-import androidx.annotation.VisibleForTesting;
 import androidx.preference.Preference;
 import androidx.preference.PreferenceCategory;
 
-import com.android.settingslib.core.lifecycle.Lifecycle;
-import com.android.settingslib.core.lifecycle.LifecycleObserver;
-import com.android.settingslib.core.lifecycle.events.OnPause;
 import com.android.settingslib.widget.FooterPreference;
 
 import java.util.ArrayList;
@@ -41,24 +36,19 @@
 /**
  * Preference controller for location footer preference category
  */
-public class LocationFooterPreferenceController extends LocationBasePreferenceController
-        implements LifecycleObserver, OnPause {
+public class LocationFooterPreferenceController extends LocationBasePreferenceController {
     private static final String TAG = "LocationFooter";
     private static final String KEY_LOCATION_FOOTER = "location_footer";
     private static final Intent INJECT_INTENT =
             new Intent(LocationManager.SETTINGS_FOOTER_DISPLAYED_ACTION);
-    private final Context mContext;
-    private final PackageManager mPackageManager;
-    private Collection<ComponentName> mFooterInjectors;
 
-    public LocationFooterPreferenceController(Context context, Lifecycle lifecycle) {
-        super(context, lifecycle);
-        mContext = context;
+    private final PackageManager mPackageManager;
+
+    public LocationFooterPreferenceController(Context context) {
+        // we don't care location mode changes, so pass in a null lifecycle to disable listening
+        super(context, null);
+
         mPackageManager = mContext.getPackageManager();
-        mFooterInjectors = new ArrayList<>();
-        if (lifecycle != null) {
-            lifecycle.addObserver(this);
-        }
     }
 
     @Override
@@ -67,37 +57,30 @@
     }
 
     /**
-     * Insert footer preferences. Send a {@link LocationManager#SETTINGS_FOOTER_DISPLAYED_ACTION}
-     * broadcast to receivers who have injected a footer
+     * Insert footer preferences.
      */
     @Override
     public void updateState(Preference preference) {
         PreferenceCategory category = (PreferenceCategory) preference;
         category.removeAll();
-        mFooterInjectors.clear();
         Collection<FooterData> footerData = getFooterData();
         for (FooterData data : footerData) {
-            // Generate a footer preference with the given text
-            FooterPreference footerPreference = new FooterPreference(preference.getContext());
-            String footerString;
             try {
-                footerString =
+                String footerString =
                         mPackageManager
                                 .getResourcesForApplication(data.applicationInfo)
                                 .getString(data.footerStringRes);
+
+                // Generate a footer preference with the given text
+                FooterPreference footerPreference = new FooterPreference(preference.getContext());
+                footerPreference.setTitle(footerString);
+                category.addPreference(footerPreference);
             } catch (NameNotFoundException exception) {
                 Log.w(
                         TAG,
                         "Resources not found for application "
                                 + data.applicationInfo.packageName);
-                continue;
             }
-            footerPreference.setTitle(footerString);
-            // Inject the footer
-            category.addPreference(footerPreference);
-            // Send broadcast to the injector announcing a footer has been injected
-            sendBroadcastFooterDisplayed(data.componentName);
-            mFooterInjectors.add(data.componentName);
         }
     }
 
@@ -117,36 +100,11 @@
     }
 
     /**
-     * Send a {@link LocationManager#SETTINGS_FOOTER_REMOVED_ACTION} broadcast to footer injectors
-     * when LocationFragment is on pause
-     */
-    @Override
-    public void onPause() {
-        // Send broadcast to the footer injectors. Notify them the footer is not visible.
-        for (ComponentName componentName : mFooterInjectors) {
-            final Intent intent = new Intent(LocationManager.SETTINGS_FOOTER_REMOVED_ACTION);
-            intent.setComponent(componentName);
-            mContext.sendBroadcast(intent);
-        }
-    }
-
-    /**
-     * Send a {@link LocationManager#SETTINGS_FOOTER_DISPLAYED_ACTION} broadcast to a footer
-     * injector.
-     */
-    @VisibleForTesting
-    void sendBroadcastFooterDisplayed(ComponentName componentName) {
-        Intent intent = new Intent(LocationManager.SETTINGS_FOOTER_DISPLAYED_ACTION);
-        intent.setComponent(componentName);
-        mContext.sendBroadcast(intent);
-    }
-
-    /**
      * Return a list of strings with text provided by ACTION_INJECT_FOOTER broadcast receivers.
      */
-    private Collection<FooterData> getFooterData() {
+    private List<FooterData> getFooterData() {
         // Fetch footer text from system apps
-        final List<ResolveInfo> resolveInfos =
+        List<ResolveInfo> resolveInfos =
                 mPackageManager.queryBroadcastReceivers(
                         INJECT_INTENT, PackageManager.GET_META_DATA);
         if (resolveInfos == null) {
@@ -158,10 +116,10 @@
             Log.d(TAG, "Found broadcast receivers: " + resolveInfos);
         }
 
-        final Collection<FooterData> footerDataList = new ArrayList<>(resolveInfos.size());
+        List<FooterData> footerDataList = new ArrayList<>(resolveInfos.size());
         for (ResolveInfo resolveInfo : resolveInfos) {
-            final ActivityInfo activityInfo = resolveInfo.activityInfo;
-            final ApplicationInfo appInfo = activityInfo.applicationInfo;
+            ActivityInfo activityInfo = resolveInfo.activityInfo;
+            ApplicationInfo appInfo = activityInfo.applicationInfo;
 
             // If a non-system app tries to inject footer, ignore it
             if ((appInfo.flags & ApplicationInfo.FLAG_SYSTEM) == 0) {
@@ -187,11 +145,7 @@
                                 + LocationManager.METADATA_SETTINGS_FOOTER_STRING);
                 continue;
             }
-            footerDataList.add(
-                    new FooterData(
-                            footerTextRes,
-                            appInfo,
-                            new ComponentName(activityInfo.packageName, activityInfo.name)));
+            footerDataList.add(new FooterData(footerTextRes, appInfo));
         }
         return footerDataList;
     }
@@ -207,14 +161,9 @@
         // Application info of receiver injecting this footer
         final ApplicationInfo applicationInfo;
 
-        // The component that injected the footer. It must be a receiver of broadcast
-        // LocationManager.SETTINGS_FOOTER_DISPLAYED_ACTION
-        final ComponentName componentName;
-
-        FooterData(int footerRes, ApplicationInfo appInfo, ComponentName componentName) {
+        FooterData(int footerRes, ApplicationInfo appInfo) {
             this.footerStringRes = footerRes;
             this.applicationInfo = appInfo;
-            this.componentName = componentName;
         }
     }
 }
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/location/LocationScanningPreferenceController.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/location/LocationScanningPreferenceController.java
index 48deffd..66b14cc 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/location/LocationScanningPreferenceController.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/location/LocationScanningPreferenceController.java
@@ -17,6 +17,7 @@
 package com.android.car.developeroptions.location;
 
 import android.content.Context;
+import android.net.wifi.WifiManager;
 import android.provider.Settings;
 
 import androidx.annotation.VisibleForTesting;
@@ -28,16 +29,17 @@
 public class LocationScanningPreferenceController extends BasePreferenceController {
     @VisibleForTesting static final String KEY_LOCATION_SCANNING = "location_scanning";
     private final Context mContext;
+    private final WifiManager mWifiManager;
 
     public LocationScanningPreferenceController(Context context) {
         super(context, KEY_LOCATION_SCANNING);
         mContext = context;
+        mWifiManager = context.getSystemService(WifiManager.class);
     }
 
     @Override
     public CharSequence getSummary() {
-        final boolean wifiScanOn = Settings.Global.getInt(mContext.getContentResolver(),
-                Settings.Global.WIFI_SCAN_ALWAYS_AVAILABLE, 0) == 1;
+        final boolean wifiScanOn = mWifiManager.isScanAlwaysAvailable();
         final boolean bleScanOn = Settings.Global.getInt(mContext.getContentResolver(),
                 Settings.Global.BLE_SCAN_ALWAYS_AVAILABLE, 0) == 1;
         int resId;
@@ -59,4 +61,4 @@
                 ? AVAILABLE
                 : UNSUPPORTED_ON_DEVICE;
     }
-}
\ No newline at end of file
+}
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/location/LocationSettings.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/location/LocationSettings.java
index 7b0377f..ce82261 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/location/LocationSettings.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/location/LocationSettings.java
@@ -29,11 +29,11 @@
 import com.android.car.developeroptions.SettingsActivity;
 import com.android.car.developeroptions.dashboard.DashboardFragment;
 import com.android.car.developeroptions.search.BaseSearchIndexProvider;
-import com.android.car.developeroptions.search.Indexable;
 import com.android.car.developeroptions.widget.SwitchBar;
 import com.android.settingslib.core.AbstractPreferenceController;
 import com.android.settingslib.core.lifecycle.Lifecycle;
 import com.android.settingslib.location.RecentLocationAccesses;
+import com.android.settingslib.search.Indexable;
 import com.android.settingslib.search.SearchIndexable;
 
 import java.util.ArrayList;
@@ -121,7 +121,7 @@
         controllers.add(new RecentLocationAccessPreferenceController(context));
         controllers.add(new LocationScanningPreferenceController(context));
         controllers.add(new LocationServicePreferenceController(context, fragment, lifecycle));
-        controllers.add(new LocationFooterPreferenceController(context, lifecycle));
+        controllers.add(new LocationFooterPreferenceController(context));
         return controllers;
     }
 
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/location/ScanningSettings.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/location/ScanningSettings.java
index 112cc06..8416d72 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/location/ScanningSettings.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/location/ScanningSettings.java
@@ -23,8 +23,8 @@
 import com.android.car.developeroptions.R;
 import com.android.car.developeroptions.dashboard.DashboardFragment;
 import com.android.car.developeroptions.search.BaseSearchIndexProvider;
-import com.android.car.developeroptions.search.Indexable;
 import com.android.settingslib.core.AbstractPreferenceController;
+import com.android.settingslib.search.Indexable;
 import com.android.settingslib.search.SearchIndexable;
 
 import java.util.ArrayList;
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/location/WifiScanningPreferenceController.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/location/WifiScanningPreferenceController.java
index 53df4e5..0f76eac 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/location/WifiScanningPreferenceController.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/location/WifiScanningPreferenceController.java
@@ -14,7 +14,7 @@
 package com.android.car.developeroptions.location;
 
 import android.content.Context;
-import android.provider.Settings;
+import android.net.wifi.WifiManager;
 
 import androidx.preference.Preference;
 import androidx.preference.SwitchPreference;
@@ -26,9 +26,11 @@
         implements PreferenceControllerMixin {
 
     private static final String KEY_WIFI_SCAN_ALWAYS_AVAILABLE = "wifi_always_scanning";
+    private final WifiManager mWifiManager;
 
     public WifiScanningPreferenceController(Context context) {
         super(context);
+        mWifiManager = context.getSystemService(WifiManager.class);
     }
 
     @Override
@@ -43,17 +45,13 @@
 
     @Override
     public void updateState(Preference preference) {
-        ((SwitchPreference) preference).setChecked(
-                Settings.Global.getInt(mContext.getContentResolver(),
-                        Settings.Global.WIFI_SCAN_ALWAYS_AVAILABLE, 0) == 1);
+        ((SwitchPreference) preference).setChecked(mWifiManager.isScanAlwaysAvailable());
     }
 
     @Override
     public boolean handlePreferenceTreeClick(Preference preference) {
         if (KEY_WIFI_SCAN_ALWAYS_AVAILABLE.equals(preference.getKey())) {
-            Settings.Global.putInt(mContext.getContentResolver(),
-                    Settings.Global.WIFI_SCAN_ALWAYS_AVAILABLE,
-                    ((SwitchPreference) preference).isChecked() ? 1 : 0);
+            mWifiManager.setScanAlwaysAvailable(((SwitchPreference) preference).isChecked());
             return true;
         }
         return false;
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/media/MediaOutputIndicatorSlice.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/media/MediaOutputIndicatorSlice.java
index 06cc9de..cd36723 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/media/MediaOutputIndicatorSlice.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/media/MediaOutputIndicatorSlice.java
@@ -31,10 +31,10 @@
 import androidx.slice.builders.ListBuilder;
 import androidx.slice.builders.SliceAction;
 
-import com.android.internal.util.CollectionUtils;
 import com.android.car.developeroptions.R;
 import com.android.car.developeroptions.Utils;
 import com.android.car.developeroptions.slices.CustomSliceable;
+import com.android.internal.util.CollectionUtils;
 import com.android.settingslib.bluetooth.A2dpProfile;
 import com.android.settingslib.bluetooth.HearingAidProfile;
 import com.android.settingslib.bluetooth.LocalBluetoothManager;
@@ -143,14 +143,14 @@
         // Return Hearing Aid device name if it is active
         BluetoothDevice activeDevice = findActiveHearingAidDevice();
         if (activeDevice != null) {
-            return activeDevice.getAliasName();
+            return activeDevice.getAlias();
         }
         // Return A2DP device name if it is active
         final A2dpProfile a2dpProfile = mProfileManager.getA2dpProfile();
         if (a2dpProfile != null) {
             activeDevice = a2dpProfile.getActiveDevice();
             if (activeDevice != null) {
-                return activeDevice.getAliasName();
+                return activeDevice.getAlias();
             }
         }
         // No active device, return default summary
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/network/AirplaneModePreferenceController.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/network/AirplaneModePreferenceController.java
index 17c1dcd..8bff680 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/network/AirplaneModePreferenceController.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/network/AirplaneModePreferenceController.java
@@ -20,19 +20,18 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageManager;
-import android.os.SystemProperties;
+import android.sysprop.TelephonyProperties;
 
 import androidx.fragment.app.Fragment;
 import androidx.preference.Preference;
 import androidx.preference.PreferenceScreen;
 import androidx.preference.SwitchPreference;
 
-import com.android.internal.telephony.TelephonyIntents;
-import com.android.internal.telephony.TelephonyProperties;
 import com.android.car.developeroptions.AirplaneModeEnabler;
 import com.android.car.developeroptions.R;
 import com.android.car.developeroptions.core.TogglePreferenceController;
 import com.android.car.developeroptions.overlay.FeatureFactory;
+import com.android.internal.telephony.TelephonyIntents;
 import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
 import com.android.settingslib.core.lifecycle.LifecycleObserver;
 import com.android.settingslib.core.lifecycle.events.OnPause;
@@ -63,8 +62,8 @@
 
     @Override
     public boolean handlePreferenceTreeClick(Preference preference) {
-        if (KEY_AIRPLANE_MODE.equals(preference.getKey()) && Boolean.parseBoolean(
-                SystemProperties.get(TelephonyProperties.PROPERTY_INECM_MODE))) {
+        if (KEY_AIRPLANE_MODE.equals(preference.getKey())
+                && TelephonyProperties.in_ecm_mode().orElse(false)) {
             // In ECM mode launch ECM app dialog
             if (mFragment != null) {
                 mFragment.startActivityForResult(
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/network/telephony/CellInfoUtil.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/network/telephony/CellInfoUtil.java
index 0be8ff6..5650d10 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/network/telephony/CellInfoUtil.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/network/telephony/CellInfoUtil.java
@@ -33,6 +33,7 @@
 
 import com.android.internal.telephony.OperatorInfo;
 
+import java.util.Collections;
 import java.util.List;
 
 /**
@@ -149,7 +150,8 @@
                 mcc,
                 mnc,
                 operatorInfo.getOperatorAlphaLong(),
-                operatorInfo.getOperatorAlphaShort());
+                operatorInfo.getOperatorAlphaShort(),
+                Collections.emptyList());
 
         CellInfoGsm ci = new CellInfoGsm();
         ci.setCellIdentity(cig);
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/network/telephony/DataServiceSetupPreferenceController.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/network/telephony/DataServiceSetupPreferenceController.java
index 9bd0480..847b35c 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/network/telephony/DataServiceSetupPreferenceController.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/network/telephony/DataServiceSetupPreferenceController.java
@@ -28,8 +28,6 @@
 
 import androidx.preference.Preference;
 
-import com.android.internal.telephony.PhoneConstants;
-
 /**
  * Preference controller for "Data service setup"
  */
@@ -49,14 +47,12 @@
 
     @Override
     public int getAvailabilityStatus(int subId) {
-        final boolean isLteOnCdma = mTelephonyManager.getLteOnCdmaMode()
-                == PhoneConstants.LTE_ON_CDMA_TRUE;
         final PersistableBundle carrierConfig = mCarrierConfigManager.getConfigForSubId(subId);
         return subId != SubscriptionManager.INVALID_SUBSCRIPTION_ID
                 && carrierConfig != null
                 && !carrierConfig.getBoolean(
                 CarrierConfigManager.KEY_HIDE_CARRIER_NETWORK_SETTINGS_BOOL)
-                && isLteOnCdma && !TextUtils.isEmpty(mSetupUrl)
+                && mTelephonyManager.isGlobalModeEnabled() && !TextUtils.isEmpty(mSetupUrl)
                 ? AVAILABLE
                 : CONDITIONALLY_UNAVAILABLE;
     }
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/network/telephony/EnabledNetworkModePreferenceController.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/network/telephony/EnabledNetworkModePreferenceController.java
index 66e282a..f99ae0f 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/network/telephony/EnabledNetworkModePreferenceController.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/network/telephony/EnabledNetworkModePreferenceController.java
@@ -28,9 +28,9 @@
 import androidx.preference.ListPreference;
 import androidx.preference.Preference;
 
+import com.android.car.developeroptions.R;
 import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.PhoneConstants;
-import com.android.car.developeroptions.R;
 
 /**
  * Preference controller for "Enabled network mode"
@@ -107,9 +107,7 @@
         final PersistableBundle carrierConfig = mCarrierConfigManager.getConfigForSubId(mSubId);
         mTelephonyManager = TelephonyManager.from(mContext).createForSubscriptionId(mSubId);
 
-        final boolean isLteOnCdma =
-                mTelephonyManager.getLteOnCdmaMode() == PhoneConstants.LTE_ON_CDMA_TRUE;
-        mIsGlobalCdma = isLteOnCdma
+        mIsGlobalCdma = mTelephonyManager.isGlobalModeEnabled()
                 && carrierConfig.getBoolean(CarrierConfigManager.KEY_SHOW_CDMA_CHOICES_BOOL);
         mShow4GForLTE = carrierConfig != null
                 ? carrierConfig.getBoolean(
@@ -131,13 +129,11 @@
                     mContext.getContentResolver(),
                     android.provider.Settings.Global.LTE_SERVICE_FORCED + mSubId,
                     0);
-            final boolean isLteOnCdma = mTelephonyManager.getLteOnCdmaMode()
-                    == PhoneConstants.LTE_ON_CDMA_TRUE;
             final int settingsNetworkMode = android.provider.Settings.Global.getInt(
                     mContext.getContentResolver(),
                     android.provider.Settings.Global.PREFERRED_NETWORK_MODE + mSubId,
                     Phone.PREFERRED_NT_MODE);
-            if (isLteOnCdma) {
+            if (mTelephonyManager.isGlobalModeEnabled()) {
                 if (lteForced == 0) {
                     preference.setEntries(
                             R.array.enabled_networks_cdma_choices);
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/network/telephony/MobileNetworkSettings.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/network/telephony/MobileNetworkSettings.java
index 184c607..69ee73f 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/network/telephony/MobileNetworkSettings.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/network/telephony/MobileNetworkSettings.java
@@ -32,7 +32,9 @@
 import android.view.MenuInflater;
 import android.view.MenuItem;
 
-import com.android.internal.telephony.TelephonyIntents;
+import androidx.annotation.VisibleForTesting;
+import androidx.preference.Preference;
+
 import com.android.car.developeroptions.R;
 import com.android.car.developeroptions.core.FeatureFlags;
 import com.android.car.developeroptions.dashboard.RestrictedDashboardFragment;
@@ -44,18 +46,16 @@
 import com.android.car.developeroptions.network.telephony.gsm.AutoSelectPreferenceController;
 import com.android.car.developeroptions.network.telephony.gsm.OpenNetworkSelectPagePreferenceController;
 import com.android.car.developeroptions.search.BaseSearchIndexProvider;
-import com.android.car.developeroptions.search.Indexable;
 import com.android.car.developeroptions.widget.PreferenceCategoryController;
+import com.android.internal.telephony.TelephonyIntents;
 import com.android.settingslib.core.AbstractPreferenceController;
+import com.android.settingslib.search.Indexable;
 import com.android.settingslib.search.SearchIndexable;
 
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
 
-import androidx.annotation.VisibleForTesting;
-import androidx.preference.Preference;
-
 @SearchIndexable(forTarget = SearchIndexable.ALL & ~SearchIndexable.ARC)
 public class MobileNetworkSettings extends RestrictedDashboardFragment {
 
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/network/telephony/NetworkScanHelper.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/network/telephony/NetworkScanHelper.java
index 31df45b..f560549 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/network/telephony/NetworkScanHelper.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/network/telephony/NetworkScanHelper.java
@@ -31,6 +31,7 @@
 import com.google.common.util.concurrent.FutureCallback;
 import com.google.common.util.concurrent.Futures;
 import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.MoreExecutors;
 import com.google.common.util.concurrent.SettableFuture;
 
 import java.lang.annotation.Retention;
@@ -180,7 +181,7 @@
                     int errCode = Integer.parseInt(t.getMessage());
                     onError(errCode);
                 }
-            });
+            }, MoreExecutors.directExecutor());
             mExecutor.execute(new NetworkScanSyncTask(
                     mTelephonyManager, (SettableFuture) mNetworkScanFuture));
         } else if (type == NETWORK_SCAN_TYPE_INCREMENTAL_RESULTS) {
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/network/telephony/PreferredNetworkModePreferenceController.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/network/telephony/PreferredNetworkModePreferenceController.java
index c81eb65..29e4f03 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/network/telephony/PreferredNetworkModePreferenceController.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/network/telephony/PreferredNetworkModePreferenceController.java
@@ -27,9 +27,9 @@
 import androidx.preference.ListPreference;
 import androidx.preference.Preference;
 
+import com.android.car.developeroptions.R;
 import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.PhoneConstants;
-import com.android.car.developeroptions.R;
 
 /**
  * Preference controller for "Preferred network mode"
@@ -103,9 +103,7 @@
         final PersistableBundle carrierConfig = mCarrierConfigManager.getConfigForSubId(mSubId);
         mTelephonyManager = TelephonyManager.from(mContext).createForSubscriptionId(mSubId);
 
-        final boolean isLteOnCdma =
-                mTelephonyManager.getLteOnCdmaMode() == PhoneConstants.LTE_ON_CDMA_TRUE;
-        mIsGlobalCdma = isLteOnCdma
+        mIsGlobalCdma = mTelephonyManager.isGlobalModeEnabled()
                 && carrierConfig.getBoolean(CarrierConfigManager.KEY_SHOW_CDMA_CHOICES_BOOL);
     }
 
@@ -132,12 +130,9 @@
             case TelephonyManager.NETWORK_MODE_GSM_UMTS:
                 return R.string.preferred_network_mode_gsm_wcdma_summary;
             case TelephonyManager.NETWORK_MODE_CDMA_EVDO:
-                switch (mTelephonyManager.getLteOnCdmaMode()) {
-                    case PhoneConstants.LTE_ON_CDMA_TRUE:
-                        return R.string.preferred_network_mode_cdma_summary;
-                    default:
-                        return R.string.preferred_network_mode_cdma_evdo_summary;
-                }
+                return mTelephonyManager.isGlobalModeEnabled()
+                        ? R.string.preferred_network_mode_cdma_summary
+                        : R.string.preferred_network_mode_cdma_evdo_summary;
             case TelephonyManager.NETWORK_MODE_CDMA_NO_EVDO:
                 return R.string.preferred_network_mode_cdma_only_summary;
             case TelephonyManager.NETWORK_MODE_EVDO_NO_CDMA:
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/notification/AppBubbleNotificationSettings.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/notification/AppBubbleNotificationSettings.java
index f6fbced..82bf610 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/notification/AppBubbleNotificationSettings.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/notification/AppBubbleNotificationSettings.java
@@ -23,8 +23,8 @@
 
 import com.android.car.developeroptions.R;
 import com.android.car.developeroptions.search.BaseSearchIndexProvider;
-import com.android.car.developeroptions.search.Indexable;
 import com.android.settingslib.core.AbstractPreferenceController;
+import com.android.settingslib.search.Indexable;
 import com.android.settingslib.search.SearchIndexable;
 
 import java.util.ArrayList;
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/notification/BubbleNotificationPreferenceController.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/notification/BubbleNotificationPreferenceController.java
index 5a26bf2..7e797c8 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/notification/BubbleNotificationPreferenceController.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/notification/BubbleNotificationPreferenceController.java
@@ -16,8 +16,7 @@
 
 package com.android.car.developeroptions.notification;
 
-import static android.provider.Settings.Secure.NOTIFICATION_BUBBLES;
-import static android.provider.Settings.Secure.NOTIFICATION_BUBBLES;
+import static android.provider.Settings.Global.NOTIFICATION_BUBBLES;
 
 import android.content.ContentResolver;
 import android.content.Context;
@@ -83,20 +82,20 @@
 
     @Override
     public boolean isChecked() {
-        return Settings.Secure.getInt(mContext.getContentResolver(),
+        return Settings.Global.getInt(mContext.getContentResolver(),
                 NOTIFICATION_BUBBLES, ON) == ON;
     }
 
     @Override
     public boolean setChecked(boolean isChecked) {
-        return Settings.Secure.putInt(mContext.getContentResolver(),
+        return Settings.Global.putInt(mContext.getContentResolver(),
                 NOTIFICATION_BUBBLES, isChecked ? ON : OFF);
     }
 
     class SettingObserver extends ContentObserver {
 
         private final Uri NOTIFICATION_BUBBLES_URI =
-                Settings.Secure.getUriFor(NOTIFICATION_BUBBLES);
+                Settings.Global.getUriFor(NOTIFICATION_BUBBLES);
 
         private final Preference mPreference;
 
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/notification/BubblePreferenceController.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/notification/BubblePreferenceController.java
index 82495c0..9a4cd85 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/notification/BubblePreferenceController.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/notification/BubblePreferenceController.java
@@ -16,7 +16,7 @@
 
 package com.android.car.developeroptions.notification;
 
-import static android.provider.Settings.Secure.NOTIFICATION_BUBBLES;
+import static android.provider.Settings.Global.NOTIFICATION_BUBBLES;
 
 import android.content.Context;
 import android.provider.Settings;
@@ -62,7 +62,7 @@
             return false;
         }
         if (mChannel != null) {
-            if (Settings.Secure.getInt(mContext.getContentResolver(),
+            if (Settings.Global.getInt(mContext.getContentResolver(),
                     NOTIFICATION_BUBBLES, SYSTEM_WIDE_ON) == SYSTEM_WIDE_OFF) {
                 return false;
             }
@@ -84,7 +84,7 @@
                 pref.setEnabled(isChannelConfigurable() && !pref.isDisabledByAdmin());
             } else {
                 pref.setChecked(mAppRow.allowBubbles
-                        && Settings.Secure.getInt(mContext.getContentResolver(),
+                        && Settings.Global.getInt(mContext.getContentResolver(),
                         NOTIFICATION_BUBBLES, SYSTEM_WIDE_ON) == SYSTEM_WIDE_ON);
                 pref.setSummary(mContext.getString(
                         R.string.bubbles_app_toggle_summary, mAppRow.label));
@@ -103,7 +103,7 @@
             RestrictedSwitchPreference pref = (RestrictedSwitchPreference) preference;
             // if the global setting is off, toggling app level permission requires extra
             // confirmation
-            if (Settings.Secure.getInt(mContext.getContentResolver(),
+            if (Settings.Global.getInt(mContext.getContentResolver(),
                     NOTIFICATION_BUBBLES, SYSTEM_WIDE_ON) == SYSTEM_WIDE_OFF
                     && !pref.isChecked()) {
                 new BubbleWarningDialogFragment()
@@ -125,7 +125,7 @@
         backend.setAllowBubbles(pkg, uid, false);
         // changing the global settings will cause the observer on the host page to reload
         // correct preference state
-        Settings.Secure.putInt(mContext.getContentResolver(),
+        Settings.Global.putInt(mContext.getContentResolver(),
                 NOTIFICATION_BUBBLES, SYSTEM_WIDE_OFF);
     }
 
@@ -135,7 +135,7 @@
         backend.setAllowBubbles(pkg, uid, true);
         // changing the global settings will cause the observer on the host page to reload
         // correct preference state
-        Settings.Secure.putInt(mContext.getContentResolver(),
+        Settings.Global.putInt(mContext.getContentResolver(),
                 NOTIFICATION_BUBBLES, SYSTEM_WIDE_ON);
     }
 }
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/notification/BubbleSummaryNotificationPreferenceController.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/notification/BubbleSummaryNotificationPreferenceController.java
index 78ab78a..1543c14 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/notification/BubbleSummaryNotificationPreferenceController.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/notification/BubbleSummaryNotificationPreferenceController.java
@@ -16,7 +16,7 @@
 
 package com.android.car.developeroptions.notification;
 
-import static android.provider.Settings.Secure.NOTIFICATION_BUBBLES;
+import static android.provider.Settings.Global.NOTIFICATION_BUBBLES;
 
 import android.content.Context;
 import android.provider.Settings;
@@ -47,7 +47,7 @@
     }
 
     private boolean areBubblesEnabled() {
-        return Settings.Secure.getInt(mContext.getContentResolver(),
+        return Settings.Global.getInt(mContext.getContentResolver(),
                 NOTIFICATION_BUBBLES, ON) == ON;
     }
 }
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/notification/BubbleSummaryPreferenceController.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/notification/BubbleSummaryPreferenceController.java
index ac24781..a02b9ab 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/notification/BubbleSummaryPreferenceController.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/notification/BubbleSummaryPreferenceController.java
@@ -16,7 +16,7 @@
 
 package com.android.car.developeroptions.notification;
 
-import static android.provider.Settings.Secure.NOTIFICATION_BUBBLES;
+import static android.provider.Settings.Global.NOTIFICATION_BUBBLES;
 
 import android.app.settings.SettingsEnums;
 import android.content.Context;
@@ -53,7 +53,7 @@
             return false;
         }
         if (mChannel != null) {
-            if (Settings.Secure.getInt(mContext.getContentResolver(),
+            if (Settings.Global.getInt(mContext.getContentResolver(),
                     NOTIFICATION_BUBBLES, SYSTEM_WIDE_ON) == SYSTEM_WIDE_OFF) {
                 return false;
             }
@@ -92,7 +92,7 @@
                 canBubble |= mChannel.canBubble();
             } else {
                canBubble |= mAppRow.allowBubbles
-                       && (Settings.Secure.getInt(mContext.getContentResolver(),
+                       && (Settings.Global.getInt(mContext.getContentResolver(),
                        NOTIFICATION_BUBBLES, SYSTEM_WIDE_ON) == SYSTEM_WIDE_ON);
             }
         }
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/notification/GlobalBubblePermissionObserverMixin.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/notification/GlobalBubblePermissionObserverMixin.java
index 08d5a51..96ced17 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/notification/GlobalBubblePermissionObserverMixin.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/notification/GlobalBubblePermissionObserverMixin.java
@@ -47,8 +47,8 @@
 
     public void onStart() {
         mContext.getContentResolver().registerContentObserver(
-                Settings.Secure.getUriFor(
-                        Settings.Secure.NOTIFICATION_BUBBLES),
+                Settings.Global.getUriFor(
+                        Settings.Global.NOTIFICATION_BUBBLES),
                 false /* notifyForDescendants */,
                 this /* observer */);
     }
@@ -56,4 +56,4 @@
     public void onStop() {
         mContext.getContentResolver().unregisterContentObserver(this /* observer */);
     }
-}
\ No newline at end of file
+}
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/notification/NotificationAccessSettings.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/notification/NotificationAccessSettings.java
index 18dd856..b6c57c3 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/notification/NotificationAccessSettings.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/notification/NotificationAccessSettings.java
@@ -37,8 +37,8 @@
 import com.android.car.developeroptions.core.instrumentation.InstrumentedDialogFragment;
 import com.android.car.developeroptions.overlay.FeatureFactory;
 import com.android.car.developeroptions.search.BaseSearchIndexProvider;
-import com.android.car.developeroptions.search.Indexable;
 import com.android.car.developeroptions.utils.ManagedServiceSettings;
+import com.android.settingslib.search.Indexable;
 import com.android.settingslib.search.SearchIndexable;
 
 import java.util.ArrayList;
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/notification/NotificationAssistantPicker.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/notification/NotificationAssistantPicker.java
index 5b037e6..3696a03 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/notification/NotificationAssistantPicker.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/notification/NotificationAssistantPicker.java
@@ -23,20 +23,18 @@
 import android.content.pm.PackageManager;
 import android.content.pm.ServiceInfo;
 import android.graphics.drawable.Drawable;
-import android.os.RemoteException;
 import android.provider.SearchIndexableResource;
 import android.provider.Settings;
 import android.service.notification.NotificationAssistantService;
 import android.text.TextUtils;
-import android.util.Log;
 
-import com.android.internal.annotations.VisibleForTesting;
 import com.android.car.developeroptions.R;
 import com.android.car.developeroptions.applications.defaultapps.DefaultAppPickerFragment;
 import com.android.car.developeroptions.search.BaseSearchIndexProvider;
-import com.android.car.developeroptions.search.Indexable;
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.settingslib.applications.DefaultAppInfo;
 import com.android.settingslib.applications.ServiceListing;
+import com.android.settingslib.search.Indexable;
 import com.android.settingslib.widget.CandidateInfo;
 
 import java.util.ArrayList;
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/notification/NotificationBackend.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/notification/NotificationBackend.java
index c459efc..0cc9f63 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/notification/NotificationBackend.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/notification/NotificationBackend.java
@@ -145,7 +145,7 @@
         try {
             if (onlyHasDefaultChannel(pkg, uid)) {
                 NotificationChannel defaultChannel =
-                        getChannel(pkg, uid, NotificationChannel.DEFAULT_CHANNEL_ID);
+                        getChannel(pkg, uid, NotificationChannel.DEFAULT_CHANNEL_ID, null);
                 defaultChannel.setImportance(enabled ? IMPORTANCE_UNSPECIFIED : IMPORTANCE_NONE);
                 updateChannel(pkg, uid, defaultChannel);
             }
@@ -195,13 +195,26 @@
         }
     }
 
-
     public NotificationChannel getChannel(String pkg, int uid, String channelId) {
         if (channelId == null) {
             return null;
         }
         try {
-            return sINM.getNotificationChannelForPackage(pkg, uid, channelId, true);
+            return sINM.getNotificationChannelForPackage(pkg, uid, channelId, null, true);
+        } catch (Exception e) {
+            Log.w(TAG, "Error calling NoMan", e);
+            return null;
+        }
+    }
+
+
+    public NotificationChannel getChannel(String pkg, int uid, String channelId,
+            String conversationId) {
+        if (channelId == null) {
+            return null;
+        }
+        try {
+            return sINM.getNotificationChannelForPackage(pkg, uid, channelId, conversationId, true);
         } catch (Exception e) {
             Log.w(TAG, "Error calling NoMan", e);
             return null;
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/notification/NotificationStation.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/notification/NotificationStation.java
index 03b0159..e9a8081 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/notification/NotificationStation.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/notification/NotificationStation.java
@@ -271,7 +271,7 @@
             StatusBarNotification[] active = mNoMan.getActiveNotifications(
                     mContext.getPackageName());
             StatusBarNotification[] dismissed = mNoMan.getHistoricalNotifications(
-                    mContext.getPackageName(), 50);
+                    mContext.getPackageName(), 50, true);
 
             List<HistoricalNotificationInfo> list
                     = new ArrayList<HistoricalNotificationInfo>(active.length + dismissed.length);
@@ -368,7 +368,7 @@
         }
         try {
             NotificationChannel channel = mNoMan.getNotificationChannelForPackage(
-                    sbn.getPackageName(), sbn.getUid(), n.getChannelId(), false);
+                    sbn.getPackageName(), sbn.getUid(), n.getChannelId(), null, false);
             sb.append("\n")
                     .append(bold(getString(R.string.notification_log_details_sound)))
                     .append(delim);
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/notification/ZenAccessSettings.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/notification/ZenAccessSettings.java
index a9e36ab..c01b073 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/notification/ZenAccessSettings.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/notification/ZenAccessSettings.java
@@ -37,8 +37,8 @@
 import com.android.car.developeroptions.applications.specialaccess.zenaccess.ZenAccessDetails;
 import com.android.car.developeroptions.applications.specialaccess.zenaccess.ZenAccessSettingObserverMixin;
 import com.android.car.developeroptions.search.BaseSearchIndexProvider;
-import com.android.car.developeroptions.search.Indexable;
 import com.android.car.developeroptions.widget.EmptyTextSettings;
+import com.android.settingslib.search.Indexable;
 import com.android.settingslib.search.SearchIndexable;
 import com.android.settingslib.widget.apppreference.AppPreference;
 
@@ -86,11 +86,7 @@
     @Override
     public void onResume() {
         super.onResume();
-        if (!ActivityManager.isLowRamDeviceStatic()) {
-            reloadList();
-        } else {
-            setEmptyText(R.string.disabled_low_ram_device);
-        }
+        reloadList();
     }
 
     @Override
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/notification/ZenCustomRuleBlockedEffectsSettings.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/notification/ZenCustomRuleBlockedEffectsSettings.java
index b856ecd..505b3ca 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/notification/ZenCustomRuleBlockedEffectsSettings.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/notification/ZenCustomRuleBlockedEffectsSettings.java
@@ -32,8 +32,6 @@
     @Override
     public void onCreate(Bundle bundle) {
         super.onCreate(bundle);
-        mFooterPreferenceMixin.createFooterPreference().setTitle(
-                R.string.zen_mode_blocked_effects_footer);
     }
 
     @Override
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/notification/ZenModeAutomationSettings.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/notification/ZenModeAutomationSettings.java
index 58008aa..f2fc762 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/notification/ZenModeAutomationSettings.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/notification/ZenModeAutomationSettings.java
@@ -33,11 +33,11 @@
 
 import com.android.car.developeroptions.R;
 import com.android.car.developeroptions.search.BaseSearchIndexProvider;
-import com.android.car.developeroptions.search.Indexable;
 import com.android.car.developeroptions.utils.ManagedServiceSettings;
 import com.android.car.developeroptions.utils.ZenServiceListing;
 import com.android.settingslib.core.AbstractPreferenceController;
 import com.android.settingslib.core.lifecycle.Lifecycle;
+import com.android.settingslib.search.Indexable;
 import com.android.settingslib.search.SearchIndexable;
 
 import java.util.ArrayList;
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/notification/ZenModeBlockedEffectsSettings.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/notification/ZenModeBlockedEffectsSettings.java
index 3148d1b..988e500 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/notification/ZenModeBlockedEffectsSettings.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/notification/ZenModeBlockedEffectsSettings.java
@@ -31,9 +31,9 @@
 
 import com.android.car.developeroptions.R;
 import com.android.car.developeroptions.search.BaseSearchIndexProvider;
-import com.android.car.developeroptions.search.Indexable;
 import com.android.settingslib.core.AbstractPreferenceController;
 import com.android.settingslib.core.lifecycle.Lifecycle;
+import com.android.settingslib.search.Indexable;
 import com.android.settingslib.search.SearchIndexable;
 
 import java.util.ArrayList;
@@ -45,8 +45,6 @@
     @Override
     public void onCreate(Bundle icicle) {
         super.onCreate(icicle);
-        mFooterPreferenceMixin.createFooterPreference().setTitle(
-                R.string.zen_mode_blocked_effects_footer);
     }
 
     @Override
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/notification/ZenModeBypassingAppsSettings.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/notification/ZenModeBypassingAppsSettings.java
index 92add24..ca932ca 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/notification/ZenModeBypassingAppsSettings.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/notification/ZenModeBypassingAppsSettings.java
@@ -26,8 +26,8 @@
 
 import com.android.car.developeroptions.R;
 import com.android.car.developeroptions.search.BaseSearchIndexProvider;
-import com.android.car.developeroptions.search.Indexable;
 import com.android.settingslib.core.AbstractPreferenceController;
+import com.android.settingslib.search.Indexable;
 import com.android.settingslib.search.SearchIndexable;
 
 import java.util.ArrayList;
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/notification/ZenModeCallsSettings.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/notification/ZenModeCallsSettings.java
index c8889ba..7a13860 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/notification/ZenModeCallsSettings.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/notification/ZenModeCallsSettings.java
@@ -24,9 +24,9 @@
 
 import com.android.car.developeroptions.R;
 import com.android.car.developeroptions.search.BaseSearchIndexProvider;
-import com.android.car.developeroptions.search.Indexable;
 import com.android.settingslib.core.AbstractPreferenceController;
 import com.android.settingslib.core.lifecycle.Lifecycle;
+import com.android.settingslib.search.Indexable;
 import com.android.settingslib.search.SearchIndexable;
 
 import java.util.ArrayList;
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/notification/ZenModeMessagesSettings.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/notification/ZenModeMessagesSettings.java
index ef004ef..b45f225 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/notification/ZenModeMessagesSettings.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/notification/ZenModeMessagesSettings.java
@@ -24,9 +24,9 @@
 
 import com.android.car.developeroptions.R;
 import com.android.car.developeroptions.search.BaseSearchIndexProvider;
-import com.android.car.developeroptions.search.Indexable;
 import com.android.settingslib.core.AbstractPreferenceController;
 import com.android.settingslib.core.lifecycle.Lifecycle;
+import com.android.settingslib.search.Indexable;
 import com.android.settingslib.search.SearchIndexable;
 
 import java.util.ArrayList;
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/notification/ZenModeRestrictNotificationsSettings.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/notification/ZenModeRestrictNotificationsSettings.java
index b27f7c4..08f4953 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/notification/ZenModeRestrictNotificationsSettings.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/notification/ZenModeRestrictNotificationsSettings.java
@@ -23,9 +23,9 @@
 
 import com.android.car.developeroptions.R;
 import com.android.car.developeroptions.search.BaseSearchIndexProvider;
-import com.android.car.developeroptions.search.Indexable;
 import com.android.settingslib.core.AbstractPreferenceController;
 import com.android.settingslib.core.lifecycle.Lifecycle;
+import com.android.settingslib.search.Indexable;
 import com.android.settingslib.search.SearchIndexable;
 import com.android.settingslib.widget.FooterPreference;
 
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/notification/ZenModeSettings.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/notification/ZenModeSettings.java
index d5b78aa..8105e23 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/notification/ZenModeSettings.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/notification/ZenModeSettings.java
@@ -39,9 +39,9 @@
 
 import com.android.car.developeroptions.R;
 import com.android.car.developeroptions.search.BaseSearchIndexProvider;
-import com.android.car.developeroptions.search.Indexable;
 import com.android.settingslib.core.AbstractPreferenceController;
 import com.android.settingslib.core.lifecycle.Lifecycle;
+import com.android.settingslib.search.Indexable;
 import com.android.settingslib.search.SearchIndexable;
 
 import java.util.ArrayList;
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/notification/ZenModeSoundVibrationSettings.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/notification/ZenModeSoundVibrationSettings.java
index 6e3d2dc..b238cc7 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/notification/ZenModeSoundVibrationSettings.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/notification/ZenModeSoundVibrationSettings.java
@@ -22,9 +22,9 @@
 
 import com.android.car.developeroptions.R;
 import com.android.car.developeroptions.search.BaseSearchIndexProvider;
-import com.android.car.developeroptions.search.Indexable;
 import com.android.settingslib.core.AbstractPreferenceController;
 import com.android.settingslib.core.lifecycle.Lifecycle;
+import com.android.settingslib.search.Indexable;
 import com.android.settingslib.search.SearchIndexable;
 
 import java.util.ArrayList;
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/password/BiometricFragment.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/password/BiometricFragment.java
index a6c43bf..9720013 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/password/BiometricFragment.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/password/BiometricFragment.java
@@ -16,23 +16,17 @@
 
 package com.android.car.developeroptions.password;
 
-import android.app.Activity;
 import android.app.settings.SettingsEnums;
 import android.content.DialogInterface;
 import android.hardware.biometrics.BiometricConstants;
 import android.hardware.biometrics.BiometricPrompt;
 import android.hardware.biometrics.BiometricPrompt.AuthenticationCallback;
 import android.hardware.biometrics.BiometricPrompt.AuthenticationResult;
-import android.hardware.biometrics.IBiometricConfirmDeviceCredentialCallback;
 import android.os.Bundle;
 import android.os.CancellationSignal;
-import android.os.Handler;
-import android.os.Looper;
-import android.util.Log;
 
 import androidx.annotation.NonNull;
 
-import com.android.car.developeroptions.R;
 import com.android.car.developeroptions.core.InstrumentedFragment;
 
 import java.util.concurrent.Executor;
@@ -85,20 +79,6 @@
         }
     };
 
-    // TODO(b/123378871): Remove when moved.
-    private final IBiometricConfirmDeviceCredentialCallback mCancelCallback
-        = new IBiometricConfirmDeviceCredentialCallback.Stub() {
-        @Override
-        public void cancel() {
-            final Activity activity = getActivity();
-            if (activity != null) {
-                activity.finish();
-            } else {
-                Log.e(TAG, "Activity null!");
-            }
-        }
-    };
-
     /**
      * @param bundle Bundle passed from {@link BiometricPrompt.Builder#buildIntent()}
      * @return
@@ -140,20 +120,17 @@
         mBiometricPrompt = new BiometricPrompt.Builder(getContext())
             .setTitle(mBundle.getString(BiometricPrompt.KEY_TITLE))
             .setUseDefaultTitle() // use default title if title is null/empty
-            .setFromConfirmDeviceCredential()
+            .setDeviceCredentialAllowed(true)
             .setSubtitle(mBundle.getString(BiometricPrompt.KEY_SUBTITLE))
             .setDescription(mBundle.getString(BiometricPrompt.KEY_DESCRIPTION))
             .setConfirmationRequired(
                     mBundle.getBoolean(BiometricPrompt.KEY_REQUIRE_CONFIRMATION, true))
-            .setNegativeButton(getResources().getString(
-                    R.string.confirm_device_credential_use_alternate_method),
-                    mClientExecutor, mNegativeButtonListener)
             .build();
         mCancellationSignal = new CancellationSignal();
 
         // TODO: CC doesn't use crypto for now
         mBiometricPrompt.authenticateUser(mCancellationSignal, mClientExecutor,
-                mAuthenticationCallback, mUserId, mCancelCallback);
+                mAuthenticationCallback, mUserId);
     }
 
     @Override
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/password/ChooseLockGeneric.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/password/ChooseLockGeneric.java
index 8e44e0b..8e3379b 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/password/ChooseLockGeneric.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/password/ChooseLockGeneric.java
@@ -59,7 +59,6 @@
 import androidx.preference.Preference;
 import androidx.preference.PreferenceScreen;
 
-import com.android.internal.widget.LockPatternUtils;
 import com.android.car.developeroptions.EncryptionInterstitial;
 import com.android.car.developeroptions.EventLogTags;
 import com.android.car.developeroptions.R;
@@ -70,13 +69,12 @@
 import com.android.car.developeroptions.biometrics.fingerprint.FingerprintEnrollFindSensor;
 import com.android.car.developeroptions.core.instrumentation.InstrumentedDialogFragment;
 import com.android.car.developeroptions.search.SearchFeatureProvider;
+import com.android.internal.widget.LockPatternUtils;
+import com.android.internal.widget.LockscreenCredential;
 import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
 import com.android.settingslib.RestrictedLockUtilsInternal;
 import com.android.settingslib.RestrictedPreference;
-import com.android.settingslib.widget.FooterPreference;
-import com.android.settingslib.widget.FooterPreferenceMixinCompat;
 
-import java.util.Arrays;
 import java.util.List;
 
 public class ChooseLockGeneric extends SettingsActivity {
@@ -112,6 +110,7 @@
         public static final String MINIMUM_QUALITY_KEY = "minimum_quality";
         public static final String HIDE_DISABLED_PREFS = "hide_disabled_prefs";
         public static final String TAG_FRP_WARNING_DIALOG = "frp_warning_dialog";
+        public static final String KEY_LOCK_SETTINGS_FOOTER = "lock_settings_footer";
 
         /**
          * Boolean extra determining whether a "screen lock options" button should be shown. This
@@ -152,7 +151,7 @@
         private boolean mPasswordConfirmed = false;
         private boolean mWaitingForConfirmation = false;
         private boolean mForChangeCredRequiredForBoot = false;
-        private byte[] mUserPassword;
+        private LockscreenCredential mUserPassword;
         private LockPatternUtils mLockPatternUtils;
         private FingerprintManager mFingerprintManager;
         private FaceManager mFaceManager;
@@ -202,7 +201,7 @@
                 .getBooleanExtra(CONFIRM_CREDENTIALS, true);
             if (getActivity() instanceof ChooseLockGeneric.InternalActivity) {
                 mPasswordConfirmed = !confirmCredentials;
-                mUserPassword = getActivity().getIntent().getByteArrayExtra(
+                mUserPassword = getActivity().getIntent().getParcelableExtra(
                         ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD);
             }
 
@@ -226,7 +225,7 @@
                 mPasswordConfirmed = savedInstanceState.getBoolean(PASSWORD_CONFIRMED);
                 mWaitingForConfirmation = savedInstanceState.getBoolean(WAITING_FOR_CONFIRMATION);
                 if (mUserPassword == null) {
-                    mUserPassword = savedInstanceState.getByteArray(
+                    mUserPassword = savedInstanceState.getParcelable(
                             ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD);
                 }
             }
@@ -385,11 +384,11 @@
             if (requestCode == CONFIRM_EXISTING_REQUEST && resultCode == Activity.RESULT_OK) {
                 mPasswordConfirmed = true;
                 mUserPassword = data != null
-                    ? data.getByteArrayExtra(ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD)
+                    ? data.getParcelableExtra(ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD)
                     : null;
                 updatePreferencesOrFinish(false /* isRecreatingActivity */);
                 if (mForChangeCredRequiredForBoot) {
-                    if (!(mUserPassword == null || mUserPassword.length == 0)) {
+                    if (mUserPassword != null && !mUserPassword.isNone()) {
                         maybeEnableEncryption(
                                 mLockPatternUtils.getKeyguardStoredPasswordQuality(mUserId), false);
                     } else {
@@ -449,7 +448,7 @@
             outState.putBoolean(PASSWORD_CONFIRMED, mPasswordConfirmed);
             outState.putBoolean(WAITING_FOR_CONFIRMATION, mWaitingForConfirmation);
             if (mUserPassword != null) {
-                outState.putByteArray(ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD, mUserPassword);
+                outState.putParcelable(ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD, mUserPassword);
             }
         }
 
@@ -487,12 +486,12 @@
 
         protected void addPreferences() {
             addPreferencesFromResource(R.xml.security_settings_picker);
-
+            final Preference footer = findPreference(KEY_LOCK_SETTINGS_FOOTER);
             if (!TextUtils.isEmpty(mCallerAppName)) {
-                FooterPreferenceMixinCompat footerMixin =
-                        new FooterPreferenceMixinCompat(this, getSettingsLifecycle());
-                FooterPreference footer = footerMixin.createFooterPreference();
+                footer.setVisible(true);
                 footer.setTitle(getFooterString());
+            } else {
+                footer.setVisible(false);
             }
 
             // Used for testing purposes
@@ -671,7 +670,7 @@
             setPreferenceSummary(ScreenLockType.MANAGED, R.string.secure_lock_encryption_warning);
         }
 
-        protected Intent getLockManagedPasswordIntent(byte[] password) {
+        protected Intent getLockManagedPasswordIntent(LockscreenCredential password) {
             return mManagedPasswordProvider.createIntent(false, password);
         }
 
@@ -770,7 +769,8 @@
             }
 
             if (quality == DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED) {
-                mChooseLockSettingsHelper.utils().clearLock(mUserPassword, mUserId);
+                mChooseLockSettingsHelper.utils().setLockCredential(
+                        LockscreenCredential.createNone(), mUserPassword, mUserId);
                 mChooseLockSettingsHelper.utils().setLockScreenDisabled(disabled, mUserId);
                 getActivity().setResult(Activity.RESULT_OK);
                 removeAllBiometricsForUserAndFinish(mUserId);
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/password/ChooseLockPassword.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/password/ChooseLockPassword.java
index d567a40..6bc66fb 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/password/ChooseLockPassword.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/password/ChooseLockPassword.java
@@ -17,13 +17,20 @@
 package com.android.car.developeroptions.password;
 
 import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_NONE;
-import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC;
-import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC;
-import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_COMPLEX;
 import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC;
-import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX;
 
 import static com.android.car.developeroptions.password.ChooseLockSettingsHelper.EXTRA_KEY_REQUESTED_MIN_COMPLEXITY;
+import static com.android.internal.widget.PasswordValidationError.CONTAINS_INVALID_CHARACTERS;
+import static com.android.internal.widget.PasswordValidationError.CONTAINS_SEQUENCE;
+import static com.android.internal.widget.PasswordValidationError.NOT_ENOUGH_DIGITS;
+import static com.android.internal.widget.PasswordValidationError.NOT_ENOUGH_LETTERS;
+import static com.android.internal.widget.PasswordValidationError.NOT_ENOUGH_LOWER_CASE;
+import static com.android.internal.widget.PasswordValidationError.NOT_ENOUGH_NON_DIGITS;
+import static com.android.internal.widget.PasswordValidationError.NOT_ENOUGH_NON_LETTER;
+import static com.android.internal.widget.PasswordValidationError.NOT_ENOUGH_SYMBOLS;
+import static com.android.internal.widget.PasswordValidationError.NOT_ENOUGH_UPPER_CASE;
+import static com.android.internal.widget.PasswordValidationError.TOO_LONG;
+import static com.android.internal.widget.PasswordValidationError.TOO_SHORT;
 
 import android.app.Activity;
 import android.app.admin.DevicePolicyManager;
@@ -60,10 +67,6 @@
 import androidx.recyclerview.widget.LinearLayoutManager;
 import androidx.recyclerview.widget.RecyclerView;
 
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.widget.LockPatternUtils;
-import com.android.internal.widget.LockPatternUtils.RequestThrottledException;
-import com.android.internal.widget.TextViewInputDisabler;
 import com.android.car.developeroptions.EncryptionInterstitial;
 import com.android.car.developeroptions.R;
 import com.android.car.developeroptions.SettingsActivity;
@@ -72,13 +75,18 @@
 import com.android.car.developeroptions.core.InstrumentedFragment;
 import com.android.car.developeroptions.notification.RedactionInterstitial;
 import com.android.car.developeroptions.widget.ImeAwareEditText;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.widget.LockPatternUtils;
+import com.android.internal.widget.LockPatternUtils.RequestThrottledException;
+import com.android.internal.widget.LockscreenCredential;
+import com.android.internal.widget.PasswordValidationError;
+import com.android.internal.widget.TextViewInputDisabler;
 
 import com.google.android.setupcompat.template.FooterBarMixin;
 import com.google.android.setupcompat.template.FooterButton;
 import com.google.android.setupdesign.GlifLayout;
 
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.List;
 
 public class ChooseLockPassword extends SettingsActivity {
@@ -124,7 +132,7 @@
             return this;
         }
 
-        public IntentBuilder setPassword(byte[] password) {
+        public IntentBuilder setPassword(LockscreenCredential password) {
             mIntent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD, password);
             return this;
         }
@@ -181,28 +189,24 @@
 
     public static class ChooseLockPasswordFragment extends InstrumentedFragment
             implements OnEditorActionListener, TextWatcher, SaveAndFinishWorker.Listener {
-        private static final String KEY_FIRST_PIN = "first_pin";
+        private static final String KEY_FIRST_PASSWORD = "first_password";
         private static final String KEY_UI_STAGE = "ui_stage";
-        private static final String KEY_CURRENT_PASSWORD = "current_password";
+        private static final String KEY_CURRENT_CREDENTIAL = "current_credential";
         private static final String FRAGMENT_TAG_SAVE_AND_FINISH = "save_and_finish_worker";
 
-        private byte[] mCurrentPassword;
-        private byte[] mChosenPassword;
+        private LockscreenCredential mCurrentCredential;
+        private LockscreenCredential mChosenPassword;
         private boolean mHasChallenge;
         private long mChallenge;
         private ImeAwareEditText mPasswordEntry;
         private TextViewInputDisabler mPasswordEntryInputDisabler;
-        private int mPasswordMinLength = LockPatternUtils.MIN_LOCK_PASSWORD_SIZE;
-        private int mPasswordMaxLength = 16;
-        private int mPasswordMinLetters = 0;
-        private int mPasswordMinUpperCase = 0;
-        private int mPasswordMinLowerCase = 0;
-        private int mPasswordMinSymbols = 0;
-        private int mPasswordMinNumeric = 0;
-        private int mPasswordMinNonLetter = 0;
-        private int mPasswordMinLengthToFulfillAllPolicies = 0;
-        private boolean mPasswordNumSequenceAllowed = true;
-        @PasswordComplexity private int mRequestedMinComplexity = PASSWORD_COMPLEXITY_NONE;
+
+        // Minimum password metrics enforced by admins.
+        private PasswordMetrics mMinMetrics;
+        private List<PasswordValidationError> mValidationErrors;
+        private boolean mPasswordReused;
+
+        @PasswordComplexity private int mMinComplexity = PASSWORD_COMPLEXITY_NONE;
         protected int mUserId;
         private byte[] mPasswordHistoryHashFactor;
 
@@ -216,7 +220,7 @@
         protected boolean mForFingerprint;
         protected boolean mForFace;
 
-        private byte[] mFirstPin;
+        private LockscreenCredential mFirstPassword;
         private RecyclerView mPasswordRestrictionView;
         protected boolean mIsAlphaMode;
         protected FooterButton mSkipOrClearButton;
@@ -228,28 +232,6 @@
         private static final int CONFIRM_EXISTING_REQUEST = 58;
         static final int RESULT_FINISHED = RESULT_FIRST_USER;
 
-        private static final int MIN_LETTER_IN_PASSWORD = 0;
-        private static final int MIN_UPPER_LETTERS_IN_PASSWORD = 1;
-        private static final int MIN_LOWER_LETTERS_IN_PASSWORD = 2;
-        private static final int MIN_SYMBOLS_IN_PASSWORD = 3;
-        private static final int MIN_NUMBER_IN_PASSWORD = 4;
-        private static final int MIN_NON_LETTER_IN_PASSWORD = 5;
-
-        // Error code returned from {@link #validatePassword(byte[])}.
-        static final int NO_ERROR = 0;
-        static final int CONTAIN_INVALID_CHARACTERS = 1 << 0;
-        static final int TOO_SHORT = 1 << 1;
-        static final int TOO_LONG = 1 << 2;
-        static final int CONTAIN_NON_DIGITS = 1 << 3;
-        static final int CONTAIN_SEQUENTIAL_DIGITS = 1 << 4;
-        static final int RECENTLY_USED = 1 << 5;
-        static final int NOT_ENOUGH_LETTER = 1 << 6;
-        static final int NOT_ENOUGH_UPPER_CASE = 1 << 7;
-        static final int NOT_ENOUGH_LOWER_CASE = 1 << 8;
-        static final int NOT_ENOUGH_DIGITS = 1 << 9;
-        static final int NOT_ENOUGH_SYMBOLS = 1 << 10;
-        static final int NOT_ENOUGH_NON_LETTER = 1 << 11;
-
         /**
          * Keep track internally of where the user is in choosing a pattern.
          */
@@ -381,13 +363,12 @@
             mForFingerprint = intent.getBooleanExtra(
                     ChooseLockSettingsHelper.EXTRA_KEY_FOR_FINGERPRINT, false);
             mForFace = intent.getBooleanExtra(ChooseLockSettingsHelper.EXTRA_KEY_FOR_FACE, false);
-            mRequestedMinComplexity = intent.getIntExtra(
+            mMinComplexity = intent.getIntExtra(
                     EXTRA_KEY_REQUESTED_MIN_COMPLEXITY, PASSWORD_COMPLEXITY_NONE);
-            mRequestedQuality = Math.max(
-                    intent.getIntExtra(LockPatternUtils.PASSWORD_TYPE_KEY, mRequestedQuality),
-                    mLockPatternUtils.getRequestedPasswordQuality(mUserId));
+            mRequestedQuality = intent.getIntExtra(
+                    LockPatternUtils.PASSWORD_TYPE_KEY, PASSWORD_QUALITY_NUMERIC);
 
-            loadDpmPasswordRequirements();
+            mMinMetrics = mLockPatternUtils.getRequestedPasswordMetrics(mUserId);
             mChooseLockSettingsHelper = new ChooseLockSettingsHelper(getActivity());
 
             if (intent.getBooleanExtra(
@@ -395,13 +376,13 @@
                 SaveAndFinishWorker w = new SaveAndFinishWorker();
                 final boolean required = getActivity().getIntent().getBooleanExtra(
                         EncryptionInterstitial.EXTRA_REQUIRE_PASSWORD, true);
-                byte[] currentBytes = intent.getByteArrayExtra(
+                LockscreenCredential currentCredential = intent.getParcelableExtra(
                         ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD);
 
                 w.setBlocking(true);
                 w.setListener(this);
                 w.start(mChooseLockSettingsHelper.utils(), required, false, 0,
-                        currentBytes, currentBytes, mRequestedQuality, mUserId);
+                        currentCredential, currentCredential, mUserId);
             }
             mTextChangedHandler = new TextChangedHandler();
         }
@@ -476,7 +457,7 @@
             Intent intent = getActivity().getIntent();
             final boolean confirmCredentials = intent.getBooleanExtra(
                     ChooseLockGeneric.CONFIRM_CREDENTIALS, true);
-            mCurrentPassword = intent.getByteArrayExtra(
+            mCurrentCredential = intent.getParcelableExtra(
                     ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD);
             mHasChallenge = intent.getBooleanExtra(
                     ChooseLockSettingsHelper.EXTRA_KEY_HAS_CHALLENGE, false);
@@ -491,15 +472,15 @@
             } else {
 
                 // restore from previous state
-                mFirstPin = savedInstanceState.getByteArray(KEY_FIRST_PIN);
+                mFirstPassword = savedInstanceState.getParcelable(KEY_FIRST_PASSWORD);
                 final String state = savedInstanceState.getString(KEY_UI_STAGE);
                 if (state != null) {
                     mUiStage = Stage.valueOf(state);
                     updateStage(mUiStage);
                 }
 
-                if (mCurrentPassword == null) {
-                    mCurrentPassword = savedInstanceState.getByteArray(KEY_CURRENT_PASSWORD);
+                if (mCurrentCredential == null) {
+                    mCurrentCredential = savedInstanceState.getParcelable(KEY_CURRENT_CREDENTIAL);
                 }
 
                 // Re-attach to the exiting worker if there is one.
@@ -557,8 +538,8 @@
         public void onSaveInstanceState(Bundle outState) {
             super.onSaveInstanceState(outState);
             outState.putString(KEY_UI_STAGE, mUiStage.name());
-            outState.putByteArray(KEY_FIRST_PIN, mFirstPin);
-            outState.putByteArray(KEY_CURRENT_PASSWORD, mCurrentPassword);
+            outState.putParcelable(KEY_FIRST_PASSWORD, mFirstPassword);
+            outState.putParcelable(KEY_CURRENT_CREDENTIAL, mCurrentCredential);
         }
 
         @Override
@@ -571,7 +552,7 @@
                         getActivity().setResult(RESULT_FINISHED);
                         getActivity().finish();
                     } else {
-                        mCurrentPassword = data.getByteArrayExtra(
+                        mCurrentCredential = data.getParcelableExtra(
                                 ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD);
                     }
                     break;
@@ -595,210 +576,27 @@
         }
 
         /**
-         * Read the requirements from {@link DevicePolicyManager} and intent and aggregate them.
-         */
-        private void loadDpmPasswordRequirements() {
-            final int dpmPasswordQuality = mLockPatternUtils.getRequestedPasswordQuality(mUserId);
-            if (dpmPasswordQuality == PASSWORD_QUALITY_NUMERIC_COMPLEX) {
-                mPasswordNumSequenceAllowed = false;
-            }
-            mPasswordMinLength = Math.max(LockPatternUtils.MIN_LOCK_PASSWORD_SIZE,
-                    mLockPatternUtils.getRequestedMinimumPasswordLength(mUserId));
-            mPasswordMaxLength = mLockPatternUtils.getMaximumPasswordLength(mRequestedQuality);
-            mPasswordMinLetters = mLockPatternUtils.getRequestedPasswordMinimumLetters(mUserId);
-            mPasswordMinUpperCase = mLockPatternUtils.getRequestedPasswordMinimumUpperCase(mUserId);
-            mPasswordMinLowerCase = mLockPatternUtils.getRequestedPasswordMinimumLowerCase(mUserId);
-            mPasswordMinNumeric = mLockPatternUtils.getRequestedPasswordMinimumNumeric(mUserId);
-            mPasswordMinSymbols = mLockPatternUtils.getRequestedPasswordMinimumSymbols(mUserId);
-            mPasswordMinNonLetter = mLockPatternUtils.getRequestedPasswordMinimumNonLetter(mUserId);
-
-            // Modify the value based on dpm policy
-            switch (dpmPasswordQuality) {
-                case PASSWORD_QUALITY_ALPHABETIC:
-                    if (mPasswordMinLetters == 0) {
-                        mPasswordMinLetters = 1;
-                    }
-                    break;
-                case PASSWORD_QUALITY_ALPHANUMERIC:
-                    if (mPasswordMinLetters == 0) {
-                        mPasswordMinLetters = 1;
-                    }
-                    if (mPasswordMinNumeric == 0) {
-                        mPasswordMinNumeric = 1;
-                    }
-                    break;
-                case PASSWORD_QUALITY_COMPLEX:
-                    // Reserve all the requirements.
-                    break;
-                default:
-                    mPasswordMinNumeric = 0;
-                    mPasswordMinLetters = 0;
-                    mPasswordMinUpperCase = 0;
-                    mPasswordMinLowerCase = 0;
-                    mPasswordMinSymbols = 0;
-                    mPasswordMinNonLetter = 0;
-            }
-
-            mPasswordMinLengthToFulfillAllPolicies = getMinLengthToFulfillAllPolicies();
-        }
-
-        /**
-         * Merges the dpm requirements and the min complexity requirements.
+         * Validates PIN/Password and returns the validation result and updates mValidationErrors
+         * and mPasswordReused to reflect validation results.
          *
-         * <p>Since there are more than one set of metrics to meet the min complexity requirement,
-         * and we are not hard-coding any one of them to be the requirements the user must fulfil,
-         * we are taking what the user has already entered into account when compiling the list of
-         * requirements from min complexity. Then we merge this list with the DPM requirements, and
-         * present the merged set as validation results to the user on the UI.
-         *
-         * <p>For example, suppose min complexity requires either ALPHABETIC(8+), or
-         * ALPHANUMERIC(6+). If the user has entered "a", the length requirement displayed on the UI
-         * would be 8. Then the user appends "1" to make it "a1". We now know the user is entering
-         * an alphanumeric password so we would update the min complexity required min length to 6.
-         * This might result in a little confusion for the user but the UI does not support showing
-         * multiple sets of requirements / validation results as options to users, this is the best
-         * we can do now.
-         */
-        private void mergeMinComplexityAndDpmRequirements(int userEnteredPasswordQuality) {
-            if (mRequestedMinComplexity == PASSWORD_COMPLEXITY_NONE) {
-                // dpm requirements are dominant if min complexity is none
-                return;
-            }
-
-            // reset dpm requirements
-            loadDpmPasswordRequirements();
-
-            PasswordMetrics minMetrics = PasswordMetrics.getMinimumMetrics(
-                    mRequestedMinComplexity, userEnteredPasswordQuality, mRequestedQuality,
-                    requiresNumeric(), requiresLettersOrSymbols());
-            mPasswordNumSequenceAllowed = mPasswordNumSequenceAllowed
-                    && minMetrics.quality != PASSWORD_QUALITY_NUMERIC_COMPLEX;
-            mPasswordMinLength = Math.max(mPasswordMinLength, minMetrics.length);
-            mPasswordMinLetters = Math.max(mPasswordMinLetters, minMetrics.letters);
-            mPasswordMinUpperCase = Math.max(mPasswordMinUpperCase, minMetrics.upperCase);
-            mPasswordMinLowerCase = Math.max(mPasswordMinLowerCase, minMetrics.lowerCase);
-            mPasswordMinNumeric = Math.max(mPasswordMinNumeric, minMetrics.numeric);
-            mPasswordMinSymbols = Math.max(mPasswordMinSymbols, minMetrics.symbols);
-            mPasswordMinNonLetter = Math.max(mPasswordMinNonLetter, minMetrics.nonLetter);
-
-            if (minMetrics.quality == PASSWORD_QUALITY_ALPHABETIC) {
-                if (!requiresLettersOrSymbols()) {
-                    mPasswordMinLetters = 1;
-                }
-            }
-            if (minMetrics.quality == PASSWORD_QUALITY_ALPHANUMERIC) {
-                if (!requiresLettersOrSymbols()) {
-                    mPasswordMinLetters = 1;
-                }
-                if (!requiresNumeric()) {
-                    mPasswordMinNumeric = 1;
-                }
-            }
-
-            mPasswordMinLengthToFulfillAllPolicies = getMinLengthToFulfillAllPolicies();
-        }
-
-        private boolean requiresLettersOrSymbols() {
-            // This is the condition for the password to be considered ALPHABETIC according to
-            // PasswordMetrics.computeForPassword()
-            return mPasswordMinLetters + mPasswordMinUpperCase
-                    + mPasswordMinLowerCase + mPasswordMinSymbols + mPasswordMinNonLetter > 0;
-        }
-
-        private boolean requiresNumeric() {
-            return mPasswordMinNumeric > 0;
-        }
-
-        /**
-         * Validates PIN/Password and returns the validation result.
-         *
-         * @param password the raw password the user typed in
-         * @return the validation result.
+         * @param credential credential the user typed in.
+         * @return whether password satisfies all the requirements.
          */
         @VisibleForTesting
-        int validatePassword(byte[] password) {
-            int errorCode = NO_ERROR;
-            final PasswordMetrics metrics = PasswordMetrics.computeForPassword(password);
-            mergeMinComplexityAndDpmRequirements(metrics.quality);
-
-            if (password == null || password.length < mPasswordMinLength) {
-                if (mPasswordMinLength > mPasswordMinLengthToFulfillAllPolicies) {
-                    errorCode |= TOO_SHORT;
-                }
-            } else if (password.length > mPasswordMaxLength) {
-                errorCode |= TOO_LONG;
+        boolean validatePassword(LockscreenCredential credential) {
+            final byte[] password = credential.getCredential();
+            mValidationErrors = PasswordMetrics.validatePassword(
+                    mMinMetrics, mMinComplexity, !mIsAlphaMode, password);
+            if (mValidationErrors.isEmpty()) {
+                mPasswordReused = mLockPatternUtils.checkPasswordHistory(
+                        password, getPasswordHistoryHashFactor(), mUserId);
             } else {
-                // The length requirements are fulfilled.
-                if (!mPasswordNumSequenceAllowed
-                        && !requiresLettersOrSymbols()
-                        && metrics.numeric == password.length) {
-                    // Check for repeated characters or sequences (e.g. '1234', '0000', '2468')
-                    // if DevicePolicyManager or min password complexity requires a complex numeric
-                    // password. There can be two cases in the UI: 1. User chooses to enroll a
-                    // PIN, 2. User chooses to enroll a password but enters a numeric-only pin. We
-                    // should carry out the sequence check in both cases.
-                    //
-                    // Conditions for the !requiresLettersOrSymbols() to be necessary:
-                    // - DPM requires NUMERIC_COMPLEX
-                    // - min complexity not NONE, user picks PASSWORD type so ALPHABETIC or
-                    // ALPHANUMERIC is required
-                    // Imagine user has entered "12345678", if we don't skip the sequence check, the
-                    // validation result would show both "requires a letter" and "sequence not
-                    // allowed", while the only requirement the user needs to know is "requires a
-                    // letter" because once the user has fulfilled the alphabetic requirement, the
-                    // password would not be containing only digits so this check would not be
-                    // performed anyway.
-                    final int sequence = PasswordMetrics.maxLengthSequence(password);
-                    if (sequence > PasswordMetrics.MAX_ALLOWED_SEQUENCE) {
-                        errorCode |= CONTAIN_SEQUENTIAL_DIGITS;
-                    }
-                }
-                // Is the password recently used?
-                if (mLockPatternUtils.checkPasswordHistory(password, getPasswordHistoryHashFactor(),
-                        mUserId)) {
-                    errorCode |= RECENTLY_USED;
-                }
+                mPasswordReused = false;
             }
-
-            // Allow non-control Latin-1 characters only.
-            for (int i = 0; i < password.length; i++) {
-                char c = (char) password[i];
-                if (c < 32 || c > 127) {
-                    errorCode |= CONTAIN_INVALID_CHARACTERS;
-                    break;
-                }
-            }
-
-            // Ensure no non-digits if we are requesting numbers. This shouldn't be possible unless
-            // user finds some way to bring up soft keyboard.
-            if (mRequestedQuality == PASSWORD_QUALITY_NUMERIC
-                    || mRequestedQuality == PASSWORD_QUALITY_NUMERIC_COMPLEX) {
-                if (metrics.letters > 0 || metrics.symbols > 0) {
-                    errorCode |= CONTAIN_NON_DIGITS;
-                }
-            }
-
-            if (metrics.letters < mPasswordMinLetters) {
-                errorCode |= NOT_ENOUGH_LETTER;
-            }
-            if (metrics.upperCase < mPasswordMinUpperCase) {
-                errorCode |= NOT_ENOUGH_UPPER_CASE;
-            }
-            if (metrics.lowerCase < mPasswordMinLowerCase) {
-                errorCode |= NOT_ENOUGH_LOWER_CASE;
-            }
-            if (metrics.symbols < mPasswordMinSymbols) {
-                errorCode |= NOT_ENOUGH_SYMBOLS;
-            }
-            if (metrics.numeric < mPasswordMinNumeric) {
-                errorCode |= NOT_ENOUGH_DIGITS;
-            }
-            if (metrics.nonLetter < mPasswordMinNonLetter) {
-                errorCode |= NOT_ENOUGH_NON_LETTER;
-            }
-            return errorCode;
+            return mValidationErrors.isEmpty() && !mPasswordReused;
         }
 
+
         /**
          * Lazily compute and return the history hash factor of the current user (mUserId), used for
          * password history check.
@@ -806,7 +604,8 @@
         private byte[] getPasswordHistoryHashFactor() {
             if (mPasswordHistoryHashFactor == null) {
                 mPasswordHistoryHashFactor = mLockPatternUtils.getPasswordHistoryHashFactor(
-                        mCurrentPassword, mUserId);
+                        mCurrentCredential != null ? mCurrentCredential
+                                : LockscreenCredential.createNone(), mUserId);
             }
             return mPasswordHistoryHashFactor;
         }
@@ -814,20 +613,22 @@
         public void handleNext() {
             if (mSaveAndFinishWorker != null) return;
             // TODO(b/120484642): This is a point of entry for passwords from the UI
-            mChosenPassword = LockPatternUtils.charSequenceToByteArray(mPasswordEntry.getText());
-            if (mChosenPassword == null || mChosenPassword.length == 0) {
+            final Editable passwordText = mPasswordEntry.getText();
+            if (TextUtils.isEmpty(passwordText)) {
                 return;
             }
+            mChosenPassword = mIsAlphaMode ? LockscreenCredential.createPassword(passwordText)
+                    : LockscreenCredential.createPin(passwordText);
             if (mUiStage == Stage.Introduction) {
-                if (validatePassword(mChosenPassword) == NO_ERROR) {
-                    mFirstPin = mChosenPassword;
+                if (validatePassword(mChosenPassword)) {
+                    mFirstPassword = mChosenPassword;
                     mPasswordEntry.setText("");
                     updateStage(Stage.NeedToConfirm);
                 } else {
-                    Arrays.fill(mChosenPassword, (byte) 0);
+                    mChosenPassword.zeroize();
                 }
             } else if (mUiStage == Stage.NeedToConfirm) {
-                if (Arrays.equals(mFirstPin, mChosenPassword)) {
+                if (mChosenPassword.equals(mFirstPassword)) {
                     startSaveAndFinish();
                 } else {
                     CharSequence tmp = mPasswordEntry.getText();
@@ -835,7 +636,7 @@
                         Selection.setSelection((Spannable) tmp, 0, tmp.length());
                     }
                     updateStage(Stage.ConfirmWrong);
-                    Arrays.fill(mChosenPassword, (byte) 0);
+                    mChosenPassword.zeroize();
                 }
             }
         }
@@ -868,79 +669,79 @@
         }
 
         /**
-         * @param errorCode error code returned from {@link #validatePassword(String)}.
+         * @param errorCode error code returned from password validation.
          * @return an array of messages describing the error, important messages come first.
          */
-        String[] convertErrorCodeToMessages(int errorCode) {
+        String[] convertErrorCodeToMessages() {
             List<String> messages = new ArrayList<>();
-            if ((errorCode & CONTAIN_INVALID_CHARACTERS) > 0) {
-                messages.add(getString(R.string.lockpassword_illegal_character));
+            for (PasswordValidationError error : mValidationErrors) {
+                switch (error.errorCode) {
+                    case CONTAINS_INVALID_CHARACTERS:
+                        messages.add(getString(R.string.lockpassword_illegal_character));
+                        break;
+                    case NOT_ENOUGH_UPPER_CASE:
+                        messages.add(getResources().getQuantityString(
+                                R.plurals.lockpassword_password_requires_uppercase,
+                                error.requirement, error.requirement));
+                        break;
+                    case NOT_ENOUGH_LOWER_CASE:
+                        messages.add(getResources().getQuantityString(
+                                R.plurals.lockpassword_password_requires_lowercase,
+                                error.requirement, error.requirement));
+                        break;
+                    case NOT_ENOUGH_LETTERS:
+                        messages.add(getResources().getQuantityString(
+                                R.plurals.lockpassword_password_requires_letters,
+                                error.requirement, error.requirement));
+                        break;
+                    case NOT_ENOUGH_DIGITS:
+                        messages.add(getResources().getQuantityString(
+                                R.plurals.lockpassword_password_requires_numeric,
+                                error.requirement, error.requirement));
+                        break;
+                    case NOT_ENOUGH_SYMBOLS:
+                        messages.add(getResources().getQuantityString(
+                                R.plurals.lockpassword_password_requires_symbols,
+                                error.requirement, error.requirement));
+                        break;
+                    case NOT_ENOUGH_NON_LETTER:
+                        messages.add(getResources().getQuantityString(
+                                R.plurals.lockpassword_password_requires_nonletter,
+                                error.requirement, error.requirement));
+                        break;
+                    case NOT_ENOUGH_NON_DIGITS:
+                        messages.add(getResources().getQuantityString(
+                                R.plurals.lockpassword_password_requires_nonnumerical,
+                                error.requirement, error.requirement));
+                        break;
+                    case TOO_SHORT:
+                        messages.add(getResources().getQuantityString(
+                                mIsAlphaMode
+                                        ? R.plurals.lockpassword_password_too_short
+                                        : R.plurals.lockpassword_pin_too_short,
+                                error.requirement, error.requirement));
+                        break;
+                    case TOO_LONG:
+                        messages.add(getResources().getQuantityString(
+                                mIsAlphaMode
+                                        ? R.plurals.lockpassword_password_too_long
+                                        : R.plurals.lockpassword_pin_too_long,
+                                error.requirement + 1, error.requirement + 1));
+                        break;
+                    case CONTAINS_SEQUENCE:
+                        messages.add(getString(R.string.lockpassword_pin_no_sequential_digits));
+                        break;
+                    default:
+                        Log.wtf(TAG, "unknown error validating password: " + error);
+                }
             }
-            if ((errorCode & CONTAIN_NON_DIGITS) > 0) {
-                messages.add(getString(R.string.lockpassword_pin_contains_non_digits));
-            }
-            if ((errorCode & NOT_ENOUGH_UPPER_CASE) > 0) {
-                messages.add(getResources().getQuantityString(
-                        R.plurals.lockpassword_password_requires_uppercase, mPasswordMinUpperCase,
-                        mPasswordMinUpperCase));
-            }
-            if ((errorCode & NOT_ENOUGH_LOWER_CASE) > 0) {
-                messages.add(getResources().getQuantityString(
-                        R.plurals.lockpassword_password_requires_lowercase, mPasswordMinLowerCase,
-                        mPasswordMinLowerCase));
-            }
-            if ((errorCode & NOT_ENOUGH_LETTER) > 0) {
-                messages.add(getResources().getQuantityString(
-                        R.plurals.lockpassword_password_requires_letters, mPasswordMinLetters,
-                        mPasswordMinLetters));
-            }
-            if ((errorCode & NOT_ENOUGH_DIGITS) > 0) {
-                messages.add(getResources().getQuantityString(
-                        R.plurals.lockpassword_password_requires_numeric, mPasswordMinNumeric,
-                        mPasswordMinNumeric));
-            }
-            if ((errorCode & NOT_ENOUGH_SYMBOLS) > 0) {
-                messages.add(getResources().getQuantityString(
-                        R.plurals.lockpassword_password_requires_symbols, mPasswordMinSymbols,
-                        mPasswordMinSymbols));
-            }
-            if ((errorCode & NOT_ENOUGH_NON_LETTER) > 0) {
-                messages.add(getResources().getQuantityString(
-                        R.plurals.lockpassword_password_requires_nonletter, mPasswordMinNonLetter,
-                        mPasswordMinNonLetter));
-            }
-            if ((errorCode & TOO_SHORT) > 0) {
-                messages.add(getResources().getQuantityString(
-                        mIsAlphaMode
-                                ? R.plurals.lockpassword_password_too_short
-                                : R.plurals.lockpassword_pin_too_short,
-                        mPasswordMinLength,
-                        mPasswordMinLength));
-            }
-            if ((errorCode & TOO_LONG) > 0) {
-                messages.add(getResources().getQuantityString(
-                        mIsAlphaMode
-                                ? R.plurals.lockpassword_password_too_long
-                                : R.plurals.lockpassword_pin_too_long,
-                        mPasswordMaxLength + 1,
-                        mPasswordMaxLength + 1));
-            }
-            if ((errorCode & CONTAIN_SEQUENTIAL_DIGITS) > 0) {
-                messages.add(getString(R.string.lockpassword_pin_no_sequential_digits));
-            }
-            if ((errorCode & RECENTLY_USED) > 0) {
+
+            if (mPasswordReused) {
                 messages.add(getString((mIsAlphaMode) ? R.string.lockpassword_password_recently_used
                         : R.string.lockpassword_pin_recently_used));
             }
-            return messages.toArray(new String[0]);
-        }
 
-        private int getMinLengthToFulfillAllPolicies() {
-            final int minLengthForLetters = Math.max(mPasswordMinLetters,
-                    mPasswordMinUpperCase + mPasswordMinLowerCase);
-            final int minLengthForNonLetters = Math.max(mPasswordMinNonLetter,
-                    mPasswordMinSymbols + mPasswordMinNumeric);
-            return minLengthForLetters + minLengthForNonLetters;
+            return messages.toArray(new String[0]);
         }
 
         /**
@@ -948,21 +749,24 @@
          */
         protected void updateUi() {
             final boolean canInput = mSaveAndFinishWorker == null;
-            byte[] password = LockPatternUtils.charSequenceToByteArray(mPasswordEntry.getText());
-            final int length = password.length;
+
+            LockscreenCredential password = mIsAlphaMode
+                    ? LockscreenCredential.createPasswordOrNone(mPasswordEntry.getText())
+                    : LockscreenCredential.createPinOrNone(mPasswordEntry.getText());
+            final int length = password.size();
             if (mUiStage == Stage.Introduction) {
                 mPasswordRestrictionView.setVisibility(View.VISIBLE);
-                final int errorCode = validatePassword(password);
-                String[] messages = convertErrorCodeToMessages(errorCode);
+                final boolean passwordCompliant = validatePassword(password);
+                String[] messages = convertErrorCodeToMessages();
                 // Update the fulfillment of requirements.
                 mPasswordRequirementAdapter.setRequirements(messages);
                 // Enable/Disable the next button accordingly.
-                setNextEnabled(errorCode == NO_ERROR);
+                setNextEnabled(passwordCompliant);
             } else {
                 // Hide password requirement view when we are just asking user to confirm the pw.
                 mPasswordRestrictionView.setVisibility(View.GONE);
                 setHeaderText(getString(mUiStage.getHint(mIsAlphaMode, getStageType())));
-                setNextEnabled(canInput && length >= mPasswordMinLength);
+                setNextEnabled(canInput && length >= LockPatternUtils.MIN_LOCK_PASSWORD_SIZE);
                 mSkipOrClearButton.setVisibility(toVisibility(canInput && length > 0));
             }
             int message = mUiStage.getMessage(mIsAlphaMode, getStageType());
@@ -975,7 +779,7 @@
 
             setNextText(mUiStage.buttonText);
             mPasswordEntryInputDisabler.setInputEnabled(canInput);
-            Arrays.fill(password, (byte) 0);
+            password.zeroize();
         }
 
         protected int toVisibility(boolean visibleOrGone) {
@@ -1027,7 +831,7 @@
             final boolean required = getActivity().getIntent().getBooleanExtra(
                     EncryptionInterstitial.EXTRA_REQUIRE_PASSWORD, true);
             mSaveAndFinishWorker.start(mLockPatternUtils, required, mHasChallenge, mChallenge,
-                    mChosenPassword, mCurrentPassword, mRequestedQuality, mUserId);
+                    mChosenPassword, mCurrentCredential, mUserId);
         }
 
         @Override
@@ -1035,13 +839,13 @@
             getActivity().setResult(RESULT_FINISHED, resultData);
 
             if (mChosenPassword != null) {
-                Arrays.fill(mChosenPassword, (byte) 0);
+                mChosenPassword.zeroize();
             }
-            if (mCurrentPassword != null) {
-                Arrays.fill(mCurrentPassword, (byte) 0);
+            if (mCurrentCredential != null) {
+                mCurrentCredential.zeroize();
             }
-            if (mFirstPin != null) {
-                Arrays.fill(mFirstPin, (byte) 0);
+            if (mFirstPassword != null) {
+                mFirstPassword.zeroize();
             }
 
             mPasswordEntry.setText("");
@@ -1082,18 +886,18 @@
 
     public static class SaveAndFinishWorker extends SaveChosenLockWorkerBase {
 
-        private byte[] mChosenPassword;
-        private byte[] mCurrentPassword;
-        private int mRequestedQuality;
+        private LockscreenCredential mChosenPassword;
+        private LockscreenCredential mCurrentCredential;
 
         public void start(LockPatternUtils utils, boolean required,
                 boolean hasChallenge, long challenge,
-                byte[] chosenPassword, byte[] currentPassword, int requestedQuality, int userId) {
+                LockscreenCredential chosenPassword, LockscreenCredential currentCredential,
+                int userId) {
             prepare(utils, required, hasChallenge, challenge, userId);
 
             mChosenPassword = chosenPassword;
-            mCurrentPassword = currentPassword;
-            mRequestedQuality = requestedQuality;
+            mCurrentCredential = currentCredential != null ? currentCredential
+                    : LockscreenCredential.createNone();
             mUserId = userId;
 
             start();
@@ -1101,13 +905,13 @@
 
         @Override
         protected Pair<Boolean, Intent> saveAndVerifyInBackground() {
-            final boolean success = mUtils.saveLockPassword(
-                    mChosenPassword, mCurrentPassword, mRequestedQuality, mUserId);
+            final boolean success = mUtils.setLockCredential(
+                    mChosenPassword, mCurrentCredential, mUserId);
             Intent result = null;
             if (success && mHasChallenge) {
                 byte[] token;
                 try {
-                    token = mUtils.verifyPassword(mChosenPassword, mChallenge, mUserId);
+                    token = mUtils.verifyCredential(mChosenPassword, mChallenge, mUserId);
                 } catch (RequestThrottledException e) {
                     token = null;
                 }
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/password/ChooseLockPattern.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/password/ChooseLockPattern.java
index 66a4803..7bc4bcc 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/password/ChooseLockPattern.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/password/ChooseLockPattern.java
@@ -36,12 +36,6 @@
 
 import androidx.fragment.app.Fragment;
 
-import com.android.internal.widget.LinearLayoutWithDefaultTouchRecepient;
-import com.android.internal.widget.LockPatternUtils;
-import com.android.internal.widget.LockPatternUtils.RequestThrottledException;
-import com.android.internal.widget.LockPatternView;
-import com.android.internal.widget.LockPatternView.Cell;
-import com.android.internal.widget.LockPatternView.DisplayMode;
 import com.android.car.developeroptions.EncryptionInterstitial;
 import com.android.car.developeroptions.R;
 import com.android.car.developeroptions.SettingsActivity;
@@ -49,14 +43,20 @@
 import com.android.car.developeroptions.Utils;
 import com.android.car.developeroptions.core.InstrumentedFragment;
 import com.android.car.developeroptions.notification.RedactionInterstitial;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.widget.LinearLayoutWithDefaultTouchRecepient;
+import com.android.internal.widget.LockPatternUtils;
+import com.android.internal.widget.LockPatternUtils.RequestThrottledException;
+import com.android.internal.widget.LockPatternView;
+import com.android.internal.widget.LockPatternView.Cell;
+import com.android.internal.widget.LockPatternView.DisplayMode;
+import com.android.internal.widget.LockscreenCredential;
 
 import com.google.android.collect.Lists;
 import com.google.android.setupcompat.template.FooterBarMixin;
 import com.google.android.setupcompat.template.FooterButton;
 import com.google.android.setupdesign.GlifLayout;
 
-import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
 
@@ -116,7 +116,7 @@
             return this;
         }
 
-        public IntentBuilder setPattern(byte[] pattern) {
+        public IntentBuilder setPattern(LockscreenCredential pattern) {
             mIntent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD, pattern);
             return this;
         }
@@ -189,7 +189,7 @@
 
         private static final String FRAGMENT_TAG_SAVE_AND_FINISH = "save_and_finish_worker";
 
-        private byte[] mCurrentPattern;
+        private LockscreenCredential mCurrentCredential;
         private boolean mHasChallenge;
         private long mChallenge;
         protected TextView mTitleText;
@@ -199,7 +199,7 @@
         protected TextView mFooterText;
         protected FooterButton mSkipOrClearButton;
         private FooterButton mNextButton;
-        protected List<LockPatternView.Cell> mChosenPattern = null;
+        @VisibleForTesting protected LockscreenCredential mChosenPattern;
         private ColorStateList mDefaultHeaderColorList;
 
         // ScrollView that contains title and header, only exist in land mode
@@ -226,7 +226,7 @@
                         getActivity().setResult(RESULT_FINISHED);
                         getActivity().finish();
                     } else {
-                        mCurrentPattern = data.getByteArrayExtra(
+                        mCurrentCredential = data.getParcelableExtra(
                                 ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD);
                     }
 
@@ -263,16 +263,19 @@
                     if (mUiStage == Stage.NeedToConfirm || mUiStage == Stage.ConfirmWrong) {
                         if (mChosenPattern == null) throw new IllegalStateException(
                                 "null chosen pattern in stage 'need to confirm");
-                        if (mChosenPattern.equals(pattern)) {
-                            updateStage(Stage.ChoiceConfirmed);
-                        } else {
-                            updateStage(Stage.ConfirmWrong);
+                        try (LockscreenCredential confirmPattern =
+                                LockscreenCredential.createPattern(pattern)) {
+                            if (mChosenPattern.equals(confirmPattern)) {
+                                updateStage(Stage.ChoiceConfirmed);
+                            } else {
+                                updateStage(Stage.ConfirmWrong);
+                            }
                         }
                     } else if (mUiStage == Stage.Introduction || mUiStage == Stage.ChoiceTooShort){
                         if (pattern.size() < LockPatternUtils.MIN_LOCK_PATTERN_SIZE) {
                             updateStage(Stage.ChoiceTooShort);
                         } else {
-                            mChosenPattern = new ArrayList<LockPatternView.Cell>(pattern);
+                            mChosenPattern = LockscreenCredential.createPattern(pattern);
                             updateStage(Stage.FirstChoiceValid);
                         }
                     } else {
@@ -459,12 +462,12 @@
                 SaveAndFinishWorker w = new SaveAndFinishWorker();
                 final boolean required = getActivity().getIntent().getBooleanExtra(
                         EncryptionInterstitial.EXTRA_REQUIRE_PASSWORD, true);
-                byte[] current = intent.getByteArrayExtra(
+                LockscreenCredential current = intent.getParcelableExtra(
                         ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD);
                 w.setBlocking(true);
                 w.setListener(this);
                 w.start(mChooseLockSettingsHelper.utils(), required,
-                        false, 0, LockPatternUtils.byteArrayToPattern(current), current, mUserId);
+                        false, 0, current, current, mUserId);
             }
             mForFingerprint = intent.getBooleanExtra(
                     ChooseLockSettingsHelper.EXTRA_KEY_FOR_FINGERPRINT, false);
@@ -542,8 +545,8 @@
             final boolean confirmCredentials = getActivity().getIntent()
                     .getBooleanExtra(ChooseLockGeneric.CONFIRM_CREDENTIALS, true);
             Intent intent = getActivity().getIntent();
-            mCurrentPattern =
-                    intent.getByteArrayExtra(ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD);
+            mCurrentCredential =
+                    intent.getParcelableExtra(ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD);
             mHasChallenge = intent.getBooleanExtra(
                     ChooseLockSettingsHelper.EXTRA_KEY_HAS_CHALLENGE, false);
             mChallenge = intent.getLongExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE, 0);
@@ -566,13 +569,10 @@
                 }
             } else {
                 // restore from previous state
-                final byte[] pattern = savedInstanceState.getByteArray(KEY_PATTERN_CHOICE);
-                if (pattern != null) {
-                    mChosenPattern = LockPatternUtils.byteArrayToPattern(pattern);
-                }
+                mChosenPattern = savedInstanceState.getParcelable(KEY_PATTERN_CHOICE);
 
-                if (mCurrentPattern == null) {
-                    mCurrentPattern = savedInstanceState.getByteArray(KEY_CURRENT_PATTERN);
+                if (mCurrentCredential == null) {
+                    mCurrentCredential = savedInstanceState.getParcelable(KEY_CURRENT_PATTERN);
                 }
                 updateStage(Stage.values()[savedInstanceState.getInt(KEY_UI_STAGE)]);
 
@@ -607,6 +607,7 @@
 
         public void handleLeftButton() {
             if (mUiStage.leftMode == LeftButtonMode.Retry) {
+                mChosenPattern.zeroize();
                 mChosenPattern = null;
                 mLockPatternView.clearPattern();
                 updateStage(Stage.Introduction);
@@ -668,12 +669,11 @@
 
             outState.putInt(KEY_UI_STAGE, mUiStage.ordinal());
             if (mChosenPattern != null) {
-                outState.putByteArray(KEY_PATTERN_CHOICE,
-                        LockPatternUtils.patternToByteArray(mChosenPattern));
+                outState.putParcelable(KEY_PATTERN_CHOICE, mChosenPattern);
             }
 
-            if (mCurrentPattern != null) {
-                outState.putByteArray(KEY_CURRENT_PATTERN, mCurrentPattern);
+            if (mCurrentCredential != null) {
+                outState.putParcelable(KEY_CURRENT_PATTERN, mCurrentCredential);
             }
         }
 
@@ -813,15 +813,15 @@
             final boolean required = getActivity().getIntent().getBooleanExtra(
                     EncryptionInterstitial.EXTRA_REQUIRE_PASSWORD, true);
             mSaveAndFinishWorker.start(mChooseLockSettingsHelper.utils(), required,
-                    mHasChallenge, mChallenge, mChosenPattern, mCurrentPattern, mUserId);
+                    mHasChallenge, mChallenge, mChosenPattern, mCurrentCredential, mUserId);
         }
 
         @Override
         public void onChosenLockSaveFinished(boolean wasSecureBefore, Intent resultData) {
             getActivity().setResult(RESULT_FINISHED, resultData);
 
-            if (mCurrentPattern != null) {
-                Arrays.fill(mCurrentPattern, (byte) 0);
+            if (mCurrentCredential != null) {
+                mCurrentCredential.zeroize();
             }
 
             if (!wasSecureBefore) {
@@ -836,16 +836,17 @@
 
     public static class SaveAndFinishWorker extends SaveChosenLockWorkerBase {
 
-        private List<LockPatternView.Cell> mChosenPattern;
-        private byte[] mCurrentPattern;
+        private LockscreenCredential mChosenPattern;
+        private LockscreenCredential mCurrentCredential;
         private boolean mLockVirgin;
 
         public void start(LockPatternUtils utils, boolean credentialRequired,
-                boolean hasChallenge, long challenge,
-                List<LockPatternView.Cell> chosenPattern, byte[] currentPattern, int userId) {
+                boolean hasChallenge, long challenge, LockscreenCredential chosenPattern,
+                LockscreenCredential currentCredential, int userId) {
             prepare(utils, credentialRequired, hasChallenge, challenge, userId);
 
-            mCurrentPattern = currentPattern;
+            mCurrentCredential = currentCredential != null ? currentCredential
+                    : LockscreenCredential.createNone();
             mChosenPattern = chosenPattern;
             mUserId = userId;
 
@@ -857,12 +858,13 @@
         @Override
         protected Pair<Boolean, Intent> saveAndVerifyInBackground() {
             final int userId = mUserId;
-            final boolean success = mUtils.saveLockPattern(mChosenPattern, mCurrentPattern, userId);
+            final boolean success = mUtils.setLockCredential(mChosenPattern, mCurrentCredential,
+                    userId);
             Intent result = null;
             if (success && mHasChallenge) {
                 byte[] token;
                 try {
-                    token = mUtils.verifyPattern(mChosenPattern, mChallenge, userId);
+                    token = mUtils.verifyCredential(mChosenPattern, mChallenge, userId);
                 } catch (RequestThrottledException e) {
                     token = null;
                 }
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/password/ConfirmDeviceCredentialActivity.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/password/ConfirmDeviceCredentialActivity.java
index 12afab1..6eef486 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/password/ConfirmDeviceCredentialActivity.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/password/ConfirmDeviceCredentialActivity.java
@@ -39,9 +39,9 @@
 import androidx.annotation.NonNull;
 import androidx.fragment.app.FragmentActivity;
 
-import com.android.internal.widget.LockPatternUtils;
 import com.android.car.developeroptions.R;
 import com.android.car.developeroptions.Utils;
+import com.android.internal.widget.LockPatternUtils;
 
 import java.util.concurrent.Executor;
 
@@ -108,10 +108,6 @@
             if (!mGoingToBackground) {
                 if (errorCode == BiometricPrompt.BIOMETRIC_ERROR_USER_CANCELED
                         || errorCode == BiometricPrompt.BIOMETRIC_ERROR_CANCELED) {
-                    if (mIsFallback) {
-                        mBiometricManager.onConfirmDeviceCredentialError(
-                                errorCode, getStringForError(errorCode));
-                    }
                     finish();
                 } else {
                     // All other errors go to some version of CC
@@ -128,10 +124,6 @@
             ConfirmDeviceCredentialUtils.checkForPendingIntent(
                     ConfirmDeviceCredentialActivity.this);
 
-            if (mIsFallback) {
-                mBiometricManager.onConfirmDeviceCredentialSuccess();
-            }
-
             setResult(Activity.RESULT_OK);
             finish();
         }
@@ -183,17 +175,11 @@
         mChooseLockSettingsHelper = new ChooseLockSettingsHelper(this);
         final LockPatternUtils lockPatternUtils = new LockPatternUtils(this);
 
-        Bundle bpBundle =
-                intent.getBundleExtra(KeyguardManager.EXTRA_BIOMETRIC_PROMPT_BUNDLE);
-        if (bpBundle != null) {
-            mIsFallback = true;
-            mTitle = bpBundle.getString(BiometricPrompt.KEY_TITLE);
-            mDetails = bpBundle.getString(BiometricPrompt.KEY_SUBTITLE);
-        } else {
-            bpBundle = new Bundle();
-            bpBundle.putString(BiometricPrompt.KEY_TITLE, mTitle);
-            bpBundle.putString(BiometricPrompt.KEY_DESCRIPTION, mDetails);
-        }
+        final Bundle bpBundle = new Bundle();
+        mTitle = bpBundle.getString(BiometricPrompt.KEY_TITLE);
+        mDetails = bpBundle.getString(BiometricPrompt.KEY_SUBTITLE);
+        bpBundle.putString(BiometricPrompt.KEY_TITLE, mTitle);
+        bpBundle.putString(BiometricPrompt.KEY_DESCRIPTION, mDetails);
 
         boolean launchedBiometric = false;
         boolean launchedCDC = false;
@@ -254,12 +240,6 @@
                 mBiometricFragment.cancel();
             }
 
-            if (mIsFallback && !mCCLaunched) {
-                mBiometricManager.onConfirmDeviceCredentialError(
-                        BiometricConstants.BIOMETRIC_ERROR_CANCELED,
-                        getString(com.android.internal.R.string.biometric_error_user_canceled));
-            }
-
             finish();
         } else {
             mGoingToBackground = false;
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/password/ConfirmDeviceCredentialBaseActivity.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/password/ConfirmDeviceCredentialBaseActivity.java
index 29fefa8..e5faa5f 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/password/ConfirmDeviceCredentialBaseActivity.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/password/ConfirmDeviceCredentialBaseActivity.java
@@ -17,9 +17,6 @@
 package com.android.car.developeroptions.password;
 
 import android.app.KeyguardManager;
-import android.hardware.biometrics.BiometricConstants;
-import android.hardware.biometrics.BiometricManager;
-import android.hardware.biometrics.IBiometricConfirmDeviceCredentialCallback;
 import android.os.Bundle;
 import android.os.UserManager;
 import android.util.Log;
@@ -50,16 +47,6 @@
     private boolean mFirstTimeVisible = true;
     private boolean mIsKeyguardLocked = false;
     private ConfirmCredentialTheme mConfirmCredentialTheme;
-    private BiometricManager mBiometricManager;
-
-    // TODO(b/123378871): Remove when moved.
-    private final IBiometricConfirmDeviceCredentialCallback mCancelCallback
-            = new IBiometricConfirmDeviceCredentialCallback.Stub() {
-        @Override
-        public void cancel() {
-            finish();
-        }
-    };
 
     private boolean isInternalActivity() {
         return (this instanceof ConfirmLockPassword.InternalActivity)
@@ -90,9 +77,6 @@
         }
         super.onCreate(savedState);
 
-        mBiometricManager = getSystemService(BiometricManager.class);
-        mBiometricManager.registerCancellationCallback(mCancelCallback);
-
         if (mConfirmCredentialTheme == ConfirmCredentialTheme.NORMAL) {
             // Prevent the content parent from consuming the window insets because GlifLayout uses
             // it to show the status bar background.
@@ -168,12 +152,6 @@
     @Override
     public void onStop() {
         super.onStop();
-        // TODO(b/123378871): Remove when moved.
-        if (!isChangingConfigurations()) {
-            mBiometricManager.onConfirmDeviceCredentialError(
-                    BiometricConstants.BIOMETRIC_ERROR_USER_CANCELED,
-                    getString(com.android.internal.R.string.biometric_error_user_canceled));
-        }
     }
 
     @Override
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/password/ConfirmLockPassword.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/password/ConfirmLockPassword.java
index f5e3dce..068094b 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/password/ConfirmLockPassword.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/password/ConfirmLockPassword.java
@@ -27,6 +27,7 @@
 import android.os.SystemClock;
 import android.os.UserManager;
 import android.os.storage.StorageManager;
+import android.text.Editable;
 import android.text.InputType;
 import android.text.TextUtils;
 import android.view.KeyEvent;
@@ -42,11 +43,12 @@
 
 import androidx.fragment.app.Fragment;
 
-import com.android.internal.widget.LockPatternChecker;
-import com.android.internal.widget.LockPatternUtils;
-import com.android.internal.widget.TextViewInputDisabler;
 import com.android.car.developeroptions.R;
 import com.android.car.developeroptions.widget.ImeAwareEditText;
+import com.android.internal.widget.LockPatternChecker;
+import com.android.internal.widget.LockPatternUtils;
+import com.android.internal.widget.LockscreenCredential;
+import com.android.internal.widget.TextViewInputDisabler;
 import com.android.settingslib.animation.AppearAnimationUtils;
 import com.android.settingslib.animation.DisappearAnimationUtils;
 
@@ -324,10 +326,13 @@
             }
 
             // TODO(b/120484642): This is a point of entry for passwords from the UI
-            final byte[] pin = LockPatternUtils.charSequenceToByteArray(mPasswordEntry.getText());
-            if (pin == null || pin.length == 0) {
+            final Editable passwordText = mPasswordEntry.getText();
+            if (TextUtils.isEmpty(passwordText)) {
                 return;
             }
+            final LockscreenCredential credential = mIsAlpha
+                    ? LockscreenCredential.createPassword(passwordText)
+                    : LockscreenCredential.createPin(passwordText);
 
             mPasswordEntryInputDisabler.setInputEnabled(false);
             final boolean verifyChallenge = getActivity().getIntent().getBooleanExtra(
@@ -336,11 +341,11 @@
             Intent intent = new Intent();
             if (verifyChallenge)  {
                 if (isInternalActivity()) {
-                    startVerifyPassword(pin, intent);
+                    startVerifyPassword(credential, intent);
                     return;
                 }
             } else {
-                startCheckPassword(pin, intent);
+                startCheckPassword(credential, intent);
                 return;
             }
 
@@ -351,7 +356,7 @@
             return getActivity() instanceof ConfirmLockPassword.InternalActivity;
         }
 
-        private void startVerifyPassword(final byte[] pin, final Intent intent) {
+        private void startVerifyPassword(LockscreenCredential credential, final Intent intent) {
             long challenge = getActivity().getIntent().getLongExtra(
                     ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE, 0);
             final int localEffectiveUserId = mEffectiveUserId;
@@ -375,18 +380,19 @@
                         }
             };
             mPendingLockCheck = (localEffectiveUserId == localUserId)
-                    ? LockPatternChecker.verifyPassword(
-                            mLockPatternUtils, pin, challenge, localUserId, onVerifyCallback)
+                    ? LockPatternChecker.verifyCredential(
+                            mLockPatternUtils, credential, challenge, localUserId, onVerifyCallback)
                     : LockPatternChecker.verifyTiedProfileChallenge(
-                            mLockPatternUtils, pin, false, challenge, localUserId,
+                            mLockPatternUtils, credential, challenge, localUserId,
                             onVerifyCallback);
         }
 
-        private void startCheckPassword(final byte[] pin, final Intent intent) {
+        private void startCheckPassword(final LockscreenCredential credential,
+                final Intent intent) {
             final int localEffectiveUserId = mEffectiveUserId;
-            mPendingLockCheck = LockPatternChecker.checkPassword(
+            mPendingLockCheck = LockPatternChecker.checkCredential(
                     mLockPatternUtils,
-                    pin,
+                    credential,
                     localEffectiveUserId,
                     new LockPatternChecker.OnCheckCallback() {
                         @Override
@@ -397,7 +403,7 @@
                                                 mIsAlpha ? StorageManager.CRYPT_TYPE_PASSWORD
                                                          : StorageManager.CRYPT_TYPE_PIN);
                                 intent.putExtra(
-                                        ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD, pin);
+                                        ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD, credential);
                             }
                             mCredentialCheckResultTracker.setResult(matched, intent, timeoutMs,
                                     localEffectiveUserId);
@@ -438,7 +444,6 @@
                     ConfirmDeviceCredentialUtils.reportSuccessfulAttempt(mLockPatternUtils,
                             mUserManager, mEffectiveUserId);
                 }
-                mBiometricManager.onConfirmDeviceCredentialSuccess();
                 startDisappearAnimation(intent);
                 ConfirmDeviceCredentialUtils.checkForPendingIntent(getActivity());
             } else {
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/password/ConfirmLockPattern.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/password/ConfirmLockPattern.java
index fedd126..96458eb 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/password/ConfirmLockPattern.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/password/ConfirmLockPattern.java
@@ -32,12 +32,13 @@
 import android.view.animation.Interpolator;
 import android.widget.TextView;
 
+import com.android.car.developeroptions.R;
 import com.android.internal.widget.LinearLayoutWithDefaultTouchRecepient;
 import com.android.internal.widget.LockPatternChecker;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.internal.widget.LockPatternView;
 import com.android.internal.widget.LockPatternView.Cell;
-import com.android.car.developeroptions.R;
+import com.android.internal.widget.LockscreenCredential;
 import com.android.settingslib.animation.AppearAnimationCreator;
 import com.android.settingslib.animation.AppearAnimationUtils;
 import com.android.settingslib.animation.DisappearAnimationUtils;
@@ -401,14 +402,15 @@
 
                 final boolean verifyChallenge = getActivity().getIntent().getBooleanExtra(
                         ChooseLockSettingsHelper.EXTRA_KEY_HAS_CHALLENGE, false);
+                final LockscreenCredential credential = LockscreenCredential.createPattern(pattern);
                 Intent intent = new Intent();
                 if (verifyChallenge) {
                     if (isInternalActivity()) {
-                        startVerifyPattern(pattern, intent);
+                        startVerifyPattern(credential, intent);
                         return;
                     }
                 } else {
-                    startCheckPattern(pattern, intent);
+                    startCheckPattern(credential, intent);
                     return;
                 }
 
@@ -419,7 +421,7 @@
                 return getActivity() instanceof ConfirmLockPattern.InternalActivity;
             }
 
-            private void startVerifyPattern(final List<LockPatternView.Cell> pattern,
+            private void startVerifyPattern(final LockscreenCredential pattern,
                     final Intent intent) {
                 final int localEffectiveUserId = mEffectiveUserId;
                 final int localUserId = mUserId;
@@ -444,15 +446,15 @@
                         }
                     };
                 mPendingLockCheck = (localEffectiveUserId == localUserId)
-                        ? LockPatternChecker.verifyPattern(
+                        ? LockPatternChecker.verifyCredential(
                                 mLockPatternUtils, pattern, challenge, localUserId,
                                 onVerifyCallback)
                         : LockPatternChecker.verifyTiedProfileChallenge(
-                                mLockPatternUtils, LockPatternUtils.patternToByteArray(pattern),
-                                true, challenge, localUserId, onVerifyCallback);
+                                mLockPatternUtils, pattern,
+                                challenge, localUserId, onVerifyCallback);
             }
 
-            private void startCheckPattern(final List<LockPatternView.Cell> pattern,
+            private void startCheckPattern(final LockscreenCredential pattern,
                     final Intent intent) {
                 if (pattern.size() < LockPatternUtils.MIN_PATTERN_REGISTER_FAIL) {
                     // Pattern size is less than the minimum, do not count it as an fail attempt.
@@ -461,7 +463,7 @@
                 }
 
                 final int localEffectiveUserId = mEffectiveUserId;
-                mPendingLockCheck = LockPatternChecker.checkPattern(
+                mPendingLockCheck = LockPatternChecker.checkCredential(
                         mLockPatternUtils,
                         pattern,
                         localEffectiveUserId,
@@ -473,7 +475,7 @@
                                     intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_TYPE,
                                                     StorageManager.CRYPT_TYPE_PATTERN);
                                     intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD,
-                                                    LockPatternUtils.patternToByteArray(pattern));
+                                                    pattern);
                                 }
                                 mCredentialCheckResultTracker.setResult(matched, intent, timeoutMs,
                                         localEffectiveUserId);
@@ -490,7 +492,6 @@
                     ConfirmDeviceCredentialUtils.reportSuccessfulAttempt(mLockPatternUtils,
                             mUserManager, mEffectiveUserId);
                 }
-                mBiometricManager.onConfirmDeviceCredentialSuccess();
                 startDisappearAnimation(intent);
                 ConfirmDeviceCredentialUtils.checkForPendingIntent(getActivity());
             } else {
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/password/ManagedLockPasswordProvider.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/password/ManagedLockPasswordProvider.java
index 5b01e65..334c055 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/password/ManagedLockPasswordProvider.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/password/ManagedLockPasswordProvider.java
@@ -19,6 +19,8 @@
 import android.content.Context;
 import android.content.Intent;
 
+import com.android.internal.widget.LockscreenCredential;
+
 /**
  * Helper for handling managed passwords in security settings UI.
  * It provides resources that should be shown in settings UI when lock password quality is set to
@@ -59,7 +61,7 @@
      * @param password Current lock password.
      * @return Intent that should update lock password to a managed password.
      */
-    Intent createIntent(boolean requirePasswordToDecrypt, byte[] password) {
+    Intent createIntent(boolean requirePasswordToDecrypt, LockscreenCredential password) {
         return null;
     }
 }
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/password/PasswordUtils.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/password/PasswordUtils.java
index 7a4d024..fb7c6ae 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/password/PasswordUtils.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/password/PasswordUtils.java
@@ -89,7 +89,8 @@
                     /* initialPid= */ -1,
                     getCallingAppPackageName(activityToken),
                     userId,
-                    message);
+                    message,
+                    /* force */ false);
         } catch (RemoteException e) {
             Log.v(TAG, "Could not talk to activity manager.", e);
         }
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/password/SetNewPasswordController.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/password/SetNewPasswordController.java
index 7689334..b213152 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/password/SetNewPasswordController.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/password/SetNewPasswordController.java
@@ -21,8 +21,6 @@
 import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_FINGERPRINT;
 import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_SOMETHING;
 
-import static com.android.internal.util.Preconditions.checkNotNull;
-
 import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.app.admin.DevicePolicyManager;
@@ -40,6 +38,8 @@
 import com.android.internal.widget.LockPatternUtils;
 import com.android.car.developeroptions.Utils;
 
+import java.util.Objects;
+
 /**
  * Business logic for {@link SetNewPasswordActivity}.
  *
@@ -99,11 +99,11 @@
             DevicePolicyManager devicePolicyManager,
             Ui ui) {
         mTargetUserId = targetUserId;
-        mPackageManager = checkNotNull(packageManager);
+        mPackageManager = Objects.requireNonNull(packageManager);
         mFingerprintManager = fingerprintManager;
         mFaceManager = faceManager;
-        mDevicePolicyManager = checkNotNull(devicePolicyManager);
-        mUi = checkNotNull(ui);
+        mDevicePolicyManager = Objects.requireNonNull(devicePolicyManager);
+        mUi = Objects.requireNonNull(ui);
     }
 
     /**
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/print/PrintSettingsFragment.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/print/PrintSettingsFragment.java
index e73798c..83b7a63 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/print/PrintSettingsFragment.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/print/PrintSettingsFragment.java
@@ -54,7 +54,7 @@
 
 import com.android.car.developeroptions.R;
 import com.android.car.developeroptions.search.BaseSearchIndexProvider;
-import com.android.car.developeroptions.search.Indexable;
+import com.android.settingslib.search.Indexable;
 import com.android.settingslib.search.SearchIndexable;
 import com.android.settingslib.widget.apppreference.AppPreference;
 
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/print/SettingsPrintServicesLoader.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/print/SettingsPrintServicesLoader.java
index bf383b5..91ae5da 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/print/SettingsPrintServicesLoader.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/print/SettingsPrintServicesLoader.java
@@ -24,9 +24,8 @@
 
 import androidx.loader.content.Loader;
 
-import com.android.internal.util.Preconditions;
-
 import java.util.List;
+import java.util.Objects;
 
 /**
  * Loader for the list of print services. Can be parametrized to select a subset.
@@ -37,7 +36,7 @@
 
     public SettingsPrintServicesLoader(@NonNull PrintManager printManager, @NonNull Context context,
             int selectionFlags) {
-        super(Preconditions.checkNotNull(context));
+        super(Objects.requireNonNull(context));
 
         mLoader = new PrintServicesLoader(printManager, context, selectionFlags) {
             @Override
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/search/BaseSearchIndexProvider.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/search/BaseSearchIndexProvider.java
index 606d551..d6b060e 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/search/BaseSearchIndexProvider.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/search/BaseSearchIndexProvider.java
@@ -36,6 +36,8 @@
 import com.android.car.developeroptions.core.PreferenceControllerMixin;
 import com.android.car.developeroptions.core.PreferenceXmlParserUtils;
 import com.android.settingslib.core.AbstractPreferenceController;
+import com.android.settingslib.search.Indexable;
+import com.android.settingslib.search.SearchIndexableRaw;
 
 import org.xmlpull.v1.XmlPullParserException;
 
@@ -64,6 +66,11 @@
     }
 
     @Override
+    public List<SearchIndexableRaw> getDynamicRawDataToIndex(Context context, boolean enabled) {
+        return null;
+    }
+
+    @Override
     @CallSuper
     public List<String> getNonIndexableKeys(Context context) {
         if (!isPageSearchEnabled(context)) {
@@ -92,7 +99,6 @@
         return nonIndexableKeys;
     }
 
-    @Override
     public List<AbstractPreferenceController> getPreferenceControllers(Context context) {
         final List<AbstractPreferenceController> controllersFromCode =
                 createPreferenceControllers(context);
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/search/DatabaseIndexingUtils.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/search/DatabaseIndexingUtils.java
deleted file mode 100644
index 8477fc7..0000000
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/search/DatabaseIndexingUtils.java
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-package com.android.car.developeroptions.search;
-
-import android.util.Log;
-
-import java.lang.reflect.Field;
-
-/**
- * Utility class for {@like DatabaseIndexingManager} to handle the mapping between Payloads
- * and Preference controllers, and managing indexable classes.
- */
-public class DatabaseIndexingUtils {
-
-    private static final String TAG = "IndexingUtil";
-
-    public static final String FIELD_NAME_SEARCH_INDEX_DATA_PROVIDER =
-            "SEARCH_INDEX_DATA_PROVIDER";
-
-    public static Indexable.SearchIndexProvider getSearchIndexProvider(final Class<?> clazz) {
-        try {
-            final Field f = clazz.getField(FIELD_NAME_SEARCH_INDEX_DATA_PROVIDER);
-            return (Indexable.SearchIndexProvider) f.get(null);
-        } catch (NoSuchFieldException e) {
-            Log.d(TAG, "Cannot find field '" + FIELD_NAME_SEARCH_INDEX_DATA_PROVIDER + "'");
-        } catch (SecurityException se) {
-            Log.d(TAG, "Security exception for field '" +
-                    FIELD_NAME_SEARCH_INDEX_DATA_PROVIDER + "'");
-        } catch (IllegalAccessException e) {
-            Log.d(TAG, "Illegal access to field '" + FIELD_NAME_SEARCH_INDEX_DATA_PROVIDER + "'");
-        } catch (IllegalArgumentException e) {
-            Log.d(TAG, "Illegal argument when accessing field '" +
-                    FIELD_NAME_SEARCH_INDEX_DATA_PROVIDER + "'");
-        }
-        return null;
-    }
-}
\ No newline at end of file
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/search/Indexable.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/search/Indexable.java
deleted file mode 100644
index fc191ff..0000000
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/search/Indexable.java
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.car.developeroptions.search;
-
-import android.content.Context;
-import android.provider.SearchIndexableResource;
-
-import androidx.annotation.Keep;
-
-import com.android.settingslib.core.AbstractPreferenceController;
-
-import java.util.List;
-
-/**
- * Interface for classes whose instances can provide data for indexing.
- *
- * Classes implementing the Indexable interface must have a static field called
- * <code>SEARCH_INDEX_DATA_PROVIDER</code>, which is an object implementing the
- * {@link Indexable.SearchIndexProvider} interface.
- *
- * See {@link android.provider.SearchIndexableResource} and {@link SearchIndexableRaw}.
- */
-public interface Indexable {
-
-    interface SearchIndexProvider {
-        /**
-         * Return a list of references for indexing.
-         *
-         * See {@link android.provider.SearchIndexableResource}
-         *
-         * @param context the context.
-         * @param enabled hint telling if the data needs to be considered into the search results
-         *                or not.
-         * @return a list of {@link android.provider.SearchIndexableResource} references.
-         * Can be null.
-         */
-        @Keep
-        List<SearchIndexableResource> getXmlResourcesToIndex(Context context, boolean enabled);
-
-        /**
-         * Return a list of raw data for indexing. See {@link SearchIndexableRaw}
-         *
-         * @param context the context.
-         * @param enabled hint telling if the data needs to be considered into the search results
-         *                or not.
-         * @return a list of {@link SearchIndexableRaw} references. Can be null.
-         */
-        @Keep
-        List<SearchIndexableRaw> getRawDataToIndex(Context context, boolean enabled);
-
-        /**
-         * Return a list of data keys that cannot be indexed. See {@link SearchIndexableRaw}
-         *
-         * @param context the context.
-         * @return a list of {@link SearchIndexableRaw} references. Can be null.
-         */
-        @Keep
-        List<String> getNonIndexableKeys(Context context);
-
-        /**
-         * @return a list of {@link AbstractPreferenceController} for ResultPayload data during
-         * Indexing.
-         */
-        @Keep
-        List<AbstractPreferenceController> getPreferenceControllers(Context context);
-    }
-}
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/search/SearchIndexableRaw.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/search/SearchIndexableRaw.java
deleted file mode 100644
index a91a920..0000000
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/search/SearchIndexableRaw.java
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.car.developeroptions.search;
-
-import android.content.Context;
-import android.provider.SearchIndexableData;
-
-/**
- * Indexable raw data for Search.
- *
- * This is the raw data used by the Indexer and should match its data model.
- *
- * See {@link Indexable} and {@link android.provider.SearchIndexableResource}.
- */
-public class SearchIndexableRaw extends SearchIndexableData {
-
-    /**
-     * Title's raw data.
-     */
-    public String title;
-
-    /**
-     * Summary's raw data when the data is "ON".
-     */
-    public String summaryOn;
-
-    /**
-     * Summary's raw data when the data is "OFF".
-     */
-    public String summaryOff;
-
-    /**
-     * Entries associated with the raw data (when the data can have several values).
-     */
-    public String entries;
-
-    /**
-     * Keywords' raw data.
-     */
-    public String keywords;
-
-    /**
-     * Fragment's or Activity's title associated with the raw data.
-     */
-    public String screenTitle;
-
-    public SearchIndexableRaw(Context context) {
-        super(context);
-    }
-}
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/search/SettingsSearchIndexablesProvider.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/search/SettingsSearchIndexablesProvider.java
index c29cb54..78dce4a 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/search/SettingsSearchIndexablesProvider.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/search/SettingsSearchIndexablesProvider.java
@@ -65,6 +65,9 @@
 import com.android.car.developeroptions.slices.SettingsSliceProvider;
 import com.android.settingslib.drawer.DashboardCategory;
 import com.android.settingslib.drawer.Tile;
+import com.android.settingslib.search.Indexable;
+import com.android.settingslib.search.SearchIndexableData;
+import com.android.settingslib.search.SearchIndexableRaw;
 
 import java.util.ArrayList;
 import java.util.Collection;
@@ -219,14 +222,12 @@
     }
 
     private List<String> getNonIndexableKeysFromProvider(Context context) {
-        final Collection<Class> values = FeatureFactory.getFactory(context)
+        final Collection<SearchIndexableData> bundles = FeatureFactory.getFactory(context)
                 .getSearchFeatureProvider().getSearchIndexableResources().getProviderValues();
         final List<String> nonIndexableKeys = new ArrayList<>();
-
-        for (Class<?> clazz : values) {
+        for (SearchIndexableData bundle : bundles) {
             final long startTime = System.currentTimeMillis();
-            Indexable.SearchIndexProvider provider = DatabaseIndexingUtils.getSearchIndexProvider(
-                    clazz);
+            Indexable.SearchIndexProvider provider = bundle.getSearchIndexProvider();
 
             List<String> providerNonIndexableKeys;
             try {
@@ -241,7 +242,8 @@
                 if (System.getProperty(SYSPROP_CRASH_ON_ERROR) != null) {
                     throw new RuntimeException(e);
                 }
-                Log.e(TAG, "Error trying to get non-indexable keys from: " + clazz.getName(), e);
+                Log.e(TAG, "Error trying to get non-indexable keys from: "
+                        + bundle.getTargetClass().getName(), e);
                 continue;
             }
 
@@ -270,13 +272,11 @@
     }
 
     private List<SearchIndexableResource> getSearchIndexableResourcesFromProvider(Context context) {
-        Collection<Class> values = FeatureFactory.getFactory(context)
+        final Collection<SearchIndexableData> bundles = FeatureFactory.getFactory(context)
                 .getSearchFeatureProvider().getSearchIndexableResources().getProviderValues();
         List<SearchIndexableResource> resourceList = new ArrayList<>();
-
-        for (Class<?> clazz : values) {
-            Indexable.SearchIndexProvider provider = DatabaseIndexingUtils.getSearchIndexProvider(
-                    clazz);
+        for (SearchIndexableData bundle : bundles) {
+            Indexable.SearchIndexProvider provider = bundle.getSearchIndexProvider();
 
             final List<SearchIndexableResource> resList =
                     provider.getXmlResourcesToIndex(context, true);
@@ -287,7 +287,7 @@
 
             for (SearchIndexableResource item : resList) {
                 item.className = TextUtils.isEmpty(item.className)
-                        ? clazz.getName()
+                        ? bundle.getTargetClass().getName()
                         : item.className;
             }
 
@@ -298,13 +298,11 @@
     }
 
     private List<SearchIndexableRaw> getSearchIndexableRawFromProvider(Context context) {
-        final Collection<Class> values = FeatureFactory.getFactory(context)
+        final Collection<SearchIndexableData> bundles = FeatureFactory.getFactory(context)
                 .getSearchFeatureProvider().getSearchIndexableResources().getProviderValues();
         final List<SearchIndexableRaw> rawList = new ArrayList<>();
-
-        for (Class<?> clazz : values) {
-            Indexable.SearchIndexProvider provider = DatabaseIndexingUtils.getSearchIndexProvider(
-                    clazz);
+        for (SearchIndexableData bundle : bundles) {
+            Indexable.SearchIndexProvider provider = bundle.getSearchIndexProvider();
             final List<SearchIndexableRaw> providerRaws = provider.getRawDataToIndex(context,
                     true /* enabled */);
 
@@ -315,7 +313,7 @@
             for (SearchIndexableRaw raw : providerRaws) {
                 // The classname and intent information comes from the PreIndexData
                 // This will be more clear when provider conversion is done at PreIndex time.
-                raw.className = clazz.getName();
+                raw.className = bundle.getTargetClass().getName();
 
             }
             rawList.addAll(providerRaws);
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/security/CredentialStorage.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/security/CredentialStorage.java
index 18abac7..268a710 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/security/CredentialStorage.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/security/CredentialStorage.java
@@ -41,18 +41,10 @@
 import androidx.appcompat.app.AlertDialog;
 import androidx.fragment.app.FragmentActivity;
 
-import com.android.internal.widget.LockPatternUtils;
-import com.android.org.bouncycastle.asn1.ASN1InputStream;
-import com.android.org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
 import com.android.car.developeroptions.R;
 import com.android.car.developeroptions.password.ChooseLockSettingsHelper;
 import com.android.car.developeroptions.vpn2.VpnUtils;
-
-import java.io.ByteArrayInputStream;
-import java.io.IOException;
-
-import sun.security.util.ObjectIdentifier;
-import sun.security.x509.AlgorithmId;
+import com.android.internal.widget.LockPatternUtils;
 
 /**
  * CredentialStorage handles resetting and installing keys into KeyStore.
@@ -118,20 +110,6 @@
         }
     }
 
-    private boolean isHardwareBackedKey(byte[] keyData) {
-        try {
-            final ASN1InputStream bIn = new ASN1InputStream(new ByteArrayInputStream(keyData));
-            final PrivateKeyInfo pki = PrivateKeyInfo.getInstance(bIn.readObject());
-            final String algOid = pki.getPrivateKeyAlgorithm().getAlgorithm().getId();
-            final String algName = new AlgorithmId(new ObjectIdentifier(algOid)).getName();
-
-            return KeyChain.isBoundKeyAlgorithm(algName);
-        } catch (IOException e) {
-            Log.e(TAG, "Failed to parse key data");
-            return false;
-        }
-    }
-
     /**
      * Install credentials if available, otherwise do nothing.
      *
@@ -165,56 +143,18 @@
             return true;
         }
 
-        boolean shouldFinish = true;
-        if (bundle.containsKey(Credentials.EXTRA_USER_PRIVATE_KEY_NAME)) {
-            final String key = bundle.getString(Credentials.EXTRA_USER_PRIVATE_KEY_NAME);
-            final byte[] value = bundle.getByteArray(Credentials.EXTRA_USER_PRIVATE_KEY_DATA);
-
-            if (!mKeyStore.importKey(key, value, uid, KeyStore.FLAG_NONE)) {
-                Log.e(TAG, "Failed to install " + key + " as uid " + uid);
-                return true;
-            }
-            // The key was prepended USER_PRIVATE_KEY by the CredentialHelper. However,
-            // KeyChain internally uses the raw alias name and only prepends USER_PRIVATE_KEY
-            // to the key name when interfacing with KeyStore.
-            // This is generally a symptom of CredentialStorage and CredentialHelper relying
-            // on internal implementation details of KeyChain and imitating its functionality
-            // rather than delegating to KeyChain for the certificate installation.
-            if (uid == Process.SYSTEM_UID || uid == KeyStore.UID_SELF) {
-                new MarkKeyAsUserSelectable(
-                        key.replaceFirst("^" + Credentials.USER_PRIVATE_KEY, "")).execute();
-                shouldFinish = false;
-            }
+        String alias = bundle.getString(Credentials.EXTRA_USER_KEY_ALIAS, null);
+        if (TextUtils.isEmpty(alias)) {
+            Log.e(TAG, "Cannot install key without an alias");
+            return true;
         }
 
-        final int flags = KeyStore.FLAG_NONE;
+        final byte[] privateKeyData = bundle.getByteArray(Credentials.EXTRA_USER_PRIVATE_KEY_DATA);
+        final byte[] certData = bundle.getByteArray(Credentials.EXTRA_USER_CERTIFICATE_DATA);
+        final byte[] caListData = bundle.getByteArray(Credentials.EXTRA_CA_CERTIFICATES_DATA);
+        new InstallKeyInKeyChain(alias, privateKeyData, certData, caListData, uid).execute();
 
-        if (bundle.containsKey(Credentials.EXTRA_USER_CERTIFICATE_NAME)) {
-            final String certName = bundle.getString(Credentials.EXTRA_USER_CERTIFICATE_NAME);
-            final byte[] certData = bundle.getByteArray(Credentials.EXTRA_USER_CERTIFICATE_DATA);
-
-            if (!mKeyStore.put(certName, certData, uid, flags)) {
-                Log.e(TAG, "Failed to install " + certName + " as uid " + uid);
-                return shouldFinish;
-            }
-        }
-
-        if (bundle.containsKey(Credentials.EXTRA_CA_CERTIFICATES_NAME)) {
-            final String caListName = bundle.getString(Credentials.EXTRA_CA_CERTIFICATES_NAME);
-            final byte[] caListData = bundle.getByteArray(Credentials.EXTRA_CA_CERTIFICATES_DATA);
-
-            if (!mKeyStore.put(caListName, caListData, uid, flags)) {
-                Log.e(TAG, "Failed to install " + caListName + " as uid " + uid);
-                return shouldFinish;
-            }
-        }
-
-        // Send the broadcast.
-        final Intent broadcast = new Intent(KeyChain.ACTION_KEYCHAIN_CHANGED);
-        sendBroadcast(broadcast);
-
-        setResult(RESULT_OK);
-        return shouldFinish;
+        return false;
     }
 
     /**
@@ -308,6 +248,67 @@
     }
 
     /**
+     * Background task to install a certificate into KeyChain.
+     */
+    private class InstallKeyInKeyChain extends AsyncTask<Void, Void, Boolean> {
+        final String mAlias;
+        private final byte[] mKeyData;
+        private final byte[] mCertData;
+        private final byte[] mCaListData;
+        private final int mUid;
+
+        InstallKeyInKeyChain(String alias, byte[] keyData, byte[] certData, byte[] caListData,
+                int uid) {
+            mAlias = alias;
+            mKeyData = keyData;
+            mCertData = certData;
+            mCaListData = caListData;
+            mUid = uid;
+        }
+
+        @Override
+        protected Boolean doInBackground(Void... unused) {
+            try (KeyChainConnection keyChainConnection = KeyChain.bind(CredentialStorage.this)) {
+                return keyChainConnection.getService()
+                        .installKeyPair(mKeyData, mCertData, mCaListData, mAlias, mUid);
+            } catch (RemoteException e) {
+                Log.w(TAG, String.format("Failed to install key %s to uid %d", mAlias, mUid), e);
+                return false;
+            } catch (InterruptedException e) {
+                Log.w(TAG, String.format("Interrupted while installing key %s", mAlias), e);
+                Thread.currentThread().interrupt();
+                return false;
+            }
+        }
+
+        @Override
+        protected void onPostExecute(Boolean result) {
+            Log.i(TAG, String.format("Marked alias %s as selectable, success? %s",
+                    mAlias, result));
+            CredentialStorage.this.onKeyInstalled(mAlias, mUid, result);
+        }
+    }
+
+    private void onKeyInstalled(String alias, int uid, boolean result) {
+        if (!result) {
+            Log.w(TAG, String.format("Error installing alias %s for uid %d", alias, uid));
+            finish();
+            return;
+        }
+
+        // Send the broadcast.
+        final Intent broadcast = new Intent(KeyChain.ACTION_KEYCHAIN_CHANGED);
+        sendBroadcast(broadcast);
+        setResult(RESULT_OK);
+
+        if (uid == Process.SYSTEM_UID || uid == KeyStore.UID_SELF) {
+            new MarkKeyAsUserSelectable(alias).execute();
+        } else {
+            finish();
+        }
+    }
+
+    /**
      * Background task to mark a given key alias as user-selectable, so that
      * it can be selected by users from the Certificate Selection prompt.
      */
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/security/CryptKeeperSettings.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/security/CryptKeeperSettings.java
index f580692..7d83f4c 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/security/CryptKeeperSettings.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/security/CryptKeeperSettings.java
@@ -28,7 +28,6 @@
 import android.os.Bundle;
 import android.os.UserHandle;
 import android.os.storage.StorageManager;
-import android.text.TextUtils;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
@@ -43,6 +42,7 @@
 import com.android.car.developeroptions.core.InstrumentedPreferenceFragment;
 import com.android.car.developeroptions.password.ChooseLockSettingsHelper;
 import com.android.car.developeroptions.password.ConfirmLockPattern;
+import com.android.internal.widget.LockscreenCredential;
 
 public class CryptKeeperSettings extends InstrumentedPreferenceFragment {
     private static final String TAG = "CryptKeeper";
@@ -193,9 +193,10 @@
         // confirmation prompt; otherwise, go back to the initial state.
         if (resultCode == Activity.RESULT_OK && data != null) {
             int type = data.getIntExtra(ChooseLockSettingsHelper.EXTRA_KEY_TYPE, -1);
-            byte[] password = data.getByteArrayExtra(ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD);
-            if (!(password == null || password.length == 0)) {
-                showFinalConfirmation(type, password);
+            LockscreenCredential password = data.getParcelableExtra(
+                    ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD);
+            if (password != null && !password.isNone()) {
+                showFinalConfirmation(type, password.getCredential());
             }
         }
     }
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/security/LockUnificationPreferenceController.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/security/LockUnificationPreferenceController.java
index 451b01e..4619dab 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/security/LockUnificationPreferenceController.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/security/LockUnificationPreferenceController.java
@@ -31,7 +31,6 @@
 import androidx.preference.Preference;
 import androidx.preference.PreferenceScreen;
 
-import com.android.internal.widget.LockPatternUtils;
 import com.android.car.developeroptions.R;
 import com.android.car.developeroptions.Utils;
 import com.android.car.developeroptions.core.PreferenceControllerMixin;
@@ -39,6 +38,8 @@
 import com.android.car.developeroptions.overlay.FeatureFactory;
 import com.android.car.developeroptions.password.ChooseLockGeneric;
 import com.android.car.developeroptions.password.ChooseLockSettingsHelper;
+import com.android.internal.widget.LockPatternUtils;
+import com.android.internal.widget.LockscreenCredential;
 import com.android.settingslib.RestrictedLockUtilsInternal;
 import com.android.settingslib.RestrictedSwitchPreference;
 import com.android.settingslib.core.AbstractPreferenceController;
@@ -70,8 +71,8 @@
     private RestrictedSwitchPreference mUnifyProfile;
 
 
-    private byte[] mCurrentDevicePassword;
-    private byte[] mCurrentProfilePassword;
+    private LockscreenCredential mCurrentDevicePassword;
+    private LockscreenCredential mCurrentProfilePassword;
     private boolean mKeepDeviceLock;
 
     @Override
@@ -89,6 +90,8 @@
                 .getSecurityFeatureProvider()
                 .getLockPatternUtils(context);
         mProfileUserId = Utils.getManagedProfileId(mUm, MY_USER_ID);
+        mCurrentDevicePassword = LockscreenCredential.createNone();
+        mCurrentProfilePassword = LockscreenCredential.createNone();
     }
 
     @Override
@@ -151,13 +154,13 @@
         } else if (requestCode == UNIFY_LOCK_CONFIRM_DEVICE_REQUEST
                 && resultCode == Activity.RESULT_OK) {
             mCurrentDevicePassword =
-                    data.getByteArrayExtra(ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD);
+                    data.getParcelableExtra(ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD);
             launchConfirmProfileLock();
             return true;
         } else if (requestCode == UNIFY_LOCK_CONFIRM_PROFILE_REQUEST
                 && resultCode == Activity.RESULT_OK) {
             mCurrentProfilePassword =
-                    data.getByteArrayExtra(ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD);
+                    data.getParcelableExtra(ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD);
             unifyLocks();
             return true;
         }
@@ -221,17 +224,8 @@
     }
 
     private void unifyKeepingWorkLock() {
-        final int profileQuality =
-                mLockPatternUtils.getKeyguardStoredPasswordQuality(mProfileUserId);
-        // PASSWORD_QUALITY_SOMETHING means pattern, everything above means PIN/password.
-        if (profileQuality == DevicePolicyManager.PASSWORD_QUALITY_SOMETHING) {
-            mLockPatternUtils.saveLockPattern(
-                    LockPatternUtils.byteArrayToPattern(mCurrentProfilePassword),
-                    mCurrentDevicePassword, MY_USER_ID);
-        } else {
-            mLockPatternUtils.saveLockPassword(
-                    mCurrentProfilePassword, mCurrentDevicePassword, profileQuality, MY_USER_ID);
-        }
+        mLockPatternUtils.setLockCredential(
+                mCurrentProfilePassword, mCurrentDevicePassword, MY_USER_ID);
         mLockPatternUtils.setSeparateProfileChallengeEnabled(mProfileUserId, false,
                 mCurrentProfilePassword);
         final boolean profilePatternVisibility =
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/security/screenlock/ScreenLockSettings.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/security/screenlock/ScreenLockSettings.java
index 90b55c1..0a2524a 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/security/screenlock/ScreenLockSettings.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/security/screenlock/ScreenLockSettings.java
@@ -23,14 +23,14 @@
 
 import androidx.fragment.app.Fragment;
 
-import com.android.internal.widget.LockPatternUtils;
 import com.android.car.developeroptions.R;
 import com.android.car.developeroptions.dashboard.DashboardFragment;
 import com.android.car.developeroptions.search.BaseSearchIndexProvider;
-import com.android.car.developeroptions.search.Indexable;
 import com.android.car.developeroptions.security.OwnerInfoPreferenceController;
+import com.android.internal.widget.LockPatternUtils;
 import com.android.settingslib.core.AbstractPreferenceController;
 import com.android.settingslib.core.lifecycle.Lifecycle;
+import com.android.settingslib.search.Indexable;
 import com.android.settingslib.search.SearchIndexable;
 
 import java.util.ArrayList;
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/sim/SimPreferenceDialog.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/sim/SimPreferenceDialog.java
index 39d3f2e..376f349 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/sim/SimPreferenceDialog.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/sim/SimPreferenceDialog.java
@@ -65,7 +65,7 @@
         mSlotId = extras.getInt(SimSettings.EXTRA_SLOT_ID, -1);
         mSubscriptionManager = SubscriptionManager.from(mContext);
         mSubInfoRecord = mSubscriptionManager.getActiveSubscriptionInfoForSimSlotIndex(mSlotId);
-        mTintArr = mContext.getResources().getIntArray(com.android.internal.R.array.sim_colors);
+        mTintArr = mContext.getResources().getIntArray(android.R.array.simColors);
         mColorStrings = mContext.getResources().getStringArray(R.array.color_picker);
         mTintSelectorPos = 0;
 
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/sim/SimSettings.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/sim/SimSettings.java
index aff0bd9..d3727f8 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/sim/SimSettings.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/sim/SimSettings.java
@@ -22,8 +22,8 @@
 import android.content.res.Resources;
 import android.graphics.drawable.BitmapDrawable;
 import android.os.Bundle;
-import android.os.SystemProperties;
 import android.provider.SearchIndexableResource;
+import android.sysprop.TelephonyProperties;
 import android.telecom.PhoneAccountHandle;
 import android.telecom.TelecomManager;
 import android.telephony.PhoneNumberUtils;
@@ -37,12 +37,11 @@
 import androidx.preference.Preference;
 import androidx.preference.PreferenceScreen;
 
-import com.android.internal.telephony.TelephonyProperties;
 import com.android.car.developeroptions.R;
 import com.android.car.developeroptions.RestrictedSettingsFragment;
 import com.android.car.developeroptions.Utils;
 import com.android.car.developeroptions.search.BaseSearchIndexProvider;
-import com.android.car.developeroptions.search.Indexable;
+import com.android.settingslib.search.Indexable;
 import com.android.settingslib.search.SearchIndexable;
 
 import java.util.ArrayList;
@@ -181,8 +180,7 @@
         if (DBG) log("[updateCellularDataValues] mSubInfoList=" + mSubInfoList);
 
         boolean callStateIdle = isCallStateIdle();
-        final boolean ecbMode = SystemProperties.getBoolean(
-                TelephonyProperties.PROPERTY_INECM_MODE, false);
+        final boolean ecbMode = TelephonyProperties.in_ecm_mode().orElse(false);
         if (sir != null) {
             simPref.setSummary(sir.getDisplayName());
             // Enable data preference in msim mode and call state idle
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/slices/SliceDataConverter.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/slices/SliceDataConverter.java
index 51cd411..1330a93 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/slices/SliceDataConverter.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/slices/SliceDataConverter.java
@@ -50,8 +50,8 @@
 import com.android.car.developeroptions.core.PreferenceXmlParserUtils.MetadataFlag;
 import com.android.car.developeroptions.dashboard.DashboardFragment;
 import com.android.car.developeroptions.overlay.FeatureFactory;
-import com.android.car.developeroptions.search.DatabaseIndexingUtils;
-import com.android.car.developeroptions.search.Indexable.SearchIndexProvider;
+import com.android.settingslib.search.Indexable.SearchIndexProvider;
+import com.android.settingslib.search.SearchIndexableData;
 
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
@@ -95,14 +95,13 @@
     public List<SliceData> getSliceData() {
         List<SliceData> sliceData = new ArrayList<>();
 
-        final Collection<Class> indexableClasses = FeatureFactory.getFactory(mContext)
+        final Collection<SearchIndexableData> bundles = FeatureFactory.getFactory(mContext)
                 .getSearchFeatureProvider().getSearchIndexableResources().getProviderValues();
 
-        for (Class clazz : indexableClasses) {
-            final String fragmentName = clazz.getName();
+        for (SearchIndexableData bundle : bundles) {
+            final String fragmentName = bundle.getTargetClass().getName();
 
-            final SearchIndexProvider provider = DatabaseIndexingUtils.getSearchIndexProvider(
-                    clazz);
+            final SearchIndexProvider provider = bundle.getSearchIndexProvider();
 
             // CodeInspection test guards against the null check. Keep check in case of bad actors.
             if (provider == null) {
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/sound/HandsFreeProfileOutputPreferenceController.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/sound/HandsFreeProfileOutputPreferenceController.java
index 7aada43..9ede35f 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/sound/HandsFreeProfileOutputPreferenceController.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/sound/HandsFreeProfileOutputPreferenceController.java
@@ -66,7 +66,7 @@
             final BluetoothDevice btDevice = mConnectedDevices.get(connectedDeviceIndex);
             mSelectedIndex = connectedDeviceIndex;
             setActiveBluetoothDevice(btDevice);
-            listPreference.setSummary(btDevice.getAliasName());
+            listPreference.setSummary(btDevice.getAlias());
         }
         return true;
     }
@@ -143,7 +143,7 @@
         mediaValues[mSelectedIndex] = defaultSummary;
         for (int i = 0, size = mConnectedDevices.size(); i < size; i++) {
             final BluetoothDevice btDevice = mConnectedDevices.get(i);
-            mediaOutputs[i] = btDevice.getAliasName();
+            mediaOutputs[i] = btDevice.getAlias();
             mediaValues[i] = btDevice.getAddress();
             if (btDevice.equals(activeDevice)) {
                 // select the active connected device.
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/sound/MediaOutputPreferenceController.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/sound/MediaOutputPreferenceController.java
index 64126c9..9229507 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/sound/MediaOutputPreferenceController.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/sound/MediaOutputPreferenceController.java
@@ -86,7 +86,7 @@
         mPreference.setVisible(deviceConnectable);
         mPreference.setSummary((activeDevice == null) ?
                 mContext.getText(R.string.media_output_default_summary) :
-                activeDevice.getAliasName());
+                activeDevice.getAlias());
     }
 
     @Override
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/support/SupportDashboardActivity.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/support/SupportDashboardActivity.java
index 677b5fc..bffb551 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/support/SupportDashboardActivity.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/support/SupportDashboardActivity.java
@@ -24,9 +24,9 @@
 import com.android.car.developeroptions.overlay.FeatureFactory;
 import com.android.car.developeroptions.overlay.SupportFeatureProvider;
 import com.android.car.developeroptions.search.BaseSearchIndexProvider;
-import com.android.car.developeroptions.search.Indexable;
-import com.android.car.developeroptions.search.SearchIndexableRaw;
+import com.android.settingslib.search.Indexable;
 import com.android.settingslib.search.SearchIndexable;
+import com.android.settingslib.search.SearchIndexableRaw;
 
 import java.util.ArrayList;
 import java.util.List;
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/system/ResetDashboardFragment.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/system/ResetDashboardFragment.java
index 7c6ad97..bf49cfd 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/system/ResetDashboardFragment.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/system/ResetDashboardFragment.java
@@ -25,9 +25,9 @@
 import com.android.car.developeroptions.dashboard.DashboardFragment;
 import com.android.car.developeroptions.network.NetworkResetPreferenceController;
 import com.android.car.developeroptions.search.BaseSearchIndexProvider;
-import com.android.car.developeroptions.search.Indexable;
 import com.android.settingslib.core.AbstractPreferenceController;
 import com.android.settingslib.core.lifecycle.Lifecycle;
+import com.android.settingslib.search.Indexable;
 import com.android.settingslib.search.SearchIndexable;
 
 import java.util.ArrayList;
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/system/SystemDashboardFragment.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/system/SystemDashboardFragment.java
index d94d914..ba51aee 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/system/SystemDashboardFragment.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/system/SystemDashboardFragment.java
@@ -29,7 +29,7 @@
 import com.android.car.developeroptions.dashboard.DashboardFragment;
 import com.android.car.developeroptions.overlay.FeatureFactory;
 import com.android.car.developeroptions.search.BaseSearchIndexProvider;
-import com.android.car.developeroptions.search.Indexable;
+import com.android.settingslib.search.Indexable;
 import com.android.settingslib.search.SearchIndexable;
 
 import java.util.Arrays;
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/system/SystemUpdatePreferenceController.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/system/SystemUpdatePreferenceController.java
index 9209d3f..efa06ea 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/system/SystemUpdatePreferenceController.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/system/SystemUpdatePreferenceController.java
@@ -89,7 +89,7 @@
     @Override
     public CharSequence getSummary() {
         CharSequence summary = mContext.getString(R.string.android_version_summary,
-                Build.VERSION.RELEASE);
+                Build.VERSION.RELEASE_OR_CODENAME);
         final FutureTask<Bundle> bundleFutureTask = new FutureTask<>(
                 // Put the API call in a future to avoid StrictMode violation.
                 () -> mUpdateManager.retrieveSystemUpdateInfo());
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/tts/TextToSpeechSettings.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/tts/TextToSpeechSettings.java
index 5b8d88e..73493a8 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/tts/TextToSpeechSettings.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/tts/TextToSpeechSettings.java
@@ -43,9 +43,9 @@
 import com.android.car.developeroptions.SettingsActivity;
 import com.android.car.developeroptions.SettingsPreferenceFragment;
 import com.android.car.developeroptions.search.BaseSearchIndexProvider;
-import com.android.car.developeroptions.search.Indexable;
 import com.android.car.developeroptions.widget.GearPreference;
 import com.android.car.developeroptions.widget.SeekBarPreference;
+import com.android.settingslib.search.Indexable;
 import com.android.settingslib.search.SearchIndexable;
 import com.android.settingslib.widget.ActionButtonsPreference;
 
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/tts/TtsEnginePreferenceFragment.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/tts/TtsEnginePreferenceFragment.java
index 25d8021..d76635b 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/tts/TtsEnginePreferenceFragment.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/tts/TtsEnginePreferenceFragment.java
@@ -17,8 +17,8 @@
 import com.android.car.developeroptions.R;
 import com.android.car.developeroptions.SettingsPreferenceFragment;
 import com.android.car.developeroptions.search.BaseSearchIndexProvider;
-import com.android.car.developeroptions.search.Indexable;
 import com.android.car.developeroptions.tts.TtsEnginePreference.RadioButtonGroupState;
+import com.android.settingslib.search.Indexable;
 import com.android.settingslib.search.SearchIndexable;
 
 import java.util.Arrays;
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/users/MultiUserFooterPreferenceController.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/users/MultiUserFooterPreferenceController.java
index e1d8aef..c3c5f83 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/users/MultiUserFooterPreferenceController.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/users/MultiUserFooterPreferenceController.java
@@ -21,29 +21,18 @@
 import androidx.annotation.VisibleForTesting;
 import androidx.preference.Preference;
 
-import com.android.car.developeroptions.R;
 import com.android.car.developeroptions.core.BasePreferenceController;
-import com.android.settingslib.widget.FooterPreference;
-import com.android.settingslib.widget.FooterPreferenceMixinCompat;
 
 public class MultiUserFooterPreferenceController extends BasePreferenceController {
 
     @VisibleForTesting
     final UserCapabilities mUserCaps;
 
-    private FooterPreferenceMixinCompat mFooterMixin;
-
-    public MultiUserFooterPreferenceController(Context context) {
-        super(context, "dummy_key");
+    public MultiUserFooterPreferenceController(Context context, String key) {
+        super(context, key);
         mUserCaps = UserCapabilities.create(context);
     }
 
-    public MultiUserFooterPreferenceController setFooterMixin(
-            FooterPreferenceMixinCompat footerMixin) {
-        mFooterMixin = footerMixin;
-        return this;
-    }
-
     @Override
     public int getAvailabilityStatus() {
         return (mUserCaps.mEnabled && !mUserCaps.mUserSwitcherEnabled)
@@ -54,8 +43,6 @@
     @Override
     public void updateState(Preference preference) {
         mUserCaps.updateAddUserCapabilities(mContext);
-        final FooterPreference pref = mFooterMixin.createFooterPreference();
-        pref.setTitle(R.string.user_settings_footer_text);
-        pref.setVisible(isAvailable());
+        preference.setVisible(isAvailable());
     }
 }
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/users/UserSettings.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/users/UserSettings.java
index 415ce36..1913222 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/users/UserSettings.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/users/UserSettings.java
@@ -57,8 +57,6 @@
 import androidx.preference.PreferenceGroup;
 import androidx.preference.PreferenceScreen;
 
-import com.android.internal.util.UserIcons;
-import com.android.internal.widget.LockPatternUtils;
 import com.android.car.developeroptions.R;
 import com.android.car.developeroptions.SettingsActivity;
 import com.android.car.developeroptions.SettingsPreferenceFragment;
@@ -67,14 +65,16 @@
 import com.android.car.developeroptions.dashboard.SummaryLoader;
 import com.android.car.developeroptions.password.ChooseLockGeneric;
 import com.android.car.developeroptions.search.BaseSearchIndexProvider;
-import com.android.car.developeroptions.search.Indexable;
 import com.android.car.developeroptions.widget.SwitchBar;
 import com.android.car.developeroptions.widget.SwitchBarController;
+import com.android.internal.util.UserIcons;
+import com.android.internal.widget.LockPatternUtils;
 import com.android.settingslib.RestrictedLockUtils;
 import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
 import com.android.settingslib.RestrictedLockUtilsInternal;
 import com.android.settingslib.RestrictedPreference;
 import com.android.settingslib.drawable.CircleFramedDrawable;
+import com.android.settingslib.search.Indexable;
 import com.android.settingslib.search.SearchIndexable;
 
 import java.io.IOException;
@@ -111,6 +111,7 @@
     private static final String KEY_USER_GUEST = "user_guest";
     private static final String KEY_ADD_USER = "user_add";
     private static final String KEY_ADD_USER_WHEN_LOCKED = "user_settings_add_users_when_locked";
+    private static final String KEY_MULTIUSER_FOOTER = "multiuser_footer";
 
     private static final int MENU_REMOVE_USER = Menu.FIRST;
 
@@ -233,8 +234,8 @@
 
         mAddUserWhenLockedPreferenceController = new AddUserWhenLockedPreferenceController(
                 activity, KEY_ADD_USER_WHEN_LOCKED);
-        mMultiUserFooterPreferenceController = new MultiUserFooterPreferenceController(activity)
-                .setFooterMixin(mFooterPreferenceMixin);
+        mMultiUserFooterPreferenceController = new MultiUserFooterPreferenceController(activity,
+                KEY_MULTIUSER_FOOTER);
 
         final PreferenceScreen screen = getPreferenceScreen();
         mAddUserWhenLockedPreferenceController.displayPreference(screen);
@@ -954,7 +955,10 @@
         final Preference addUserOnLockScreen = getPreferenceScreen().findPreference(
                 mAddUserWhenLockedPreferenceController.getPreferenceKey());
         mAddUserWhenLockedPreferenceController.updateState(addUserOnLockScreen);
-        mMultiUserFooterPreferenceController.updateState(null /* preference */);
+
+        final Preference multiUserFooterPrefence = getPreferenceScreen().findPreference(
+                mMultiUserFooterPreferenceController.getPreferenceKey());
+        mMultiUserFooterPreferenceController.updateState(multiUserFooterPrefence);
         mUserListCategory.setVisible(mUserCaps.mUserSwitcherEnabled);
 
         updateAddUser(context);
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/utils/ManagedServiceSettings.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/utils/ManagedServiceSettings.java
index c266613..2f14cab 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/utils/ManagedServiceSettings.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/utils/ManagedServiceSettings.java
@@ -17,7 +17,6 @@
 package com.android.car.developeroptions.utils;
 
 import android.annotation.Nullable;
-import android.app.ActivityManager;
 import android.app.Dialog;
 import android.app.admin.DevicePolicyManager;
 import android.app.settings.SettingsEnums;
@@ -91,12 +90,8 @@
     @Override
     public void onResume() {
         super.onResume();
-        if (!ActivityManager.isLowRamDeviceStatic()) {
-            mServiceListing.reload();
-            mServiceListing.setListening(true);
-        } else {
-            setEmptyText(R.string.disabled_low_ram_device);
-        }
+        mServiceListing.reload();
+        mServiceListing.setListening(true);
     }
 
     @Override
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/vpn2/AppDialogFragment.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/vpn2/AppDialogFragment.java
index 011760e..472ea18 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/vpn2/AppDialogFragment.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/vpn2/AppDialogFragment.java
@@ -22,6 +22,7 @@
 import android.content.DialogInterface;
 import android.content.pm.PackageInfo;
 import android.net.IConnectivityManager;
+import android.net.VpnManager;
 import android.os.Bundle;
 import android.os.RemoteException;
 import android.os.ServiceManager;
@@ -145,7 +146,8 @@
         }
         final int userId = getUserId();
         try {
-            mService.setVpnPackageAuthorization(mPackageInfo.packageName, userId, false);
+            mService.setVpnPackageAuthorization(
+                    mPackageInfo.packageName, userId, VpnManager.TYPE_VPN_NONE);
             onDisconnect(dialog);
         } catch (RemoteException e) {
             Log.e(TAG, "Failed to forget authorization of " + mPackageInfo.packageName +
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/vpn2/AppVpnInfo.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/vpn2/AppVpnInfo.java
index 87991ac..1c405fe 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/vpn2/AppVpnInfo.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/vpn2/AppVpnInfo.java
@@ -2,8 +2,6 @@
 
 import android.annotation.NonNull;
 
-import com.android.internal.util.Preconditions;
-
 import java.util.Objects;
 
 /**
@@ -16,7 +14,7 @@
 
     public AppVpnInfo(int userId, @NonNull String packageName) {
         this.userId = userId;
-        this.packageName = Preconditions.checkNotNull(packageName);
+        this.packageName = Objects.requireNonNull(packageName);
     }
 
     @Override
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/wallpaper/WallpaperSuggestionActivity.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/wallpaper/WallpaperSuggestionActivity.java
index e6bcf32..aed64f2 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/wallpaper/WallpaperSuggestionActivity.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/wallpaper/WallpaperSuggestionActivity.java
@@ -29,9 +29,9 @@
 import com.android.car.developeroptions.R;
 import com.android.car.developeroptions.core.SubSettingLauncher;
 import com.android.car.developeroptions.search.BaseSearchIndexProvider;
-import com.android.car.developeroptions.search.Indexable;
-import com.android.car.developeroptions.search.SearchIndexableRaw;
+import com.android.settingslib.search.Indexable;
 import com.android.settingslib.search.SearchIndexable;
+import com.android.settingslib.search.SearchIndexableRaw;
 
 import com.google.android.setupcompat.util.WizardManagerHelper;
 
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/wfd/WifiDisplaySettings.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/wfd/WifiDisplaySettings.java
index e2205d3..04616f5 100755
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/wfd/WifiDisplaySettings.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/wfd/WifiDisplaySettings.java
@@ -61,12 +61,12 @@
 import androidx.preference.PreferenceViewHolder;
 import androidx.preference.SwitchPreference;
 
-import com.android.internal.app.MediaRouteDialogPresenter;
 import com.android.car.developeroptions.R;
 import com.android.car.developeroptions.SettingsPreferenceFragment;
 import com.android.car.developeroptions.dashboard.SummaryLoader;
 import com.android.car.developeroptions.search.BaseSearchIndexProvider;
-import com.android.car.developeroptions.search.Indexable;
+import com.android.internal.app.MediaRouteDialogPresenter;
+import com.android.settingslib.search.Indexable;
 import com.android.settingslib.search.SearchIndexable;
 
 import java.util.ArrayList;
@@ -523,7 +523,7 @@
         if (DEBUG) {
             Slog.d(TAG, "Setting listen mode to: " + enable);
         }
-        mWifiP2pManager.listen(mWifiP2pChannel, enable, new ActionListener() {
+        final ActionListener listener = new ActionListener() {
             @Override
             public void onSuccess() {
                 if (DEBUG) {
@@ -537,7 +537,12 @@
                 Slog.e(TAG, "Failed to " + (enable ? "entered" : "exited")
                         + " listen mode with reason " + reason + ".");
             }
-        });
+        };
+        if (enable) {
+            mWifiP2pManager.startListening(mWifiP2pChannel, listener);
+        } else {
+            mWifiP2pManager.stopListening(mWifiP2pChannel, listener);
+        }
     }
 
     private void setWifiP2pChannels(final int lc, final int oc) {
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/widget/ChartGridView.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/widget/ChartGridView.java
index b608544..457e71e 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/widget/ChartGridView.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/widget/ChartGridView.java
@@ -31,9 +31,10 @@
 import android.util.AttributeSet;
 import android.view.View;
 
-import com.android.internal.util.Preconditions;
 import com.android.car.developeroptions.R;
 
+import java.util.Objects;
+
 /**
  * Background of {@link ChartView} that renders grid lines as requested by
  * {@link ChartAxis#getTickPoints()}.
@@ -89,8 +90,8 @@
     }
 
     void init(ChartAxis horiz, ChartAxis vert) {
-        mHoriz = Preconditions.checkNotNull(horiz, "missing horiz");
-        mVert = Preconditions.checkNotNull(vert, "missing vert");
+        mHoriz = Objects.requireNonNull(horiz, "missing horiz");
+        mVert = Objects.requireNonNull(vert, "missing vert");
     }
 
     void setBounds(long start, long end) {
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/widget/ChartSweepView.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/widget/ChartSweepView.java
index e61938b..fcea0de 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/widget/ChartSweepView.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/widget/ChartSweepView.java
@@ -35,9 +35,10 @@
 import android.view.MotionEvent;
 import android.view.View;
 
-import com.android.internal.util.Preconditions;
 import com.android.car.developeroptions.R;
 
+import java.util.Objects;
+
 /**
  * Sweep across a {@link ChartView} at a specific {@link ChartAxis} value, which
  * a user can drag.
@@ -155,7 +156,7 @@
     };
 
     void init(ChartAxis axis) {
-        mAxis = Preconditions.checkNotNull(axis, "missing axis");
+        mAxis = Objects.requireNonNull(axis, "missing axis");
     }
 
     public void setNeighbors(ChartSweepView... neighbors) {
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/widget/ChartView.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/widget/ChartView.java
index ff5293d..b04f408 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/widget/ChartView.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/widget/ChartView.java
@@ -25,9 +25,10 @@
 import android.view.ViewDebug;
 import android.widget.FrameLayout;
 
-import com.android.internal.util.Preconditions;
 import com.android.car.developeroptions.R;
 
+import java.util.Objects;
+
 /**
  * Container for two-dimensional chart, drawn with a combination of
  * {@link ChartGridView} and {@link ChartSweepView} children. The entire chart uses
@@ -70,8 +71,8 @@
     }
 
     void init(ChartAxis horiz, ChartAxis vert) {
-        mHoriz = Preconditions.checkNotNull(horiz, "missing horiz");
-        mVert = Preconditions.checkNotNull(vert, "missing vert");
+        mHoriz = Objects.requireNonNull(horiz, "missing horiz");
+        mVert = Objects.requireNonNull(vert, "missing vert");
     }
 
     public void setOptimalWidth(int optimalWidth, float optimalWidthWeight) {
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/wifi/CaptivePortalNetworkCallback.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/wifi/CaptivePortalNetworkCallback.java
index 2b3955b..d9e94d6 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/wifi/CaptivePortalNetworkCallback.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/wifi/CaptivePortalNetworkCallback.java
@@ -19,7 +19,7 @@
 import android.net.Network;
 import android.net.NetworkCapabilities;
 
-import com.android.internal.util.Preconditions;
+import java.util.Objects;
 
 /** Listens for changes to NetworkCapabilities to update the ConnectedAccessPointPreference. */
 final class CaptivePortalNetworkCallback extends NetworkCallback {
@@ -31,8 +31,8 @@
 
     CaptivePortalNetworkCallback(
             Network network, ConnectedAccessPointPreference connectedApPreference) {
-        mNetwork = Preconditions.checkNotNull(network);
-        mConnectedApPreference = Preconditions.checkNotNull(connectedApPreference);
+        mNetwork = Objects.requireNonNull(network);
+        mConnectedApPreference = Objects.requireNonNull(connectedApPreference);
     }
 
     @Override
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/wifi/ConfigureWifiSettings.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/wifi/ConfigureWifiSettings.java
index 4f5ea7e..8753614 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/wifi/ConfigureWifiSettings.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/wifi/ConfigureWifiSettings.java
@@ -28,9 +28,9 @@
 import com.android.car.developeroptions.R;
 import com.android.car.developeroptions.dashboard.DashboardFragment;
 import com.android.car.developeroptions.search.BaseSearchIndexProvider;
-import com.android.car.developeroptions.search.Indexable;
 import com.android.car.developeroptions.wifi.p2p.WifiP2pPreferenceController;
 import com.android.settingslib.core.AbstractPreferenceController;
+import com.android.settingslib.search.Indexable;
 import com.android.settingslib.search.SearchIndexable;
 
 import java.util.ArrayList;
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/wifi/NetworkRequestDialogFragment.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/wifi/NetworkRequestDialogFragment.java
index 6c32f5c..bf213bb 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/wifi/NetworkRequestDialogFragment.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/wifi/NetworkRequestDialogFragment.java
@@ -30,6 +30,7 @@
 import android.net.wifi.WifiManager.NetworkRequestUserSelectionCallback;
 import android.os.Bundle;
 import android.os.Handler;
+import android.os.HandlerExecutor;
 import android.os.Message;
 import android.text.TextUtils;
 import android.view.LayoutInflater;
@@ -304,7 +305,7 @@
         final WifiManager wifiManager = getContext().getApplicationContext()
                 .getSystemService(WifiManager.class);
         if (wifiManager != null) {
-            wifiManager.registerNetworkRequestMatchCallback(this, mHandler);
+            wifiManager.registerNetworkRequestMatchCallback(new HandlerExecutor(mHandler), this);
         }
         // Sets time-out to stop scanning.
         mHandler.sendEmptyMessageDelayed(MESSAGE_STOP_SCAN_WIFI_LIST, DELAY_TIME_STOP_SCAN_MS);
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/wifi/RequestToggleWiFiActivity.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/wifi/RequestToggleWiFiActivity.java
index 5e24362..4c86e04 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/wifi/RequestToggleWiFiActivity.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/wifi/RequestToggleWiFiActivity.java
@@ -29,12 +29,11 @@
 import android.os.Bundle;
 import android.text.TextUtils;
 import android.util.Log;
-import android.widget.Toast;
 
 import androidx.annotation.NonNull;
 
-import com.android.internal.app.AlertActivity;
 import com.android.car.developeroptions.R;
+import com.android.internal.app.AlertActivity;
 
 /**
  * This activity handles requests to toggle WiFi by collecting user
@@ -313,11 +312,6 @@
                         finish();
                     }
                 } break;
-
-                case WifiManager.ERROR: {
-                    Toast.makeText(activity, R.string.wifi_error, Toast.LENGTH_SHORT).show();
-                    finish();
-                } break;
             }
         }
     }
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/wifi/WifiConfigController.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/wifi/WifiConfigController.java
index faba3c7..aac5b88 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/wifi/WifiConfigController.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/wifi/WifiConfigController.java
@@ -42,7 +42,6 @@
 import android.text.InputType;
 import android.text.TextUtils;
 import android.text.TextWatcher;
-import android.util.FeatureFlagUtils;
 import android.util.Log;
 import android.view.KeyEvent;
 import android.view.View;
@@ -254,8 +253,7 @@
         mMeteredSettingsSpinner = mView.findViewById(R.id.metered_settings);
         mHiddenSettingsSpinner = mView.findViewById(R.id.hidden_settings);
         mPrivacySettingsSpinner = mView.findViewById(R.id.privacy_settings);
-        if (mContext.getResources().getBoolean(
-                com.android.internal.R.bool.config_wifi_connected_mac_randomization_supported)) {
+        if (mWifiManager.isConnectedMacRandomizationSupported()) {
             View privacySettingsLayout = mView.findViewById(R.id.privacy_settings_fields);
             privacySettingsLayout.setVisibility(View.VISIBLE);
         }
@@ -289,11 +287,12 @@
                                 config.macRandomizationSetting);
                 mPrivacySettingsSpinner.setSelection(prefMacValue);
 
-                if (config.getIpAssignment() == IpAssignment.STATIC) {
+                if (config.getIpConfiguration().getIpAssignment() == IpAssignment.STATIC) {
                     mIpSettingsSpinner.setSelection(STATIC_IP);
                     showAdvancedFields = true;
                     // Display IP address.
-                    StaticIpConfiguration staticConfig = config.getStaticIpConfiguration();
+                    StaticIpConfiguration staticConfig = config.getIpConfiguration()
+                            .getStaticIpConfiguration();
                     if (staticConfig != null && staticConfig.ipAddress != null) {
                         addRow(group, R.string.wifi_ip_address,
                                 staticConfig.ipAddress.getAddress().getHostAddress());
@@ -307,10 +306,11 @@
                     showAdvancedFields = true;
                 }
 
-                if (config.getProxySettings() == ProxySettings.STATIC) {
+                ProxySettings proxySettings = config.getIpConfiguration().getProxySettings();
+                if (proxySettings == ProxySettings.STATIC) {
                     mProxySettingsSpinner.setSelection(PROXY_STATIC);
                     showAdvancedFields = true;
-                } else if (config.getProxySettings() == ProxySettings.PAC) {
+                } else if (proxySettings == ProxySettings.PAC) {
                     mProxySettingsSpinner.setSelection(PROXY_PAC);
                     showAdvancedFields = true;
                 } else {
@@ -331,17 +331,10 @@
                 showProxyFields();
                 final CheckBox advancedTogglebox =
                         (CheckBox) mView.findViewById(R.id.wifi_advanced_togglebox);
-                mView.findViewById(R.id.wifi_advanced_toggle).setVisibility(
-                        mAccessPoint.isCarrierAp() ? View.GONE : View.VISIBLE);
                 advancedTogglebox.setOnCheckedChangeListener(this);
                 advancedTogglebox.setChecked(showAdvancedFields);
                 mView.findViewById(R.id.wifi_advanced_fields)
                         .setVisibility(showAdvancedFields ? View.VISIBLE : View.GONE);
-                if (mAccessPoint.isCarrierAp()) {
-                    addRow(group, R.string.wifi_carrier_connect,
-                            String.format(mContext.getString(R.string.wifi_carrier_content),
-                            mAccessPoint.getCarrierName()));
-                }
             }
 
             if (mMode == WifiConfigUiBase.MODE_MODIFY) {
@@ -644,7 +637,7 @@
                 config.allowedKeyManagement.set(KeyMgmt.IEEE8021X);
                 if (mAccessPointSecurity == AccessPoint.SECURITY_EAP_SUITE_B) {
                     config.allowedKeyManagement.set(KeyMgmt.SUITE_B_192);
-                    config.requirePMF = true;
+                    config.requirePmf = true;
                     config.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.GCMP_256);
                     config.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.GCMP_256);
                     config.allowedGroupManagementCiphers.set(WifiConfiguration.GroupMgmtCipher
@@ -761,7 +754,7 @@
                 break;
             case AccessPoint.SECURITY_SAE:
                 config.allowedKeyManagement.set(KeyMgmt.SAE);
-                config.requirePMF = true;
+                config.requirePmf = true;
                 if (mPasswordView.length() != 0) {
                     String password = mPasswordView.getText().toString();
                     config.preSharedKey = '"' + password + '"';
@@ -770,7 +763,7 @@
 
             case AccessPoint.SECURITY_OWE:
                 config.allowedKeyManagement.set(KeyMgmt.OWE);
-                config.requirePMF = true;
+                config.requirePmf = true;
                 break;
 
             default:
@@ -978,10 +971,6 @@
             mEapIdentityView = (TextView) mView.findViewById(R.id.identity);
             mEapAnonymousView = (TextView) mView.findViewById(R.id.anonymous);
 
-            if (mAccessPoint != null && mAccessPoint.isCarrierAp()) {
-                mEapMethodSpinner.setSelection(mAccessPoint.getCarrierApEapType());
-            }
-
             loadCertificates(
                     mEapCaCertSpinner,
                     Credentials.CA_CERTIFICATE,
@@ -1149,9 +1138,6 @@
                 setUserCertInvisible();
                 setPasswordInvisible();
                 setIdentityInvisible();
-                if (mAccessPoint != null && mAccessPoint.isCarrierAp()) {
-                    setEapMethodInvisible();
-                }
                 break;
         }
 
@@ -1246,13 +1232,14 @@
                 mDns2View.addTextChangedListener(this);
             }
             if (config != null) {
-                StaticIpConfiguration staticConfig = config.getStaticIpConfiguration();
+                StaticIpConfiguration staticConfig = config.getIpConfiguration()
+                        .getStaticIpConfiguration();
                 if (staticConfig != null) {
                     if (staticConfig.ipAddress != null) {
                         mIpAddressView.setText(
                                 staticConfig.ipAddress.getAddress().getHostAddress());
                         mNetworkPrefixLengthView.setText(Integer.toString(staticConfig.ipAddress
-                                .getNetworkPrefixLength()));
+                                .getPrefixLength()));
                     }
 
                     if (staticConfig.gateway != null) {
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/wifi/WifiInfoPreferenceController.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/wifi/WifiInfoPreferenceController.java
index 301037b..9e6663a 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/wifi/WifiInfoPreferenceController.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/wifi/WifiInfoPreferenceController.java
@@ -22,7 +22,6 @@
 import android.content.IntentFilter;
 import android.net.wifi.WifiInfo;
 import android.net.wifi.WifiManager;
-import android.provider.Settings;
 import android.text.TextUtils;
 
 import androidx.core.text.BidiFormatter;
@@ -58,7 +57,7 @@
         super(context);
         mWifiManager = wifiManager;
         mFilter = new IntentFilter();
-        mFilter.addAction(WifiManager.LINK_CONFIGURATION_CHANGED_ACTION);
+        mFilter.addAction(WifiManager.ACTION_LINK_CONFIGURATION_CHANGED);
         mFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
 
         lifecycle.addObserver(this);
@@ -98,8 +97,8 @@
     public void updateWifiInfo() {
         if (mWifiMacAddressPref != null) {
             final WifiInfo wifiInfo = mWifiManager.getConnectionInfo();
-            final boolean macRandomizationSupported = mContext.getResources().getBoolean(
-                com.android.internal.R.bool.config_wifi_connected_mac_randomization_supported);
+            final boolean macRandomizationSupported =
+                    mWifiManager.isConnectedMacRandomizationSupported();
             final String macAddress = wifiInfo == null ? null : wifiInfo.getMacAddress();
 
             if (macRandomizationSupported && WifiInfo.DEFAULT_MAC_ADDRESS.equals(macAddress)) {
@@ -123,8 +122,8 @@
         @Override
         public void onReceive(Context context, Intent intent) {
             String action = intent.getAction();
-            if (action.equals(WifiManager.LINK_CONFIGURATION_CHANGED_ACTION) ||
-                    action.equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)) {
+            if (action.equals(WifiManager.ACTION_LINK_CONFIGURATION_CHANGED)
+                    || action.equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)) {
                 updateWifiInfo();
             }
         }
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/wifi/WifiNoInternetDialog.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/wifi/WifiNoInternetDialog.java
index e13aee1..519d47f 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/wifi/WifiNoInternetDialog.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/wifi/WifiNoInternetDialog.java
@@ -38,9 +38,9 @@
 import android.view.View;
 import android.widget.CheckBox;
 
+import com.android.car.developeroptions.R;
 import com.android.internal.app.AlertActivity;
 import com.android.internal.app.AlertController;
-import com.android.car.developeroptions.R;
 
 public final class WifiNoInternetDialog extends AlertActivity implements
         DialogInterface.OnClickListener {
@@ -118,7 +118,7 @@
         }
         mNetworkName = nc.getSSID();
         if (mNetworkName != null) {
-            mNetworkName = WifiInfo.removeDoubleQuotes(mNetworkName);
+            mNetworkName = WifiInfo.sanitizeSsid(mNetworkName);
         }
 
         createDialog();
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/wifi/WifiScanModeActivity.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/wifi/WifiScanModeActivity.java
index 7a75cbd..cb2861a 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/wifi/WifiScanModeActivity.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/wifi/WifiScanModeActivity.java
@@ -24,7 +24,6 @@
 import android.content.pm.PackageManager;
 import android.net.wifi.WifiManager;
 import android.os.Bundle;
-import android.provider.Settings;
 
 import androidx.appcompat.app.AlertDialog;
 import androidx.fragment.app.DialogFragment;
@@ -79,8 +78,7 @@
     }
 
     private void doPositiveClick() {
-        Settings.Global.putInt(getContentResolver(),
-                Settings.Global.WIFI_SCAN_ALWAYS_AVAILABLE, 1);
+        getApplicationContext().getSystemService(WifiManager.class).setScanAlwaysAvailable(true);
         setResult(RESULT_OK);
         finish();
     }
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/wifi/WifiScanningRequiredFragment.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/wifi/WifiScanningRequiredFragment.java
index d5aa87f..505aceb 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/wifi/WifiScanningRequiredFragment.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/wifi/WifiScanningRequiredFragment.java
@@ -23,8 +23,8 @@
 import android.content.Context;
 import android.content.DialogInterface;
 import android.content.Intent;
+import android.net.wifi.WifiManager;
 import android.os.Bundle;
-import android.provider.Settings;
 import android.text.TextUtils;
 import android.util.Log;
 import android.widget.Toast;
@@ -69,8 +69,7 @@
         ContentResolver contentResolver = context.getContentResolver();
         switch(which) {
             case DialogInterface.BUTTON_POSITIVE:
-                Settings.Global.putInt(contentResolver,
-                        Settings.Global.WIFI_SCAN_ALWAYS_AVAILABLE, 1);
+                context.getSystemService(WifiManager.class).setScanAlwaysAvailable(true);
                 Toast.makeText(
                         context,
                         context.getString(R.string.wifi_settings_scanning_required_enabled),
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/wifi/WifiSettings.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/wifi/WifiSettings.java
index 5807647..963806b 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/wifi/WifiSettings.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/wifi/WifiSettings.java
@@ -17,6 +17,7 @@
 package com.android.car.developeroptions.wifi;
 
 import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
+import static android.net.wifi.WifiConfiguration.NetworkSelectionStatus.NETWORK_SELECTION_ENABLED;
 import static android.os.UserManager.DISALLOW_CONFIG_WIFI;
 
 import android.annotation.NonNull;
@@ -61,20 +62,19 @@
 import com.android.car.developeroptions.core.FeatureFlags;
 import com.android.car.developeroptions.core.SubSettingLauncher;
 import com.android.car.developeroptions.dashboard.SummaryLoader;
-import com.android.car.developeroptions.datausage.DataUsageUtils;
 import com.android.car.developeroptions.datausage.DataUsagePreference;
+import com.android.car.developeroptions.datausage.DataUsageUtils;
 import com.android.car.developeroptions.location.ScanningSettings;
 import com.android.car.developeroptions.search.BaseSearchIndexProvider;
-import com.android.car.developeroptions.search.Indexable;
-import com.android.car.developeroptions.search.SearchIndexableRaw;
 import com.android.car.developeroptions.widget.SummaryUpdater.OnSummaryChangeListener;
 import com.android.car.developeroptions.widget.SwitchBarController;
 import com.android.car.developeroptions.wifi.details.WifiNetworkDetailsFragment;
 import com.android.car.developeroptions.wifi.dpp.WifiDppUtils;
-import com.android.car.developeroptions.wifi.savedaccesspoints.SavedAccessPointsWifiSettings;
 import com.android.settingslib.RestrictedLockUtils;
 import com.android.settingslib.RestrictedLockUtilsInternal;
+import com.android.settingslib.search.Indexable;
 import com.android.settingslib.search.SearchIndexable;
+import com.android.settingslib.search.SearchIndexableRaw;
 import com.android.settingslib.wifi.AccessPoint;
 import com.android.settingslib.wifi.AccessPoint.AccessPointListener;
 import com.android.settingslib.wifi.AccessPointPreference;
@@ -699,7 +699,8 @@
         }
         WifiConfiguration.NetworkSelectionStatus networkStatus =
                 config.getNetworkSelectionStatus();
-        if (networkStatus == null || networkStatus.isNetworkEnabled()) {
+        if (networkStatus == null
+                || networkStatus.getNetworkSelectionStatus() == NETWORK_SELECTION_ENABLED) {
             return false;
         }
         int reason = networkStatus.getNetworkSelectionDisableReason();
@@ -965,10 +966,8 @@
         final Context context = getContext();
         final PowerManager powerManager = context.getSystemService(PowerManager.class);
         final ContentResolver contentResolver = context.getContentResolver();
-        return Settings.Global.getInt(contentResolver,
-                Settings.Global.WIFI_WAKEUP_ENABLED, 0) == 1
-                && Settings.Global.getInt(contentResolver,
-                Settings.Global.WIFI_SCAN_ALWAYS_AVAILABLE, 0) == 1
+        return mWifiManager.isAutoWakeupEnabled()
+                && mWifiManager.isScanAlwaysAvailable()
                 && Settings.Global.getInt(contentResolver,
                 Settings.Global.AIRPLANE_MODE_ON, 0) == 0
                 && !powerManager.isPowerSaveMode();
@@ -979,8 +978,8 @@
         // Don't use WifiManager.isScanAlwaysAvailable() to check the Wi-Fi scanning mode. Instead,
         // read the system settings directly. Because when the device is in Airplane mode, even if
         // Wi-Fi scanning mode is on, WifiManager.isScanAlwaysAvailable() still returns "off".
-        final boolean wifiScanningMode = Settings.Global.getInt(getActivity().getContentResolver(),
-                Settings.Global.WIFI_SCAN_ALWAYS_AVAILABLE, 0) == 1;
+        // TODO(b/149421497): Fix this?
+        final boolean wifiScanningMode = mWifiManager.isScanAlwaysAvailable();
         final CharSequence description = wifiScanningMode ? getText(R.string.wifi_scan_notify_text)
                 : getText(R.string.wifi_scan_notify_text_scanning_off);
         final LinkifyUtils.OnClickListener clickListener =
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/wifi/WifiSummaryUpdater.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/wifi/WifiSummaryUpdater.java
index 40fd848..5683c90 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/wifi/WifiSummaryUpdater.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/wifi/WifiSummaryUpdater.java
@@ -90,7 +90,7 @@
         if (!mWifiTracker.connected) {
             return mContext.getString(R.string.disconnected);
         }
-        String ssid = WifiInfo.removeDoubleQuotes(mWifiTracker.ssid);
+        String ssid = WifiInfo.sanitizeSsid(mWifiTracker.ssid);
         if (TextUtils.isEmpty(mWifiTracker.statusLabel)) {
             return ssid;
         }
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/wifi/WifiUtils.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/wifi/WifiUtils.java
index 16db4f0..bbf8284 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/wifi/WifiUtils.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/wifi/WifiUtils.java
@@ -193,7 +193,7 @@
                 config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.IEEE8021X);
                 if (security == AccessPoint.SECURITY_EAP_SUITE_B) {
                     config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.SUITE_B_192);
-                    config.requirePMF = true;
+                    config.requirePmf = true;
                     config.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.GCMP_256);
                     config.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.GCMP_256);
                     config.allowedGroupManagementCiphers.set(WifiConfiguration.GroupMgmtCipher
@@ -207,7 +207,7 @@
                 break;
             case AccessPoint.SECURITY_SAE:
                 config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.SAE);
-                config.requirePMF = true;
+                config.requirePmf = true;
                 if (!TextUtils.isEmpty(password)) {
                     config.preSharedKey = '"' + password + '"';
                 }
@@ -215,7 +215,7 @@
 
             case AccessPoint.SECURITY_OWE:
                 config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.OWE);
-                config.requirePMF = true;
+                config.requirePmf = true;
                 break;
 
             default:
@@ -272,7 +272,7 @@
             return CONNECT_TYPE_OPEN_NETWORK;
         } else if (accessPoint.isSaved() && config != null
                 && config.getNetworkSelectionStatus() != null
-                && config.getNetworkSelectionStatus().getHasEverConnected()) {
+                && config.getNetworkSelectionStatus().hasEverConnected()) {
             return CONNECT_TYPE_SAVED_NETWORK;
         } else if (accessPoint.isPasspoint()) {
             // Access point provided by an installed Passpoint provider, connect using
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/wifi/WifiWakeupPreferenceController.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/wifi/WifiWakeupPreferenceController.java
index 2661f4d..182dc5a 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/wifi/WifiWakeupPreferenceController.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/wifi/WifiWakeupPreferenceController.java
@@ -24,6 +24,7 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.location.LocationManager;
+import android.net.wifi.WifiManager;
 import android.provider.Settings;
 import android.text.TextUtils;
 
@@ -59,6 +60,9 @@
     SwitchPreference mPreference;
     @VisibleForTesting
     LocationManager mLocationManager;
+
+    @VisibleForTesting
+    WifiManager mWifiManager;
     private final BroadcastReceiver mLocationReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
@@ -73,6 +77,7 @@
         super(context);
         mFragment = fragment;
         mLocationManager = (LocationManager) context.getSystemService(Service.LOCATION_SERVICE);
+        mWifiManager = context.getSystemService(WifiManager.class);
         lifecycle.addObserver(this);
     }
 
@@ -152,8 +157,7 @@
     }
 
     private boolean getWifiScanningEnabled() {
-        return Settings.Global.getInt(mContext.getContentResolver(),
-                Settings.Global.WIFI_SCAN_ALWAYS_AVAILABLE, 0) == 1;
+        return mWifiManager.isScanAlwaysAvailable();
     }
 
     private void showScanningDialog() {
@@ -164,13 +168,11 @@
     }
 
     private boolean getWifiWakeupEnabled() {
-        return Settings.Global.getInt(mContext.getContentResolver(),
-                Settings.Global.WIFI_WAKEUP_ENABLED, 0) == 1;
+        return mWifiManager.isAutoWakeupEnabled();
     }
 
     private void setWifiWakeupEnabled(boolean enabled) {
-        Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.WIFI_WAKEUP_ENABLED,
-                enabled ? 1 : 0);
+        mWifiManager.setAutoWakeupEnabled(enabled);
     }
 
     @Override
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/wifi/details/WifiPrivacyPreferenceController.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/wifi/details/WifiPrivacyPreferenceController.java
index 2b5ba22..308f6bb 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/wifi/details/WifiPrivacyPreferenceController.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/wifi/details/WifiPrivacyPreferenceController.java
@@ -63,9 +63,8 @@
 
     @Override
     public int getAvailabilityStatus() {
-        return mContext.getResources().getBoolean(
-                com.android.internal.R.bool.config_wifi_connected_mac_randomization_supported) ?
-                AVAILABLE : CONDITIONALLY_UNAVAILABLE;
+        return mWifiManager.isConnectedMacRandomizationSupported()
+                ?  AVAILABLE : CONDITIONALLY_UNAVAILABLE;
     }
 
     @Override
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/wifi/dpp/WifiDppUtils.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/wifi/dpp/WifiDppUtils.java
index 882dcf7..ee7a3e2 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/wifi/dpp/WifiDppUtils.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/wifi/dpp/WifiDppUtils.java
@@ -20,8 +20,9 @@
 import android.content.Context;
 import android.content.Intent;
 import android.hardware.biometrics.BiometricPrompt;
-import android.net.wifi.WifiConfiguration.KeyMgmt;
+import android.net.wifi.SoftApConfiguration;
 import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiConfiguration.KeyMgmt;
 import android.net.wifi.WifiManager;
 import android.os.CancellationSignal;
 import android.os.Handler;
@@ -29,15 +30,12 @@
 import android.os.VibrationEffect;
 import android.os.Vibrator;
 import android.text.TextUtils;
-import android.util.FeatureFlagUtils;
 
 import com.android.car.developeroptions.R;
-
 import com.android.settingslib.wifi.AccessPoint;
 
-import java.util.List;
-
 import java.time.Duration;
+import java.util.List;
 
 /**
  * Here are the items shared by both WifiDppConfiguratorActivity & WifiDppEnrolleeActivity
@@ -173,6 +171,19 @@
                 WifiQrCode.SECURITY_NO_PASSWORD : WifiQrCode.SECURITY_WEP;
     }
 
+    private static String getSecurityString(SoftApConfiguration config) {
+        switch (config.getSecurityType()) {
+            case SoftApConfiguration.SECURITY_TYPE_WPA3_SAE:
+            // TODO: add support for SoftApConfiguration.SECURITY_TYPE_WPA3_SAE_TRANSITION
+                return WifiQrCode.SECURITY_SAE;
+            case SoftApConfiguration.SECURITY_TYPE_WPA2_PSK:
+                return WifiQrCode.SECURITY_WPA_PSK;
+            case SoftApConfiguration.SECURITY_TYPE_OPEN:
+            default:
+                return WifiQrCode.SECURITY_NO_PASSWORD;
+        }
+    }
+
     /**
      * Returns an intent to launch QR code generator. It may return null if the security is not
      * supported by QR code generator.
@@ -235,20 +246,19 @@
      * the security is not supported by QR code generator.
      *
      * @param context The context to use for the content resolver
-     * @param wifiManager An instance of {@link WifiManager}
-     * @param wifiConfiguration {@link WifiConfiguration} of the Wi-Fi hotspot
+     * @param softApConfiguration {@link WifiConfiguration} of the Wi-Fi hotspot
      * @return Intent for launching QR code generator
      */
     public static Intent getHotspotConfiguratorIntentOrNull(Context context,
-            WifiManager wifiManager, WifiConfiguration wifiConfiguration) {
+            SoftApConfiguration softApConfiguration) {
         final Intent intent = new Intent(context, WifiDppConfiguratorActivity.class);
-        if (isSupportHotspotConfiguratorQrCodeGenerator(wifiConfiguration)) {
+        if (isSupportHotspotConfiguratorQrCodeGenerator(softApConfiguration)) {
             intent.setAction(WifiDppConfiguratorActivity.ACTION_CONFIGURATOR_QR_CODE_GENERATOR);
         } else {
             return null;
         }
 
-        setConfiguratorIntentExtra(intent, wifiManager, wifiConfiguration);
+        setConfiguratorIntentExtra(intent, softApConfiguration);
 
         intent.putExtra(EXTRA_WIFI_NETWORK_ID, WifiConfiguration.INVALID_NETWORK_ID);
         intent.putExtra(EXTRA_IS_HOTSPOT, true);
@@ -289,6 +299,30 @@
     }
 
     /**
+     * Set all extra except {@code EXTRA_WIFI_NETWORK_ID} for the intent to
+     * launch configurator activity later.
+     *
+     * @param intent the target to set extra
+     * @param softApConfig the Wi-Fi network for launching configurator activity
+     */
+    private static void setConfiguratorIntentExtra(
+            Intent intent, SoftApConfiguration softApConfig) {
+        final String ssid = softApConfig.getSsid();
+        final String security = getSecurityString(softApConfig);
+        String preSharedKey = softApConfig.getPassphrase();
+
+        if (!TextUtils.isEmpty(ssid)) {
+            intent.putExtra(EXTRA_WIFI_SSID, ssid);
+        }
+        if (!TextUtils.isEmpty(security)) {
+            intent.putExtra(EXTRA_WIFI_SECURITY, security);
+        }
+        if (!TextUtils.isEmpty(preSharedKey)) {
+            intent.putExtra(EXTRA_WIFI_PRE_SHARED_KEY, preSharedKey);
+        }
+    }
+
+    /**
      * Shows authentication screen to confirm credentials (pin, pattern or password) for the current
      * user of the device.
      *
@@ -367,12 +401,13 @@
     }
 
     private static boolean isSupportHotspotConfiguratorQrCodeGenerator(
-            WifiConfiguration wifiConfiguration) {
+            SoftApConfiguration softApConfiguration) {
         // QR code generator produces QR code with ZXing's Wi-Fi network config format,
         // it supports PSK and WEP and non security
         // KeyMgmt.NONE is for WEP or non security
-        return wifiConfiguration.allowedKeyManagement.get(KeyMgmt.WPA2_PSK) ||
-                wifiConfiguration.allowedKeyManagement.get(KeyMgmt.NONE);
+        int securityType = softApConfiguration.getSecurityType();
+        return securityType == SoftApConfiguration.SECURITY_TYPE_WPA2_PSK
+                || securityType == SoftApConfiguration.SECURITY_TYPE_OPEN;
     }
 
     private static boolean isSupportWifiDpp(Context context, int accesspointSecurity) {
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/wifi/dpp/WifiNetworkConfig.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/wifi/dpp/WifiNetworkConfig.java
index c70cd2d..58091cd 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/wifi/dpp/WifiNetworkConfig.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/wifi/dpp/WifiNetworkConfig.java
@@ -257,7 +257,7 @@
             final WifiConfiguration enhancedOpenNetworkWifiConfiguration =
                     getBasicWifiConfiguration();
             enhancedOpenNetworkWifiConfiguration.allowedKeyManagement.set(KeyMgmt.OWE);
-            enhancedOpenNetworkWifiConfiguration.requirePMF = true;
+            enhancedOpenNetworkWifiConfiguration.requirePmf = true;
             wifiConfigurations.add(enhancedOpenNetworkWifiConfiguration);
             return wifiConfigurations;
         }
@@ -286,7 +286,7 @@
             }
         } else if (mSecurity.startsWith(SECURITY_SAE)) {
             wifiConfiguration.allowedKeyManagement.set(KeyMgmt.SAE);
-            wifiConfiguration.requirePMF = true;
+            wifiConfiguration.requirePmf = true;
             if (mPreSharedKey.length() != 0) {
                 wifiConfiguration.preSharedKey = addQuotationIfNeeded(mPreSharedKey);
             }
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/wifi/p2p/WifiP2pSettings.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/wifi/p2p/WifiP2pSettings.java
index a9f1ff5..bc60bec 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/wifi/p2p/WifiP2pSettings.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/wifi/p2p/WifiP2pSettings.java
@@ -147,7 +147,7 @@
                 } else {
                     updateSearchMenu(false);
                 }
-            } else if (WifiP2pManager.WIFI_P2P_PERSISTENT_GROUPS_CHANGED_ACTION.equals(action)) {
+            } else if (WifiP2pManager.ACTION_WIFI_P2P_PERSISTENT_GROUPS_CHANGED.equals(action)) {
                 if (mWifiP2pManager != null) {
                     mWifiP2pManager.requestPersistentGroupInfo(mChannel, WifiP2pSettings.this);
                 }
@@ -334,7 +334,7 @@
         mIntentFilter.addAction(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION);
         mIntentFilter.addAction(WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION);
         mIntentFilter.addAction(WifiP2pManager.WIFI_P2P_DISCOVERY_CHANGED_ACTION);
-        mIntentFilter.addAction(WifiP2pManager.WIFI_P2P_PERSISTENT_GROUPS_CHANGED_ACTION);
+        mIntentFilter.addAction(WifiP2pManager.ACTION_WIFI_P2P_PERSISTENT_GROUPS_CHANGED);
         final PreferenceScreen preferenceScreen = getPreferenceScreen();
 
         getActivity().registerReceiver(mReceiver, mIntentFilter);
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/wifi/slice/ContextualWifiSlice.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/wifi/slice/ContextualWifiSlice.java
index d0f490d..ae2a211 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/wifi/slice/ContextualWifiSlice.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/wifi/slice/ContextualWifiSlice.java
@@ -18,7 +18,7 @@
 
 import android.content.Context;
 import android.net.Uri;
-import android.net.wifi.WifiSsid;
+import android.net.wifi.WifiManager;
 import android.text.TextUtils;
 import android.util.Log;
 
@@ -69,6 +69,6 @@
     }
 
     private boolean hasWorkingNetwork() {
-        return !TextUtils.equals(getActiveSSID(), WifiSsid.NONE) && !isCaptivePortal();
+        return !TextUtils.equals(getActiveSSID(), WifiManager.UNKNOWN_SSID) && !isCaptivePortal();
     }
 }
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/wifi/slice/WifiScanWorker.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/wifi/slice/WifiScanWorker.java
index d10821c..c880f80 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/wifi/slice/WifiScanWorker.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/wifi/slice/WifiScanWorker.java
@@ -34,7 +34,6 @@
 
 import androidx.annotation.VisibleForTesting;
 
-import com.android.internal.util.Preconditions;
 import com.android.car.developeroptions.slices.SliceBackgroundWorker;
 import com.android.car.developeroptions.wifi.WifiUtils;
 import com.android.settingslib.wifi.AccessPoint;
@@ -42,6 +41,7 @@
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Objects;
 
 /**
  * {@link SliceBackgroundWorker} for Wi-Fi, used by WifiSlice.
@@ -185,7 +185,7 @@
         private boolean mIsCaptivePortal;
 
         CaptivePortalNetworkCallback(Network network) {
-            mNetwork = Preconditions.checkNotNull(network);
+            mNetwork = Objects.requireNonNull(network);
         }
 
         @Override
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/wifi/slice/WifiSlice.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/wifi/slice/WifiSlice.java
index 5563add..e30716a 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/wifi/slice/WifiSlice.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/wifi/slice/WifiSlice.java
@@ -34,12 +34,11 @@
 import android.net.ConnectivityManager;
 import android.net.NetworkCapabilities;
 import android.net.NetworkInfo;
-import android.net.NetworkInfo.State;
 import android.net.NetworkInfo.DetailedState;
+import android.net.NetworkInfo.State;
 import android.net.Uri;
 import android.net.wifi.WifiInfo;
 import android.net.wifi.WifiManager;
-import android.net.wifi.WifiSsid;
 import android.os.Bundle;
 import android.text.Spannable;
 import android.text.SpannableString;
@@ -321,9 +320,9 @@
 
     protected String getActiveSSID() {
         if (mWifiManager.getWifiState() != WifiManager.WIFI_STATE_ENABLED) {
-            return WifiSsid.NONE;
+            return WifiManager.UNKNOWN_SSID;
         }
-        return WifiInfo.removeDoubleQuotes(mWifiManager.getConnectionInfo().getSSID());
+        return WifiInfo.sanitizeSsid(mWifiManager.getConnectionInfo().getSSID());
     }
 
     private boolean isWifiEnabled() {
@@ -340,7 +339,7 @@
         switch (mWifiManager.getWifiState()) {
             case WifiManager.WIFI_STATE_ENABLED:
                 final String ssid = getActiveSSID();
-                if (TextUtils.equals(ssid, WifiSsid.NONE)) {
+                if (TextUtils.equals(ssid, WifiManager.UNKNOWN_SSID)) {
                     return mContext.getText(R.string.disconnected);
                 }
                 return ssid;
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/wifi/tether/WifiTetherApBandPreferenceController.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/wifi/tether/WifiTetherApBandPreferenceController.java
index a03e822..3e4bde0 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/wifi/tether/WifiTetherApBandPreferenceController.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/wifi/tether/WifiTetherApBandPreferenceController.java
@@ -18,7 +18,7 @@
 
 import android.content.Context;
 import android.content.res.Resources;
-import android.net.wifi.WifiConfiguration;
+import android.net.wifi.SoftApConfiguration;
 import android.util.Log;
 
 import androidx.annotation.VisibleForTesting;
@@ -34,33 +34,32 @@
 
     private String[] mBandEntries;
     private String[] mBandSummaries;
-    private int mBandIndex;
-    private boolean isDualMode;
+    private int mBand;
 
     public WifiTetherApBandPreferenceController(Context context,
             OnTetherConfigUpdateListener listener) {
         super(context, listener);
-        isDualMode = mWifiManager.isDualModeSupported();
         updatePreferenceEntries();
     }
 
     @Override
     public void updateDisplay() {
-        final WifiConfiguration config = mWifiManager.getWifiApConfiguration();
+        final SoftApConfiguration config = mWifiManager.getSoftApConfiguration();
         if (config == null) {
-            mBandIndex = 0;
-            Log.d(TAG, "Updating band index to 0 because no config");
+            mBand = SoftApConfiguration.BAND_2GHZ;
+            Log.d(TAG, "Updating band to 2GHz because no config");
         } else if (is5GhzBandSupported()) {
-            mBandIndex = validateSelection(config.apBand);
-            Log.d(TAG, "Updating band index to " + mBandIndex);
+            mBand = validateSelection(config.getBand());
+            Log.d(TAG, "Updating band to " + mBand);
         } else {
-            config.apBand = 0;
-            mWifiManager.setWifiApConfiguration(config);
-            mBandIndex = config.apBand;
-            Log.d(TAG, "5Ghz not supported, updating band index to " + mBandIndex);
+            SoftApConfiguration newConfig = new SoftApConfiguration.Builder(config)
+                    .setBand(SoftApConfiguration.BAND_2GHZ)
+                    .build();
+            mWifiManager.setSoftApConfiguration(newConfig);
+            mBand = newConfig.getBand();
+            Log.d(TAG, "5Ghz not supported, updating band to " + mBand);
         }
-        ListPreference preference =
-                (ListPreference) mPreference;
+        ListPreference preference = (ListPreference) mPreference;
         preference.setEntries(mBandSummaries);
         preference.setEntryValues(mBandEntries);
 
@@ -68,16 +67,23 @@
             preference.setEnabled(false);
             preference.setSummary(R.string.wifi_ap_choose_2G);
         } else {
-            preference.setValue(Integer.toString(config.apBand));
+            preference.setValue(Integer.toString(config.getBand()));
             preference.setSummary(getConfigSummary());
         }
     }
 
     String getConfigSummary() {
-        if (mBandIndex == WifiConfiguration.AP_BAND_ANY) {
-           return mContext.getString(R.string.wifi_ap_prefer_5G);
+        switch (mBand) {
+            case SoftApConfiguration.BAND_2GHZ | SoftApConfiguration.BAND_5GHZ:
+                return mContext.getString(R.string.wifi_ap_prefer_5G);
+            case SoftApConfiguration.BAND_2GHZ:
+                return mBandSummaries[0];
+            case SoftApConfiguration.BAND_5GHZ:
+                return mBandSummaries[1];
+            default:
+                Log.e(TAG, "Unknown band: " + mBand);
+                return mContext.getString(R.string.wifi_ap_prefer_5G);
         }
-        return mBandSummaries[mBandIndex];
     }
 
     @Override
@@ -87,27 +93,22 @@
 
     @Override
     public boolean onPreferenceChange(Preference preference, Object newValue) {
-        mBandIndex = validateSelection(Integer.parseInt((String) newValue));
-        Log.d(TAG, "Band preference changed, updating band index to " + mBandIndex);
+        mBand = validateSelection(Integer.parseInt((String) newValue));
+        Log.d(TAG, "Band preference changed, updating band to " + mBand);
         preference.setSummary(getConfigSummary());
         mListener.onTetherConfigUpdated();
         return true;
     }
 
     private int validateSelection(int band) {
-        // Reset the band to 2.4 GHz if we get a weird config back to avoid a crash.
-        final boolean isDualMode = mWifiManager.isDualModeSupported();
-
         // unsupported states:
-        // 1: no dual mode means we can't have AP_BAND_ANY - default to 5GHZ
-        // 2: no 5 GHZ support means we can't have AP_BAND_5GHZ - default to 2GHZ
-        // 3: With Dual mode support we can't have AP_BAND_5GHZ - default to ANY
-        if (!isDualMode && WifiConfiguration.AP_BAND_ANY == band) {
-            return WifiConfiguration.AP_BAND_5GHZ;
-        } else if (!is5GhzBandSupported() && WifiConfiguration.AP_BAND_5GHZ == band) {
-            return WifiConfiguration.AP_BAND_2GHZ;
-        } else if (isDualMode && WifiConfiguration.AP_BAND_5GHZ == band) {
-            return WifiConfiguration.AP_BAND_ANY;
+        // 1: BAND_5GHZ only - include 2GHZ since some of countries doesn't support 5G hotspot
+        // 2: no 5 GHZ support means we can't have BAND_5GHZ - default to 2GHZ
+        if (SoftApConfiguration.BAND_5GHZ == band) {
+            if (!is5GhzBandSupported()) {
+                return SoftApConfiguration.BAND_2GHZ;
+            }
+            return SoftApConfiguration.BAND_5GHZ | SoftApConfiguration.BAND_2GHZ;
         }
 
         return band;
@@ -116,26 +117,18 @@
     @VisibleForTesting
     void updatePreferenceEntries() {
         Resources res = mContext.getResources();
-        int entriesRes = R.array.wifi_ap_band_config_full;
-        int summariesRes = R.array.wifi_ap_band_summary_full;
-        // change the list options if this is a dual mode device
-        if (isDualMode) {
-            entriesRes = R.array.wifi_ap_band_dual_mode;
-            summariesRes = R.array.wifi_ap_band_dual_mode_summary;
-        }
+        int entriesRes = R.array.wifi_ap_band;
+        int summariesRes = R.array.wifi_ap_band_summary;
         mBandEntries = res.getStringArray(entriesRes);
         mBandSummaries = res.getStringArray(summariesRes);
     }
 
     private boolean is5GhzBandSupported() {
         final String countryCode = mWifiManager.getCountryCode();
-        if (!mWifiManager.isDualBandSupported() || countryCode == null) {
-            return false;
-        }
-        return true;
+        return mWifiManager.is5GHzBandSupported() && countryCode != null;
     }
 
-    public int getBandIndex() {
-        return mBandIndex;
+    public int getBand() {
+        return mBand;
     }
 }
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/wifi/tether/WifiTetherPasswordPreferenceController.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/wifi/tether/WifiTetherPasswordPreferenceController.java
index 82d7e51..7ffda79 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/wifi/tether/WifiTetherPasswordPreferenceController.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/wifi/tether/WifiTetherPasswordPreferenceController.java
@@ -17,7 +17,7 @@
 package com.android.car.developeroptions.wifi.tether;
 
 import android.content.Context;
-import android.net.wifi.WifiConfiguration;
+import android.net.wifi.SoftApConfiguration;
 import android.text.TextUtils;
 
 import androidx.preference.EditTextPreference;
@@ -48,12 +48,13 @@
 
     @Override
     public void updateDisplay() {
-        final WifiConfiguration config = mWifiManager.getWifiApConfiguration();
-        if (config == null || (config.getAuthType() == WifiConfiguration.KeyMgmt.WPA2_PSK
-                && TextUtils.isEmpty(config.preSharedKey))) {
+        final SoftApConfiguration config = mWifiManager.getSoftApConfiguration();
+        if (config == null || (
+                config.getSecurityType() == SoftApConfiguration.SECURITY_TYPE_WPA2_PSK
+                && TextUtils.isEmpty(config.getPassphrase()))) {
             mPassword = generateRandomPassword();
         } else {
-            mPassword = config.preSharedKey;
+            mPassword = config.getPassphrase();
         }
         ((ValidatedEditTextPreference) mPreference).setValidator(this);
         ((ValidatedEditTextPreference) mPreference).setIsPassword(true);
@@ -79,8 +80,8 @@
      */
     public String getPasswordValidated(int securityType) {
         // don't actually overwrite unless we get a new config in case it was accidentally toggled.
-        if (securityType == WifiConfiguration.KeyMgmt.NONE) {
-            return "";
+        if (securityType == SoftApConfiguration.SECURITY_TYPE_OPEN) {
+            return null;
         } else if (!isTextValid(mPassword)) {
             mPassword = generateRandomPassword();
             updatePasswordDisplay((EditTextPreference) mPreference);
@@ -89,7 +90,7 @@
     }
 
     public void updateVisibility(int securityType) {
-        mPreference.setVisible(securityType != WifiConfiguration.KeyMgmt.NONE);
+        mPreference.setVisible(securityType != SoftApConfiguration.SECURITY_TYPE_OPEN);
     }
 
     @Override
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/wifi/tether/WifiTetherPreferenceController.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/wifi/tether/WifiTetherPreferenceController.java
index 3ce80a5..66ee41a 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/wifi/tether/WifiTetherPreferenceController.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/wifi/tether/WifiTetherPreferenceController.java
@@ -16,15 +16,14 @@
 
 package com.android.car.developeroptions.wifi.tether;
 
-import android.content.BroadcastReceiver;
+import android.annotation.NonNull;
 import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
 import android.net.ConnectivityManager;
-import android.net.wifi.WifiConfiguration;
+import android.net.wifi.SoftApConfiguration;
+import android.net.wifi.WifiClient;
 import android.net.wifi.WifiManager;
-import android.provider.Settings;
 import android.text.BidiFormatter;
+import android.util.Log;
 
 import androidx.annotation.VisibleForTesting;
 import androidx.preference.Preference;
@@ -39,9 +38,12 @@
 import com.android.settingslib.core.lifecycle.events.OnStart;
 import com.android.settingslib.core.lifecycle.events.OnStop;
 
+import java.util.List;
+
 public class WifiTetherPreferenceController extends AbstractPreferenceController
         implements PreferenceControllerMixin, LifecycleObserver, OnStart, OnStop {
 
+    private static final String TAG = "WifiTetherPreferenceController";
     private static final String WIFI_TETHER_SETTINGS = "wifi_tether";
 
     private final ConnectivityManager mConnectivityManager;
@@ -128,13 +130,14 @@
                     }
 
                     @Override
-                    public void onNumClientsChanged(int numClients) {
+                    public void onConnectedClientsChanged(List<WifiClient> clients) {
                         if (mPreference != null
                                 && mSoftApState == WifiManager.WIFI_AP_STATE_ENABLED) {
                             // Only show the number of clients when state is on
+                            int numberOfClients = clients.size();
                             mPreference.setSummary(mContext.getResources().getQuantityString(
-                                    R.plurals.wifi_tether_connected_summary, numClients,
-                                    numClients));
+                                    R.plurals.wifi_tether_connected_summary, numberOfClients,
+                                    numberOfClients));
                         }
                     }
                 });
@@ -147,8 +150,7 @@
                 mPreference.setSummary(R.string.wifi_tether_starting);
                 break;
             case WifiManager.WIFI_AP_STATE_ENABLED:
-                WifiConfiguration wifiConfig = mWifiManager.getWifiApConfiguration();
-                updateConfigSummary(wifiConfig);
+                updateConfigSummary(mWifiManager.getSoftApConfiguration());
                 break;
             case WifiManager.WIFI_AP_STATE_DISABLING:
                 mPreference.setSummary(R.string.wifi_tether_stopping);
@@ -165,12 +167,13 @@
         }
     }
 
-    private void updateConfigSummary(WifiConfiguration wifiConfig) {
-        final String s = mContext.getString(
-                com.android.internal.R.string.wifi_tether_configure_ssid_default);
-
+    private void updateConfigSummary(@NonNull SoftApConfiguration softApConfig) {
+        if (softApConfig == null) {
+            // should never happen.
+            Log.e(TAG, "Ap config null unexpectedly");
+            return;
+        }
         mPreference.setSummary(mContext.getString(R.string.wifi_tether_enabled_subtext,
-                BidiFormatter.getInstance().unicodeWrap(
-                        (wifiConfig == null) ? s : wifiConfig.SSID)));
+                BidiFormatter.getInstance().unicodeWrap(softApConfig.getSsid())));
     }
 }
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/wifi/tether/WifiTetherSSIDPreferenceController.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/wifi/tether/WifiTetherSSIDPreferenceController.java
index 14b637c..d64f646 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/wifi/tether/WifiTetherSSIDPreferenceController.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/wifi/tether/WifiTetherSSIDPreferenceController.java
@@ -18,15 +18,13 @@
 
 import android.content.Context;
 import android.content.Intent;
-import android.net.wifi.WifiConfiguration;
+import android.net.wifi.SoftApConfiguration;
 import android.util.Log;
-import android.view.View;
 
 import androidx.annotation.VisibleForTesting;
 import androidx.preference.EditTextPreference;
 import androidx.preference.Preference;
 
-import com.android.car.developeroptions.R;
 import com.android.car.developeroptions.widget.ValidatedEditTextPreference;
 import com.android.car.developeroptions.wifi.dpp.WifiDppUtils;
 
@@ -54,17 +52,16 @@
 
     @Override
     public void updateDisplay() {
-        final WifiConfiguration config = mWifiManager.getWifiApConfiguration();
+        final SoftApConfiguration config = mWifiManager.getSoftApConfiguration();
         if (config != null) {
-            mSSID = config.SSID;
+            mSSID = config.getSsid();
         } else {
             mSSID = DEFAULT_SSID;
         }
         ((ValidatedEditTextPreference) mPreference).setValidator(this);
 
         if (mWifiManager.isWifiApEnabled() && config != null) {
-            final Intent intent = WifiDppUtils.getHotspotConfiguratorIntentOrNull(mContext,
-                    mWifiManager, config);
+            final Intent intent = WifiDppUtils.getHotspotConfiguratorIntentOrNull(mContext, config);
 
             if (intent == null) {
                 Log.e(TAG, "Invalid security to share hotspot");
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/wifi/tether/WifiTetherSecurityPreferenceController.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/wifi/tether/WifiTetherSecurityPreferenceController.java
index 4ac12d2..6b20d52 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/wifi/tether/WifiTetherSecurityPreferenceController.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/wifi/tether/WifiTetherSecurityPreferenceController.java
@@ -1,7 +1,7 @@
 package com.android.car.developeroptions.wifi.tether;
 
 import android.content.Context;
-import android.net.wifi.WifiConfiguration;
+import android.net.wifi.SoftApConfiguration;
 
 import androidx.preference.ListPreference;
 import androidx.preference.Preference;
@@ -28,12 +28,11 @@
 
     @Override
     public void updateDisplay() {
-        final WifiConfiguration config = mWifiManager.getWifiApConfiguration();
-        if (config != null && config.getAuthType() == WifiConfiguration.KeyMgmt.NONE) {
-            mSecurityValue = WifiConfiguration.KeyMgmt.NONE;
-
+        final SoftApConfiguration config = mWifiManager.getSoftApConfiguration();
+        if (config != null && config.getSecurityType() == SoftApConfiguration.SECURITY_TYPE_OPEN) {
+            mSecurityValue = SoftApConfiguration.SECURITY_TYPE_OPEN;
         } else {
-            mSecurityValue = WifiConfiguration.KeyMgmt.WPA2_PSK;
+            mSecurityValue = SoftApConfiguration.SECURITY_TYPE_WPA2_PSK;
         }
 
         final ListPreference preference = (ListPreference) mPreference;
@@ -54,7 +53,7 @@
     }
 
     private String getSummaryForSecurityType(int securityType) {
-        if (securityType == WifiConfiguration.KeyMgmt.NONE) {
+        if (securityType == SoftApConfiguration.SECURITY_TYPE_OPEN) {
             return mSecurityEntries[1];
         }
         // WPA2 PSK
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/wifi/tether/WifiTetherSettings.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/wifi/tether/WifiTetherSettings.java
index 1071563..5bbcd8d 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/wifi/tether/WifiTetherSettings.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/wifi/tether/WifiTetherSettings.java
@@ -24,7 +24,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
-import android.net.wifi.WifiConfiguration;
+import android.net.wifi.SoftApConfiguration;
 import android.net.wifi.WifiManager;
 import android.os.Bundle;
 import android.os.UserManager;
@@ -186,10 +186,10 @@
 
     @Override
     public void onTetherConfigUpdated() {
-        final WifiConfiguration config = buildNewConfig();
-        mPasswordPreferenceController.updateVisibility(config.getAuthType());
+        final SoftApConfiguration config = buildNewConfig();
+        mPasswordPreferenceController.updateVisibility(config.getSecurityType());
 
-        /**
+        /*
          * if soft AP is stopped, bring up
          * else restart with new config
          * TODO: update config on a running access point when framework support is added
@@ -200,19 +200,18 @@
             mRestartWifiApAfterConfigChange = true;
             mSwitchBarController.stopTether();
         }
-        mWifiManager.setWifiApConfiguration(config);
+        mWifiManager.setSoftApConfiguration(config);
     }
 
-    private WifiConfiguration buildNewConfig() {
-        final WifiConfiguration config = new WifiConfiguration();
+    private SoftApConfiguration buildNewConfig() {
         final int securityType = mSecurityPreferenceController.getSecurityType();
 
-        config.SSID = mSSIDPreferenceController.getSSID();
-        config.allowedKeyManagement.set(securityType);
-        config.preSharedKey = mPasswordPreferenceController.getPasswordValidated(securityType);
-        config.allowedAuthAlgorithms.set(WifiConfiguration.AuthAlgorithm.OPEN);
-        config.apBand = mApBandPreferenceController.getBandIndex();
-        return config;
+        return new SoftApConfiguration.Builder()
+                .setSsid(mSSIDPreferenceController.getSSID())
+                .setPassphrase(mPasswordPreferenceController.getPasswordValidated(securityType),
+                        securityType)
+                .setBand(mApBandPreferenceController.getBand())
+                .build();
     }
 
     private void startTether() {
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/wifi/tether/WifiTetherSoftApManager.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/wifi/tether/WifiTetherSoftApManager.java
index f31a64f..fa4fc0b 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/wifi/tether/WifiTetherSoftApManager.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/wifi/tether/WifiTetherSoftApManager.java
@@ -1,7 +1,11 @@
 package com.android.car.developeroptions.wifi.tether;
 
+import android.net.wifi.WifiClient;
 import android.net.wifi.WifiManager;
 import android.os.Handler;
+import android.os.HandlerExecutor;
+
+import java.util.List;
 
 /**
  * Wrapper for {@link android.net.wifi.WifiManager.SoftApCallback} to pass the robo test
@@ -18,8 +22,8 @@
         }
 
         @Override
-        public void onNumClientsChanged(int numClients) {
-            mWifiTetherSoftApCallback.onNumClientsChanged(numClients);
+        public void onConnectedClientsChanged(List<WifiClient> clients) {
+            mWifiTetherSoftApCallback.onConnectedClientsChanged(clients);
         }
     };
     private Handler mHandler;
@@ -32,7 +36,7 @@
     }
 
     public void registerSoftApCallback() {
-        mWifiManager.registerSoftApCallback(mSoftApCallback, mHandler);
+        mWifiManager.registerSoftApCallback(new HandlerExecutor(mHandler), mSoftApCallback);
     }
 
     public void unRegisterSoftApCallback() {
@@ -42,6 +46,11 @@
     public interface WifiTetherSoftApCallback {
         void onStateChanged(int state, int failureReason);
 
-        void onNumClientsChanged(int numClients);
+        /**
+         * Called when the connected clients to soft AP changes.
+         *
+         * @param clients the currently connected clients
+         */
+        void onConnectedClientsChanged(List<WifiClient> clients);
     }
 }
diff --git a/tests/CarLibTests/Android.bp b/tests/CarLibTests/Android.bp
new file mode 100644
index 0000000..242ae83
--- /dev/null
+++ b/tests/CarLibTests/Android.bp
@@ -0,0 +1,44 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+android_app {
+    name: "CarLibTestApp",
+    platform_apis: true,
+}
+
+//###########################################################
+// Robolectric test target for testing car test lib classes #
+//###########################################################
+android_robolectric_test {
+    enabled: true,
+
+    name: "CarLibTests",
+
+    srcs: ["src/**/*.java"],
+
+    java_resource_dirs: ["config"],
+
+    libs: [
+        "Robolectric_all-target",
+        "robolectric_android-all-stub",
+        "mockito-robolectric-prebuilt",
+        "truth-prebuilt",
+        "androidx.test.core",
+        "android.car.testapi",
+        "androidx.test.rules",
+    ],
+
+    instrumentation_for: "CarLibTestApp",
+
+}
diff --git a/tests/CarLibTests/Android.mk b/tests/CarLibTests/Android.mk
deleted file mode 100644
index bf86890..0000000
--- a/tests/CarLibTests/Android.mk
+++ /dev/null
@@ -1,44 +0,0 @@
-# Copyright (C) 2019 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-#
-
-LOCAL_PATH:= $(call my-dir)
-
-include $(CLEAR_VARS)
-
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-
-LOCAL_PACKAGE_NAME := CarLibTests
-
-LOCAL_MODULE_TAGS := tests
-LOCAL_PRIVATE_PLATFORM_APIS := true
-
-LOCAL_PROGUARD_ENABLED := disabled
-
-LOCAL_JAVA_LIBRARIES := \
-    android.car \
-    android.test.runner \
-    android.test.base \
-
-LOCAL_STATIC_JAVA_LIBRARIES := \
-    junit \
-    android.car.testapi \
-    androidx.test.rules \
-    androidx.test.core \
-    mockito-target-minus-junit4 \
-    com.android.car.test.utils \
-    truth-prebuilt \
-
-include $(BUILD_PACKAGE)
diff --git a/tests/CarLibTests/AndroidManifest.xml b/tests/CarLibTests/AndroidManifest.xml
index 03379c7..df64d87 100644
--- a/tests/CarLibTests/AndroidManifest.xml
+++ b/tests/CarLibTests/AndroidManifest.xml
@@ -16,13 +16,5 @@
   -->
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
     package="android.car.testapi.tests">
-
-  <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-                   android:targetPackage="android.car.testapi.tests"
-                   android:label="Unit Tests for Car APIs"/>
-
-  <application>
-      <uses-library android:name="android.test.runner" />
-  </application>
-
+    <application/>
 </manifest>
diff --git a/tests/CarLibTests/config/robolectric.properties b/tests/CarLibTests/config/robolectric.properties
new file mode 100644
index 0000000..c0a0ca2
--- /dev/null
+++ b/tests/CarLibTests/config/robolectric.properties
@@ -0,0 +1,15 @@
+# Copyright (C) 2019 Google Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+sdk=NEWEST_SDK
diff --git a/tests/CarLibTests/src/android/car/CarAppFocusManagerTest.java b/tests/CarLibTests/src/android/car/CarAppFocusManagerTest.java
new file mode 100644
index 0000000..b3c794e
--- /dev/null
+++ b/tests/CarLibTests/src/android/car/CarAppFocusManagerTest.java
@@ -0,0 +1,284 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.car;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+import static org.robolectric.Shadows.shadowOf;
+
+import android.app.Application;
+import android.car.CarAppFocusManager.OnAppFocusChangedListener;
+import android.car.CarAppFocusManager.OnAppFocusOwnershipCallback;
+import android.car.testapi.CarAppFocusController;
+import android.car.testapi.FakeCar;
+import android.os.Looper;
+
+import androidx.test.core.app.ApplicationProvider;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.internal.DoNotInstrument;
+import org.robolectric.shadows.ShadowBinder;
+
+@RunWith(RobolectricTestRunner.class)
+@DoNotInstrument
+public class CarAppFocusManagerTest {
+    @Rule
+    public MockitoRule rule = MockitoJUnit.rule();
+
+    private Application mContext;
+
+    private FakeCar mFakeCar;
+    private CarAppFocusManager mCarAppFocusManager;
+    private CarAppFocusController mCarAppFocusController;
+    private Looper mAppFocusServiceLooper;
+
+    private static final int APP1_UID = 1041;
+    private static final int APP1_PID = 1043;
+    private static final int APP2_UID = 1072;
+    private static final int APP2_PID = 1074;
+    private static final int APP3_UID = 1111;
+    private static final int APP3_PID = 2222;
+
+    @Mock OnAppFocusOwnershipCallback mApp1Callback;
+    @Mock OnAppFocusChangedListener mApp1Listener;
+    @Mock OnAppFocusOwnershipCallback mApp2Callback;
+    @Mock OnAppFocusChangedListener mApp2Listener;
+    @Mock OnAppFocusOwnershipCallback mApp3Callback;
+    @Mock OnAppFocusChangedListener mApp3Listener;
+
+    @Before
+    public void setUp() {
+        ShadowBinder.reset();
+        mContext = ApplicationProvider.getApplicationContext();
+        mFakeCar = FakeCar.createFakeCar(mContext);
+        mCarAppFocusManager =
+                (CarAppFocusManager) mFakeCar.getCar().getCarManager(Car.APP_FOCUS_SERVICE);
+        mCarAppFocusController = mFakeCar.getAppFocusController();
+        mAppFocusServiceLooper = mCarAppFocusController.getLooper();
+    }
+
+    @Test
+    public void defaultState_noFocusesHeld() {
+        assertThat(mCarAppFocusManager.getActiveAppTypes()).isEmpty();
+    }
+
+    @Test
+    public void requestNavFocus_noCurrentFocus_requestShouldSucceed() {
+        int result = mCarAppFocusManager.requestAppFocus(
+                CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION, mApp1Callback);
+        assertThat(result).isEqualTo(CarAppFocusManager.APP_FOCUS_REQUEST_SUCCEEDED);
+    }
+
+    @Test
+    public void requestNavFocus_noCurrentFocus_callbackIsRun() {
+        mCarAppFocusManager.requestAppFocus(CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION,
+                mApp1Callback);
+        shadowOf(mAppFocusServiceLooper).runToEndOfTasks();
+
+        verify(mApp1Callback)
+                .onAppFocusOwnershipGranted(CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION);
+    }
+
+    @Test
+    public void requestNavFocus_noCurrentFocus_holdsOwnership() {
+        mCarAppFocusManager.requestAppFocus(CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION,
+                mApp1Callback);
+
+        assertThat(
+                mCarAppFocusManager
+                        .isOwningFocus(mApp1Callback, CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION))
+                .isTrue();
+    }
+
+    @Test
+    public void requestNavFocus_noCurrentFocus_onlyNavActive() {
+        mCarAppFocusManager.requestAppFocus(CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION,
+                mApp1Callback);
+
+        assertThat(mCarAppFocusManager.getActiveAppTypes())
+                .isEqualTo(new int[] {CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION});
+    }
+
+    private void setCallingApp(int uid, int pid) {
+        ShadowBinder.setCallingUid(uid);
+        ShadowBinder.setCallingPid(pid);
+    }
+
+    private void app2GainsFocus_app1BroughtToForeground() {
+        setCallingApp(APP2_UID, APP2_PID);
+        mCarAppFocusManager.requestAppFocus(CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION,
+                mApp2Callback);
+        mCarAppFocusController.setForegroundUid(APP1_UID);
+        mCarAppFocusController.setForegroundPid(APP1_PID);
+        setCallingApp(APP2_UID, APP1_PID);
+    }
+
+    @Test
+    public void requestNavFocus_currentOwnerInBackground_requestShouldSucceed() {
+        app2GainsFocus_app1BroughtToForeground();
+
+        assertThat(
+                mCarAppFocusManager
+                        .requestAppFocus(
+                                CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION, mApp1Callback))
+                .isEqualTo(CarAppFocusManager.APP_FOCUS_REQUEST_SUCCEEDED);
+    }
+
+    @Test
+    public void requestNavFocus_currentOwnerInBackground_callbackIsRun() {
+        app2GainsFocus_app1BroughtToForeground();
+        mCarAppFocusManager
+                .requestAppFocus(CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION, mApp1Callback);
+        shadowOf(mAppFocusServiceLooper).runToEndOfTasks();
+
+        verify(mApp1Callback)
+                .onAppFocusOwnershipGranted(CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION);
+    }
+
+    @Test
+    public void requestNavFocus_currentOwnerInBackground_holdsOwnership() {
+        app2GainsFocus_app1BroughtToForeground();
+        mCarAppFocusManager
+                .requestAppFocus(CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION, mApp1Callback);
+
+        assertThat(
+                mCarAppFocusManager
+                        .isOwningFocus(mApp1Callback, CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION))
+                .isTrue();
+    }
+
+    @Test
+    public void requestNavFocus_currentOwnerInForeground_requestFails() {
+        setCallingApp(APP2_UID, APP2_PID);
+        mCarAppFocusController.setForegroundUid(APP2_UID);
+        mCarAppFocusController.setForegroundPid(APP2_PID);
+        mCarAppFocusManager.requestAppFocus(CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION,
+                mApp2Callback);
+        setCallingApp(APP1_UID, APP1_PID);
+
+        assertThat(
+                mCarAppFocusManager
+                        .requestAppFocus(
+                                CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION, mApp1Callback))
+                .isEqualTo(CarAppFocusManager.APP_FOCUS_REQUEST_FAILED);
+    }
+
+    @Test
+    public void requestAppFocus_callingAppNotified() {
+        setCallingApp(APP1_UID, APP1_PID);
+        mCarAppFocusManager
+                .addFocusListener(mApp1Listener, CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION);
+        mCarAppFocusManager.requestAppFocus(CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION,
+                mApp1Callback);
+        shadowOf(mAppFocusServiceLooper).runToEndOfTasks();
+
+        verify(mApp1Listener)
+                .onAppFocusChanged(eq(CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION), anyBoolean());
+    }
+
+    @Test
+    public void requestAppFocus_otherAppNotified() {
+        setCallingApp(APP2_UID, APP2_PID);
+        mCarAppFocusManager
+                .addFocusListener(mApp2Listener, CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION);
+        setCallingApp(APP1_UID, APP1_PID);
+        mCarAppFocusManager.requestAppFocus(CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION,
+                mApp1Callback);
+        shadowOf(mAppFocusServiceLooper).runToEndOfTasks();
+
+        verify(mApp2Listener)
+                .onAppFocusChanged(eq(CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION), eq(true));
+    }
+
+    @Test
+    public void requestAppFocus_focusLost_otherAppRequest_callbackRun() {
+        setCallingApp(APP2_UID, APP2_PID);
+        mCarAppFocusManager.requestAppFocus(CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION,
+                mApp2Callback);
+        setCallingApp(APP1_UID, APP1_PID);
+        mCarAppFocusManager.requestAppFocus(CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION,
+                mApp1Callback);
+        shadowOf(mAppFocusServiceLooper).runToEndOfTasks();
+
+        verify(mApp2Callback)
+                .onAppFocusOwnershipLost(eq(CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION));
+    }
+
+    @Test
+    public void abandonAppFocus_callingAppNotified() {
+        setCallingApp(APP1_UID, APP1_PID);
+        mCarAppFocusManager
+                .addFocusListener(mApp1Listener, CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION);
+        mCarAppFocusManager.requestAppFocus(CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION,
+                mApp1Callback);
+        mCarAppFocusManager
+                .abandonAppFocus(mApp1Callback, CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION);
+        shadowOf(mAppFocusServiceLooper).runToEndOfTasks();
+
+        verify(mApp1Listener)
+                .onAppFocusChanged(eq(CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION), eq(false));
+    }
+
+    @Test
+    public void abandonAppFocus_otherAppNotified() {
+        setCallingApp(APP2_UID, APP2_PID);
+        mCarAppFocusManager
+                .addFocusListener(mApp2Listener, CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION);
+        setCallingApp(APP1_UID, APP1_PID);
+        mCarAppFocusManager.requestAppFocus(CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION,
+                mApp1Callback);
+        mCarAppFocusManager
+                .abandonAppFocus(mApp1Callback, CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION);
+        shadowOf(mAppFocusServiceLooper).runToEndOfTasks();
+
+        verify(mApp2Listener)
+                .onAppFocusChanged(eq(CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION), eq(false));
+    }
+
+    @Test
+    public void gainAppFocus_multipleListenersRegistered_bothUnownedTrigger() {
+        setCallingApp(APP1_UID, APP1_PID);
+        mCarAppFocusManager
+                .addFocusListener(mApp1Listener, CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION);
+        setCallingApp(APP2_UID, APP2_PID);
+        mCarAppFocusManager
+                .addFocusListener(mApp2Listener, CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION);
+        setCallingApp(APP3_UID, APP3_PID);
+        mCarAppFocusManager
+                .addFocusListener(mApp3Listener, CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION);
+        mCarAppFocusManager
+                .requestAppFocus(CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION, mApp3Callback);
+        shadowOf(mAppFocusServiceLooper).runToEndOfTasks();
+
+        verify(mApp1Listener)
+                .onAppFocusChanged(eq(CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION), eq(true));
+        verify(mApp2Listener)
+                .onAppFocusChanged(eq(CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION), eq(true));
+        verify(mApp3Listener)
+                .onAppFocusChanged(eq(CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION), eq(true));
+    }
+}
diff --git a/tests/CarLibTests/src/android/car/CarNavigationStatusManagerTest.java b/tests/CarLibTests/src/android/car/CarNavigationStatusManagerTest.java
new file mode 100644
index 0000000..a4cb5fb
--- /dev/null
+++ b/tests/CarLibTests/src/android/car/CarNavigationStatusManagerTest.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.car;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.car.navigation.CarNavigationInstrumentCluster;
+import android.car.navigation.CarNavigationStatusManager;
+import android.car.testapi.CarNavigationStatusController;
+import android.car.testapi.FakeCar;
+import android.content.Context;
+import android.os.Bundle;
+
+import androidx.test.core.app.ApplicationProvider;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.internal.DoNotInstrument;
+
+@RunWith(RobolectricTestRunner.class)
+@DoNotInstrument
+public class CarNavigationStatusManagerTest {
+    private Context mContext;
+    private FakeCar mFakeCar;
+    private Car mCar;
+    private CarNavigationStatusManager mCarNavigationStatusManager;
+    private CarNavigationStatusController mCarNavigationStatusController;
+
+    @Before
+    public void setUp() {
+        mContext = ApplicationProvider.getApplicationContext();
+        mFakeCar = FakeCar.createFakeCar(mContext);
+        mCar = mFakeCar.getCar();
+        mCarNavigationStatusManager =
+                (CarNavigationStatusManager) mCar.getCarManager(Car.CAR_NAVIGATION_SERVICE);
+        mCarNavigationStatusController = mFakeCar.getCarNavigationStatusController();
+
+        // There should be no value after set up of the service.
+        assertThat(mCarNavigationStatusController.getCurrentNavState()).isNull();
+    }
+
+    @Test
+    public void onNavigationStateChanged_bundleIsReceived() {
+        Bundle bundle = new Bundle();
+        mCarNavigationStatusManager.sendNavigationStateChange(bundle);
+
+        assertThat(mCarNavigationStatusController.getCurrentNavState()).isEqualTo(bundle);
+    }
+
+    @Test
+    public void getInstrumentClusterInfo_returnsImageCodeCluster() {
+        // default cluster should be an image code cluster (no custom images)
+        assertThat(mCarNavigationStatusManager.getInstrumentClusterInfo().getType()).isEqualTo(
+                CarNavigationInstrumentCluster.CLUSTER_TYPE_IMAGE_CODES_ONLY);
+    }
+
+    @Test
+    public void setImageCodeClusterInfo_returnsImageCodeCluster() {
+        mCarNavigationStatusController.setImageCodeClusterInfo(42);
+
+        CarNavigationInstrumentCluster instrumentCluster =
+                mCarNavigationStatusManager.getInstrumentClusterInfo();
+
+        assertThat(instrumentCluster.getType())
+                .isEqualTo(CarNavigationInstrumentCluster.CLUSTER_TYPE_IMAGE_CODES_ONLY);
+        assertThat(instrumentCluster.getMinIntervalMillis()).isEqualTo(42);
+    }
+
+    @Test
+    public void setCustomImageClusterInfo_returnsCustomImageCluster() {
+        mCarNavigationStatusController.setCustomImageClusterInfo(
+                100,
+                1024,
+                768,
+                32);
+
+        CarNavigationInstrumentCluster instrumentCluster =
+                mCarNavigationStatusManager.getInstrumentClusterInfo();
+
+        assertThat(instrumentCluster.getType())
+                .isEqualTo(CarNavigationInstrumentCluster.CLUSTER_TYPE_CUSTOM_IMAGES_SUPPORTED);
+        assertThat(instrumentCluster.getMinIntervalMillis()).isEqualTo(100);
+        assertThat(instrumentCluster.getImageWidth()).isEqualTo(1024);
+        assertThat(instrumentCluster.getImageHeight()).isEqualTo(768);
+        assertThat(instrumentCluster.getImageColorDepthBits()).isEqualTo(32);
+    }
+}
diff --git a/tests/CarLibTests/src/android/car/CarProjectionManagerTest.java b/tests/CarLibTests/src/android/car/CarProjectionManagerTest.java
index 970cac9..c822fba 100644
--- a/tests/CarLibTests/src/android/car/CarProjectionManagerTest.java
+++ b/tests/CarLibTests/src/android/car/CarProjectionManagerTest.java
@@ -34,11 +34,11 @@
 import android.car.testapi.FakeCar;
 import android.content.Context;
 import android.content.Intent;
-import android.net.wifi.WifiConfiguration;
+import android.net.MacAddress;
+import android.net.wifi.SoftApConfiguration;
 import android.util.ArraySet;
 
 import androidx.test.core.app.ApplicationProvider;
-import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Before;
 import org.junit.Rule;
@@ -50,6 +50,8 @@
 import org.mockito.Spy;
 import org.mockito.junit.MockitoJUnit;
 import org.mockito.junit.MockitoRule;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.internal.DoNotInstrument;
 
 import java.util.Arrays;
 import java.util.Collections;
@@ -58,7 +60,8 @@
 import java.util.concurrent.Executor;
 import java.util.concurrent.TimeUnit;
 
-@RunWith(AndroidJUnit4.class)
+@RunWith(RobolectricTestRunner.class)
+@DoNotInstrument
 public class CarProjectionManagerTest {
     @Rule
     public MockitoRule rule = MockitoJUnit.rule();
@@ -91,7 +94,7 @@
 
     @Test
     public void startAp_fail() throws InterruptedException {
-        mController.setWifiConfiguration(null);
+        mController.setSoftApConfiguration(null);
 
         mProjectionManager.startProjectionAccessPoint(mApCallback);
         mApCallback.mFailed.await(DEFAULT_TIMEOUT_MS, TimeUnit.MILLISECONDS);
@@ -100,16 +103,16 @@
 
     @Test
     public void startAp_success() throws InterruptedException {
-        WifiConfiguration wifiConfiguration = new WifiConfiguration();
-        wifiConfiguration.SSID = "Hello";
-        wifiConfiguration.BSSID = "AA:BB:CC:CC:DD:EE";
-        wifiConfiguration.preSharedKey = "password";
-
-        mController.setWifiConfiguration(wifiConfiguration);
+        SoftApConfiguration config = new SoftApConfiguration.Builder()
+                .setSsid("Hello")
+                .setBssid(MacAddress.fromString("AA:BB:CC:CC:DD:EE"))
+                .setPassphrase("password", SoftApConfiguration.SECURITY_TYPE_WPA2_PSK)
+                .build();
+        mController.setSoftApConfiguration(config);
 
         mProjectionManager.startProjectionAccessPoint(mApCallback);
         mApCallback.mStarted.await(DEFAULT_TIMEOUT_MS, TimeUnit.MILLISECONDS);
-        assertThat(mApCallback.mWifiConfiguration).isEqualTo(wifiConfiguration);
+        assertThat(mApCallback.mSoftApConfiguration).isEqualTo(config);
     }
 
     @Test
@@ -256,11 +259,11 @@
         CountDownLatch mStarted = new CountDownLatch(1);
         CountDownLatch mFailed = new CountDownLatch(1);
         int mFailureReason = -1;
-        WifiConfiguration mWifiConfiguration;
+        SoftApConfiguration mSoftApConfiguration;
 
         @Override
-        public void onStarted(WifiConfiguration wifiConfiguration) {
-            mWifiConfiguration = wifiConfiguration;
+        public void onStarted(SoftApConfiguration softApConfiguration) {
+            mSoftApConfiguration = softApConfiguration;
             mStarted.countDown();
         }
 
diff --git a/tests/CarLibTests/src/android/car/CarPropertyManagerTest.java b/tests/CarLibTests/src/android/car/CarPropertyManagerTest.java
index bc322c3..4d87c05 100644
--- a/tests/CarLibTests/src/android/car/CarPropertyManagerTest.java
+++ b/tests/CarLibTests/src/android/car/CarPropertyManagerTest.java
@@ -26,7 +26,6 @@
 import android.car.testapi.FakeCar;
 
 import androidx.test.core.app.ApplicationProvider;
-import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Before;
 import org.junit.Rule;
@@ -34,8 +33,11 @@
 import org.junit.runner.RunWith;
 import org.mockito.junit.MockitoJUnit;
 import org.mockito.junit.MockitoRule;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.internal.DoNotInstrument;
 
-@RunWith(AndroidJUnit4.class)
+@RunWith(RobolectricTestRunner.class)
+@DoNotInstrument
 public class CarPropertyManagerTest {
     private static final int FAN_SPEED_VALUE = 42;
     private static final float TEMPERATURE_VALUE = 42.24f;
diff --git a/tests/CarLibTests/src/android/car/CarUxRestrictionsManagerTest.java b/tests/CarLibTests/src/android/car/CarUxRestrictionsManagerTest.java
new file mode 100644
index 0000000..2fac09c
--- /dev/null
+++ b/tests/CarLibTests/src/android/car/CarUxRestrictionsManagerTest.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.car;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.verify;
+
+import android.app.Application;
+import android.car.drivingstate.CarUxRestrictions;
+import android.car.drivingstate.CarUxRestrictionsManager;
+import android.car.drivingstate.CarUxRestrictionsManager.OnUxRestrictionsChangedListener;
+import android.car.testapi.CarUxRestrictionsController;
+import android.car.testapi.FakeCar;
+import android.os.RemoteException;
+
+import androidx.test.core.app.ApplicationProvider;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.internal.DoNotInstrument;
+
+@RunWith(RobolectricTestRunner.class)
+@DoNotInstrument
+public class CarUxRestrictionsManagerTest {
+    @Rule public MockitoRule rule = MockitoJUnit.rule();
+
+    CarUxRestrictionsManager mCarUxRestrictionsManager;
+    CarUxRestrictionsController mCarUxRestrictionsController;
+
+    @Mock OnUxRestrictionsChangedListener mListener;
+
+    @Before
+    public void setUp() {
+        Application context = ApplicationProvider.getApplicationContext();
+        FakeCar fakeCar = FakeCar.createFakeCar(context);
+        Car carApi = fakeCar.getCar();
+
+        mCarUxRestrictionsManager =
+                (CarUxRestrictionsManager) carApi.getCarManager(Car.CAR_UX_RESTRICTION_SERVICE);
+        mCarUxRestrictionsController = fakeCar.getCarUxRestrictionController();
+    }
+
+    @Test
+    public void getRestrictionMode_noRestrictionsSet_noRestrictionsPresent() {
+        assertThat(mCarUxRestrictionsManager.getRestrictionMode()).isEqualTo(0);
+    }
+
+    @Test
+    public void setUxRestrictions_restrictionsRegistered() throws RemoteException {
+        mCarUxRestrictionsController.setUxRestrictions(CarUxRestrictions.UX_RESTRICTIONS_NO_VIDEO);
+
+        assertThat(mCarUxRestrictionsManager.getRestrictionMode())
+                .isEqualTo(CarUxRestrictions.UX_RESTRICTIONS_NO_VIDEO);
+    }
+
+    @Test
+    public void clearUxRestrictions_restrictionsCleared() throws RemoteException {
+        mCarUxRestrictionsController
+                .setUxRestrictions(CarUxRestrictions.UX_RESTRICTIONS_FULLY_RESTRICTED);
+        mCarUxRestrictionsController.clearUxRestrictions();
+
+        assertThat(mCarUxRestrictionsManager.getRestrictionMode())
+                .isEqualTo(0);
+    }
+
+    @Test
+    public void isListenerRegistered_noListenerSet_returnsFalse() {
+        assertThat(mCarUxRestrictionsController.isListenerRegistered()).isFalse();
+    }
+
+    @Test
+    public void isListenerRegistered_listenerSet_returnsTrue() {
+        mCarUxRestrictionsManager.registerListener(mListener);
+
+        assertThat(mCarUxRestrictionsController.isListenerRegistered()).isTrue();
+    }
+
+    @Test
+    public void setUxRestrictions_listenerRegistered_listenerTriggered() throws RemoteException {
+        mCarUxRestrictionsManager.registerListener(mListener);
+        mCarUxRestrictionsController
+                .setUxRestrictions(CarUxRestrictions.UX_RESTRICTIONS_NO_TEXT_MESSAGE);
+
+        verify(mListener).onUxRestrictionsChanged(any());
+    }
+}
+
diff --git a/tests/DefaultStorageMonitoringCompanionApp/Android.mk b/tests/DefaultStorageMonitoringCompanionApp/Android.mk
index 66c04c3..ff28cf6 100644
--- a/tests/DefaultStorageMonitoringCompanionApp/Android.mk
+++ b/tests/DefaultStorageMonitoringCompanionApp/Android.mk
@@ -23,8 +23,7 @@
 LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
 
 LOCAL_PACKAGE_NAME := DefaultStorageMonitoringCompanionApp
-
-LOCAL_SDK_VERSION := system_current
+LOCAL_PRIVATE_PLATFORM_APIS := true
 
 LOCAL_AAPT_FLAGS := --auto-add-overlay
 
@@ -36,6 +35,6 @@
 
 LOCAL_DEX_PREOPT := false
 
-LOCAL_JAVA_LIBRARIES += android.car-system-stubs
+LOCAL_JAVA_LIBRARIES += android.car
 
 include $(BUILD_PACKAGE)
diff --git a/tests/DefaultStorageMonitoringCompanionApp/src/com/google/android/car/defaultstoragemonitoringcompanionapp/MainActivity.java b/tests/DefaultStorageMonitoringCompanionApp/src/com/google/android/car/defaultstoragemonitoringcompanionapp/MainActivity.java
index b76388e..02adca2 100644
--- a/tests/DefaultStorageMonitoringCompanionApp/src/com/google/android/car/defaultstoragemonitoringcompanionapp/MainActivity.java
+++ b/tests/DefaultStorageMonitoringCompanionApp/src/com/google/android/car/defaultstoragemonitoringcompanionapp/MainActivity.java
@@ -111,7 +111,11 @@
         @Override
         public void onServiceConnected(ComponentName name, IBinder service) {
             Log.d(TAG, "Connected to " + name.flattenToString());
-
+            if (!mCar.isFeatureEnabled(Car.STORAGE_MONITORING_SERVICE)) {
+                Log.e(TAG, "Car.STORAGE_MONITORING_SERVICE feature not supported, will finish");
+                finish();
+                return;
+            }
             CarStorageMonitoringManager storageMonitoringManager =
                     (CarStorageMonitoringManager) mCar.getCarManager(
                             Car.STORAGE_MONITORING_SERVICE);
@@ -120,6 +124,7 @@
                     storageMonitoringManager.getWearEstimateHistory();
             if (wearEstimateChanges.isEmpty()) {
                 finish();
+                return;
             }
 
             WearEstimateChange currentChange =
@@ -127,6 +132,7 @@
 
             if (!DEBUG && currentChange.isAcceptableDegradation) {
                 finish();
+                return;
             }
 
             mNotificationTextView.setText(wearChangeToString(currentChange));
diff --git a/tests/DiagnosticTools/Android.mk b/tests/DiagnosticTools/Android.mk
new file mode 100644
index 0000000..c2453ae
--- /dev/null
+++ b/tests/DiagnosticTools/Android.mk
@@ -0,0 +1,48 @@
+#
+# Copyright (C) 2019 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+
+# Only compile source java files in this apk.
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_PACKAGE_NAME := DiagnosticTools
+LOCAL_PRIVATE_PLATFORM_APIS := true
+
+LOCAL_CERTIFICATE := platform
+
+#LOCAL_SDK_VERSION := current
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_PROGUARD_ENABLED := disabled
+
+LOCAL_DEX_PREOPT := false
+
+LOCAL_STATIC_ANDROID_LIBRARIES := androidx.recyclerview_recyclerview \
+    com.google.android.material_material \
+    androidx-constraintlayout_constraintlayout \
+    androidx.appcompat_appcompat \
+    androidx-constraintlayout_constraintlayout-solver
+
+LOCAL_JAVA_LIBRARIES := android.car
+
+include $(BUILD_PACKAGE)
+
+# Use the following include to make our test apk.
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/tests/DiagnosticTools/AndroidManifest.xml b/tests/DiagnosticTools/AndroidManifest.xml
new file mode 100644
index 0000000..55ee4bc
--- /dev/null
+++ b/tests/DiagnosticTools/AndroidManifest.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.google.android.car.diagnostictools">
+    <uses-permission android:name="android.car.permission.CAR_DIAGNOSTICS"/>
+    <uses-permission android:name="android.car.permission.CLEAR_CAR_DIAGNOSTICS"/>
+    <application android:label="OBD-II Diagnostic Tools" android:theme="@style/AppTheme">
+        <activity android:name=".ECUListActivity">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+        </activity>
+        <activity android:name=".DTCListActivity"/>
+        <activity android:name=".DTCDetailActivity"/>
+        <activity android:name=".LiveDataActivity"/>
+    </application>
+</manifest>
diff --git a/tests/DiagnosticTools/res/drawable/ic_arrow_back_black_24dp.xml b/tests/DiagnosticTools/res/drawable/ic_arrow_back_black_24dp.xml
new file mode 100644
index 0000000..75ae95c
--- /dev/null
+++ b/tests/DiagnosticTools/res/drawable/ic_arrow_back_black_24dp.xml
@@ -0,0 +1,26 @@
+<!--
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+  android:height="24dp"
+  android:tint="#FFFFFF"
+  android:viewportHeight="24.0"
+  android:viewportWidth="24.0"
+  android:width="24dp">
+  <path
+    android:fillColor="#FF000000"
+    android:pathData="M20,11H7.83l5.59,-5.59L12,4l-8,8 8,8 1.41,-1.41L7.83,13H20v-2z"/>
+</vector>
diff --git a/tests/DiagnosticTools/res/drawable/ic_delete_sweep_black_24dp.xml b/tests/DiagnosticTools/res/drawable/ic_delete_sweep_black_24dp.xml
new file mode 100644
index 0000000..6c933bf
--- /dev/null
+++ b/tests/DiagnosticTools/res/drawable/ic_delete_sweep_black_24dp.xml
@@ -0,0 +1,26 @@
+<!--
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+  android:height="24dp"
+  android:tint="#FFFFFF"
+  android:viewportHeight="24.0"
+  android:viewportWidth="24.0"
+  android:width="24dp">
+  <path
+    android:fillColor="#FF000000"
+    android:pathData="M15,16h4v2h-4zM15,8h7v2h-7zM15,12h6v2h-6zM3,18c0,1.1 0.9,2 2,2h6c1.1,0 2,-0.9 2,-2L13,8L3,8v10zM14,5h-3l-1,-1L6,4L5,5L2,5v2h12z"/>
+</vector>
diff --git a/tests/DiagnosticTools/res/drawable/ic_select_all_black_24dp.xml b/tests/DiagnosticTools/res/drawable/ic_select_all_black_24dp.xml
new file mode 100644
index 0000000..5729483
--- /dev/null
+++ b/tests/DiagnosticTools/res/drawable/ic_select_all_black_24dp.xml
@@ -0,0 +1,26 @@
+<!--
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+  android:height="24dp"
+  android:tint="#FFFFFF"
+  android:viewportHeight="24.0"
+  android:viewportWidth="24.0"
+  android:width="24dp">
+  <path
+    android:fillColor="#FF000000"
+    android:pathData="M3,5h2L5,3c-1.1,0 -2,0.9 -2,2zM3,13h2v-2L3,11v2zM7,21h2v-2L7,19v2zM3,9h2L5,7L3,7v2zM13,3h-2v2h2L13,3zM19,3v2h2c0,-1.1 -0.9,-2 -2,-2zM5,21v-2L3,19c0,1.1 0.9,2 2,2zM3,17h2v-2L3,15v2zM9,3L7,3v2h2L9,3zM11,21h2v-2h-2v2zM19,13h2v-2h-2v2zM19,21c1.1,0 2,-0.9 2,-2h-2v2zM19,9h2L21,7h-2v2zM19,17h2v-2h-2v2zM15,21h2v-2h-2v2zM15,5h2L17,3h-2v2zM7,17h10L17,7L7,7v10zM9,9h6v6L9,15L9,9z"/>
+</vector>
diff --git a/tests/DiagnosticTools/res/drawable/ic_show_chart_black_24dp.xml b/tests/DiagnosticTools/res/drawable/ic_show_chart_black_24dp.xml
new file mode 100644
index 0000000..210ede0
--- /dev/null
+++ b/tests/DiagnosticTools/res/drawable/ic_show_chart_black_24dp.xml
@@ -0,0 +1,26 @@
+<!--
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+  android:height="24dp"
+  android:tint="#FFFFFF"
+  android:viewportHeight="24.0"
+  android:viewportWidth="24.0"
+  android:width="24dp">
+  <path
+    android:fillColor="#FF000000"
+    android:pathData="M3.5,18.49l6,-6.01 4,4L22,6.92l-1.41,-1.41 -7.09,7.97 -4,-4L2,16.99z"/>
+</vector>
diff --git a/tests/DiagnosticTools/res/layout/activity_dtc_details.xml b/tests/DiagnosticTools/res/layout/activity_dtc_details.xml
new file mode 100644
index 0000000..4feb8c4
--- /dev/null
+++ b/tests/DiagnosticTools/res/layout/activity_dtc_details.xml
@@ -0,0 +1,89 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+  -->
+
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+  xmlns:app="http://schemas.android.com/apk/res-auto"
+  xmlns:tools="http://schemas.android.com/tools"
+  android:layout_width="match_parent"
+  android:layout_height="wrap_content">
+  <androidx.constraintlayout.widget.ConstraintLayout
+    android:id="@+id/linearLayout"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <Button
+      android:id="@+id/freeze_frame_clear"
+      android:layout_width="wrap_content"
+      android:layout_height="wrap_content"
+      android:layout_marginEnd="16dp"
+      android:onClick="clearFreezeFrameButtonPress"
+      android:text="Clear Freeze Frame Data"
+      app:layout_constraintBottom_toTopOf="@+id/freeze_frame_data"
+      app:layout_constraintEnd_toEndOf="parent"
+      app:layout_constraintTop_toBottomOf="@+id/dtc_details"/>
+    <ProgressBar
+      android:id="@+id/freeze_frame_loading"
+      style="?android:attr/progressBarStyle"
+      android:layout_width="0dp"
+      android:layout_height="128dp"
+      android:visibility="invisible"
+      app:layout_constraintBottom_toBottomOf="parent"
+      app:layout_constraintEnd_toEndOf="parent"
+      app:layout_constraintHorizontal_bias="0.0"
+      app:layout_constraintStart_toStartOf="parent"
+      app:layout_constraintTop_toBottomOf="@+id/freeze_frame_title"/>
+    <TextView
+      android:id="@+id/dtc_details"
+      android:layout_width="0dp"
+      android:layout_height="wrap_content"
+      android:text="TextView"
+      android:textSize="24sp"
+      app:layout_constraintEnd_toEndOf="parent"
+      app:layout_constraintStart_toStartOf="parent"
+      app:layout_constraintTop_toBottomOf="@+id/toolbar"/>
+    <TextView
+      android:id="@+id/freeze_frame_title"
+      android:layout_width="0dp"
+      android:layout_height="wrap_content"
+      android:layout_marginTop="8dp"
+      android:text="Freeze Frame:"
+      android:textSize="36sp"
+      app:layout_constraintStart_toStartOf="parent"
+      app:layout_constraintTop_toBottomOf="@+id/dtc_details"/>
+    <Toolbar
+      android:id="@+id/toolbar"
+      android:layout_width="match_parent"
+      android:layout_height="wrap_content"
+      android:background="?android:attr/colorPrimary"
+      android:elevation="4dp"
+      android:theme="@android:attr/actionBarTheme"/>
+    <androidx.recyclerview.widget.RecyclerView
+      android:id="@+id/freeze_frame_data"
+      android:layout_width="match_parent"
+      android:layout_height="0dp"
+      android:layout_marginTop="8dp"
+      android:background="#00FF0000"
+      android:minHeight="72dp"
+      android:visibility="visible"
+      app:layout_constraintBottom_toBottomOf="parent"
+      app:layout_constraintEnd_toEndOf="@+id/freeze_frame_loading"
+      app:layout_constraintHeight_default="spread"
+      app:layout_constraintHeight_min="300dp"
+      app:layout_constraintStart_toStartOf="@+id/freeze_frame_loading"
+      app:layout_constraintTop_toBottomOf="@+id/freeze_frame_title"/>
+  </androidx.constraintlayout.widget.ConstraintLayout>
+</ScrollView>
\ No newline at end of file
diff --git a/tests/DiagnosticTools/res/layout/activity_dtc_list.xml b/tests/DiagnosticTools/res/layout/activity_dtc_list.xml
new file mode 100644
index 0000000..e5f92a5
--- /dev/null
+++ b/tests/DiagnosticTools/res/layout/activity_dtc_list.xml
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+  -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+  xmlns:app="http://schemas.android.com/apk/res-auto"
+  xmlns:tools="http://schemas.android.com/tools"
+  android:id="@+id/relativeLayout2"
+  android:layout_width="match_parent"
+  android:layout_height="match_parent"
+  android:orientation="vertical">
+  <Toolbar
+    android:id="@+id/toolbar"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:background="?android:attr/colorPrimary"
+    android:elevation="4dp"
+    android:theme="@android:attr/actionBarTheme"/>
+  <FrameLayout
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+    <androidx.recyclerview.widget.RecyclerView
+      android:id="@+id/dtc_list"
+      android:layout_width="match_parent"
+      android:layout_height="match_parent"
+      android:scrollbars="vertical"/>
+    <com.google.android.material.floatingactionbutton.FloatingActionButton
+      android:id="@+id/floatingActionButton"
+      android:layout_width="wrap_content"
+      android:layout_height="wrap_content"
+      android:layout_margin="16dp"
+      android:layout_gravity="bottom|end"
+      android:clickable="true"
+      android:onClick="onClickActionButton"
+      android:src="@drawable/ic_delete_sweep_black_24dp"/>
+  </FrameLayout>
+</LinearLayout>
\ No newline at end of file
diff --git a/tests/DiagnosticTools/res/layout/activity_live_data.xml b/tests/DiagnosticTools/res/layout/activity_live_data.xml
new file mode 100644
index 0000000..dc085a6
--- /dev/null
+++ b/tests/DiagnosticTools/res/layout/activity_live_data.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+  -->
+
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+  android:layout_width="match_parent"
+  android:layout_height="match_parent">
+  <LinearLayout
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:orientation="vertical">
+    <Toolbar
+      android:id="@+id/toolbar"
+      android:layout_width="match_parent"
+      android:layout_height="wrap_content"
+      android:background="?android:attr/colorPrimary"
+      android:elevation="4dp"
+      android:theme="@android:attr/actionBarTheme"/>
+    <androidx.recyclerview.widget.RecyclerView
+      android:id="@+id/live_data_list"
+      android:layout_width="match_parent"
+      android:layout_height="match_parent"
+      android:scrollbars="vertical"/>
+  </LinearLayout>
+</ScrollView>
\ No newline at end of file
diff --git a/tests/DiagnosticTools/res/layout/diagnostic_tools.xml b/tests/DiagnosticTools/res/layout/diagnostic_tools.xml
new file mode 100644
index 0000000..44c6f64
--- /dev/null
+++ b/tests/DiagnosticTools/res/layout/diagnostic_tools.xml
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+  -->
+
+<LinearLayout
+  xmlns:android="http://schemas.android.com/apk/res/android"
+  xmlns:app="http://schemas.android.com/apk/res-auto"
+  xmlns:tools="http://schemas.android.com/tools"
+  android:layout_width="match_parent"
+  android:layout_height="match_parent"
+  android:orientation="vertical">
+
+  <Toolbar
+    android:id="@+id/toolbar"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:background="?android:attr/colorPrimary"
+    android:elevation="4dp"
+    android:theme="@android:attr/actionBarTheme"/>
+
+  <FrameLayout
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+    <androidx.recyclerview.widget.RecyclerView
+      android:id="@+id/my_recycler_view"
+      android:layout_width="match_parent"
+      android:layout_height="match_parent"
+      android:scrollbars="vertical"/>
+    <com.google.android.material.floatingactionbutton.FloatingActionButton
+      android:id="@+id/floatingActionButton"
+      android:layout_width="wrap_content"
+      android:layout_height="wrap_content"
+      android:clickable="true"
+      android:layout_gravity="bottom|end"
+      android:layout_margin="16dp"
+      android:src="@drawable/ic_delete_sweep_black_24dp"
+      android:onClick="onClickActionButton"/>
+  </FrameLayout>
+
+
+</LinearLayout>
+
+
diff --git a/tests/DiagnosticTools/res/layout/row_layout.xml b/tests/DiagnosticTools/res/layout/row_layout.xml
new file mode 100644
index 0000000..62728da
--- /dev/null
+++ b/tests/DiagnosticTools/res/layout/row_layout.xml
@@ -0,0 +1,75 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+  -->
+
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+  xmlns:app="http://schemas.android.com/apk/res-auto"
+  xmlns:tools="http://schemas.android.com/tools"
+  android:id="@+id/relativeLayout"
+  android:layout_width="match_parent"
+  android:layout_height="wrap_content"
+  android:padding="12dp">
+
+  <CheckBox
+    android:id="@+id/checkBox"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:layout_marginTop="8dp"
+    android:layout_marginBottom="8dp"
+    android:layout_marginEnd="8dp"
+    app:layout_constraintBottom_toBottomOf="parent"
+    app:layout_constraintEnd_toEndOf="parent"
+    app:layout_constraintTop_toTopOf="parent"/>
+
+  <TextView
+    android:id="@+id/firstLine"
+    android:layout_width="fill_parent"
+    android:layout_height="wrap_content"
+    android:layout_alignWithParentIfMissing="true"
+    android:gravity="center_vertical"
+    android:text="Example application"
+    android:textSize="32sp"
+    app:layout_constraintBottom_toTopOf="@id/secondLine"
+    app:layout_constraintRight_toRightOf="parent"
+    app:layout_constraintTop_toTopOf="parent"/>
+  <TextView
+    android:id="@+id/dtc_number"
+    android:layout_width="wrap_content"
+    android:layout_height="0dp"
+    android:layout_marginEnd="8dp"
+    android:layout_centerHorizontal="true"
+    android:layout_centerVertical="true"
+    android:gravity="center"
+    android:text="TextView"
+    android:textSize="32sp"
+    app:layout_constraintBottom_toBottomOf="parent"
+    app:layout_constraintEnd_toStartOf="@+id/checkBox"
+    app:layout_constraintTop_toTopOf="parent"
+    app:layout_constraintVertical_bias="0.0"/>
+  <TextView
+    android:id="@+id/secondLine"
+    android:layout_width="fill_parent"
+    android:layout_height="wrap_content"
+    android:ellipsize="marquee"
+    android:singleLine="true"
+    android:text="Description"
+    android:textSize="24sp"
+    android:visibility="visible"
+    app:layout_constraintBottom_toBottomOf="parent"
+    app:layout_constraintRight_toRightOf="parent"
+    app:layout_constraintStart_toStartOf="parent"/>
+
+</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
diff --git a/tests/DiagnosticTools/res/raw/system_dtcs.json b/tests/DiagnosticTools/res/raw/system_dtcs.json
new file mode 100644
index 0000000..c41bd28
--- /dev/null
+++ b/tests/DiagnosticTools/res/raw/system_dtcs.json
@@ -0,0 +1,149 @@
+{
+    "P0008": { "short_description":"Engine Position System Performance - Bank 1",  "long_description":"<strong>1. Meaning:<\/strong><br> Bank 1 camshaft and the crankshaft have a variation in mechanical timing. Basically the engine control module (ECM) is experiencing timing issues.<br> <br> <strong>3. Main Symptoms:<\/strong><br> •\tCheck Engine Light comes on<br> •\tDecreased power<br> •\tRough acceleration<br> •\tLower fuel economy<br> <br> <strong>4. Possible Causes:<\/strong><br> •\tTiming chain is experiencing tension<br> •\tTiming chain is stretched<br> •\tCrankshaft reluctor wheel is not referenced to TDC (top dead center)<br> •\tWiring damage<br> •\tWorn out timing components<br> •\tInternal damage to ECM<br> <br> <strong>5. Diagnostic Steps:<\/strong><br> •\tUse an advanced diagnostic tool to pull engine codes<br> •\tVisually inspect the VCT/VVT circuit for open/damaged wires."},
+    "P0010": { "short_description":"Intake Camshaft Position Actuator Circuit / Open (Bank 1)",  "long_description":"<strong>1. Meaning:<\/strong><br> Bank 1 camshaft and the crankshaft have a variation in mechanical timing. The problem occurs when the engine experiences high RPM. The ECM doesn’t properly adjust valve lift at high RPM.<br> <br> <strong>3. Main Symptoms:<\/strong><br> •\tCheck Engine Light comes on<br> •\tEngine performs poorly at high RPM<br> •\tCar runs roughly<br> •\tLower fuel economy<br> •\tCar fails emission test<br> <br> <strong>4. Possible Causes:<\/strong><br> •\tSludge in engine oil<br> •\tFaulty OVC (oil control valve)<br> •\tInternal damage to ECM<br> •\tECM timing is out of sync<br> •\tWiring damage<br> •\tMalfunction of crankshaft or camshaft sensor<br> •\tA short in VCT/VVT circuit, or the circuit is open<br> <br> <strong>5. Diagnostic Steps:<\/strong><br> •\tUse an advanced diagnostic tool to pull engine codes<br> •\tInspect the VVT/VCT solenoid system for dirty oil<br> •\tInspect the circuit for wiring problems"},
+    "P0011": { "short_description":"Intake Camshaft Position Timing - Over-Advanced (Bank 1)",  "long_description":"<strong>1. Meaning:<\/strong><br> •\tThe camshaft timing for bank 1 is above and beyond the limit set by the ECM. This causes an over-advanced condition that occurs either during retarding or advancing of the camshaft timing<br> <br> <strong>3. Main Symptoms:<\/strong><br> •\tCheck Engine Light comes on<br> •\tHard starting<br> •\tPoor idle<br> •\tCar may run rough or stall<br> •\tPoor fuel economy<br> •\tCar may fail emission test<br> <br> <strong>4. Possible Causes:<\/strong><br> •\tCamshaft remains advanced despite ECM commanding it to retard<br> •\tBank 1 oil control solenoid may be clogged or stuck<br> •\tOil may be too thick and is thus blocking passages in bank 1<br> •\tWiring problems in VCT/VVT<br> •\tOil continuously flows to VCT piston chamber<br> <br> <strong>5. Diagnostic Steps:<\/strong><br> •\tCheck that engine oil is clean and has the recommended viscosity<br> •\tVisually inspect wiring in the CVT system<br> •\tPull engine codes and live data using advanced diagnostic tool"},
+    "P0012": { "short_description":"Intake Camshaft Position Timing - Over-Retarded (Bank 1)",  "long_description":"<strong>1. Meaning:<\/strong><br> •\tBank 1 is having an over-retarded timing condition that occurs either during retarding or advancing<br> <br> <strong>3. Main Symptoms:<\/strong><br> •\tCheck Engine Light comes on<br> •\tHard starting<br> •\tPoor fuel economy<br> •\tCar may run rough or stall<br> •\tCar may fail emission test<br> <br> <strong>4. Possible Causes:<\/strong><br> •\tCamshaft timing is incorrect<br> •\tWiring problems in VCT/VVT<br> •\tOil continuously flows to VCT piston chamber<br> •\tTiming valve solenoid control has failed and is stuck in open position<br> •\tOil may be too thick and is thus blocking passages in bank 1<br> <br> <strong>5. Diagnostic Steps:<\/strong><br> •\tCheck that engine oil is clean<br> •\tVisually inspect wiring in the CVT system<br> •\tPull engine codes and live data using advanced diagnostic tool<br> •\tUsing bidirectional scanner, command the timing valve solenoid control valve to open and close then see if camshaft timings change. If they change it means the valve is not the problem"},
+    "P0014": { "short_description":"Exhaust Camshaft Position Timing - Over-Advanced (Bank 1)",  "long_description":"<strong>1. Meaning:<\/strong><br> •\tBank 1 camshaft is having an over-advanced timing condition that occurs either during retarding or advancing<br> <br> <strong>3. Main Symptoms:<\/strong><br> •\tCheck Engine Light comes on<br> •\tHard starting<br> •\tPoor fuel economy<br> •\tCar may run rough or stall<br> •\tCar may fail emission test<br> <br> <br> <strong>4. Possible Causes:<\/strong><br> •\tCamshaft timing is incorrect<br> •\tWiring problems in VCT/VVT<br> •\tOil continuously flows to VCT piston chamber<br> •\tTiming valve solenoid control has failed and is stuck in open position<br> •\tOil may be too thick and is thus blocking passages in bank 1<br> <br> <br> <strong>5. Diagnostic Steps:<\/strong><br> •\tCheck that engine oil is clean and full in the tank<br> •\tVisually inspect wiring in the CVT system<br> •\tPull engine codes and live data using advanced diagnostic tool<br> •\tUsing bidirectional scanner, command the timing valve solenoid control valve to open and close then see if camshaft timings change. If they change it means the valve is not the problem"},
+    "P0016": { "short_description":"Crankshaft Position Camshaft Position Correlation Bank 1 Sensor A",  "long_description":"<strong>1. Meaning:<\/strong><br> •\tThe crankshaft and camshaft signals are out of time. Meaning the ECM can detect that the timing of the crankshaft and that of the camshaft do not correlate <br> <br> <strong>3. Main Symptoms:<\/strong><br> •\tCheck Engine Light comes on<br> •\tEngine may crank but fail to start<br> •\tEngine may continue to run but will record poor performance<br> •\tRattling sound in the harmonic balancer<br> •\tPoor fuel economy<br> <br> <br> <strong>4. Possible Causes:<\/strong><br> •\tTiming chain is overstretched<br> •\tTone ring on camshaft and/or crankshaft is has slipped or broken<br> •\tTiming chain has jumped teeth and put camshaft timing out of position<br> •\tProblems with camshaft phaser and putting the phaser out of position<br> •\tWiring to crank/cam sensor is damaged<br> <br> <br> <strong>5. Diagnostic Steps:<\/strong><br> •\tInspect oil control valve (OCV) for connection or wiring problems<br> •\tCheck that engine oil is clean, full and has correct viscosity<br> •\tPull engine codes and live data using advanced diagnostic tool<br> •\tUsing bidirectional scanner, command the OVC on and off then see if camshaft timings change. If they change it means the valve is not the problem"},
+    "P0021": { "short_description":"Intake Camshaft Position Timing - Over-Advanced (Bank 2)",  "long_description":"<strong>1. Meaning:<\/strong><br> •\tThe camshaft timing for bank 2 is above and beyond the limit set by the ECM. This causes an over-advanced condition that occurs either during retarding or advancing of the camshaft timing<br> <br> <strong>3. Main Symptoms:<\/strong><br> •\tCheck Engine Light comes on<br> •\tHard starting<br> •\tPoor idle<br> •\tCar may run rough or stall<br> •\tPoor fuel economy<br> •\tCar may fail emission test<br> <br> <br> <strong>4. Possible Causes:<\/strong><br> •\tCamshaft remains advanced despite ECM commanding it to retard<br> •\tBank 2 oil control solenoid may be clogged or stuck<br> •\tOil may be too thick and is thus blocking passages in bank 2<br> •\tWiring problems in VCT/VVT<br> •\tOil continuously flows to VCT piston chamber<br> <br> <br> <strong>5. Diagnostic Steps:<\/strong><br> •\tCheck that engine oil is clean and has the recommended viscosity<br> •\tVisually inspect wiring in the CVT system<br> •\tPull engine codes and live data using advanced diagnostic tool"},
+    "P0022": { "short_description":"“A” camshaft position – timing over-retarded (Bank 2)",  "long_description":"<strong>1. Meaning:<\/strong><br> •\tThe camshaft timing for bank 2 is over-retarded<br> <br> <strong>3. Main Symptoms:<\/strong><br> •\tCheck Engine Light comes on<br> •\tCar stalls<br> •\tCar hard starts<br> <br> <br> <strong>4. Possible Causes:<\/strong><br> •\tCamshaft remains retarded despite ECM commanding it to advance<br> •\tWiring problems in VCT/VVT<br> •\tOil continuously flows to VCT piston chamber<br> •\tTiming valve controlled solenoid has failed and remains open<br> <br> <br> <strong>5. Diagnostic Steps:<\/strong><br> •\tVisually inspect wiring in the CVT system<br> •\tPull engine codes and live data using advanced diagnostic tool<br> •\tReset the codes. If P0022 returns use a bidirectional tool to check whether VCT solenoid is working"},
+    "P0030": { "short_description":"Heated Oxygen Sensor (H02S) Heater Control Circuit Bank 1 Sensor 1",  "long_description":"<strong>1. Meaning:<\/strong><br> •\tBank 1, sensor 1 of the O2 sensor heater circuit is faulty. As such, the engine isn’t achieving closed loop and therefore the car has increased emissions<br> <br> <strong>3. Main Symptoms:<\/strong><br> •\tCheck Engine Light comes on<br> •\tLonger time needed to achieve closed loop<br> •\tDecreased fuel economy<br> •\tEngine may go into fixed fuel mix<br> <br> <br> <strong>4. Possible Causes:<\/strong><br> •\tH02S sensor in bank 1, circuit 1 is not sending the correct signal to ECM<br> •\tDamaged or failed element in heater circuit<br> •\tOpen in O2 sensor heater’s circuit<br> •\tOpen/short in O2 sensor heater’s battery<br> •\tDefective ECM (this is the least likely cause)<br> <br> <br> <strong>5. Diagnostic Steps:<\/strong><br> •\tInspect wiring and power to the O2 sensor and ensure there’s no damage/open/short<br> •\tUse code reader to pull engine codes<br> •\tCheck voltage of O2 sensor and ensure it matches manufacturer’s specs<br> •\tReplace O2 sensor if necessary"},
+    "P0031": { "short_description":"Heated Oxygen Sensor (HO2S) Heater Circuit Low Voltage Bank 1 Sensor 1",  "long_description":"<strong>1. Meaning:<\/strong><br> •\tThe resistance of the heater circuit in bank 1 is too low to heat fuel until it achieves air to fuel ratio of 14:7. The problem is coming from the 1st sensor of bank 1. This code is usually triggered when resistance level is below 0.8A<br> <br> <strong>3. Main Symptoms:<\/strong><br> •\tCheck Engine Light comes on<br> •\tAlthough not always, ECM may enter failsafe mode<br> <br> <br> <strong>4. Possible Causes:<\/strong><br> •\tShort or open in the O2 heater circuit<br> •\tDefective O2 sensor heater<br> •\tThere’s a wiring problem in the circuit leading to the heater. It may be broken or frayed<br> •\tDefective ECM (this is the least likely cause)<br> <br> <br> <strong>5. Diagnostic Steps:<\/strong><br> •\tInspect wiring and power to the O2 sensor and ensure there’s no damage/open/short<br> •\tUse code reader to pull engine codes"},
+    "P0037": { "short_description":"Heated Oxygen Sensor (H02S) Heater Control Circuit Bank 1 Sensor 2",  "long_description":"<strong>1. Meaning:<\/strong><br> •\tBank 1, sensor 2 of the O2 sensor heater circuit is faulty. As such, the engine isn’t achieving closed loop and therefore the car has increased emissions<br> <br> <strong>3. Main Symptoms:<\/strong><br> •\tCheck Engine Light comes on<br> •\tLonger time needed to achieve closed loop<br> •\tDecreased fuel economy<br> •\tEngine may go into fixed fuel mix<br> <br> <br> <strong>4. Possible Causes:<\/strong><br> •\tH02S sensor in bank 1, circuit 2 is not sending the correct signal to ECM<br> •\tDamaged or failed element in heater circuit<br> •\tOpen in O2 sensor heater’s circuit<br> •\tOpen/short in O2 sensor heater’s battery<br> •\tDefective ECM (this is the least likely cause)<br> <br> <br> <strong>5. Diagnostic Steps:<\/strong><br> •\tInspect wiring and power to the O2 sensor and ensure there’s no damage/open/short<br> •\tUse code reader to pull engine codes<br> •\tCheck voltage of O2 sensor and ensure it matches manufacturer’s specs<br> •\tReplace O2 sensor if necessary"},
+    "P0087": { "short_description":"Fuel Rail/System Pressure - Too Low",  "long_description":"<strong>1. Meaning:<\/strong><br> •\tThe ECM has determined that the pressure of fuel going to the fuel pump module is below the pressure that was commanded by the ECM<br> <br> <strong>3. Main Symptoms:<\/strong><br> •\tCheck Engine Light comes on<br> •\tVehicle may have a rich fuel condition<br> •\tIt may also misfire<br> •\tVehicle may run poorly<br> <br> <br> <strong>4. Possible Causes:<\/strong><br> •\tClogged fuel filter or fuel screen<br> •\tDefective fuel pressure sensor<br> •\tDefective fuel pump<br> •\tFault in fuel line that’s causing a restriction<br> <br> <br> <strong>5. Diagnostic Steps:<\/strong><br> •\tUse code reader to pull engine codes<br> •\tVisually inspect fuel tank, fuel filter and fuel lines<br> •\tTake manual readings of fuel pressure and compare with specifications<br> •\tRun a fuel pump test"},
+    "P0102": { "short_description":"Mass or Volume Air Flow Circuit low Input",  "long_description":"<strong>1. Meaning:<\/strong><br> •\tThe mass airflow (MAF) sensor is not performing within the normal expectation and is therefore sending a lower signal than normal (due to low voltage)<br> <br> <strong>3. Main Symptoms:<\/strong><br> •\tCheck Engine Light comes on<br> •\tExtremely low fuel consumption and thus internal engine problems<br> •\tEngine runs roughly<br> •\tCar idles and stalls frequently<br> <br> <br> <strong>4. Possible Causes:<\/strong><br> •\tDefective MAF sensor<br> •\tPresence of dirt and debris in MAF (restricts airflow)<br> •\tLeaks in air intake system<br> •\tImproper wiring of the circuit to MAF sensor<br> <br> <br> <strong>5. Diagnostic Steps:<\/strong><br> •\tUse code reader to pull engine codes <br> •\tVisually inspect MAF sensor wiring and circuit<br> •\tCheck for air leaks in air intake system<br> •\tInspect MAF to see if there’s dirt and debris"},
+    "P0106": { "short_description":"Manifold Absolute Pressure/Barometric Pressure Circuit Range/Performance Problem",  "long_description":"<strong>1. Meaning:<\/strong><br> •\tThe Powertrain Control Module (PCM) has not detected a change in engine speed, throttle angle and/or exhaust gas recirculation (EGR) despite an increase in manifold absolute pressure (MAP). Increase in MAP indicates increase in engine load<br> <br> <strong>3. Main Symptoms:<\/strong><br> •\tCheck Engine Light comes on<br> •\tPoor fuel economy<br> •\tEngine fails to idle<br> •\tEngine produces black smoke (visible at tailpipe)<br> •\tErratic acceleration<br> <br> <br> <strong>4. Possible Causes:<\/strong><br> •\tFaulty MAP sensor<br> •\tAir intake component is loose, cracked or doesn’t have its plastic fitting<br> •\tWater or dirt affecting connector to MAP sensor<br> •\tCorrosion may be causing poor signal to and from MAP sensor<br> •\tPCM is defective (least likely but not unlikely)<br> <br> <br> <strong>5. Diagnostic Steps:<\/strong><br> •\tUse an advanced scanner to pull engine codes <br> •\tWith that scanner, take the reading of MAP sensor when engine is off but key is on. It should be similar or close to barometric pressure (BARO) reading<br> •\tStart the engine and see if MAP sensor readings drop significantly. If they do the sensor is working properly "},
+    "P0107": { "short_description":"Manifold Absolute Pressure/Barometric Pressure Circuit Low Input",  "long_description":"<strong>1. Meaning:<\/strong><br> •\tPCM has detected that the voltage in the MAP sensor is less than .25 volts, which is too low to send a reliable signal<br> <br> <strong>3. Main Symptoms:<\/strong><br> •\tCheck Engine Light comes on<br> •\tVehicle is hard to start<br> •\tPoor fuel economy<br> •\tEngine fails to idle<br> •\tEngine produces black smoke (visible at tailpipe)<br> •\tEngine cranks for long<br> •\tErratic acceleration<br> <br> <br> <strong>4. Possible Causes:<\/strong><br> •\tFaulty MAP sensor<br> •\tOpen or short in signal circuit, 5volt reference circuit or ground circuit<br> •\tPCM is defective (least likely but not unlikely)<br> <br> <br> <strong>5. Diagnostic Steps:<\/strong><br> •\tUse an advanced scanner to pull engine codes <br> •\tWith that scanner, take the voltage of MAP sensor when engine is on and key is on. If it’s less than .5V turn the engine off, remove the MAP sensor and use a volt/ohm meter to check for 5 volts on the 5 volt reference circuit"},
+    "P0108": { "short_description":"Manifold Absolute Pressure/Barometric Pressure Circuit High Input",  "long_description":"<strong>1. Meaning:<\/strong><br> •\tPCM has detected that the voltage in the MAP sensor is more than 5 volts or generally more than commanded under the prevailing circumstance<br> <br> <strong>3. Main Symptoms:<\/strong><br> •\tCheck Engine Light comes on<br> •\tPoor fuel economy<br> •\tEngine fails to idle and may not start completely<br> •\tEngine produces black smoke (visible at tailpipe)<br> •\tEngine cranks for long<br> •\tErratic acceleration<br> <br> <br> <strong>4. Possible Causes:<\/strong><br> •\tFaulty MAP sensor<br> •\tOpen or short in signal circuit, 5volt reference circuit or ground circuit<br> •\tLeak in vacuum system, especially in the engine or supply line to MAP sensor<br> •\tPCM is defective (least likely but not unlikely)<br> <br> <br> <strong>5. Diagnostic Steps:<\/strong><br> •\tUse an advanced scanner to pull engine codes <br> •\tWith that scanner, take the reading of MAP sensor when engine is off but key is on. It should be similar or close to barometric pressure (BARO) reading<br> •\tIf the difference between the two readings is more than .5 then you’re looking at a faulty MAP sensor"},
+    "P0113": { "short_description":"Intake Air Temperature Circuit High Input",  "long_description":"<strong>1. Meaning:<\/strong><br> •\tThe PCM has detected that the signal voltage from the intake air temperature (IAT) is above 5V, which is more than the expected range<br> <br> <strong>3. Main Symptoms:<\/strong><br> •\tCheck Engine Light comes on<br> •\tEngine may run extra lean<br> <br> <br> <strong>4. Possible Causes:<\/strong><br> •\tDefective IAT sensor<br> •\tLoose or faulty wiring at IAT sensor<br> •\tOpen or short in IAT ground circuit, signal circuit or reference circuit<br> •\tPCM is defective (least likely but not unlikely)<br> <br> <br> <strong>5. Diagnostic Steps:<\/strong><br> •\tUse an advanced scanner to pull engine codes.<br> •\tView live data from the IAT sensor. If the reading is less than -30 degrees then the sensor is likely to be faulty. If otherwise then it’s probably an intermittent problem<br> •\tCheck the wiring for opens and loose connections"},
+    "P0116": { "short_description":"Engine Coolant Temperature Circuit Range/Performance Problem",  "long_description":"<strong>1. Meaning:<\/strong><br> •\tThe PCM has seen a sudden and quick change in engine coolant temperature (ECT) at a time when there shouldn’t be such a change<br> <br> <strong>3. Main Symptoms:<\/strong><br> •\tCheck Engine Light comes on<br> •\tPoor fuel economy<br> •\tEngine fails to idle and may not start completely<br> •\tEngine produces black smoke (visible at tailpipe)<br> <br> <br> <strong>4. Possible Causes:<\/strong><br> •\tThermostat is either missing or open<br> •\tDefective ECT sensor<br> •\tOpen or short in ECT signal or ground circuit<br> <br> <br> <strong>5. Diagnostic Steps:<\/strong><br> •\tUsing an OBD2 scanner, start by diagnosing and resetting any other ECT codes<br> •\tNow, check the ECT reading. When the engine is cold the reading should match ambient temperature reading. If it doesn’t then there’s a problem with the ECT sensor"},
+    "P0118": { "short_description":"Engine Coolant Temperature Circuit High Input",  "long_description":"<strong>1. Meaning:<\/strong><br> •\tThe PCM has determined that ECT is less than freezing temp yet the engine has been running for several minutes, which shouldn’t happen<br> <br> <strong>3. Main Symptoms:<\/strong><br> •\tCheck Engine Light comes on<br> •\tPoor fuel economy<br> •\tEngine fails to idle and may not start completely<br> •\tEngine produces black smoke (visible at tailpipe)<br> <br> <br> <strong>4. Possible Causes:<\/strong><br> •\tDefective ECT sensor<br> •\tOpen or short in ECT signal or ground circuit<br> •\tPCM is defective (least likely but not unlikely)<br> <br> <br> <strong>5. Diagnostic Steps:<\/strong><br> •\tUsing an OBD2 scanner, check the reading of ECT. If it’s a logical reading then the problem is intermittent<br> •\tPerform a wiggle test while looking out for drop-outs. If there are any then there’s a bad connection to or from the ECT sensor"},
+    "P0121": { "short_description":"Throttle/Pedal Position Sensor/Switch A Circuit Range/Performance Problem",  "long_description":"<strong>1. Meaning:<\/strong><br> •\tThe PCM has detected that the throttle position sensor (TPS) voltage is more or less than it should be for the current RPM<br> <br> <strong>3. Main Symptoms:<\/strong><br> •\tCheck Engine Light comes on<br> •\tCar produces black smoke (visible at tailpipe)<br> •\tCar stumbles when you accelerate or decelerate<br> •\tEngine may fail to start completely<br> <br> <br> <strong>4. Possible Causes:<\/strong><br> •\tDefective TPS<br> •\tOpen or short in TPS circuit<br> •\tLoose or bad connection to TPS<br> •\tPCM is defective (least likely but not unlikely)<br> <br> <br> <strong>5. Diagnostic Steps:<\/strong><br> •\tVisually inspect all wiring to TPS for loose, open or short connections <br> •\tUsing an OBD2 scanner, check for live and freeze frame data from TPS. If it doesn’t read .5 at idle and 4.5 at full throttle the TPS is faulty"},
+    "P0122": { "short_description":"Throttle/Pedal Position Sensor/Switch A Circuit Low Input",  "long_description":"<strong>1. Meaning:<\/strong><br> •\tPCM is reporting that the TPS has recorded a voltage that is lower than the normal minimum limit. The value varies from one car to another but the code may come when the voltage hits .20V or less<br> <br> <strong>3. Main Symptoms:<\/strong><br> •\tCheck Engine Light comes on<br> •\tExtremely high idle<br> •\tRough or low idle<br> •\tCar stalls<br> •\tAcceleration is low or completely lacking<br> <br> <br> <strong>4. Possible Causes:<\/strong><br> •\tDefective TPS<br> •\tOpen or short in TPS circuit<br> •\tImproper mounting of TPS after replacement<br> •\tTPS has loosened<br> •\tPCM is defective (least likely but not unlikely)<br> <br> <br> <strong>5. Diagnostic Steps:<\/strong><br> •\tVisually inspect all wiring to TPS for loose, open or short connections<br> •\tCheck that TPS is tightly in position, especially if you recently replaced it"},
+    "P0123": { "short_description":"Throttle/Pedal Position Sensor/Switch A Circuit High Input",  "long_description":"<strong>1. Meaning:<\/strong><br> •\tPCM is reporting that the TPS has recorded a voltage that is higher than the normal maximum limit, usually around 5 volts<br> <br> <strong>3. Main Symptoms:<\/strong><br> •\tCheck Engine Light comes on<br> •\tExtremely high idle<br> •\tRough or low idle<br> •\tFrequent surges<br> <br> <br> <strong>4. Possible Causes:<\/strong><br> •\tDefective TPS<br> •\tOpen or short in TPS circuit<br> •\tImproper mounting of TPS after replacement<br> •\tPCM is defective (least likely but not unlikely)<br> <br> <br> <strong>5. Diagnostic Steps:<\/strong><br> •\tVisually inspect all wiring to TPS for loose, open or short connections<br> •\tCheck that TPS is tightly in position, especially if you recently replaced it"},
+    "P0128": { "short_description":"Coolant Thermostat (Coolant Temp Below Thermostat Regulating Temperature)",  "long_description":"<strong>1. Meaning:<\/strong><br> •\tPCM has detected that the engine has not attained the required temperature despite being on for enough time to attain that temperature<br> <br> <strong>3. Main Symptoms:<\/strong><br> •\tCheck Engine Light MAY come on<br> •\tEngine temp drops when the vehicle is in high speed<br> •\tEngine takes abnormally long to warm<br> <br> <br> <strong>4. Possible Causes:<\/strong><br> •\tMost likely cause is that thermostat is leaking or stuck in open position<br> •\tEngine coolant level is too low<br> •\tDefective IAT sensor<br> •\tDefective ECT sensor<br> •\tDefective cooling fan<br> <br> <br> <strong>5. Diagnostic Steps:<\/strong><br> •\tCheck whether coolant strength and level are in the recommended range<br> •\tCheck whether IAT sensor, ECT sensor and coolant fan are working<br> •\tIf all the above are okay then the thermostat is the problem"},
+    "P0130": { "short_description":"O2 Sensor Circuit Malfunction (Bank 1 Sensor 1)",  "long_description":"<strong>1. Meaning:<\/strong><br> •\tECM has determined that the O2 sensor voltage remained lower than normal (below .4v) for too long (20 seconds or more)<br> <br> <strong>3. Main Symptoms:<\/strong><br> •\tCheck Engine Light comes on<br> •\tCar produces black smoke (visible at tailpipe)<br> •\tPoor fuel economy<br> •\tEngine may fail to start completely<br> •\tIf it starts it may run rough and/or stumble<br> <br> <br> <strong>4. Possible Causes:<\/strong><br> •\tMostly a problem related to corrosion, loose terminal or burnt wire in the O2 sensor connector<br> •\tDefective O2 sensor<br> •\tOpen or short in wiring to O2 sensor<br> •\tUnmetered oxygen is getting back into exhaust system, most likely from holes in the system<br> <br> <br> <strong>5. Diagnostic Steps:<\/strong><br> •\tVisually inspect all wiring to O2 sensor for loose, open or short connections<br> •\tUse a wiggle test to determine where the voltage drops out<br> •\tUsing an OBD2 scanner, check whether sensor 1 of bank 1 is switching properly. Normally it should switch evenly between rich and lean in rapid successions"},
+    "P0131": { "short_description":"O2 Sensor Circuit Low Voltage (Bank 1 Sensor 1)",  "long_description":"<strong>1. Meaning:<\/strong><br> •\tECM has determined that there’s a low voltage condition in bank 1 sensor 1; i.e. O2 sensor voltage remained too low for longer than 2 minutes<br> <br> <strong>3. Main Symptoms:<\/strong><br> •\tCheck Engine Light comes on<br> •\tCar produces black smoke (visible at tailpipe)<br> •\tPoor fuel economy<br> •\tEngine may fail to start completely<br> •\tIf it starts it may run rough and/or stumble<br> <br> <br> <strong>4. Possible Causes:<\/strong><br> •\tMostly a problem related to corrosion, loose terminal or burnt wire in the O2 sensor 1 connector<br> •\tDefective O2 sensor<br> •\tOpen or short in wiring to O2 sensor<br> •\tO2 circuit is experiencing high resistance<br> <br> <br> <strong>5. Diagnostic Steps:<\/strong><br> •\tVisually inspect all wiring to O2 sensor 1 for loose, open or short connections<br> •\tUse a wiggle test to determine where the voltage drops out<br> •\tUsing an OBD2 scanner, check whether sensor 1 of bank 1 is switching properly."},
+    "P0132": { "short_description":"O2 Sensor Circuit High Voltage (Bank 1 Sensor 1)",  "long_description":"<strong>1. Meaning:<\/strong><br> •\tThe heated O2 sensor in bank 1 sensor 1 is giving a higher voltage reading than it should. For most vehicles the code comes when voltage exceeds 1.5V<br> <br> <strong>3. Main Symptoms:<\/strong><br> •\tGenerally doesn’t come with symptoms. However, in some cases the Check Engine Light may come on and fuel economy may reduce<br> <br> <strong>4. Possible Causes:<\/strong><br> •\tFuel temp is excessively high<br> •\tShort, open or broken wire in O2 sensor circuit<br> <br> <br> <strong>5. Diagnostic Steps:<\/strong><br> •\tVisually inspect all wiring to O2 sensor for loose, open or short connections<br> •\tUse a wiggle test to determine where the voltage drops out<br> •\tUsing an OBD2 scanner, check whether sensor 1 of bank 1 is switching properly."},
+    "P0133": { "short_description":"O2 Sensor Circuit Slow Response (Bank 1 Sensor 1)",  "long_description":"<strong>1. Meaning:<\/strong><br> •\tO2 sensor or ECM can’t adjust air to fuel ratio as it’s supposed to even when the engine is running<br> <br> <strong>3. Main Symptoms:<\/strong><br> •\tGenerally doesn’t come with symptoms. However, in some cases the Check Engine Light may come on and fuel economy may reduce<br> <br> <strong>4. Possible Causes:<\/strong><br> •\tFirst O2 sensor in bank 1 is faulty<br> •\tShort, open or broken wire in O2 sensor circuit<br> •\tExhaust leak<br> <br> <br> <strong>5. Diagnostic Steps:<\/strong><br> •\tVisually inspect all wiring to first O2 sensor for loose, open or short connections<br> •\tUse a wiggle test to determine where the voltage drops out<br> •\tVisually check for exhaust leaks or air inlet leaks<br> •\tUsing an OBD2 scanner, check whether sensor 1 of bank 1 is switching properly."},
+    "P0134": { "short_description":"O2 Sensor Circuit No Activity Detected (Bank 1 Sensor 1)",  "long_description":"<strong>1. Meaning:<\/strong><br> •\tPCM has perceived that first O2 sensor in bank 1 is inactive or open because it has not warmed up after more than 1 minute of the engine running<br> <br> <strong>3. Main Symptoms:<\/strong><br> •\tCheck Engine Light comes on<br> •\tCar produces black smoke (visible at tailpipe)<br> •\tPoor fuel economy<br> •\tEngine may run rough and/or stumble<br> <br> <br> <strong>4. Possible Causes:<\/strong><br> •\tFaulty O2 sensor<br> •\tShort, open or broken wire in O2 sensor circuit<br> •\tExhaust leak<br> •\tDefective heater circuit in O2 sensor<br> •\tHeater circuit has blown fuse<br> •\tPCM is defective (least likely but not unlikely)<br> <br> <br> <strong>5. Diagnostic Steps:<\/strong><br> •\tVisually inspect all wiring to first O2 sensor (bank 1) for loose, open or short connections<br> •\tUse a wiggle test to determine where the voltage drops out<br> •\tVisually check for exhaust leaks or air inlet leaks"},
+    "P0135": { "short_description":"O2 Sensor Heater Circuit Malfunction (Bank 1 Sensor 1)",  "long_description":"<strong>1. Meaning:<\/strong><br> •\tUsually, when O2 heater attains operating temperature, O2 sensor switches based on ambient temp. If ECM determines that the O2 sensor took too long to switch this code is set. It applies to the first sensor of bank 1<br> <br> <strong>3. Main Symptoms:<\/strong><br> •\tCheck Engine Light comes on<br> •\tPoor fuel economy<br> <br> <br> <strong>4. Possible Causes:<\/strong><br> •\tShort, open or broken wire in O2 heating system<br> •\tHigh resistance in O2 heater element or circuit<br> <br> <br> <strong>5. Diagnostic Steps:<\/strong><br> •\tVisually inspect all wiring to first O2 sensor (bank 1) for loose, open or short connections<br> •\tUse a wiggle test to determine where the voltage drops out<br> •\tIf the code is persistent replace O2 sensor<br> "},
+    "P0136": { "short_description":"O2 Sensor Circuit Malfunction (Bank 1 Sensor 2)",  "long_description":"<strong>1. Meaning:<\/strong><br> •\tECM has determined that the there’s a low voltage condition in bank 1 sensor 2; i.e. O2 sensor voltage remained too low for longer than 2 minutes<br> <br> <strong>3. Main Symptoms:<\/strong><br> •\tCheck Engine Light comes on<br> •\tCar produces black smoke (visible at tailpipe)<br> •\tPoor fuel economy<br> •\tEngine may fail to start completely<br> •\tIf it starts it may run rough and/or stumble<br> <br> <br> <strong>4. Possible Causes:<\/strong><br> •\tMostly a problem related to corrosion, loose terminal or burnt wire in the O2 sensor 2 connector<br> •\tDefective O2 sensor<br> •\tOpen or short in wiring to O2 sensor<br> •\tO2 circuit is experiencing high resistance<br> <br> <br> <strong>5. Diagnostic Steps:<\/strong><br> •\tVisually inspect all wiring to O2 sensor 2 for loose, open or short connections<br> •\tUse a wiggle test to determine where the voltage drops out<br> •\tUsing an OBD2 scanner, check whether sensor 2 of bank 1 is switching properly."},
+    "P0137": { "short_description":"O2 Sensor Circuit Low Voltage (Bank 1 Sensor 2)",  "long_description":"<strong>1. Meaning:<\/strong><br> •\tBasically same as P0136. The PCM has detected that the O2 sensor may be inactive<br> <br> <strong>3. Main Symptoms:<\/strong><br> •\tGenerally doesn’t come with symptoms. However, in some cases the Check Engine Light may come on and fuel economy may reduce<br> <br> <strong>4. Possible Causes:<\/strong><br> •\tFaulty O2 sensor<br> •\tShort, open or broken wire in O2 sensor circuit<br> •\tDefective heater circuit in O2 sensor<br> •\tHigh resistance in O2 heater element or circuit<br> •\tFaulty fuel pump regulator resulting in very high or very low fuel pressure<br> •\tExhaust leak<br> <br> <br> <strong>5. Diagnostic Steps:<\/strong><br> •\tVisually inspect all wiring to O2 sensor for loose, open or short connections<br> •\tUse a wiggle test to determine where the voltage drops out<br> •\tVisually check for exhaust leaks or air inlet leaks<br> •\tIf the code is persistent replace O2 sensor"},
+    "P0138": { "short_description":"O2 Sensor Circuit High Voltage (Bank 1 Sensor 2)",  "long_description":"<strong>1. Meaning:<\/strong><br> •\tThe heated O2 sensor in bank 1 sensor 2 is giving a higher voltage reading than it should. For most vehicles the code comes when voltage exceeds 1.5V<br> <br> <strong>3. Main Symptoms:<\/strong><br> •\tGenerally doesn’t come with symptoms. However, in some cases the Check Engine Light may come on and fuel economy may reduce<br> <br> <strong>4. Possible Causes:<\/strong><br> •\tFuel temp is excessively high<br> •\tShort, open or broken wire in O2 sensor circuit<br> <br> <br> <strong>5. Diagnostic Steps:<\/strong><br> •\tVisually inspect all wiring to O2 sensor for loose, open or short connections<br> •\tUse a wiggle test to determine where the voltage drops out<br> •\tUsing an OBD2 scanner, check whether sensor 1 of bank 2 is switching properly."},
+    "P0139": { "short_description":"O2 Sensor Circuit Slow Response (Bank 1 Sensor 2)",  "long_description":"<strong>1. Meaning:<\/strong><br> •\tO2 sensor or ECM can’t adjust air to fuel ratio as it’s supposed to even when the engine is running<br> <br> <strong>3. Main Symptoms:<\/strong><br> •\tGenerally doesn’t come with symptoms. However, in some cases the Check Engine Light may come on and fuel economy may reduce<br> <br> <strong>4. Possible Causes:<\/strong><br> •\tSecond O2 sensor in bank 1 is faulty<br> •\tShort, open or broken wire in O2 sensor circuit<br> •\tExhaust leak<br> <br> <br> <strong>5. Diagnostic Steps:<\/strong><br> •\tVisually inspect all wiring to second O2 sensor for loose, open or short connections<br> •\tUse a wiggle test to determine where the voltage drops out<br> •\tVisually check for exhaust leaks or air inlet leaks<br> •\tUsing an OBD2 scanner, check whether sensor 2 of bank 1 is switching properly."},
+    "P0140": { "short_description":"O2 Sensor Circuit No Activity Detected (Bank 2 Sensor 2)",  "long_description":"<strong>1. Meaning:<\/strong><br> •\tPCM has perceived that second O2 sensor in bank 2 is inactive or open because it has not warmed up after more than 1 minute of the engine running<br> <br> <strong>3. Main Symptoms:<\/strong><br> •\tCheck Engine Light comes on<br> •\tCar produces black smoke (visible at tailpipe)<br> •\tPoor fuel economy<br> •\tEngine may run rough and/or stumble<br> <br> <br> <strong>4. Possible Causes:<\/strong><br> •\tFaulty O2 sensor<br> •\tShort, open or broken wire in O2 sensor circuit<br> •\tExhaust leak<br> •\tDefective heater circuit in O2 sensor<br> •\tHeater circuit has blown fuse<br> •\tPCM is defective (least likely but not unlikely)<br> <br> <br> <strong>5. Diagnostic Steps:<\/strong><br> •\tVisually inspect all wiring to second O2 sensor (in bank 2) for loose, open or short connections<br> •\tUse a wiggle test to determine where the voltage drops out<br> •\tVisually check for exhaust leaks or air inlet leaks"},
+    "P0141": { "short_description":"O2 Sensor Heater Circuit Malfunction (Bank 1 Sensor 2)",  "long_description":"<strong>1. Meaning:<\/strong><br> •\tWhen O2 heater attains operating temperature, O2 sensor switches based on ambient temp. If ECM determines that the O2 sensor took too long to switch this code is set. It applies to the second sensor of bank 1<br> <br> <strong>3. Main Symptoms:<\/strong><br> •\tCheck Engine Light comes on<br> •\tPoor fuel economy<br> <br> <br> <strong>4. Possible Causes:<\/strong><br> •\tShort, open or broken wire in O2 heating system<br> •\tHigh resistance in O2 heater element or circuit<br> <br> <br> <strong>5. Diagnostic Steps:<\/strong><br> •\tVisually inspect all wiring to second O2 sensor (bank 1) for loose, open or short connections<br> •\tUse a wiggle test to determine where the voltage drops out<br> •\tIf the code is persistent replace O2 sensor"},
+    "P0151": { "short_description":"O2 Sensor Circuit Low Voltage (Bank 2 Sensor 1)",  "long_description":"<strong>1. Meaning:<\/strong><br> •\tECM has determined that there’s a low voltage condition in bank 2 sensor 1; i.e. O2 sensor voltage remained too low for longer than 2 minutes<br> <br> <strong>3. Main Symptoms:<\/strong><br> •\tCheck Engine Light comes on<br> •\tCar produces black smoke (visible at tailpipe)<br> •\tPoor fuel economy<br> •\tEngine may fail to start completely<br> •\tIf it starts it may run rough and/or stumble<br> <br> <br> <strong>4. Possible Causes:<\/strong><br> •\tMostly a problem related to corrosion, loose terminal or burnt wire in the O2 sensor 2 connector<br> •\tDefective O2 sensor<br> •\tOpen or short in wiring to O2 sensor<br> •\tO2 circuit is experiencing high resistance<br> <br> <br> <strong>5. Diagnostic Steps:<\/strong><br> •\tVisually inspect all wiring to O2 sensor 1 for loose, open or short connections<br> •\tUse a wiggle test to determine where the voltage drops out<br> •\tUsing an OBD2 scanner, check whether sensor 1 of bank 2 is switching properly."},
+    "P0153": { "short_description":"O2 Sensor Circuit Slow Response (Bank 2 Sensor 1)",  "long_description":"<strong>1. Meaning:<\/strong><br> •\tO2 sensor or ECM can’t adjust air to fuel ratio as it’s supposed to even when the engine is runningc<br> <br> <strong>3. Main Symptoms:<\/strong><br> •\tGenerally doesn’t come with symptoms. However, in some cases the Check Engine Light may come on and fuel economy may reduce<br> <br> <strong>4. Possible Causes:<\/strong><br> •\tFirst O2 sensor in bank 2 is faulty<br> •\tShort, open or broken wire in O2 sensor circuit<br> •\tExhaust leak<br> <br> <br> <strong>5. Diagnostic Steps:<\/strong><br> •\tVisually inspect all wiring to second O2 sensor for loose, open or short connections<br> •\tUse a wiggle test to determine where the voltage drops out<br> •\tVisually check for exhaust leaks or air inlet leaks<br> •\tUsing an OBD2 scanner, check whether sensor 1 of bank 2 is switching properly."},
+    "P0154": { "short_description":"O2 Sensor Circuit No Activity Detected (Bank 2 Sensor 1)",  "long_description":"<strong>1. Meaning:<\/strong><br> •\tPCM has perceived that first O2 sensor in bank 2 is inactive or open because it has not warmed up after more than 1 minute of the engine running<br> <br> <strong>3. Main Symptoms:<\/strong><br> •\tCheck Engine Light comes on<br> •\tCar produces black smoke (visible at tailpipe)<br> •\tPoor fuel economy<br> •\tEngine may run rough and/or stumble<br> <br> <br> <strong>4. Possible Causes:<\/strong><br> •\tFaulty O2 sensor<br> •\tShort, open or broken wire in O2 sensor circuit<br> •\tExhaust leak<br> •\tDefective heater circuit in O2 sensor<br> •\tHeater circuit has blown fuse<br> •\tPCM is defective (least likely but not unlikely)<br> <br> <br> <strong>5. Diagnostic Steps:<\/strong><br> •\tVisually inspect all wiring to first O2 sensor (bank 2) for loose, open or short connections<br> •\tUse a wiggle test to determine where the voltage drops out<br> •\tVisually check for exhaust leaks or air inlet leaks"},
+    "P0157": { "short_description":"O2 Sensor Circuit Low Voltage (Bank 2 Sensor 2)",  "long_description":"<strong>1. Meaning:<\/strong><br> •\tECM has determined that there’s a low voltage condition in bank 2 sensor 2; i.e. O2 sensor voltage remained too low for longer than 2 minutes<br> <br> <strong>3. Main Symptoms:<\/strong><br> •\tCheck Engine Light comes on<br> •\tCar produces black smoke (visible at tailpipe)<br> •\tPoor fuel economy<br> •\tEngine may fail to start completely<br> •\tIf it starts it may run rough and/or stumble<br> <br> <br> <strong>4. Possible Causes:<\/strong><br> •\tMostly a problem related to corrosion, loose terminal or burnt wire in the O2 sensor 2 connector<br> •\tDefective O2 sensor<br> •\tOpen or short in wiring to O2 sensor<br> •\tO2 circuit is experiencing high resistance<br> <br> <br> <strong>5. Diagnostic Steps:<\/strong><br> •\tVisually inspect all wiring to O2 sensor 2 for loose, open or short connections<br> •\tUse a wiggle test to determine where the voltage drops out<br> •\tUsing an OBD2 scanner, check whether sensor 2 of bank 2 is switching properly."},
+    "P0161": { "short_description":"O2 Sensor Heater Circuit Malfunction (Bank 2 Sensor 2)",  "long_description":"<strong>1. Meaning:<\/strong><br> •\tWhen O2 heater attains operating temperature, O2 sensor switches based on ambient temp. If ECM determines that the O2 sensor took too long to switch this code is set. It applies to the second sensor of bank 2<br> <br> <strong>3. Main Symptoms:<\/strong><br> •\tCheck Engine Light comes on<br> •\tPoor fuel economy<br> <br> <br> <strong>4. Possible Causes:<\/strong><br> •\tShort, open or broken wire in O2 heating system<br> •\tHigh resistance in O2 heater element or circuit<br> <br> <br> <strong>5. Diagnostic Steps:<\/strong><br> •\tVisually inspect all wiring to second O2 sensor (bank 2) for loose, open or short connections<br> •\tUse a wiggle test to determine where the voltage drops out<br> •\tIf the code is persistent replace O2 sensor"},
+    "P0171": { "short_description":"System Too Lean (Bank 1)",  "long_description":"<strong>1. Meaning:<\/strong><br> •\tThere’s a lean condition in bank 1; i.e. there’s excess oxygen in the exhaust<br> <br> <strong>3. Main Symptoms:<\/strong><br> •\tSignificant decrease in engine power<br> •\tCar hesitates then surges upon acceleration<br> •\tRough idle<br> <br> <br> <strong>4. Possible Causes:<\/strong><br> •\tDirty or defective MAF sensor<br> •\tMAF sensor has a vacuum leak<br> •\tPositive Crankcase Ventilation (PCV) valve is stuck in open position<br> •\tLeak either in PCV or vacuum system<br> <br> <br> <strong>5. Diagnostic Steps:<\/strong><br> •\tInspect MAF sensor in bank 1 for dirt and debris<br> •\tCheck whether fuel pressure is correct<br> •\tCheck vacuum and PCV for leaks<br> •\tRun a smog test using OBD2 scanner"},
+    "P0172": { "short_description":"System Too Rich (Bank 1)",  "long_description":"<strong>1. Meaning:<\/strong><br> •\tThere’s a rich condition in bank 1; i.e. there’s too little oxygen in the exhaust<br> <br> <strong>3. Main Symptoms:<\/strong><br> •\tGenerally doesn’t come with symptoms but the Check Engine Light may come on and engine may misfire<br> <br> <strong>4. Possible Causes:<\/strong><br> •\tDirty or defective MAF sensor<br> •\tMAF sensor has a vacuum leak<br> •\tProblem relating to fuel pressure or delivery<br> <br> <br> <strong>5. Diagnostic Steps:<\/strong><br> •\tInspect MAF sensor in bank 1 for dirt and debris<br> •\tCheck whether fuel pressure is correct<br> •\tInspect fuel lines and injectors for any leaks/openings and dirt<br> •\tCheck vacuum, PCV and exhaust for leaks<br> •\tRun a smog test using OBD2 scanner"},
+    "P0174": { "short_description":"System Too Lean (Bank 2)",  "long_description":"<strong>1. Meaning:<\/strong><br> •\tThere’s a lean condition in bank 2; i.e. there’s excess oxygen in the exhaust<br> <br> <strong>3. Main Symptoms:<\/strong><br> •\tSignificant decrease in engine power<br> •\tCar hesitates then surges upon acceleration<br> •\tRough idle<br> <br> <br> <strong>4. Possible Causes:<\/strong><br> •\tDirty or defective MAF sensor<br> •\tMAF sensor has a vacuum leak<br> •\tPositive Crankcase Ventilation (PCV) valve is stuck in open position<br> •\tLeak either in PCV or vacuum system<br> <br> <br> <strong>5. Diagnostic Steps:<\/strong><br> •\tInspect MAF sensor in bank 2 for dirt and debris<br> •\tCheck whether fuel pressure is correct<br> •\tCheck vacuum and PCV for leaks<br> •\tRun a smog test using OBD2 scanner"},
+    "P0175": { "short_description":"System Too Rich (Bank 2)",  "long_description":"<strong>1. Meaning:<\/strong><br> •\tThere’s a rich condition in bank 2; i.e. there’s too little oxygen in the exhaust<br> <br> <strong>3. Main Symptoms:<\/strong><br> •\tGenerally doesn’t come with symptoms but the Check Engine Light may come on and engine may misfire<br> <br> <strong>4. Possible Causes:<\/strong><br> •\tDirty or defective MAF sensor<br> •\tMAF sensor has a vacuum leak<br> •\tProblem relating to fuel pressure or delivery<br> <br> <br> <strong>5. Diagnostic Steps:<\/strong><br> •\tInspect MAF sensor in bank 2 for dirt and debris<br> •\tCheck whether fuel pressure is correct<br> •\tInspect fuel lines and injectors for any leaks/openings and dirt<br> •\tCheck vacuum, PCV and exhaust for leaks<br> •\tRun a smog test using OBD2 scanner"},
+    "P0193": { "short_description":"Fuel Rail Pressure Sensor Circuit High Input",  "long_description":"<strong>1. Meaning:<\/strong><br> •\tThe PCM has detected that the pressure of fuel is not within the range that’s predetermined by the car manufacturer <br> <br> <strong>3. Main Symptoms:<\/strong><br> •\tCheck Engine Light comes on<br> •\tEngine cranks but doesn’t start<br> •\tCar hesitates upon acceleration<br> <br> <br> <strong>4. Possible Causes:<\/strong><br> •\tDefective fuel rail pressure (FRP) sensor<br> •\tDefective fuel pump<br> •\tOpen or short in FRP circuit<br> •\tLow or no fuel in tank<br> <br> <br> <strong>5. Diagnostic Steps:<\/strong><br> •\tInspect all wiring and connections in FRP circuit and check for shorts, opens, broken and melted wires<br> •\tCheck for corroded or burnt terminals in connectors<br> •\tUse a scan tool to pull codes and freeze frame data"},
+    "P0300": { "short_description":"Random/Multiple Cylinder Misfire Detected",  "long_description":"<strong>1. Meaning:<\/strong><br> •\tPCM has detected that there’s an engine cylinder that’s not firing properly. It could be one or more cylinders. PCM hasn’t specified the exact cylinder<br> <br> <strong>3. Main Symptoms:<\/strong><br> •\tCheck Engine Light comes on<br> •\tCheck Engine Light may flash<br> •\tEngine lacks power<br> •\tEngine may be hard to start<br> •\tEngine may stumble and hesitate frequently<br> <br> <br> <strong>4. Possible Causes:<\/strong><br> •\tDefective or worn out spark plugs<br> •\tLow fuel pressure<br> •\tVacuum leak<br> •\tDefective catalytic converter<br> •\tDefective fuel injector<br> •\tDefective coil<br> •\tDefective camshaft position sensor<br> •\tDefective crankshaft sensor<br> •\tProblem with distributor<br> <br> <br> <strong>5. Diagnostic Steps:<\/strong><br> •\tUsing a scan tool, pull codes and see if there are any other besides P0300. Address the others first<br> •\tInspect whether there are loose, open or short wires in ignition coils<br> •\tInspect whether spark plugs and their wires are in good condition<br> •\tCheck that fuel pressure is within the recommended range<br> •\tInspect fuel injectors to see whether they are in good condition"},
+    "P0301": { "short_description":"Cylinder 1 Misfire Detected",  "long_description":"<strong>1. Meaning:<\/strong><br> •\tPCM has detected that cylinder #1 is not firing properly<br> <br> <strong>3. Main Symptoms:<\/strong><br> •\tCheck Engine Light comes on<br> •\tCheck Engine Light may flash<br> •\tEngine lacks power<br> •\tEngine may be hard to start<br> •\tEngine may stumble and hesitate frequently<br> <br> <br> <strong>4. Possible Causes:<\/strong><br> •\tDefective or worn out spark plugs in cylinder 1<br> •\tLow fuel pressure<br> •\tVacuum leak<br> •\tDefective catalytic converter<br> •\tDefective fuel injector<br> •\tDefective coil<br> •\tDefective camshaft position sensor<br> •\tDefective crankshaft sensor<br> •\tProblem with distributor<br> <br> <br> <strong>5. Diagnostic Steps:<\/strong><br> •\tUsing a scan tool, pull codes and see if there are any other besides P0301. Address the others first<br> •\tInspect whether there are loose, open or short wires in ignition coils in cylinder 1<br> •\tInspect whether cylinder 1 spark plugs and their wires are in good condition<br> •\tCheck that fuel pressure is within the recommended range<br> •\tInspect fuel injectors to see whether they are in good condition"},
+    "P0302": { "short_description":"Cylinder 2 Misfire Detected",  "long_description":"<strong>1. Meaning:<\/strong><br> •\tPCM has detected that cylinder #2 is not firing properly<br> <br> <strong>3. Main Symptoms:<\/strong><br> •\tCheck Engine Light comes on<br> •\tCheck Engine Light may flash<br> •\tEngine lacks power<br> •\tEngine may be hard to start<br> •\tEngine may stumble and hesitate frequently<br> <br> <br> <strong>4. Possible Causes:<\/strong><br> •\tDefective or worn out spark plugs in cylinder 2<br> •\tLow fuel pressure<br> •\tVacuum leak<br> •\tDefective catalytic converter<br> •\tDefective fuel injector<br> •\tDefective coil<br> •\tDefective camshaft position sensor<br> •\tDefective crankshaft sensor<br> •\tProblem with distributor<br> <br> <br> <strong>5. Diagnostic Steps:<\/strong><br> •\tUsing a scan tool, pull codes and see if there are any other besides P0302. Address the others first<br> •\tInspect whether there are loose, open or short wires in ignition coils in cylinder 2<br> •\tInspect whether cylinder 2 spark plugs and their wires are in good condition<br> •\tCheck that fuel pressure is within the recommended range<br> •\tInspect fuel injectors to see whether they are in good condition"},
+    "P0303": { "short_description":"Cylinder 3 Misfire Detected",  "long_description":"<strong>1. Meaning:<\/strong><br> •\tPCM has detected that cylinder #3 is not firing properly<br> <br> <strong>3. Main Symptoms:<\/strong><br> •\tCheck Engine Light comes on<br> •\tCheck Engine Light may flash<br> •\tEngine lacks power<br> •\tEngine may be hard to start<br> •\tEngine may stumble and hesitate frequently<br> <br> <br> <strong>4. Possible Causes:<\/strong><br> •\tDefective or worn out spark plugs in cylinder 3<br> •\tLow fuel pressure<br> •\tVacuum leak<br> •\tDefective catalytic converter<br> •\tDefective fuel injector<br> •\tDefective coil<br> •\tDefective camshaft position sensor<br> •\tDefective crankshaft sensor<br> •\tProblem with distributor<br> <br> <br> <strong>5. Diagnostic Steps:<\/strong><br> •\tUsing a scan tool, pull codes and see if there are any other besides P0303. Address the others first<br> •\tInspect whether there are loose, open or short wires in ignition coils in cylinder 3<br> •\tInspect whether cylinder 3 spark plugs and their wires are in good condition<br> •\tCheck that fuel pressure is within the recommended range<br> •\tInspect fuel injectors to see whether they are in good condition"},
+    "P0304": { "short_description":"Cylinder 4 Misfire Detected",  "long_description":"<strong>1. Meaning:<\/strong><br> •\tPCM has detected that cylinder #4 is not firing properly<br> <br> <strong>3. Main Symptoms:<\/strong><br> •\tCheck Engine Light comes on<br> •\tCheck Engine Light may flash<br> •\tEngine lacks power<br> •\tEngine may be hard to start<br> •\tEngine may stumble and hesitate frequently<br> <br> <br> <strong>4. Possible Causes:<\/strong><br> •\tDefective or worn out spark plugs in cylinder 4<br> •\tLow fuel pressure<br> •\tVacuum leak<br> •\tDefective catalytic converter<br> •\tDefective fuel injector<br> •\tDefective coil<br> •\tDefective camshaft position sensor<br> •\tDefective crankshaft sensor<br> •\tProblem with distributor<br> <br> <br> <strong>5. Diagnostic Steps:<\/strong><br> •\tUsing a scan tool, pull codes and see if there are any other besides P0304. Address the others first<br> •\tInspect whether there are loose, open or short wires in ignition coils in cylinder 4<br> •\tInspect whether cylinder 4 spark plugs and their wires are in good condition<br> •\tCheck that fuel pressure is within the recommended range<br> •\tInspect fuel injectors to see whether they are in good condition"},
+    "P0305": { "short_description":"Cylinder 5 Misfire Detected",  "long_description":"<strong>1. Meaning:<\/strong><br> •\tPCM has detected that cylinder #5 is not firing properly<br> <br> <strong>3. Main Symptoms:<\/strong><br> •\tCheck Engine Light comes on<br> •\tCheck Engine Light may flash<br> •\tEngine lacks power<br> •\tEngine may be hard to start<br> •\tEngine may stumble and hesitate frequently<br> <br> <br> <strong>4. Possible Causes:<\/strong><br> •\tDefective or worn out spark plugs in cylinder 5<br> •\tLow fuel pressure<br> •\tVacuum leak<br> •\tDefective catalytic converter<br> •\tDefective fuel injector<br> •\tDefective coil<br> •\tDefective camshaft position sensor<br> •\tDefective crankshaft sensor<br> •\tProblem with distributor<br> <br> <br> <strong>5. Diagnostic Steps:<\/strong><br> •\tUsing a scan tool, pull codes and see if there are any other besides P0305. Address the others first<br> •\tInspect whether there are loose, open or short wires in ignition coils in cylinder 5<br> •\tInspect whether cylinder 5 spark plugs and their wires are in good condition<br> •\tCheck that fuel pressure is within the recommended range<br> •\tInspect fuel injectors to see whether they are in good condition"},
+    "P0306": { "short_description":"Cylinder 6 Misfire Detected",  "long_description":"<strong>1. Meaning:<\/strong><br> •\tPCM has detected that cylinder #6 is not firing properly<br> <br> <strong>3. Main Symptoms:<\/strong><br> •\tCheck Engine Light comes on<br> •\tCheck Engine Light may flash<br> •\tEngine lacks power<br> •\tEngine may be hard to start<br> •\tEngine may stumble and hesitate frequently<br> <br> <br> <strong>4. Possible Causes:<\/strong><br> •\tDefective or worn out spark plugs in cylinder 6<br> •\tLow fuel pressure<br> •\tVacuum leak<br> •\tDefective catalytic converter<br> •\tDefective fuel injector<br> •\tDefective coil<br> •\tDefective camshaft position sensor<br> •\tDefective crankshaft sensor<br> •\tProblem with distributor<br> <br> <br> <strong>5. Diagnostic Steps:<\/strong><br> •\tUsing a scan tool, pull codes and see if there are any other besides P0306. Address the others first<br> •\tInspect whether there are loose, open or short wires in ignition coils in cylinder 6<br> •\tInspect whether cylinder 6 spark plugs and their wires are in good condition<br> •\tCheck that fuel pressure is within the recommended range<br> •\tInspect fuel injectors to see whether they are in good condition"},
+    "P0316": { "short_description":"Misfire Detected On Startup (First 1000 Revolutions)",  "long_description":"<strong>1. Meaning:<\/strong><br> •\tPCM has detected a misfire less than 1,000 revolutions after startup<br> <br> <strong>3. Main Symptoms:<\/strong><br> •\tCheck Engine Light comes on<br> •\tEngine lacks power<br> •\tRough idle<br> <br> <br> <strong>4. Possible Causes:<\/strong><br> •\tDefective or worn out spark plugs<br> •\tNo fuel<br> •\tLow fuel pressure<br> •\tVacuum leak<br> •\tDefective catalytic converter<br> •\tDefective fuel injector<br> •\tDefective coil<br> •\tDefective crankshaft sensor<br> •\tWiring fault in crankshaft position sensor<br> •\tProblem with PCM<br> <br> <br> <strong>5. Diagnostic Steps:<\/strong><br> •\tPull all codes then address other misfire codes first<br> •\tCheck all wiring and connectors in crankshaft and camshaft position sensors<br> •\tReview freeze frame data to narrow down the problem further"},
+    "P0320": { "short_description":"Ignition/Distributor Engine Speed Input Circuit Malfunction",  "long_description":"<strong>1. Meaning:<\/strong><br> •\tThere’s an electrical circuit fault in either the crankshaft position (CKP) sensor or camshaft position (CMP) sensor<br> <br> <strong>3. Main Symptoms:<\/strong><br> •\tCheck Engine Light comes on<br> •\tEngine cranks but doesn’t start<br> •\tEngine lacks power<br> •\tEngine misfires, hesitates and stumbles<br> <br> <br> <strong>4. Possible Causes:<\/strong><br> •\tOpen or short in power supply circuit and/or control circuit between PCM and ignition/distributor/engine speed sensor<br> •\tDefective ignition/distributor/engine speed sensor<br> <br> <br> <strong>5. Diagnostic Steps:<\/strong><br> •\tCheck that all the connectors and wiring to ignition/distributor/engine speed sensors are in good condition<br> •\tRemove connectors and see if their terminals are burnt/corroded<br> •\tPull and reset P0320 code, drive the car for several minutes and see if the code returns. If it does then probably a sensor needs replacing"},
+    "P0325": { "short_description":"Knock Sensor 1 Circuit Malfunction (Bank 1 or Single Sensor)",  "long_description":"<strong>1. Meaning:<\/strong><br> •\tPCM has detected that the engine’s knock sensor 1 in circuit bank 1 is not working properly<br> <br> <strong>3. Main Symptoms:<\/strong><br> •\tGenerally doesn’t come with symptoms but Check Engine Light may come on.<br> •\tEngine may also lose power<br> <br> <br> <strong>4. Possible Causes:<\/strong><br> •\tFirst sensor in circuit bank 1 may be faulty<br> •\tOpen or short in wiring to the sensor<br> •\tProblem with engine coolant<br> •\tEngine is excessively lean<br> •\tPCM has failed (least likely)<br> <br> <br> <strong>5. Diagnostic Steps:<\/strong><br> •\tVisually inspect all wiring to knock sensor 1 in circuit bank 1. Ensure there are no shorts or open wires<br> •\tView coolant temp data to check for issues<br> •\tIf there are none, clear the code and test drive the car. If it comes back the sensor is defective"},
+    "P0327": { "short_description":"Knock Sensor 1 Circuit low Input (Bank 1 or Single Sensor)",  "long_description":"<strong>1. Meaning:<\/strong><br> •\tKnock sensor on bank 1 is has low output voltage than normal; usually .5V or less<br> <br> <strong>3. Main Symptoms:<\/strong><br> •\tGenerally doesn’t come with symptoms but Check Engine Light may come on<br> •\tYou may also notice fluctuating RPM and loss of engine power<br> <br> <br> <strong>4. Possible Causes:<\/strong><br> •\tSensor in circuit bank 1 may be faulty<br> •\tOpen or short in wiring to the sensor<br> •\tPCM has failed (least likely)<br> <br> <br> <strong>5. Diagnostic Steps:<\/strong><br> •\tCheck the resistance of the knock sensor and ensure it matches manufacturer’s specifications<br> •\tVisually inspect all wiring to knock sensor 1 in circuit bank 1. Ensure there are no shorts or open wires<br> •\tIf the above don’t work replace knock sensor"},
+    "P0328": { "short_description":"Knock Sensor 1 Circuit high Input (Bank 1 or Single Sensor)",  "long_description":"<strong>1. Meaning:<\/strong><br> •\tKnock sensor on bank 1 is has high output voltage than normal; usually .5V or less<br> <br> <strong>3. Main Symptoms:<\/strong><br> •\tCheck Engine Light comes on<br> •\tLoss of power<br> •\tIrregular RPM<br> •\tEngine pings when accelerating<br> •\tKnocking sound coming from engine compartment<br> <br> <br> <strong>4. Possible Causes:<\/strong><br> •\tKnock sensor 1 in circuit 1 may be faulty<br> •\tOpen or short in wiring to the sensor<br> •\tLow fuel pressure<br> •\tLoose knock sensor<br> •\tUsing incorrect type of fuel<br> •\tPCM has failed (least likely)<br> <br> <br> <strong>5. Diagnostic Steps:<\/strong><br> •\tEnsure the correct fuel was used<br> •\tVisually inspect all wiring to knock sensor 1 in circuit bank 1. Ensure there are no shorts or open wires<br> •\tMeasure resistance of the sensor. If it doesn’t match manufacturer specs replace the sensor"},
+    "P0332": { "short_description":"Knock Sensor 2 Circuit Low Input (Bank 2)",  "long_description":"<strong>1. Meaning:<\/strong><br> •\tKnock sensor on bank 2 is has low output voltage than normal; usually .5V or less<br> <br> <strong>3. Main Symptoms:<\/strong><br> •\tGenerally doesn’t come with symptoms but Check Engine Light may come on<br> •\tYou may also notice fluctuating RPM and loss of engine power<br> <br> <br> <strong>4. Possible Causes:<\/strong><br> •\tSensor in circuit bank 2 may be faulty<br> •\tOpen or short in wiring to the sensor<br> •\tPCM has failed (least likely)<br> <br> <br> <strong>5. Diagnostic Steps:<\/strong><br> •\tCheck the resistance of the knock sensor and ensure it matches manufacturer’s specifications<br> •\tVisually inspect all wiring to knock sensor 2 in circuit bank 2. Ensure there are no shorts or open wires<br> •\tIf the above don’t work replace knock sensor"},
+    "P0335": { "short_description":"Crankshaft Position Sensor A Circuit Malfunction",  "long_description":"<strong>1. Meaning:<\/strong><br> •\tPCM has detected that crankshaft position (CKP) sensor is not producing pulses or the pulses are not normal. It uses these pulses to determine the position of the crankshaft<br> <br> <strong>3. Main Symptoms:<\/strong><br> •\tCheck Engine Light comes on<br> •\tEngine may fail to start<br> •\tVehicle may run rough<br> <br> <br> <strong>4. Possible Causes:<\/strong><br> •\tDefective CKP sensor<br> •\tOpen or short in CKP sensor wiring<br> •\tTiming belt is broken<br> •\tPCM has failed (least likely)<br> <br> <br> <strong>5. Diagnostic Steps:<\/strong><br> •\tStart by checking if there’s an RPM signal (using a scanner)<br> •\tIf it’s not there check all the wires and connectors to the sensor. Repair as necessary<br> •\tCheck the sensor’s resistance and compare with manufacturer’s recommendation. If they don’t match replace sensor"},
+    "P0336": { "short_description":"Crankshaft Position Sensor A Circuit Range/Performance",  "long_description":"<strong>1. Meaning:<\/strong><br> •\tPCM has not received proper information from CKP sensor and therefore cannot adjust ignition timing and fuel injection accordingly<br> <br> <strong>3. Main Symptoms:<\/strong><br> •\tCheck Engine Light comes on<br> •\tEngine may fail to start<br> •\tIntermittent stalling<br> •\tIntermittent misfire<br> <br> <br> <strong>4. Possible Causes:<\/strong><br> •\tDefective CKP sensor<br> •\tProblem with CKP sensor wiring<br> •\tReluctor ring is dislodged from its stationary location<br> •\tReluctor ring is broken<br> •\tPCM has failed (least likely)<br> <br> <br> <strong>5. Diagnostic Steps:<\/strong><br> •\tVisually inspect CKP sensor for wiring issues<br> •\tInspect reluctor ring if it has broken/damaged teeth or dirt lodged in the teeth<br> •\tCheck sensor resistance and compare with manufacturer specs. If they don’t match replace sensor"},
+    "P0340": { "short_description":"Camshaft Position Sensor Circuit Malfunction",  "long_description":"<strong>1. Meaning:<\/strong><br> •\tThere’s a problem in the camshaft position sensor (CPS) circuit. As such, PCM can’t perform ignition spark and fuel injector timing properly<br> <br> <strong>3. Main Symptoms:<\/strong><br> •\tCheck Engine Light comes on<br> •\tEngine may fail to start<br> •\tVehicle may run rough<br> •\tRough idle<br> •\tMisfire <br> <br> <br> <strong>4. Possible Causes:<\/strong><br> •\tDefective CPS<br> •\tOpen or short in CPS wiring<br> •\tDefective CKP sensor<br> •\tPCM has failed (least likely)<br> <br> <br> <strong>5. Diagnostic Steps:<\/strong><br> •\tInspect wiring to CPS and ensure there are no open or broken wires<br> •\tCheck CPS voltage if it’s within the manufacturer’s specs. If it’s not replace sensor<br> •\tCheck CKP sensor as well to determine whether it’s the source of the problem"},
+    "P0341": { "short_description":"Camshaft Position Sensor Circuit Range/Performance",  "long_description":"<strong>1. Meaning:<\/strong><br> •\tThe signal that the PCM is receiving from the camshaft position sensor (CPS ) is inconsistent with what it should be<br> <br> <strong>3. Main Symptoms:<\/strong><br> •\tGenerally doesn’t come with symptoms but Check Engine Light may come on<br> •\tPossible poor fuel economy<br> <br> <br> <strong>4. Possible Causes:<\/strong><br> •\tDefective CPS<br> •\tOpen or short in CPS wiring<br> •\tDefective reluctor wheel<br> •\tInterference in CPS wiring (from spark plug)<br> <br> <br> <strong>5. Diagnostic Steps:<\/strong><br> •\tInspect wiring to CPS and ensure there are no open or broken wires<br> •\tInspect reluctor wheel and check for damage or missing teeth<br> •\tReplace the CPS"},
+    "P0345": { "short_description":"Camshaft Position Sensor A Circuit Malfunction (Bank 2)",  "long_description":"<strong>1. Meaning:<\/strong><br> •\tPCM has detected a problem in the CPS circuit of bank 2. The problem could be anywhere in the circuit; sensor, wring or PC<br> <br> <strong>3. Main Symptoms:<\/strong><br> •\tCheck Engine Light comes on<br> •\tLow engine power<br> •\tHard starting or no starting completely<br> •\tVehicle runs rough and/or misfires<br> <br> <br> <strong>4. Possible Causes:<\/strong><br> •\tOpen, grounded or short wiring in CPS circuit of bank 2<br> •\tDefective CPS<br> •\tDefective CKP sensor<br> <br> <br> <strong>5. Diagnostic Steps:<\/strong><br> •\tInspect wiring to CPS in bank 2 and ensure there are no open or broken wires<br> •\tCheck CPS voltage to ensure it’s within the range specified by manufacturer<br> •\tCheck circuit connectors for corrosion and burns<br> •\tCheck that CKP sensor is operating as it should<br> •\tReplace CPS if all the above check out"},
+    "P0351": { "short_description":"Ignition Coil A Primary/Secondary Circuit Malfunction",  "long_description":"<strong>1. Meaning:<\/strong><br> •\tThere’s a short in the driver circuit for the engine’s coil #1 (the coil for cylinder #1)<br> <br> <strong>3. Main Symptoms:<\/strong><br> •\tCheck Engine Light comes on<br> •\tEngine misfire<br> <br> <br> <strong>4. Possible Causes:<\/strong><br> •\tShort or open in the coil on plug (COP) driver circuit<br> •\tLoose or broken connection at coil<br> •\tDefective COP<br> •\tDefective PCM (least likely)<br> <br> <br> <strong>5. Diagnostic Steps:<\/strong><br> •\tIf the engine is not currently misfiring then it’s an intermittent problem. Start by checking coil #1 wires visually as well as using wiggle test<br> •\tCheck for loose connections and broken connector locks<br> •\tIf all the above check out replace coil #1"},
+    "P0354": { "short_description":"Ignition Coil D Primary/Secondary Circuit Malfunction",  "long_description":"<strong>1. Meaning:<\/strong><br> •\tThere’s a short in the driver circuit for the engine’s coil #4 (the coil for cylinder #4)<br> <br> <strong>3. Main Symptoms:<\/strong><br> •\tCheck Engine Light comes on<br> •\tEngine misfire<br> <br> <br> <strong>4. Possible Causes:<\/strong><br> •\tShort or open in the coil on plug (COP) driver circuit<br> •\tLoose or broken connection at coil<br> •\tDefective COP<br> •\tDefective PCM (least likely)<br> <br> <br> <strong>5. Diagnostic Steps:<\/strong><br> •\tIf the engine is not currently misfiring then it’s an intermittent problem. Start by checking coil #4 wires visually as well as using wiggle test<br> •\tCheck for loose connections and broken connector locks<br> •\tIf all the above check out replace coil #4"},
+    "P0400": { "short_description":"Exhaust Gas Recirculation Flow Malfunction",  "long_description":"<strong>1. Meaning:<\/strong><br> •\tThere’s either an insufficient or non-existent amount of exhaust gases entering the cylinders<br> <br> <strong>3. Main Symptoms:<\/strong><br> •\tIncreased NOx emissions<br> •\tIncreased combustion temperatures <br> <br> <br> <strong>4. Possible Causes:<\/strong><br> •\tExhaust gas recirculation (EGR) passage is plugged<br> •\tEGR solenoid is faulty<br> •\tWiring or harness to EGR solenoid is faulty<br> •\tEGR valve is faulty<br> •\tEither vacuum lines are damaged or disconnected from EGR valve or EGR valve solenoid<br> <br> <br> <strong>5. Diagnostic Steps:<\/strong><br> •\tWith a bidirectional scan tool, open and close EGR valve while the engine is running. If the engine stumbles then there’s a wiring problem<br> •\tIf the engine doesn’t stumble but it dies then the ports are plugged<br> •\tAfter the above visually inspect all hoses, vacuum lines, solenoid and solenoid harnesses for damage<br> •\tUsing the scanner, check solenoid voltage to ensure its within normal range<br> •\tIf all the above check out replace EGR valve"},
+    "P0401": { "short_description":"Exhaust Gas Recirculation Flow Insufficient Detected",  "long_description":"<strong>1. Meaning:<\/strong><br> •\tPCM has detected an insufficient amount of EGR<br> <br> <strong>3. Main Symptoms:<\/strong><br> •\tMost notable symptom is engine pinging when the vehicle is in high speed or under load<br> <br> <strong>4. Possible Causes:<\/strong><br> •\tDefective differential pressure feedback EGR (DPFE) sensor<br> •\tDefective EGR valve<br> •\tEGR valve can’t open because of lack of vacuum<br> •\tBlockage in EGR tube<br> <br> <br> <strong>5. Diagnostic Steps:<\/strong><br> •\tCheck EGR valve and its tubing for deposits<br> •\tCheck DPFE sensor voltage to ensure its within specified range<br> •\tIf not replace the sensor. If it is replace the EGR valve"},
+    "P0402": { "short_description":"Exhaust Gas Recirculation Flow Excessive Detected",  "long_description":"<strong>1. Meaning:<\/strong><br> •\tPCM has detected an excessive amount of EGR<br> <br> <strong>3. Main Symptoms:<\/strong><br> •\tMost notable symptom is engine surging off idle<br> <br> <strong>4. Possible Causes:<\/strong><br> •\tDefective differential pressure feedback EGR (DPFE) sensor<br> •\tDefective EGR valve<br> •\tEGR valve can’t open because of lack of vacuum<br> •\tBlockage in EGR tube<br> <br> <br> <strong>5. Diagnostic Steps:<\/strong><br> •\tCheck DPFE sensor voltage to ensure its within specified range<br> •\tIf not replace the sensor."},
+    "P0403": { "short_description":"Exhaust Gas Recirculation Circuit Malfunction",  "long_description":"<strong>1. Meaning:<\/strong><br> •\tPCM has detected that the EGR circuit has malfunctioned and isn’t sending the proper voltage at the proper time<br> <br> <strong>3. Main Symptoms:<\/strong><br> •\tCheck Engine Light comes on<br> •\tEngine may misfire under acceleration<br> •\tRough idle<br> <br> <br> <strong>4. Possible Causes:<\/strong><br> •\tShort or open in circuit leading to EGR solenoid<br> •\tPins connecting EGR solenoid are worn out or loose<br> •\tPresence of water in EGR solenoid harness<br> •\tEGR solenoid isn’t getting voltage supply<br> <br> <br> <strong>5. Diagnostic Steps:<\/strong><br> •\tPull codes and freeze frame data to confirm P0403 code<br> •\tCheck all wiring and connections to EGR solenoid<br> •\tDisconnect and check EGR valve circuit for a short or open<br> •\tClear code and do test drive. If it comes back replace solenoid"},
+    "P0404": { "short_description":"Exhaust Gas Recirculation Circuit Range/Performance",  "long_description":"<strong>1. Meaning:<\/strong><br> •\tPCM has determined that EGR valve is open when it should be closed or its closed when it should be open<br> <br> <strong>3. Main Symptoms:<\/strong><br> •\tCheck Engine Light comes on<br> •\tEngine may idle rough<br> •\tEngine may fail to idle<br> •\tVehicle may fail emissions<br> <br> <br> <strong>4. Possible Causes:<\/strong><br> •\tOpen or short in reference, ground or PCM controlled circuit<br> •\tDefective PCM (least likely)<br> <br> <br> <strong>5. Diagnostic Steps:<\/strong><br> •\tVisually inspect all wiring to EGR to ensure there are no shorts or opens<br> •\tUsing a bidirectional scanner, open and close EGR valve as you watch the EGR position. If it’s not close to the “desired” position then it’s likely a bad valve"},
+    "P0405": { "short_description":"Exhaust Gas Recirculation Sensor A Circuit Low",  "long_description":"<strong>1. Meaning:<\/strong><br> •\tEGR valve pintle is not moving as it should either because of unusually low voltage or its position is lower than the PCM has commanded<br> <br> <strong>3. Main Symptoms:<\/strong><br> •\tCheck Engine Light comes on<br> <br> <strong>4. Possible Causes:<\/strong><br> •\tOpen or short in reference or ground circuit<br> •\tDefective EGR valve<br> •\tDefective PCM (least likely)<br> <br> <br> <strong>5. Diagnostic Steps:<\/strong><br> •\tVisually inspect all wiring to EGR to ensure there are no shorts or opens<br> •\tUsing a bidirectional scanner, open and close EGR valve and watch how it responds. If its moving properly then it’s an intermittent problem"},
+    "P0406": { "short_description":"Exhaust Gas Recirculation Sensor A Circuit High",  "long_description":"<strong>1. Meaning:<\/strong><br> •\tEGR sensor has had abnormally high voltage reading for too long<br> <br> <strong>3. Main Symptoms:<\/strong><br> •\tCheck Engine Light comes on<br> •\tMay surge when you drive<br> •\tMay stall intermittently<br> •\tVehicle may fail emissions<br> <br> <br> <strong>4. Possible Causes:<\/strong><br> •\tDefective EGR valve<br> •\tShort or open in EGR wiring circuit<br> •\tDebris build up in EGR valve and is holding it open<br> <br> <br> <strong>5. Diagnostic Steps:<\/strong><br> •\tPull codes and freeze frame data<br> •\tVisually inspect all wiring to EGR to ensure there are no shorts or opens<br> •\tUsing a scan tool, view EGR position during startup and while running to ensure its correct<br> •\tClear code and do test drive"},
+    "P0411": { "short_description":"Secondary Air Injection System Incorrect Flow Detected",  "long_description":"<strong>1. Meaning:<\/strong><br> •\tThe flow coming from the secondary air injection system is out of the range specified by PCM<br> <br> <strong>3. Main Symptoms:<\/strong><br> •\tCheck Engine Light comes on<br> •\tEngine may hesitate when you accelerate<br> <br> <br> <strong>4. Possible Causes:<\/strong><br> •\tDefective air injection pump<br> •\tCheck valve is damaged or missing<br> •\tDamage or leak in exhaust component<br> <br> <br> <strong>5. Diagnostic Steps:<\/strong><br> •\tVisually inspect air injection system and check for broken components, damaged hoses and excess carbon<br> •\tReset the code and do a test drive"},
+    "P0420": { "short_description":"Catalyst System Efficiency Below Threshold (Bank 1)",  "long_description":"<strong>1. Meaning:<\/strong><br> •\tCatalytic converter is not working as efficiently as it should and the vehicle is therefore emitting more harmful substances<br> <br> <strong>3. Main Symptoms:<\/strong><br> •\tCheck Engine Light comes on<br> •\tEngine lacks power<br> •\tReduced fuel economy<br> <br> <br> <strong>4. Possible Causes:<\/strong><br> •\tDefective O2 sensor<br> •\tDefective engine coolant temp<br> •\tWiring to downstream O2 sensor is damaged or improperly done<br> •\tLeaking fuel injector<br> •\tOil is contaminated<br> •\tUsing leaded fuel where unleaded fuel was required<br> •\tDefective catalytic converter, exhaust pipe, muffler or exhaust manifold<br> <br> <br> <strong>5. Diagnostic Steps:<\/strong><br> •\tInspect exhaust system for damage and leaks.<br> •\tCheck voltage of downstream O2 sensor while the engine is running. If its not steady (jumpy between .1 and .9 V) then the catalytic converter needs replacing"},
+    "P0421": { "short_description":"Warm Up Catalyst Efficiency Below Threshold (Bank 1)",  "long_description":"<strong>1. Meaning:<\/strong><br> •\tDownstream O2 sensor on bank 1 has detected that the converter is not working according to specification<br> <br> <strong>3. Main Symptoms:<\/strong><br> •\tCheck Engine Light comes on<br> •\tEngine may lack power<br> •\tEngine may hesitate when accelerating <br> <br> <br> <br> <strong>4. Possible Causes:<\/strong><br> •\tDefective catalytic converter<br> •\tDefective O2 sensor, particularly downstream O2 sensor<br> •\tFouled up spark plug<br> <br> <br> <strong>5. Diagnostic Steps:<\/strong><br> •\tMeasure voltage of O2 sensor (both) and ensure they match the specs. If not replace as necessary<br> •\tVisually inspect catalytic converters and check for red fumes (when car is running)<br> •\tSmell fumes and try to find traces of excessive fuel<br> •\tIf the latter two are present replace catalytic converter"},
+    "P0430": { "short_description":"Catalyst System Efficiency Below Threshold (Bank 2)",  "long_description":"<strong>1. Meaning:<\/strong><br> •\tCatalytic converter is not working as efficiently as it should. Its therefore releasing more harmful pollutants<br> <br> <strong>3. Main Symptoms:<\/strong><br> •\tCheck Engine Light comes on<br> •\tEngine may lack power<br> •\tReduced fuel economy<br> <br> <br> <strong>4. Possible Causes:<\/strong><br> •\tDefective O2 sensor<br> •\tLeak in exhaust system<br> •\tDefective catalytic converter<br> <br> <br> <strong>5. Diagnostic Steps:<\/strong><br> •\tCheck exhaust system for any damages and leaks<br> •\tWith the car running, check the voltage of downstream O2 sensor. If it’s not steady then the catalytic converter is at fault"},
+    "P0440": { "short_description":"Evaporative Emission Control System Malfunction",  "long_description":"<strong>1. Meaning:<\/strong><br> • A component in the EVAP system is not working properly<br> <br> <strong>3. Main Symptoms:<\/strong><br> •\tCheck Engine Light may come on<br> <br> <strong>4. Possible Causes:<\/strong><br> •\tGas cap is not working or has not been installed properly<br> •\tCanister is plugged and defective<br> •\tPurge solenoid has failed<br> <br> <br> <strong>5. Diagnostic Steps:<\/strong><br> •\tInspect gas cap to see if its installed tightly<br> •\tCheck for disconnected or cracked EVAP hoses<br> •\tInspect charcoal canister and fuel tank for leaks and damages<br> •\tCheck that purge valve (solenoid) has no leaks"},
+    "P0441": { "short_description":"Evaporative Emission Control System Incorrect Purge flow",  "long_description":"<strong>1. Meaning:<\/strong><br> •\tPCM has detected that there’s no purge flow (i.e. purge control valve is still closed) despite commanding a purge<br> <br> <strong>3. Main Symptoms:<\/strong><br> •\tCheck Engine Light may come on<br> •\tRough or erratic idle<br> <br> <br> <strong>4. Possible Causes:<\/strong><br> •\tLoose or damaged EVAP hoses<br> •\tDefective purge valve<br> •\tGas cap is loose, missing or damaged<br> •\tCharcoal canister is damaged or defective<br> <br> <br> <strong>5. Diagnostic Steps:<\/strong><br> •\tInspect gas cap to see if its installed tightly<br> •\tCheck for disconnected or cracked EVAP hoses<br> •\tInspect charcoal canister and fuel tank for leaks and damages<br> •\tCheck that purge valve (solenoid) has no leaks"},
+    "P0442": { "short_description":"Evaporative Emission Control System leak Detected (small leak)",  "long_description":"<strong>1. Meaning:<\/strong><br> •\tPCM has detected a very small vapor leak somewhere in the EVAP control system <br> <br> <strong>3. Main Symptoms:<\/strong><br> •\tCheck Engine Light may come on<br> •\tReduced fuel economy<br> <br> <br> <strong>4. Possible Causes:<\/strong><br> •\tLoose or damaged EVAP hoses<br> •\tDefective purge valve<br> •\tGas cap is loose, missing or damaged<br> •\tCharcoal canister is leaking<br> •\tFuel tank is leaking<br> <br> <br> <strong>5. Diagnostic Steps:<\/strong><br> •\tInspect gas cap to see if its installed tightly<br> •\tCheck for disconnected or cracked EVAP hoses<br> •\tInspect charcoal canister and fuel tank for leaks and damages<br> •\tCheck that purge valve (solenoid) has no leaks<br> •\tIf the above don’t narrow down the problem perform a smoke test"},
+    "P0443": { "short_description":"Evaporative Emission Control System Purge Control Valve circuit Malfunction",  "long_description":"<strong>1. Meaning:<\/strong><br> •\tEither there’s an open in the purge control valve circuit or the circuit has abnormal voltage (too high or too low)<br> <br> <strong>3. Main Symptoms:<\/strong><br> •\tCheck Engine Light may come on<br> •\tCar may have lean condition<br> <br> <br> <strong>4. Possible Causes:<\/strong><br> •\tPurge solenoid has short or open<br> •\tShort or open somewhere in the wiring harness to purge valve<br> •\tDriver circuit in PCM has an open or short<br> •\tWater intrusion has caused connector to break or wear out<br> <br> <br> <strong>5. Diagnostic Steps:<\/strong><br> •\tUsing a scan tool, command purge valve to open. Listen for a clicking sound (one or many times)<br> •\tIf it doesn’t click examine solenoid and connectors for breakages and signs of extreme wearing out<br> •\tCheck all the circuits for wiring problems"},
+    "P0446": { "short_description":"Evaporative Emission Control System Vent Control Circuit Malfunction",  "long_description":"<strong>1. Meaning:<\/strong><br> •\tPCM has detected an open or short in EVAP control circuit or a short to ground circuit<br> <br> <strong>3. Main Symptoms:<\/strong><br> •\tCheck Engine Light comes on<br> <br> <strong>4. Possible Causes:<\/strong><br> •\tDefective EVAP vent valve<br> •\tBlockage in vent valve<br> •\tVent valve control circuit has an open or short<br> <br> <br> <strong>5. Diagnostic Steps:<\/strong><br> •\tExamine all wiring to vent valve<br> •\tIf the above checks out replace vent valve"},
+    "P0449": { "short_description":"Evaporative Emission Control System Vent Valve/Solenoid Circuit Malfunction",  "long_description":"<strong>1. Meaning:<\/strong><br> •\tPCM has detected a problem in the circuit that controls the EVAP system vent<br> <br> <strong>3. Main Symptoms:<\/strong><br> •\tCheck Engine Light comes on<br> <br> <strong>4. Possible Causes:<\/strong><br> •\tDefective EVAP vent valve<br> •\tWiring issue in the EVAP vent valve<br> •\tCircuit issue in the EVAP vent valve<br> <br> <br> <strong>5. Diagnostic Steps:<\/strong><br> •\tCheck all wires leading to the vent valve for shorts and opens<br> •\tCheck fuses that power the vent solenoid (in case there are any)<br> •\tExamine if vent valve has cracks or openings<br> •\tUsing a bidirectional scanner, actuate the valve to see if its working"},
+    "P0452": { "short_description":"Evaporative Emission System Pressure Sensor/Switch Low",  "long_description":"<strong>1. Meaning:<\/strong><br> •\tPCM has detected that the fuel tank pressure is abnormally low<br> <br> <strong>3. Main Symptoms:<\/strong><br> •\tCheck Engine Light comes on<br> <br> <strong>4. Possible Causes:<\/strong><br> •\tDefective fuel tank pressure (FTP) sensor<br> •\tWiring problem in the circuits that lead to FTP sensor<br> •\tBroken or cracked vapor line (either to the tank or vacuum canister)<br> •\tLoose gas cap leading to loss of vacuum<br> •\tLeaking gasket in fuel pump module<br> <br> <br> <strong>5. Diagnostic Steps:<\/strong><br> •\tCheck vapor hoses for any breakages and opens<br> •\tSince diagnosing this problem is extremely hard (due to the location of the FTP sensor), its recommended that you get a professional to do the job"},
+    "P0455": { "short_description":"Evaporative Emission Control System Leak Detected (large leak)",  "long_description":"<strong>1. Meaning:<\/strong><br> •\tPCM has detected a large vapor leak somewhere in the EVAP control system <br> <br> <strong>3. Main Symptoms:<\/strong><br> •\tCheck Engine Light may come on<br> •\tReduced fuel economy<br> <br> <br> <strong>4. Possible Causes:<\/strong><br> •\tLoose or damaged EVAP hoses<br> •\tGas cap is loose, missing or damaged<br> •\tNon-compatible gas cap<br> <br> <br> <strong>5. Diagnostic Steps:<\/strong><br> •\tInspect gas cap to see if its installed tightly<br> •\tCheck for disconnected or cracked EVAP hoses<br> •\tInspect charcoal canister and fuel tank for leaks and damages<br> •\tIf the above don’t work replace the gas cap"},
+    "P0456": { "short_description":"Evaporative Emissions System - Small leak detected",  "long_description":"<strong>1. Meaning:<\/strong><br> •\tFTP sensor has indicated a small leak in EVAP system<br> <br> <strong>3. Main Symptoms:<\/strong><br> •\tCheck Engine Light comes on<br> <br> <strong>4. Possible Causes:<\/strong><br> •\tFaulty gas cap<br> •\tLeak in fuel tank hoses or EVAP hoses<br> •\tLeak in vent valve or purge valve<br> •\tLeak in EVAP canister<br> <br> <br> <strong>5. Diagnostic Steps:<\/strong><br> •\tUse a bidirectional tool to activate vent solenoid as you monitor FTP sensor. It will tell you if the system is sealing properly or not<br> •\tIf it is then use a smoke test to determine the leak"},
+    "P0457": { "short_description":"Evaporative Emission Control System (EVAP) Leak Detected",  "long_description":"<strong>1. Meaning:<\/strong><br> •\tThere’s a vacuum leak and the EVAP system can’t draw fuel vapors into the system for efficient burning<br> <br> <strong>3. Main Symptoms:<\/strong><br> •\tCheck Engine Light comes on<br> •\tSmell of fuel in exhaust<br> •\tReduced fuel economy<br> <br> <br> <strong>4. Possible Causes:<\/strong><br> •\tGas cap is either loose, cracked or missing<br> •\tLoose, disconnected or cracked hose in EVAP system<br> •\tCracked vacuum canister<br> <br> <br> <strong>5. Diagnostic Steps:<\/strong><br> •\tInspect gas cap and check if its loose or has debris that’s preventing it from fitting tightly<br> •\tInspect vacuum hoses for cracks and breaks<br> •\tInspect charcoal canister for leaks"},
+    "P0463": { "short_description":"Evaporative Emission Control System Pressure Sensor High Input",  "long_description":"<strong>1. Meaning:<\/strong><br> •\tThe signal from the fuel level sensor is above 5 volts for a prolonged period of time<br> <br> <strong>3. Main Symptoms:<\/strong><br> •\tCheck Engine Light comes on<br> •\tFuel light may come on and sound alarm<br> •\tFluctuating fuel level gauge<br> •\tFuel level gauge may erroneously read empty or full<br> <br> <br> <strong>4. Possible Causes:<\/strong><br> •\tDefective fuel level sensor<br> •\tProblem with fuel level sensor circuit<br> •\tDefective instrument cluster<br> •\tDamaged fuel tank<br> <br> <br> <strong>5. Diagnostic Steps:<\/strong><br> •\tVisually inspect fuel tank for damage or leaks<br> •\tInspect wiring harness<br> •\tDo voltage test on fuel level sensor circuit<br> •\tIf all those check out you may have to replace fuel tank"},
+    "P0500": { "short_description":"Vehicle Speed Sensor Malfunction",  "long_description":"<strong>1. Meaning:<\/strong><br> •\tThe vehicle speed, as given by the vehicle speed sensor (VSS) is not within the expected range<br> <br> <strong>3. Main Symptoms:<\/strong><br> •\tCheck Engine Light comes on<br> •\tSpeedometer may fail to work<br> •\tLoss of ABS (anti-lock brakes)<br> •\tABS light may come on<br> •\tTransmission may not work properly<br> <br> <br> <strong>4. Possible Causes:<\/strong><br> •\tDefective VSS<br> •\tWiring problem in VSS circuit<br> •\tPCM is not configured properly for the tire size of the vehicle<br> <br> <br> <strong>5. Diagnostic Steps:<\/strong><br> •\tVisually inspect all wiring to VSS and check for open, short, broken and chaffed wires<br> •\tCheck the voltage of the speed sensor"},
+    "P0506": { "short_description":"Idle Control System RPM lower Than Expected",  "long_description":"<strong>1. Meaning:<\/strong><br> •\tPCM has detected that the engine idle speed is lower than the pre-programmed RPM<br> <br> <strong>3. Main Symptoms:<\/strong><br> •\tCheck Engine Light comes on<br> •\tRough idle due to lower idle speed<br> <br> <br> <strong>4. Possible Causes:<\/strong><br> •\tVacuum leak<br> •\tAir is restricted in the exhaust or intake air path<br> •\tDefective positive crankcase ventilation valve<br> •\tProblem with throttle body<br> •\tFailed PCM<br> <br> <br> <strong>5. Diagnostic Steps:<\/strong><br> •\tCheck for vacuum leaks, damages and restriction<br> •\tNote that this code is informational more than anything, so look out for other codes that it comes with and address them first"},
+    "P0507": { "short_description":"Idle Control System RPM higher Than Expected",  "long_description":"<strong>1. Meaning:<\/strong><br> •\tPCM has detected that the engine idle speed is higher than the pre-programmed RPM (typically over 200 RPM)<br> <br> <strong>3. Main Symptoms:<\/strong><br> •\tCheck Engine Light comes on<br> <br> <strong>4. Possible Causes:<\/strong><br> •\tVacuum leak<br> •\tDefective positive crankcase ventilation valve<br> •\tLeaking air intake<br> •\tProblem with throttle body<br> •\tDefective EVAP system<br> •\tDefective idle air controller (IAC) or a problem with IAC circuit<br> •\tFailed PCM<br> <br> <br> <strong>5. Diagnostic Steps:<\/strong><br> •\tCheck for vacuum leaks, damages and restriction<br> •\tNote that this code is informational more than anything, so look out for other codes that it comes with and address those first"},
+    "P0521": { "short_description":"Engine Oil Pressure Sensor/Switch Range/Performance",  "long_description":"<strong>1. Meaning:<\/strong><br> •\tPCM has seen an unexpected value in engine oil pressure sensor. The value could be fixed when it should fluctuate or simply out of the normal range<br> <br> <strong>3. Main Symptoms:<\/strong><br> •\tCheck Engine Light comes on<br> •\tOil pressure gauge may read too low or too high<br> •\tOil pressure light may come on<br> <br> <br> <strong>4. Possible Causes:<\/strong><br> •\tOil level in the engine is too low<br> •\tOil pressure is too low<br> •\tDirty oil<br> •\tFaulty wiring to oil pressure sensor<br> <br> <br> <strong>5. Diagnostic Steps:<\/strong><br> •\tCheck that oil level is not too low<br> •\tCheck that the right oil was used<br> •\tVisually inspect all wiring to oil pressure sensor and check for open, short or broken wires<br> •\tIf you can, take the oil pressure reading and compare with the reading shown by the vehicle’s PCM. That should tell you if the problem is with oil pressure"},
+    "P0522": { "short_description":"Engine Oil Pressure Sensor/Switch Low Voltage",  "long_description":"<strong>1. Meaning:<\/strong><br> •\tPCM has detected a very low value in engine oil pressure sensor<br> <br> <strong>3. Main Symptoms:<\/strong><br> •\tCheck Engine Light comes on<br> •\tOil pressure light comes on<br> •\tOil pressure gauge reads zero or low value<br> •\tEngine may fail to start<br> •\tEngine may stall when you’re driving<br> <br> <br> <strong>4. Possible Causes:<\/strong><br> •\tWiring problem in oil pressure sensor circuit<br> •\tDefective oil pressure sensor<br> •\tLow oil level or blockage in oil passage<br> •\tUsing wrong type of oil<br> <br> <br> <strong>5. Diagnostic Steps:<\/strong><br> •\tCheck oil level and condition<br> •\tVisually inspect all wiring to oil pressure sensor<br> •\tCheck the sensor’s voltage to ensure it meets manufacturer’s specs<br> •\tIf you can, take the oil pressure reading and compare with the reading shown by the vehicle’s PCM. That should tell you if the problem is with oil pressure"},
+    "P0562": { "short_description":"System Voltage Low",  "long_description":"<strong>1. Meaning:<\/strong><br> •\tPCM has detected that the ignition feed circuit has very low voltage. Meaning the charging system might not be working<br> <br> <strong>3. Main Symptoms:<\/strong><br> •\tCheck Engine Light comes on<br> •\tRed battery light on<br> •\tReduced fuel economy<br> •\tTransmission may fail<br> •\tEngine may fail to start<br> •\tEngine may start then stall and die<br> <br> <br> <strong>4. Possible Causes:<\/strong><br> •\tDefective alternator<br> •\tHigh resistance either in alternator-battery circuit, alternator-PCM circuit or both<br> •\tDefective PCM<br> <br> <br> <strong>5. Diagnostic Steps:<\/strong><br> •\tCheck whether battery voltage is sufficient<br> •\tEnsure battery is properly connected then check alternator belt<br> •\tUsing a digital volt ohm meter (DVOM), check whether the charging system is working<br> •\tReset the code then do a test drive. If the code returns check PCM voltage"},
+    "P0606": { "short_description":"ECM/PCM Processor Fault",  "long_description":"<strong>1. Meaning:<\/strong><br> •\tPCM has detected an integrity fault in its own system<br> <br> <strong>3. Main Symptoms:<\/strong><br> •\tCheck Engine Light comes on<br> <br> <strong>4. Possible Causes:<\/strong><br> •\tFailed PCM<br> <br> <strong>5. Diagnostic Steps:<\/strong><br> •\tRe-flash PCM with updated software. If that doesn’t work replace the PCM"},
+    "P0700": { "short_description":"Transmission Control System Malfunction",  "long_description":"<strong>1. Meaning:<\/strong><br> •\tIf the transmission control module (TCM) detects a fault in the transmission system and sets a code, this code is also stored in the PCM<br> <br> <strong>3. Main Symptoms:<\/strong><br> •\tCheck Engine Light comes on<br> •\tTransmission may exhibit problems<br> <br> <br> <strong>4. Possible Causes:<\/strong><br> •\tAny transmission-related problem can trigger this code<br> <br> <strong>5. Diagnostic Steps:<\/strong><br> •\tThis is only an informational code, so pull all transmission codes, address them and do a test drive to fix this code"},
+    "P0705": { "short_description":"Transmission Range Sensor Circuit Malfunction (PRNDL Input)",  "long_description":"<strong>1. Meaning:<\/strong><br> •\tPCM has detected a malfunction in the transmission range sensor (TRS)<br> <br> <strong>3. Main Symptoms:<\/strong><br> •\tCheck Engine Light comes on<br> •\tActuating starter may be impossible<br> •\tBack up lights may fail<br> •\tEngine may only start at neutral<br> •\tIrregular shift RPMs<br> •\tDelayed transmission engagement<br> •\tReduced fuel economy<br> <br> <br> <strong>4. Possible Causes:<\/strong><br> •\tDefective TRS<br> •\tLoose TRS<br> •\tShorted wire in TRS circuit<br> •\tLoose or corroded connector at the external TRS. Pins may also be bent<br> <br> <br> <strong>5. Diagnostic Steps:<\/strong><br> •\tMore often than not this code means replacing the TRS"},
+    "P0715": { "short_description":"Input/Turbine Speed Sensor Circuit Malfunction",  "long_description":"<strong>1. Meaning:<\/strong><br> •\tThe actual transmission input speed does not match the desired input speed <br> <br> <strong>3. Main Symptoms:<\/strong><br> •\tCheck Engine Light comes on<br> •\tTransmission shifts erratically<br> •\tTransmission may fail to shift<br> •\tFailure in speedometer<br> •\tLower fuel economy<br> <br> <br> <strong>4. Possible Causes:<\/strong><br> •\tDefective input speed sensor<br> •\tDefective output speed sensor<br> •\tA wiring problem in input/output speed sensor circuit<br> •\tLoose or burnt connector in transmission circuit<br> •\tDefective or improperly programmed PCM<br> <br> <br> <strong>5. Diagnostic Steps:<\/strong><br> •\tVisually inspect all wiring and connectors for loose, burnt, open or broken connections<br> •\tUsing a scanner, pull all codes and freeze frame data to see which sensor is malfunctioning (input/output)<br> •\tReplace sensor if necessary"},
+    "P0720": { "short_description":"Output Speed Sensor Circuit Malfunction",  "long_description":"<strong>1. Meaning:<\/strong><br> •\tPCM has determined that there’s a malfunction in the Output Shaft Speed Sensor (OSS) of the transmission system<br> <br> <strong>3. Main Symptoms:<\/strong><br> •\tCheck Engine Light comes on<br> •\tDelayed shifts<br> •\tFailed speedometer<br> <br> <br> <strong>4. Possible Causes:<\/strong><br> •\tDefective OSS<br> •\tDefective transmission fluid temperature sensor<br> •\tWiring problem in the OSS circuit<br> <br> <br> <strong>5. Diagnostic Steps:<\/strong><br> •\tCheck that all wiring to OSS is properly done and there are no open, broken or shorted wires<br> •\tMeasure OSS voltage and compare with manufacturer’s specs. If they don’t match replace OSS<br> •\tMeasure transmission fluid temperature sensor voltage and compare with manufacturer’s specs. If they don’t match replace the sensor"},
+    "P0741": { "short_description":"Torque Converter Clutch Circuit Performance or Stuck Off",  "long_description":"<strong>1. Meaning:<\/strong><br> •\tTCM has detected a problem within the circuit that controls the torque converter clutch (TCC) solenoid<br> <br> <strong>3. Main Symptoms:<\/strong><br> •\tCheck Engine Light comes on<br> •\tSlightly reduced fuel economy<br> <br> <br> <strong>4. Possible Causes:<\/strong><br> •\tShorted wire in transmission’s ground circuit<br> •\tInternal short in TCC solenoid<br> •\tDefective TCM<br> <br> <br> <strong>5. Diagnostic Steps:<\/strong><br> •\tCheck that all wiring in the transmission circuit is properly done and there are no open, broken or shorted wires<br> •\tCheck the resistance of the TCC solenoid and compare with manufacturer’s specs<br> •\tMonitor TCM using an advanced scan tool and see if its performance parameters are within normal range"},
+    "P1101": { "short_description":"MAF Sensor Out Of Self-Test Range./KOER Not Able To Complete KOER Aborted",  "long_description":"<strong>1. Meaning:<\/strong><br> •\tThe PCM has detected an irregular (or abnormal) voltage from the Mass Air Flow (MAF) sensor<br> <br> <strong>3. Main Symptoms:<\/strong><br> •\tCheck Engine Light comes on<br> •\tEngine performs erratically upon startup<br> •\tReduced vehicle power<br> •\tRough idling<br> <br> <br> <strong>4. Possible Causes:<\/strong><br> •\tDefective MAF sensor<br> •\tWiring problem in MAF sensor circuit<br> •\tFaulty connector(s) in MAF system<br> <br> <br> <strong>5. Diagnostic Steps:<\/strong><br> •\tCheck that all wiring in the MAF sensor circuit is properly done and there are no open, broken or shorted wires<br> •\tCheck air filters for dirt and debris that might be obstructing air flow<br> •\tPerform smoke test in vacuum system to check for leaks before and after MAF sensor<br> •\tCheck the voltage in MAF sensor and compare with manufacturer’s specs. If they don’t match replace the sensor<br> •\tCheck continuity between PCM and MAF sensor"},
+    "P1133": { "short_description":"HO2S Insufficient Switching Sensor 1",  "long_description":"<strong>1. Meaning:<\/strong><br> •\tPCM has determined that the front heated O2 sensor (HO2S) is not functioning properly <br> <br> <strong>3. Main Symptoms:<\/strong><br> •\tCheck Engine Light comes on<br> •\tHard starting<br> •\tReduced fuel economy<br> •\tRough or erratic idling<br> <br> <br> <strong>4. Possible Causes:<\/strong><br> •\tDefective O2 sensor<br> •\tDamaged, broken, shorted or corroded wires/connectors in front HO2S<br> •\tEGR valve is stuck open<br> •\tMisfires on at least one cylinder<br> <br> <br> <strong>5. Diagnostic Steps:<\/strong><br> •\tCheck that all O2 sensor wires and connectors are not damaged, open, broken, shorted or burnt<br> •\tUsing a scanner, determine whether oxygen sensors are switching enough times<br> •\tIf not, measure their voltage to determine which sensor is faulty. Replace as necessary"},
+    "P1135": { "short_description":"Air/Fuel Ratio Sensor Heater Circuit Malfunction Bank 1 Sensor 1",  "long_description":"<strong>1. Meaning:<\/strong><br> •\tThis Toyota code means the PCM has detected an oxygen sensor heater circuit malfunction<br> <br> <strong>3. Main Symptoms:<\/strong><br> •\tCheck Engine Light comes on<br> •\tLonger time needed to achieve closed loop<br> •\tDecreased fuel economy<br> •\tEngine may go into fixed fuel mix<br> <br> <br> <strong>4. Possible Causes:<\/strong><br> •\tH02S sensor in bank 1, circuit 1 is not sending the correct signal to ECM<br> •\tDamaged or failed element in heater circuit<br> •\tOpen in O2 sensor heater’s circuit<br> •\tOpen/short in O2 sensor heater’s battery<br> •\tDefective ECM (this is the least likely cause)<br> <br> <br> <strong>5. Diagnostic Steps:<\/strong><br> •\tInspect wiring and power to the O2 sensor and ensure there’s no damage/open/short<br> •\tUse code reader to pull engine codes<br> •\tCheck voltage of O2 sensor and ensure it matches manufacturer’s specs<br> •\tReplace O2 sensor if necessary"},
+    "P1399": { "short_description":"Random Cylinder Misfire Detected",  "long_description":"<strong>1. Meaning:<\/strong><br> •\tThis Honda code means the PCM has detected that there’s an engine cylinder that’s not firing properly. It could be one or more cylinders. PCM hasn’t specified the exact cylinder<br> <br> <strong>3. Main Symptoms:<\/strong><br> •\tCheck Engine Light comes on<br> •\tCheck Engine Light may flash<br> •\tEngine lacks power<br> •\tEngine may be hard to start<br> •\tEngine may stumble and hesitate frequently<br> <br> <br> <strong>4. Possible Causes:<\/strong><br> •\tDefective or worn out spark plugs<br> •\tLow fuel pressure<br> •\tVacuum leak<br> •\tDefective catalytic converter<br> •\tDefective fuel injector<br> •\tDefective coil<br> •\tDefective camshaft position sensor<br> •\tDefective crankshaft sensor<br> •\tProblem with distributor<br> <br> <br> <strong>5. Diagnostic Steps:<\/strong><br> •\tUsing a scan tool, pull codes and see if there are any other besides P0300. Address the others first<br> •\tInspect whether there are loose, open or short wires in ignition coils<br> •\tInspect whether spark plugs and their wires are in good condition<br> •\tCheck that fuel pressure is within the recommended range<br> •\tInspect fuel injectors to see whether they are in good condition"},
+    "P1443": { "short_description":"Evaporative Emission Control System Control Valve",  "long_description":"<strong>1. Meaning:<\/strong><br> •\tThis is a predominantly Ford, Nissan and Range Rover code that means the same thing as P0443. Please refer to that code (#79 on this list)"},
+    "P1450": { "short_description":"Unable To Bleed Up Fuel Tank Vacuum",  "long_description":"<strong>1. Meaning:<\/strong><br> •\tThis is a Ford, Jaguar, Lincoln, Mercedes, Mercury and Oldsmobile code that means that the Evaporative Emission Control System has failed to bleed up the fuel tank<br> <br> <strong>3. Main Symptoms:<\/strong><br> •\tCheck Engine Light comes on<br> •\tEngine may fail to start<br> <br> <br> <strong>4. Possible Causes:<\/strong><br> •\tDefective vent valve<br> •\tBlockage in vacuum lines<br> •\tDamaged charcoal canister<br> •\tOverfilling fuel tank<br> <br> <br> <strong>5. Diagnostic Steps:<\/strong><br> •\tCheck that all vent valve wires and connectors are not damaged, open, broken, shorted or burnt<br> •\tRemove any blockage in vacuum lines. Refer to your application manual for this procedure<br> •\tVisually inspect charcoal canister for any damages<br> •\tCheck that the fuel amount is within the recommended range"},
+    "P1456": { "short_description":"Fuel Tank Temperature Sensor Circuit Malfunction",  "long_description":"<strong>1. Meaning:<\/strong><br> •\tThis Honda code means the same thing as P0442. Please refer to that code (#78 on this list)"},
+    "P1457": { "short_description":"This is a manufacturer-specific code that means different things in different cars<br> •\tIn Honda it means Evaporative emission (EVAP) canister purge system (canister system) - leak detected<br> •\tIn Acura it means Evaporative emission (EVAP) canister purge system (canister system) - leak detected<br> •\tIn Audi it means Exhaust gas recirculation temperature (EGRT) sensor 2/Bank 2 - open circuit/short to positive<br> •\tIn Isuzu it means Evaporative emission (EVAP) canister purge system (canister system) - leak detected<br> •\tIn Kia it means Evaporative emission (EVAP) canister purge valve (low)<br> •\tIn Volkswagen it means Exhaust gas recirculation temperature (EGRT) open circuit/short to positive"},
+    "P1491": { "short_description":"This is a manufacturer-specific code that means different things in different cars<br> •\tIn Acura it means Exhaust gas recirculation (EGR) system - valve lift insufficient<br> •\tIn Chrysler it means Radiator Fan Relay Circuit Conditions<br> •\tIn Honda it means Exhaust gas recirculation (EGR) system - valve lift insufficient<br> •\tIn Infiniti it means Evaporative emission (EVAP) canister purge control system – malfunction<br> •\tIn Isuzu it means Exhaust gas recirculation (EGR) system - valve lift insufficient<br> •\tIn Mercedes it means AC system - pressure too high<br> •\tIn Nissan it means Evaporative emission (EVAP) canister purge system - bypass vacuum valve malfunction"},
+    "P1494": { "short_description":"EVAP Leak Detection Pump Pressure Switch Condition",  "long_description":"<strong>1. Meaning:<\/strong><br> •\tA Chrysler code, this code means the same as P0442, i.e. PCM has detected a very small vapor leak somewhere in the EVAP control system <br> <br> <strong>3. Main Symptoms:<\/strong><br> •\tCheck Engine Light may come on<br> •\tReduced fuel economy<br> <br> <br> <strong>4. Possible Causes:<\/strong><br> •\tLoose or damaged EVAP hoses<br> •\tDefective purge valve<br> •\tGas cap is loose, missing or damaged<br> •\tCharcoal canister is leaking<br> •\tFuel tank is leaking<br> <br> <br> <strong>5. Diagnostic Steps:<\/strong><br> •\tInspect gas cap to see if its installed tightly<br> •\tCheck for disconnected or cracked EVAP hoses<br> •\tInspect charcoal canister and fuel tank for leaks and damages<br> •\tCheck that purge valve (solenoid) has no leaks<br> •\tIf the above don’t narrow down the problem perform a smoke test"},
+    "P1516": { "short_description":"Throttle actuator control module / throttle actuator position performance",  "long_description":"<strong>1. Meaning:<\/strong><br> •\tA GM code, this DTC means that the voltage being sent by the throttle actuator position sensor (TAPS) doesn’t match the voltage being received by the actual throttle position sensor<br> <br> <strong>3. Main Symptoms:<\/strong><br> •\tCheck Engine Light comes on<br> •\tVehicle may fail to accelerate<br> •\tIntermittent surging<br> •\tReduced fuel economy<br> <br> <br> <strong>4. Possible Causes:<\/strong><br> •\tDefective pedal position sensor<br> •\tOpen or short in circuit supplying power to pedal position sensor<br> •\tDefective PCM<br> <br> <br> <strong>5. Diagnostic Steps:<\/strong><br> •\tVisually inspect all wiring to pedal position sensor and ensure there are no open, broken or shorted wires<br> •\tRun a resistance test on sensor. If it fails replace it<br> •\tDo a test drive, if the code returns run a test on PCM"},
+    "P1684": { "short_description":"Battery Power to Module Disconnected",  "long_description":"<strong>1. Meaning:<\/strong><br> •\tThis code appears on Dodge and Chrysler vehicles. It means the transmission control module (TCM) is disconnected from the battery’s power B+ or ground<br> <br> <strong>3. Main Symptoms:<\/strong><br> •\tCheck Engine Light comes on<br> <br> <strong>4. Possible Causes:<\/strong><br> •\tRecently disconnected battery<br> •\tTCM was either replaced or disconnected<br> •\tShort in TCM harness<br> •\tDefective TCM<br> <br> <br> <strong>5. Diagnostic Steps:<\/strong><br> •\tCheck all wiring to TCM and ensure there are no shorted, broken or open wires<br> •\tUse a scan tool to reset the code<br> •\tDo a voltage test on TCM to determine if its defective"},
+    "P2096": { "short_description":"Post Catalyst Fuel Trim System Too Lean Bank 1",  "long_description":"<strong>1. Meaning:<\/strong><br> •\tThere’s a lean condition (i.e. too much air and too little fuel) in cylinder #1 on a V6 or V8 engine.<br> <br> <strong>3. Main Symptoms:<\/strong><br> •\tCheck Engine Light comes on<br> •\tReduced fuel economy<br> •\tErratic acceleration<br> •\tEngine misfires<br> •\tRough idle<br> •\tMay produce spark knock<br> <br> <br> <strong>4. Possible Causes:<\/strong><br> •\tLarge vacuum leak<br> •\tLarge air leak somewhere around the 1st cylinder<br> •\tLow fuel pressure<br> •\tMisfiring plugs that cause the engine to run rough<br> •\tDefective O2 sensor<br> •\tDefective exhaust system<br> <br> <br> <strong>5. Diagnostic Steps:<\/strong><br> •\tUsing a scan tool, pull all the codes and address all the others first<br> •\tVisually inspect exhaust system for any damaged or worn out components<br> •\tCheck for vacuum leaks in the engine, particularly between the intake manifold and MAF sensor<br> •\tCheck that plug wires are not burning<br> •\tIf the vehicle has very little acceleration power, check for clogged converter<br> •\tIf none of the above work replace MAF sensor then downstream O2 sensor (in that order)"},
+    "P2097": { "short_description":"Post Catalyst Fuel Trim System Too Rich Bank 1",  "long_description":"<strong>1. Meaning:<\/strong><br> •\tThere’s a rich condition (i.e. too little oxygen content) in cylinder #1<br> <br> <strong>3. Main Symptoms:<\/strong><br> •\tCheck Engine Light comes on<br> •\tEngine lacks power<br> •\tReduced fuel economy<br> <br> <br> <strong>4. Possible Causes:<\/strong><br> •\tDefective catalytic converter<br> •\tDefective O2 sensor(s)<br> •\tDefective MAF sensor or manifold air pressure sensor<br> •\tLeak in exhaust system<br> •\tWiring problem e.g. burnt, open, broken or disconnected wire<br> <br> <br> <strong>5. Diagnostic Steps:<\/strong><br> •\tVisually inspect wiring harnesses for broken, open, burnt or disconnected wires<br> •\tCheck the exhaust for leaks and damages<br> •\tPull all codes and freeze frame data. Reset the codes and do a test drive<br> •\tIf code P2097 returns check resistance of MAF sensor and O2 sensors. Replace as necessary"},
+    "P2101": { "short_description":"Throttle Actuator \"A\" Control Motor Circuit Range/Performance",  "long_description":"<strong>1. Meaning:<\/strong><br> •\tThis code is set when there’s an electrical or mechanical problem in the throttle actuator A (TA-A)<br> <br> <strong>3. Main Symptoms:<\/strong><br> •\tCheck Engine Light comes on<br> •\tEngine fails to accelerate<br> •\tFixed idle speed<br> <br> <br> <strong>4. Possible Causes:<\/strong><br> •\tDefective TA-A<br> •\tOpen or short in TA-A circuit<br> •\tDefective PCM (least likely)<br> <br> <br> <strong>5. Diagnostic Steps:<\/strong><br> •\tLocate TA-A and check all its wiring harnesses. Ensure there are no open, broken, disconnected, shorted or burnt wires and connectors<br> •\tReset the code and do a test drive. If it returns test the actuator"},
+    "P2138": { "short_description":"Throttle/Pedal Pos Sensor/Switch D / E Voltage Correlation",  "long_description":"<strong>1. Meaning:<\/strong><br> •\tPCM has detected a fault in either the D or E (or both) circuit of the throttle position sensor<br> <br> <strong>3. Main Symptoms:<\/strong><br> •\tCheck Engine Light comes on<br> •\tVehicle may fail to start<br> •\tVehicle may stall<br> •\tPoor acceleration<br> <br> <br> <strong>4. Possible Causes:<\/strong><br> •\tDefective throttle position sensor<br> •\tDefective throttle body motor<br> •\tWiring or connector problem in throttle body motor circuit<br> <br> <br> <strong>5. Diagnostic Steps:<\/strong><br> •\tVisually inspect throttle body motor circuit for loose, broken, burnt or open wires and connectors<br> •\tTest resistance of throttle motor and throttle position sensor. Replace as necessary"},
+    "P2181": { "short_description":"Cooling System Performance",  "long_description":"<strong>1. Meaning:<\/strong><br> •\tA vague OBD2 code, the P2181 suggests that there’s somewhere in the engine where the temperature is out of range (either too hot or too cold)<br> <br> <strong>3. Main Symptoms:<\/strong><br> •\tCheck Engine Light comes on<br> •\tTemperature gauge indicates too high or too low temp<br> •\tIf the temp is too cold the engine will have a rich condition<br> <br> <br> <strong>4. Possible Causes:<\/strong><br> •\tDefective engine coolant temperature (ECT) sensor<br> •\tWiring or connector problem in ECT circuit<br> •\tThermostat is stuck open or closed<br> •\tPresence of air in cooling system<br> •\tLow engine coolant level<br> <br> <br> <strong>5. Diagnostic Steps:<\/strong><br> •\tIf the engine is running too cold replace thermostat<br> •\tCheck to ensure there are no open, broken, disconnected, shorted or burnt wires and connectors in ECT circuit<br> •\tCheck whether fan is working. If it’s wobbling or has a leak tighten or replace the fan. Remember to check its fuse as well<br> •\tTest the resistance of ECT sensor. If it’s off the recommended reading replace the sensor"},
+    "P2195": { "short_description":"O2 Sensor Signal Stuck Lean Bank 1 Sensor 1",  "long_description":"<strong>1. Meaning:<\/strong><br> •\tO2 sensor 1 on cylinder 1 (bank 1) is reading an air/fuel ratio that has strayed so far from the normal 14.7:1 that the PCM is no longer able to correct the ratio<br> <br> <strong>3. Main Symptoms:<\/strong><br> •\tCheck Engine Light comes on<br> <br> <strong>4. Possible Causes:<\/strong><br> •\tDefective O2 sensor or A/F (air/fuel) ratio sensor<br> •\tOpen or short in O2 sensor circuit<br> •\tDefective fuel pressure system leading to too high or too low fuel pressure<br> •\tFuel leak<br> •\tLeak in engine vacuum or intake air<br> •\tLeak in PCV system<br> •\tLeak in fuel system (tank or hoses)<br> •\tDefective MAF sensor<br> •\tDefective PCM<br> <br> <br> <strong>5. Diagnostic Steps:<\/strong><br> •\tVisually inspect all wiring harnesses to O2 sensor circuits, especially sensor 1<br> •\tCheck vacuum, fuel tank and PCV systems for leaks<br> •\tUsing a scan tool, monitor short and long term fuel trim values and compare with manufacturer specs<br> •\tAlso take readings for MAF and O2 sensor 1 and compare with specs<br> •\tCheck the resistance of those sensors to ensure they work properly. Replace as necessary"},
+    "P2196": { "short_description":"O2 Sensor Signal Stuck Rich Bank 1 Sensor 1",  "long_description":"<strong>1. Meaning:<\/strong><br> •\tThis code is similar to P2195; i.e. O2 sensor 1 on cylinder 1 (bank 1) is reading an air/fuel ratio that has strayed so far from the normal 14.7:1 that the PCM is no longer able to correct the ratio<br> <br> <strong>3. Main Symptoms:<\/strong><br> •\tCheck Engine Light comes on<br> <br> <strong>4. Possible Causes:<\/strong><br> •\tDefective O2 sensor or A/F (air/fuel) ratio sensor<br> •\tOpen or short in O2 sensor circuit<br> •\tDefective fuel pressure system leading to too high or too low fuel pressure<br> •\tFuel leak<br> •\tLeak in engine vacuum or intake air<br> •\tLeak in PCV system<br> •\tLeak in fuel system (tank or hoses)<br> •\tDefective MAF sensor<br> •\tDefective PCM<br> <br> <br> <strong>5. Diagnostic Steps:<\/strong><br> •\tVisually inspect all wiring harnesses to O2 sensor circuits, especially sensor 1<br> •\tCheck vacuum, fuel tank and PCV systems for leaks<br> •\tUsing a scan tool, monitor short and long term fuel trim values and compare with manufacturer specs<br> •\tAlso take readings for MAF and O2 sensor 1 and compare with specs<br> •\tCheck the resistance of those sensors to ensure they work properly. Replace as necessary"},
+    "P2270": { "short_description":"O2 Sensor Signal Biased/Stuck Lean Bank 1 Sensor 2",  "long_description":"<strong>1. Meaning:<\/strong><br> •\tThe signal being put out by sensor 2 on bank 1 is stuck lean (the sensor has detected too much air)<br> <br> <strong>3. Main Symptoms:<\/strong><br> •\tCheck Engine Light comes on<br> •\tEngine may run rough<br> <br> <br> <strong>4. Possible Causes:<\/strong><br> •\tProblem with fuel injector<br> •\tExhaust leak near sensor 2 of bank 1<br> •\tDefective sensor 2 of bank 1<br> •\tIncorrect fuel pressure<br> •\tLeak in engine coolant<br> •\tDefective purge solenoid valve<br> •\tDefective PCM (least likely)<br> <br> <br> <strong>5. Diagnostic Steps:<\/strong><br> •\tVisually inspect all wiring harnesses to O2 sensor circuits, especially sensor 1 of bank 1<br> •\tCheck for exhaust leaks<br> •\tUsing a scan tool, monitor sensor readings and compare with manufacturer specs<br> •\tProceed to test resistance of sensors and replace as necessary"},
+    "P2646": { "short_description":"“A” Rocker Arm Actuator System Performance/Stuck Off Bank 1",  "long_description":"<strong>1. Meaning:<\/strong><br> •\tThe “A” rocker arm actuator control circuit is either stuck in the off position or not working as it should<br> <br> <strong>3. Main Symptoms:<\/strong><br> •\tCheck Engine Light comes on<br> •\tEngine may lack power<br> •\tEngine valve train may be excessively noisy<br> <br> <br> <strong>4. Possible Causes:<\/strong><br> •\tLow oil pressure<br> •\tClogged oil passages<br> •\tRocker arm actuator has built-up slug<br> •\tOil used is too thick<br> <br> <br> <strong>5. Diagnostic Steps:<\/strong><br> •\tVerify that the oil used is of the right viscosity<br> •\tCheck A” rocker arm actuator hoses and passages for blockage<br> •\tClear code and do test drive. If it returns perform manufacturer pinpoint test for A” rocker arm actuator"},
+    "P2A00": { "short_description":"O2 Sensor Circuit Range/Performance Bank 1 Sensor 1",  "long_description":"<strong>1. Meaning:<\/strong><br> •\tThe upstream O2 sensor circuit has failed to cycle as expected by the PCM over a period of time predetermined by the PCM<br> <br> <strong>3. Main Symptoms:<\/strong><br> •\tCheck Engine Light comes on<br> •\tReduced fuel economy<br> •\tPoor engine performance<br> •\tEngine misfires<br> <br> <br> <strong>4. Possible Causes:<\/strong><br> •\tDefective O2 sensor 1 in bank 1<br> •\tBurnt, open, broken, shorted or disconnected wire/connector in the sensor circuit<br> •\tVacuum leak<br> •\tDefective MAF sensor<br> •\tLeak in engine exhaust<br> <br> <br> <strong>5. Diagnostic Steps:<\/strong><br> •\tVisually inspect all wiring harnesses to O2 sensor circuits, especially sensor 1 of bank 1<br> •\tCheck for leaks in engine and vacuum system<br> •\tAddress other codes, reset all codes and do test drive. If the code returns check resistance of MAF sensor and O2 sensor. Replace as necessary"},
+    "U0001": { "short_description":"Controller Area Network (CAN) Data Bus: High Speed Bus/Communication Control Module",  "long_description":"<strong>1. Meaning:<\/strong><br> •\tThe high speed bus is a communication line between the totally integrated power module (TIPM) and other vehicle modules. When this code sets it means there’s a module (especially ABS module) that has failed to communicate with TIPM<br> <br> <strong>3. Main Symptoms:<\/strong><br> •\tCheck Engine Light comes on<br> •\tVehicle may fail to start on one or few attempts<br> •\tKey alarm may activate intermittently<br> <br> <br> <strong>4. Possible Causes:<\/strong><br> •\tOpen in either positive or negative CAN bus circuit<br> •\tOpen in power or ground supply circuit to the module that set the code<br> •\tShort to ground on CAN bus circuit<br> •\tLow voltage<br> •\tProblem with TIPM<br> <br> <br> <strong>5. Diagnostic Steps:<\/strong><br> •\tUsing a bidirectional scan tool, operate each module independently to find out which one is not working<br> •\tOnce you’ve pinpointed the module check that its circuits have no loose, open, broken or disconnected wires and connectors<br> •\tDo the same for TIPM<br> •\tUse an ohmmeter to check continuity on wire terminals in the module and TIPM<br> •\tIf all the above don’t work replace the TIPM"},
+    "U0073": { "short_description":"Control Module Communication Bus “A” Off",  "long_description":"<strong>1. Meaning:<\/strong><br> •\tProblem with CAN bus making it hard for modules to exchange information and to communicate with scan tool<br> <br> <strong>3. Main Symptoms:<\/strong><br> •\tCheck Engine Light comes on<br> •\tInstrument cluster indicator “light” on<br> •\tReduced fuel economy<br> •\tEngine lacks power<br> <br> <br> <strong>4. Possible Causes:<\/strong><br> •\tOpen in the “A” CAN bus + or – circuit <br> •\tShort to power or ground in “A” CAN bus circuit<br> <br> <br> <strong>5. Diagnostic Steps:<\/strong><br> •\tPull all communication codes and address them first then this one last<br> •\tVisually inspect all bus communication connections (connectors and wires) for breaks, shorts, opens, chafing, burns and melted spots<br> •\tReset all codes and do a test drive. If this code returns disconnect one control module at a time and see if the scanner can finally communicate with PCM"},
+    "U0101": { "short_description":"Lost Communication With Transmission Control Module",  "long_description":"<strong>1. Meaning:<\/strong><br> •\tThere’s no communication between the transmission control module (TCM) and other control modules<br> <br> <strong>3. Main Symptoms:<\/strong><br> •\tCheck Engine Light comes on<br> •\tCar doesn’t shift gears<br> •\tStays in one gear, usually 2nd or 3rd gear<br> <br> <br> <strong>4. Possible Causes:<\/strong><br> •\tOpen in CAN bus + or – circuit<br> •\tShort to power or ground in either + or – CAN bus circuit<br> •\tDefective TCM (least likely)<br> <br> <br> <strong>5. Diagnostic Steps:<\/strong><br> •\tUsing a bidirectional scan tool, control TCM and see if it responds. If it doesn’t probe it further<br> •\tIf it responds then check all the wires, connectors and fuses that make the circuit<br> •\tWith key on engine off, check the voltage of CAN C+ and C-. If the readings don’t match manufacturer’s specs then the communication circuits are bad"},
+    "U0107": { "short_description":"Lost Communication With Throttle Actuator Control Module",  "long_description":"<strong>1. Meaning:<\/strong><br> •\tThere’s no communication between the throttle actuator control (TAC) module and other control modules<br> <br> <strong>3. Main Symptoms:<\/strong><br> •\tCheck Engine Light comes on<br> •\tElectronic throttle control light comes on or flashes<br> •\tNo throttle response<br> <br> <br> <strong>4. Possible Causes:<\/strong><br> •\tOpen in CAN bus + or – circuit<br> •\tShort to power or ground in either + or – CAN bus circuit<br> •\tDefective TAC module (least likely)<br> <br> <br> <strong>5. Diagnostic Steps:<\/strong><br> •\tUsing a bidirectional scan tool, control TAC module and see if it responds. If it doesn’t probe it further<br> •\tIf it responds then check all the wires, connectors and fuses that make the circuit<br> •\tWith key on engine off, check the voltage of CAN C+ and C-. If the readings don’t match manufacturer’s specs then the communication circuits are bad"},
+    "U0121": { "short_description":"Lost Communication With Anti-Lock Brake System Control Module",  "long_description":"<strong>1. Meaning:<\/strong><br> •\tThere’s no communication between the anti-lock brake system (ABS) control module and other control modules<br> <br> <strong>3. Main Symptoms:<\/strong><br> •\tCheck Engine Light comes on<br> •\tABS warning light comes on<br> •\tTRAC or ESP/ESC (or both) warning light comes on<br> <br> <br> <strong>4. Possible Causes:<\/strong><br> •\tOpen in CAN bus + or – circuit<br> •\tShort to power or ground in either + or – CAN bus circuit<br> •\tDefective ABS control module (least likely)<br> <br> <br> <strong>5. Diagnostic Steps:<\/strong><br> •\tUsing a bidirectional scan tool, control ABS control module and see if it responds. If it doesn’t probe it further<br> •\tIf it responds then check all the wires, connectors and fuses that make the circuit<br> •\tWith key on engine off, check the voltage of CAN C+ and C-. If the readings don’t match manufacturer’s specs then the communication circuits are bad"},
+    "U0155": { "short_description":"Lost Communication With Instrument Panel Control Module",  "long_description":"<strong>1. Meaning:<\/strong><br> •\tThere’s no communication between the instrument panel control (IPC) module and other control modules<br> <br> <strong>3. Main Symptoms:<\/strong><br> •\tCheck Engine Light comes on<br> •\tAll indicator lights in instrument panel/cluster come on, or<br> •\tNo indicator lights in instrument panel/cluster come on<br> <br> <br> <strong>4. Possible Causes:<\/strong><br> <br> •\tOpen in CAN bus + or – circuit<br> •\tShort to power or ground in either + or – CAN bus circuit<br> •\tDefective IPC module (least likely)<br> <br> <strong>5. Diagnostic Steps:<\/strong><br> •\tUsing a bidirectional scan tool, control IPC module and see if it responds. If it doesn’t probe it further<br> •\tIf it responds then check all the wires, connectors and fuses that make the circuit<br> •\tWith key on engine off, check the voltage of CAN C+ and C-. If the readings don’t match manufacturer’s specs then the communication circuits are bad"},
+    "U1120": { "short_description":"Lost Wheel Distance",  "long_description":"<strong>1. Meaning:<\/strong><br> •\tPrimarily a Chrysler code, the U1120 means the ABS module is not able to communicate with speed sensors<br> <br> <strong>3. Main Symptoms:<\/strong><br> •\tCheck Engine Light comes on<br> •\tABS warning light may come on<br> <br> <br> <strong>4. Possible Causes:<\/strong><br> •\tOpen in CAN bus + or – circuit<br> •\tShort to power or ground in either + or – CAN bus circuit<br> •\tDefective ABS control module (least likely)<br> <br> <br> <strong>5. Diagnostic Steps:<\/strong><br> •\tUsing a bidirectional scan tool, control ABS control module and see if it responds. If it doesn’t probe it further<br> •\tIf it responds then check all the wires, connectors and fuses that make the circuit<br> •\tWith key on engine off, check the voltage of CAN C+ and C-. If the readings don’t match manufacturer’s specs then the communication circuits are bad"},
+    "U1900": { "short_description":"CAN Communication Bus Fault",  "long_description":"<strong>1. Meaning:<\/strong><br> •\tPrimarily a Ford code, the U1900 means the same as U0001 <br> <br> <strong>3. Main Symptoms:<\/strong><br> •\tCheck Engine Light comes on<br> •\tVehicle may fail to start on one or few attempts<br> •\tKey alarm may activate intermittently<br> <br> <br> <strong>4. Possible Causes:<\/strong><br> •\tOpen in either positive or negative CAN bus circuit<br> •\tOpen in power or ground supply circuit to the module that set the code<br> •\tShort to ground on CAN bus circuit<br> •\tLow voltage<br> •\tProblem with TIPM<br> <br> <br> <strong>5. Diagnostic Steps:<\/strong><br> •\tUsing a bidirectional scan tool, operate each module independently to find out which one is not working<br> •\tOnce you’ve pinpointed the module check that its circuits have no loose, open, broken or disconnected wires and connectors<br> •\tDo the same for TIPM<br> •\tUse an ohmmeter to check continuity on wire terminals in the module and TIPM<br> •\tIf all the above don’t work replace the TIPM"},
+    "B0092": { "short_description":"Left Side Restraints Sensor 2",  "long_description":"<strong>1. Meaning:<\/strong><br> •\tA Ford code, the B0092 means that the left side airbag sensor has detected a problem with the airbag system<br> <br> <strong>3. Main Symptoms:<\/strong><br> •\tAirbag warning lights may come on<br> •\tAbnormal illumination of airbag warning lights<br> <br> <br> <strong>4. Possible Causes:<\/strong><br> •\tOpen, short or broken wire in left side restraint sensor 2 harness<br> •\tDefective left side restraint sensor 2<br> <br> <br> <strong>5. Diagnostic Steps:<\/strong><br> •\tVisually inspect all wiring to left side restraint sensor 2 for loose, open or short connections<br> •\tCheck resistance of left side restraint sensor 2 and compare with specs. If they don’t match replace sensor"},
+    "B1015": { "short_description":"This Is A Manufacturer-Specific Code That Means Different Things In Different Cars<br> •\tIn GM It Means Passenger Deploy. Loop Resistance High<br> •\tIn Chrysler It Means Rear Defrost Switch Request Input Circuit/Performance<br> •\tIn Ford It Means Electronic Instrument Cluster Unconfigured<br> •\tIn Mazda It Means Electronic Instrument Cluster Unconfigured<br> •\tIn Mitsubishi It Means Heater Water Temperature Sensor Performance"},
+    "B1047": { "short_description":"Driver-Side Side Air Bag Module And Other Air Bag Module Circuits Short",  "long_description":"<strong>1. Meaning:<\/strong><br> •\tThere’s a short in the side airbag on the driver’s side<br> <br> <strong>3. Main Symptoms:<\/strong><br> •\tAirbag warning lights may come on<br> •\tAbnormal illumination of airbag warning lights<br> <br> <br> <strong>4. Possible Causes:<\/strong><br> •\tOpen or short in the circuit leading to driver-side side air bag module<br> •\tDefective driver-side side air bag module<br> •\tDefective SRS (airbag) module<br> <br> <br> <strong>5. Diagnostic Steps:<\/strong><br> •\tVisually inspect all wiring to the driver-side side air bag module for loose, open or short connections<br> •\tPerform resistance test on driver-side side air bag module<br> •\tPerform resistance test on airbag control module"},
+    "B1057": { "short_description":"Driver Airbag Module Short",  "long_description":"<strong>1. Meaning:<\/strong><br> •\tThe airbag diagnosis sensor on the driver’s side has detected a short in the circuit<br> <br> <strong>3. Main Symptoms:<\/strong><br> •\tAirbag warning light comes on<br> <br> <strong>4. Possible Causes:<\/strong><br> •\tShort in driver airbag’s harness<br> •\tProblem with spiral cable<br> •\tDefective driver airbag<br> •\tProblem with electrical connection in driver airbag<br> <br> <br> <strong>5. Diagnostic Steps:<\/strong><br> •\tVisually inspect the wiring harness to driver’s airbag<br> •\tPerform resistance test on driver’s airbag module<br> •\tCheck resistance of spiral cable and airbag diagnosis sensor and replace as necessary"},
+    "B1318": { "short_description":"Battery Voltage Low",  "long_description":"<strong>1. Meaning:<\/strong><br> •\tThis is a Ford and Jaguar code that is set when the PCM detects that battery voltage has fallen below a predetermined level <br> <br> <strong>3. Main Symptoms:<\/strong><br> •\tCheck Engine Light comes on<br> •\tRed battery light on<br> •\tReduced fuel economy<br> •\tTransmission may fail<br> •\tEngine may fail to start<br> •\tEngine may start then stall and die<br> <br> <br> <strong>4. Possible Causes:<\/strong><br> •\tDefective alternator<br> •\tUsing an incorrect battery<br> •\tUnmaintained battery<br> •\tHigh resistance either in alternator-battery circuit, alternator-PCM circuit or both<br> •\tDefective PCM<br> <br> <br> <strong>5. Diagnostic Steps:<\/strong><br> •\tCheck whether battery voltage is sufficient<br> •\tEnsure battery is properly connected then check alternator belt<br> •\tUsing a digital volt ohm meter (DVOM), check whether the charging system is working<br> •\tReset the code then do a test drive. If the code returns check PCM voltage"},
+    "B1342": { "short_description":"ECU Is Defective",  "long_description":"<strong>1. Meaning:<\/strong><br> •\tThe Electronic Control Unit (ECU) has failed<br> <br> <strong>3. Main Symptoms:<\/strong><br> •\tCheck Engine Light comes on<br> •\tOther warning lights may come on depending on which module is affected<br> <br> <br> <strong>4. Possible Causes:<\/strong><br> •\tDefective ECU<br> •\tDamaged controller(s) due to abnormal system voltages<br> <br> <br> <strong>5. Diagnostic Steps:<\/strong><br> •\tDiagnose and address all other codes first before replacing ECU<br> •\tCheck whether system voltage is within manufacturer’s recommendation<br> •\tInspect wiring to ECU and ensure that there are no open, shorted or broken wires<br> •\tReset all codes and do test drive. If the code returns you may have to replace ECU"},
+    "B1650": { "short_description":"Occupant Classification System Fault",  "long_description":"<strong>1. Meaning:<\/strong><br> •\tPCM or airbag control module has detected a malfunction in the occupant classification system<br> <br> <strong>3. Main Symptoms:<\/strong><br> •\tAirbag warning light comes on<br> •\tCheck Engine Light may come on<br> <br> <br> <strong>4. Possible Causes:<\/strong><br> •\tProblem in the occupant classification system<br> •\tWiring problem in the right front seat<br> •\tProblem with airbag sensor assembly center<br> <br> <br> <strong>5. Diagnostic Steps:<\/strong><br> •\tVisually inspect all wiring in the occupant classification system for loose, open or short connections<br> •\tPerform resistance test on occupant classification system and airbag sensor assembly center and replace as necessary<br> •\tPerform resistance test on airbag control module. If it doesn’t pass the test replace the module"},
+    "B1676": { "short_description":"Battery Pack Voltage Out Of Range",  "long_description":"<strong>1. Meaning:<\/strong><br> •\tThis anti-lock brake system (ABS) code is set when the ABS module detects a voltage signal that’s less than 9v or more than 19v for more than 8 seconds<br> <br> <strong>3. Main Symptoms:<\/strong><br> •\tABS warning light comes on<br> •\tCheck Engine Light may come on<br> <br> <br> <strong>4. Possible Causes:<\/strong><br> •\tBlown fuse in ABS<br> •\tProblem in the charging system<br> •\tWiring problem in ABS module connector<br> •\tDefective ABS module<br> <br> <br> <strong>5. Diagnostic Steps:<\/strong><br> •\tVisually inspect all wiring and connectors in ABS module as well as charging system for loose, open or short connections<br> •\tCheck that all fuses in ABS are not blown. If any is blown replace<br> •\tTest resistance of ABS module and compare with manufacturer’s specs<br> •\tReset code and do test drive. If it returns consider replacing the module"},
+    "C0265": { "short_description":"EBCM Motor Relay Circuit Low When On ",  "long_description":"<strong>1. Meaning:<\/strong><br> •\tThe Electronic Brake Control Module (EBCM) is sending an abnormally low voltage signal<br> <br> <strong>3. Main Symptoms:<\/strong><br> •\tABS warning light comes on<br> •\tCheck Engine Light may come on<br> <br> <br> <strong>4. Possible Causes:<\/strong><br> •\tShorted or open wire in EBCM harness<br> •\tPoor electrical connection in EBCM circuit<br> •\tDefective EBCM<br> <br> <br> <strong>5. Diagnostic Steps:<\/strong><br> •\tVisually inspect all wiring and connectors in EBCM for loose, open or shorted connections<br> •\tTest EBCM for resistance and compare readings with manufacturer’s specs. If they are out of range consider replacing"},
+    "C1130": { "short_description":"Engine Signal 1",  "long_description":"<strong>1. Meaning:<\/strong><br> •\tThe ABS control module has detected that there’s a problem with the engine control unit (ECU) or PCM<br> <br> <strong>3. Main Symptoms:<\/strong><br> •\tABS warning light comes on<br> •\tCheck Engine Light comes on<br> <br> <br> <strong>4. Possible Causes:<\/strong><br> •\tDefective ECU<br> •\tDefective ABS actuator and/or ABS control module<br> •\tProblem with CAN communication line<br> •\tDamaged controller(s) due to abnormal system voltages<br> <br> <br> <strong>5. Diagnostic Steps:<\/strong><br> •\tDiagnose and address all other codes first before replacing ECU<br> •\tCheck whether system voltage is within manufacturer’s recommendation<br> •\tInspect wiring to ECU and ensure that there are no open, shorted or broken wires<br> •\tCheck all fuses and replace as necessary<br> •\tRun tests on ABS module<br> •\tRun tests on CAN line<br> •\tReset all codes and do test drive. If the code returns you may have to replace ECU"},
+    "C1145": { "short_description":"Right Front Wheel Speed Sensor Input Circuit Failure",  "long_description":"<strong>1. Meaning:<\/strong><br> •\tThe right front wheel speed, as given by the wheel’s speed sensor is not within the expected range<br> <br> <strong>3. Main Symptoms:<\/strong><br> •\tABS warning light comes on<br> •\tCheck Engine Light comes on<br> <br> <br> <strong>4. Possible Causes:<\/strong><br> •\tDefective right front wheel speed sensor<br> •\tWiring problem in that speed sensor’s circuit<br> •\tPCM is not configured properly for the size of the right front wheel<br> <br> <br> <strong>5. Diagnostic Steps:<\/strong><br> •\tVisually inspect all wiring to the right front wheel speed sensor and check for open, short, broken and chaffed wires<br> •\tCheck the voltage of the speed sensor and match with manufacturer specs. If they don’t match replace the sensor"},
+    "C1201": { "short_description":"Engine Control System Malfunction",  "long_description":"<strong>1. Meaning:<\/strong><br> •\tThe Electronic Control Unit (ECU) has failed<br> <br> <strong>3. Main Symptoms:<\/strong><br> •\tCheck Engine Light comes on<br> •\tOther warning lights may come on depending on which module is affected<br> <br> <br> <strong>4. Possible Causes:<\/strong><br> •\tDefective ECU<br> •\tDamaged controller(s) due to abnormal system voltages<br> <br> <br> <strong>5. Diagnostic Steps:<\/strong><br> •\tDiagnose and address all other codes first before replacing ECU<br> •\tCheck whether system voltage is within manufacturer’s recommendation<br> •\tInspect wiring to ECU and ensure that there are no open, shorted or broken wires<br> •\tReset all codes and do test drive. If the code returns you may have to replace ECU"},
+    "C121C": { "short_description":"Torque Request Signal Denied",  "long_description":"<strong>1. Meaning:<\/strong><br> •\tThis camshaft-related code is an informational. It comes with codes P0344, P0345 or P0365. Addressing those codes will get rid of this one"},
+    "C1223": { "short_description":"ABS Control System Malfunction",  "long_description":"<strong>1. Meaning:<\/strong><br> •\tThis code is set when the car’s vehicle stability control (VSC) system detects any malfunction in the ABS<br> <br> <strong>3. Main Symptoms:<\/strong><br> •\tABS warning light comes on<br> •\tCheck Engine Light comes on<br> <br> <br> <strong>4. Possible Causes:<\/strong><br> •\tOpen or short in the ABS circuit<br> •\tDefective ABS sensor<br> •\tDefective ABS control module<br> <br> <br> <strong>5. Diagnostic Steps:<\/strong><br> •\tVisually inspect all wiring in the ABS circuit for loose, open or short connections<br> •\tPerform resistance test on ABS sensors and module. Replace as necessary<br> •\tReset code and do drive cycle, if it returns probe other systems, including PCM"},
+    "C1233": { "short_description":"Left Front Wheel Speed Sensor Input Signal Missing",  "long_description":"<strong>1. Meaning:<\/strong><br> •\tPCM or vehicle speed sensor (VSS) has failed to receive signals from the Left Front Wheel Speed Sensor<br> <br> <strong>3. Main Symptoms:<\/strong><br> •\tABS warning light comes on<br> •\tCheck Engine Light comes on<br> <br> <br> <strong>4. Possible Causes:<\/strong><br> •\tPoor connection in Left Front Wheel Speed Sensor<br> •\tOpen, shorted or broken wire in Left Front Wheel Speed Sensor harness<br> •\tDefective Left Front Wheel Speed Sensor<br> <br> <br> <strong>5. Diagnostic Steps:<\/strong><br> •\tVisually inspect all wiring in the Left Front Wheel Speed Sensor circuit for loose, open or short connections<br> •\tPerform resistance test on Left Front Wheel Speed Sensor. Replace if necessary<br> •\tReset code and do drive cycle, if it returns probe ABS system entirely"},
+    "C1234": { "short_description":"Right Front Wheel Speed Sensor Input Signal Missing",  "long_description":"<strong>1. Meaning:<\/strong><br> •\tPCM or vehicle speed sensor (VSS) has failed to receive signals from the Right Front Wheel Speed Sensor<br> <br> <strong>3. Main Symptoms:<\/strong><br> •\tABS warning light comes on<br> •\tCheck Engine Light comes on<br> <br> <br> <strong>4. Possible Causes:<\/strong><br> •\tPoor connection in Right Front Wheel Speed Sensor<br> •\tOpen, shorted or broken wire in Right Front Wheel Speed Sensor harness<br> •\tDefective Right Front Wheel Speed Sensor<br> <br> <br> <strong>5. Diagnostic Steps:<\/strong><br> •\tVisually inspect all wiring in the Right Front Wheel Speed Sensor circuit for loose, open or short connections<br> •\tPerform resistance test on Right Front Wheel Speed Sensor. Replace if necessary<br> •\tReset code and do drive cycle, if it returns probe ABS system entirely"},
+    "C1241": { "short_description":"Low Battery Positive Voltage",  "long_description":"<strong>1. Meaning:<\/strong><br> •\tThis code is set when there’s a problem with the skid control ECU (master cylinder solenoid)<br> <br> <strong>3. Main Symptoms:<\/strong><br> •\tABS warning light comes on<br> •\tCheck Engine Light comes on<br> <br> <br> <strong>4. Possible Causes:<\/strong><br> •\tLow battery voltage<br> •\tUsing an incorrect battery<br> •\tUnmaintained battery<br> •\tHigh resistance either in alternator-battery circuit, alternator-PCM circuit or both<br> •\tDefective charging system<br> <br> <br> <strong>5. Diagnostic Steps:<\/strong><br> •\tCheck whether battery voltage is sufficient<br> •\tEnsure battery is properly connected then check alternator belt<br> •\tUsing a digital volt ohm meter (DVOM), check whether the charging system is working<br> •\tReset the code then do a test drive. If the code returns check PCM voltage"},
+    "C1713": { "short_description":"Right Rear Height Control Sensor<br> Circuit<br> ",  "long_description":"<strong>1. Meaning:<\/strong><br> •\tPCM has detected that after switching ignition ON, a voltage of .3V or less, or 4.7V or more was achieved for more than 1 second at each height control sensor sub−assy rear<br> <br> <strong>3. Main Symptoms:<\/strong><br> •\tCheck Engine Light comes on<br> •\tHeight control indicator lamp (N) comes on or blinks<br> •\tVehicle won’t be able to perform height control function<br> <br> <br> <strong>4. Possible Causes:<\/strong><br> •\tDefective Right Rear Height Control Sensor<br> •\tWiring issue in Right Rear Height Control Sensor<br> •\tWorn out suspension and ride control parts<br> •\tProblem with shocks and/or struts<br> <br> <br> <strong>5. Diagnostic Steps:<\/strong><br> •\tVisually inspect all wiring in the RIGHT Rear Height Control Sensor circuit for loose, open or short connections<br> •\tPerform resistance test on Right Rear Height Control Sensor. Replace if necessary<br> •\tEnsure that all parts, including ball joints, springs (for ride height), shocks and struts are well maintained"}
+ }
\ No newline at end of file
diff --git a/tests/DiagnosticTools/res/raw/system_float_sensors.json b/tests/DiagnosticTools/res/raw/system_float_sensors.json
new file mode 100644
index 0000000..4311e10
--- /dev/null
+++ b/tests/DiagnosticTools/res/raw/system_float_sensors.json
@@ -0,0 +1,36 @@
+{
+  "FUEL_TANK_LEVEL_INPUT": {
+    "units": "%",
+    "translation": "x/2.55",
+    "name": "Fuel Tank Level Input"
+  },
+  "ENGINE_RPM": {
+    "units": "RPM",
+    "translation": "x/4",
+    "name": "Engine RPM"
+  },
+  "CALCULATED_ENGINE_LOAD": {
+    "units": "%",
+    "translation": "x/2.55",
+    "name": "Calculated Engine Load"
+  },
+  "SHORT_TERM_FUEL_TRIM_BANK1": {
+    "units": "%",
+    "conversion": {
+      "scale": 0.78125,
+      "offset": -100
+    },
+    "translation": "x/1.28 - 100",
+    "name": "Short Term Fuel Trim: Bank 1"
+  },
+  "VEHICLE_SPEED": {
+    "units": "KMH",
+    "translation": "",
+    "name": "Vehicle Speed"
+  },
+  "ENGINE_COOLANT_TEMPERATURE": {
+    "units": "C",
+    "translation": "x-40",
+    "name": "Engine Coolant Temperature"
+  }
+}
\ No newline at end of file
diff --git a/tests/DiagnosticTools/res/raw/system_integer_sensors.json b/tests/DiagnosticTools/res/raw/system_integer_sensors.json
new file mode 100644
index 0000000..a9f2775
--- /dev/null
+++ b/tests/DiagnosticTools/res/raw/system_integer_sensors.json
@@ -0,0 +1,23 @@
+{
+  "AMBIENT_AIR_TEMPERATURE": {
+    "units": "C",
+    "translation": "x-40",
+    "name": "Ambient Air Temperature"
+  },
+  "RUNTIME_SINCE_ENGINE_START": {
+    "units": "seconds",
+    "translation": "x-40",
+    "name": "Runtime Since Engine Start"
+  },
+  "FUEL_SYSTEM_STATUS": {
+    "units": "",
+    "mapping": {
+      "1": "Open loop due to insufficient engine temperature",
+      "2": "Closed loop, using oxygen sensor feedback to determine fuel mix",
+      "4": "Open loop due to engine load OR fuel cut due to deceleration",
+      "8": "Open loop due to system failure",
+      "16": "Closed loop, using at least one oxygen sensor but there is a fault in the feedback system"
+    },
+    "name": "Fuel System Status"
+  }
+}
\ No newline at end of file
diff --git a/tests/DiagnosticTools/res/raw/vendor_dtcs.json b/tests/DiagnosticTools/res/raw/vendor_dtcs.json
new file mode 100644
index 0000000..9e26dfe
--- /dev/null
+++ b/tests/DiagnosticTools/res/raw/vendor_dtcs.json
@@ -0,0 +1 @@
+{}
\ No newline at end of file
diff --git a/tests/DiagnosticTools/res/raw/vendor_ecus.json b/tests/DiagnosticTools/res/raw/vendor_ecus.json
new file mode 100644
index 0000000..973e627
--- /dev/null
+++ b/tests/DiagnosticTools/res/raw/vendor_ecus.json
@@ -0,0 +1,7 @@
+{
+  "123": "Door Control Unit",
+  "124": "Engine Control Unit",
+  "125": "Engine Power Steering Control Unit",
+  "222": "Seat Control Unit",
+  "221": "Telematic Control Unit"
+}
\ No newline at end of file
diff --git a/tests/DiagnosticTools/res/raw/vendor_float_sensors.json b/tests/DiagnosticTools/res/raw/vendor_float_sensors.json
new file mode 100644
index 0000000..9e26dfe
--- /dev/null
+++ b/tests/DiagnosticTools/res/raw/vendor_float_sensors.json
@@ -0,0 +1 @@
+{}
\ No newline at end of file
diff --git a/tests/DiagnosticTools/res/raw/vendor_integer_sensors.json b/tests/DiagnosticTools/res/raw/vendor_integer_sensors.json
new file mode 100644
index 0000000..9e26dfe
--- /dev/null
+++ b/tests/DiagnosticTools/res/raw/vendor_integer_sensors.json
@@ -0,0 +1 @@
+{}
\ No newline at end of file
diff --git a/tests/DiagnosticTools/res/values-af/strings.xml b/tests/DiagnosticTools/res/values-af/strings.xml
new file mode 100644
index 0000000..57424bd
--- /dev/null
+++ b/tests/DiagnosticTools/res/values-af/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="display_freeze_frame_info" msgid="1425573367248263107">"Wys Vries Raam-inligting"</string>
+</resources>
diff --git a/tests/DiagnosticTools/res/values-am/strings.xml b/tests/DiagnosticTools/res/values-am/strings.xml
new file mode 100644
index 0000000..44fa319
--- /dev/null
+++ b/tests/DiagnosticTools/res/values-am/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="display_freeze_frame_info" msgid="1425573367248263107">"የክፈፍ እሰር መረጃን አሳይ"</string>
+</resources>
diff --git a/tests/DiagnosticTools/res/values-ar/strings.xml b/tests/DiagnosticTools/res/values-ar/strings.xml
new file mode 100644
index 0000000..d2e46ff
--- /dev/null
+++ b/tests/DiagnosticTools/res/values-ar/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="display_freeze_frame_info" msgid="1425573367248263107">"عرض معلومات عن الإطار الثابت"</string>
+</resources>
diff --git a/tests/DiagnosticTools/res/values-as/strings.xml b/tests/DiagnosticTools/res/values-as/strings.xml
new file mode 100644
index 0000000..98fc05f
--- /dev/null
+++ b/tests/DiagnosticTools/res/values-as/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="display_freeze_frame_info" msgid="1425573367248263107">"ফ্ৰীজ ফ্ৰে’মৰ তথ্য দেখুৱাওক"</string>
+</resources>
diff --git a/tests/DiagnosticTools/res/values-az/strings.xml b/tests/DiagnosticTools/res/values-az/strings.xml
new file mode 100644
index 0000000..95f4ea9
--- /dev/null
+++ b/tests/DiagnosticTools/res/values-az/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="display_freeze_frame_info" msgid="1425573367248263107">"Stop-kadr məlumatını göstərin"</string>
+</resources>
diff --git a/tests/DiagnosticTools/res/values-b+sr+Latn/strings.xml b/tests/DiagnosticTools/res/values-b+sr+Latn/strings.xml
new file mode 100644
index 0000000..fa16fed
--- /dev/null
+++ b/tests/DiagnosticTools/res/values-b+sr+Latn/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="display_freeze_frame_info" msgid="1425573367248263107">"Prikaži informacije o zamrznutom okviru"</string>
+</resources>
diff --git a/tests/DiagnosticTools/res/values-be/strings.xml b/tests/DiagnosticTools/res/values-be/strings.xml
new file mode 100644
index 0000000..e73e14c
--- /dev/null
+++ b/tests/DiagnosticTools/res/values-be/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="display_freeze_frame_info" msgid="1425573367248263107">"Інфармацыя пра замарожаны кадр экрана"</string>
+</resources>
diff --git a/tests/DiagnosticTools/res/values-bg/strings.xml b/tests/DiagnosticTools/res/values-bg/strings.xml
new file mode 100644
index 0000000..71b4550
--- /dev/null
+++ b/tests/DiagnosticTools/res/values-bg/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="display_freeze_frame_info" msgid="1425573367248263107">"Показване на информацията за фиксиране на рамката"</string>
+</resources>
diff --git a/tests/DiagnosticTools/res/values-bn/strings.xml b/tests/DiagnosticTools/res/values-bn/strings.xml
new file mode 100644
index 0000000..56ce8c7
--- /dev/null
+++ b/tests/DiagnosticTools/res/values-bn/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="display_freeze_frame_info" msgid="1425573367248263107">"ফ্রিজ ফ্রেমের তথ্য দেখাও"</string>
+</resources>
diff --git a/tests/DiagnosticTools/res/values-bs/strings.xml b/tests/DiagnosticTools/res/values-bs/strings.xml
new file mode 100644
index 0000000..f049f27
--- /dev/null
+++ b/tests/DiagnosticTools/res/values-bs/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="display_freeze_frame_info" msgid="1425573367248263107">"Prikaz informacija o zamrznutom okviru"</string>
+</resources>
diff --git a/tests/DiagnosticTools/res/values-ca/strings.xml b/tests/DiagnosticTools/res/values-ca/strings.xml
new file mode 100644
index 0000000..9bcaa94
--- /dev/null
+++ b/tests/DiagnosticTools/res/values-ca/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="display_freeze_frame_info" msgid="1425573367248263107">"Mostra informació sobre el bloqueig del marc"</string>
+</resources>
diff --git a/tests/DiagnosticTools/res/values-cs/strings.xml b/tests/DiagnosticTools/res/values-cs/strings.xml
new file mode 100644
index 0000000..b769c80
--- /dev/null
+++ b/tests/DiagnosticTools/res/values-cs/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="display_freeze_frame_info" msgid="1425573367248263107">"Zobrazit informace o ukotveném rámci"</string>
+</resources>
diff --git a/tests/DiagnosticTools/res/values-da/strings.xml b/tests/DiagnosticTools/res/values-da/strings.xml
new file mode 100644
index 0000000..5472fde
--- /dev/null
+++ b/tests/DiagnosticTools/res/values-da/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="display_freeze_frame_info" msgid="1425573367248263107">"Vis oplysninger om stillbillede"</string>
+</resources>
diff --git a/tests/DiagnosticTools/res/values-de/strings.xml b/tests/DiagnosticTools/res/values-de/strings.xml
new file mode 100644
index 0000000..9aabaf5
--- /dev/null
+++ b/tests/DiagnosticTools/res/values-de/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="display_freeze_frame_info" msgid="1425573367248263107">"Informationen zum Freeze Frame anzeigen"</string>
+</resources>
diff --git a/tests/DiagnosticTools/res/values-el/strings.xml b/tests/DiagnosticTools/res/values-el/strings.xml
new file mode 100644
index 0000000..ec201ec
--- /dev/null
+++ b/tests/DiagnosticTools/res/values-el/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="display_freeze_frame_info" msgid="1425573367248263107">"Εμφάνιση πληροφοριών σταθεροποίησης πλαισίου"</string>
+</resources>
diff --git a/tests/DiagnosticTools/res/values-en-rAU/strings.xml b/tests/DiagnosticTools/res/values-en-rAU/strings.xml
new file mode 100644
index 0000000..bd4ccb9
--- /dev/null
+++ b/tests/DiagnosticTools/res/values-en-rAU/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="display_freeze_frame_info" msgid="1425573367248263107">"Display freeze frame Info"</string>
+</resources>
diff --git a/tests/DiagnosticTools/res/values-en-rCA/strings.xml b/tests/DiagnosticTools/res/values-en-rCA/strings.xml
new file mode 100644
index 0000000..bd4ccb9
--- /dev/null
+++ b/tests/DiagnosticTools/res/values-en-rCA/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="display_freeze_frame_info" msgid="1425573367248263107">"Display freeze frame Info"</string>
+</resources>
diff --git a/tests/DiagnosticTools/res/values-en-rGB/strings.xml b/tests/DiagnosticTools/res/values-en-rGB/strings.xml
new file mode 100644
index 0000000..bd4ccb9
--- /dev/null
+++ b/tests/DiagnosticTools/res/values-en-rGB/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="display_freeze_frame_info" msgid="1425573367248263107">"Display freeze frame Info"</string>
+</resources>
diff --git a/tests/DiagnosticTools/res/values-en-rIN/strings.xml b/tests/DiagnosticTools/res/values-en-rIN/strings.xml
new file mode 100644
index 0000000..bd4ccb9
--- /dev/null
+++ b/tests/DiagnosticTools/res/values-en-rIN/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="display_freeze_frame_info" msgid="1425573367248263107">"Display freeze frame Info"</string>
+</resources>
diff --git a/tests/DiagnosticTools/res/values-en-rXC/strings.xml b/tests/DiagnosticTools/res/values-en-rXC/strings.xml
new file mode 100644
index 0000000..6203c22
--- /dev/null
+++ b/tests/DiagnosticTools/res/values-en-rXC/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="display_freeze_frame_info" msgid="1425573367248263107">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‎‏‏‎‎‏‏‏‏‎‎‏‎‎‎‏‎‏‎‎‏‏‏‎‏‏‎‏‏‎‎‏‎‏‎‏‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‎‏‏‏‏‏‎‎‎‎‏‏‎Display Freeze Frame Info‎‏‎‎‏‎"</string>
+</resources>
diff --git a/tests/DiagnosticTools/res/values-es-rUS/strings.xml b/tests/DiagnosticTools/res/values-es-rUS/strings.xml
new file mode 100644
index 0000000..437c8df
--- /dev/null
+++ b/tests/DiagnosticTools/res/values-es-rUS/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="display_freeze_frame_info" msgid="1425573367248263107">"Mostrar información del fotograma bloqueado"</string>
+</resources>
diff --git a/tests/DiagnosticTools/res/values-es/strings.xml b/tests/DiagnosticTools/res/values-es/strings.xml
new file mode 100644
index 0000000..e1c0523
--- /dev/null
+++ b/tests/DiagnosticTools/res/values-es/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="display_freeze_frame_info" msgid="1425573367248263107">"Mostrar información del marco inmovilizado"</string>
+</resources>
diff --git a/tests/DiagnosticTools/res/values-et/strings.xml b/tests/DiagnosticTools/res/values-et/strings.xml
new file mode 100644
index 0000000..cdeb1c6
--- /dev/null
+++ b/tests/DiagnosticTools/res/values-et/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="display_freeze_frame_info" msgid="1425573367248263107">"Kuva hangunud kaadri teave"</string>
+</resources>
diff --git a/tests/DiagnosticTools/res/values-eu/strings.xml b/tests/DiagnosticTools/res/values-eu/strings.xml
new file mode 100644
index 0000000..5adc732
--- /dev/null
+++ b/tests/DiagnosticTools/res/values-eu/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="display_freeze_frame_info" msgid="1425573367248263107">"Bistaratu fotogramaren informazioa"</string>
+</resources>
diff --git a/tests/DiagnosticTools/res/values-fa/strings.xml b/tests/DiagnosticTools/res/values-fa/strings.xml
new file mode 100644
index 0000000..9785166
--- /dev/null
+++ b/tests/DiagnosticTools/res/values-fa/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="display_freeze_frame_info" msgid="1425573367248263107">"نمایش اطلاعات Freeze Frame"</string>
+</resources>
diff --git a/tests/DiagnosticTools/res/values-fi/strings.xml b/tests/DiagnosticTools/res/values-fi/strings.xml
new file mode 100644
index 0000000..e19f9f9
--- /dev/null
+++ b/tests/DiagnosticTools/res/values-fi/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="display_freeze_frame_info" msgid="1425573367248263107">"Näytä pysäytetyn kehyksen tiedot"</string>
+</resources>
diff --git a/tests/DiagnosticTools/res/values-fr-rCA/strings.xml b/tests/DiagnosticTools/res/values-fr-rCA/strings.xml
new file mode 100644
index 0000000..c67b69e
--- /dev/null
+++ b/tests/DiagnosticTools/res/values-fr-rCA/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="display_freeze_frame_info" msgid="1425573367248263107">"Afficher les renseignements de l\'arrêt sur image"</string>
+</resources>
diff --git a/tests/DiagnosticTools/res/values-fr/strings.xml b/tests/DiagnosticTools/res/values-fr/strings.xml
new file mode 100644
index 0000000..f7b39e9
--- /dev/null
+++ b/tests/DiagnosticTools/res/values-fr/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="display_freeze_frame_info" msgid="1425573367248263107">"Afficher les informations sur l\'image fixe"</string>
+</resources>
diff --git a/tests/DiagnosticTools/res/values-gl/strings.xml b/tests/DiagnosticTools/res/values-gl/strings.xml
new file mode 100644
index 0000000..6f9c271
--- /dev/null
+++ b/tests/DiagnosticTools/res/values-gl/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="display_freeze_frame_info" msgid="1425573367248263107">"Mostrar información de marco fixo"</string>
+</resources>
diff --git a/tests/DiagnosticTools/res/values-gu/strings.xml b/tests/DiagnosticTools/res/values-gu/strings.xml
new file mode 100644
index 0000000..bf3308f
--- /dev/null
+++ b/tests/DiagnosticTools/res/values-gu/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="display_freeze_frame_info" msgid="1425573367248263107">"ફ્રેમને સ્થિર કરવા માટેની માહિતી બતાવો"</string>
+</resources>
diff --git a/tests/DiagnosticTools/res/values-hi/strings.xml b/tests/DiagnosticTools/res/values-hi/strings.xml
new file mode 100644
index 0000000..0975220
--- /dev/null
+++ b/tests/DiagnosticTools/res/values-hi/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="display_freeze_frame_info" msgid="1425573367248263107">"फ़्रेम को फ़्रीज़ करने की जानकारी दिखाएं"</string>
+</resources>
diff --git a/tests/DiagnosticTools/res/values-hr/strings.xml b/tests/DiagnosticTools/res/values-hr/strings.xml
new file mode 100644
index 0000000..f049f27
--- /dev/null
+++ b/tests/DiagnosticTools/res/values-hr/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="display_freeze_frame_info" msgid="1425573367248263107">"Prikaz informacija o zamrznutom okviru"</string>
+</resources>
diff --git a/tests/DiagnosticTools/res/values-hu/strings.xml b/tests/DiagnosticTools/res/values-hu/strings.xml
new file mode 100644
index 0000000..3f6c0db
--- /dev/null
+++ b/tests/DiagnosticTools/res/values-hu/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="display_freeze_frame_info" msgid="1425573367248263107">"Freeze Frame-adat megjelenítése"</string>
+</resources>
diff --git a/tests/DiagnosticTools/res/values-hy/strings.xml b/tests/DiagnosticTools/res/values-hy/strings.xml
new file mode 100644
index 0000000..f121ac9
--- /dev/null
+++ b/tests/DiagnosticTools/res/values-hy/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="display_freeze_frame_info" msgid="1425573367248263107">"Ցուցադրել սառեցված կադրի տվյալները"</string>
+</resources>
diff --git a/tests/DiagnosticTools/res/values-in/strings.xml b/tests/DiagnosticTools/res/values-in/strings.xml
new file mode 100644
index 0000000..cf97c3c
--- /dev/null
+++ b/tests/DiagnosticTools/res/values-in/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="display_freeze_frame_info" msgid="1425573367248263107">"Tampilkan Info Freeze Frame"</string>
+</resources>
diff --git a/tests/DiagnosticTools/res/values-is/strings.xml b/tests/DiagnosticTools/res/values-is/strings.xml
new file mode 100644
index 0000000..e6b6ae9
--- /dev/null
+++ b/tests/DiagnosticTools/res/values-is/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="display_freeze_frame_info" msgid="1425573367248263107">"Birta upplýsingar um frystan ramma"</string>
+</resources>
diff --git a/tests/DiagnosticTools/res/values-it/strings.xml b/tests/DiagnosticTools/res/values-it/strings.xml
new file mode 100644
index 0000000..d35ff17
--- /dev/null
+++ b/tests/DiagnosticTools/res/values-it/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="display_freeze_frame_info" msgid="1425573367248263107">"Mostra informazioni sul fotogramma di blocco"</string>
+</resources>
diff --git a/tests/DiagnosticTools/res/values-iw/strings.xml b/tests/DiagnosticTools/res/values-iw/strings.xml
new file mode 100644
index 0000000..e0065b7
--- /dev/null
+++ b/tests/DiagnosticTools/res/values-iw/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="display_freeze_frame_info" msgid="1425573367248263107">"הצגת פרטים על הקפאת מסגרת"</string>
+</resources>
diff --git a/tests/DiagnosticTools/res/values-ja/strings.xml b/tests/DiagnosticTools/res/values-ja/strings.xml
new file mode 100644
index 0000000..4c75450
--- /dev/null
+++ b/tests/DiagnosticTools/res/values-ja/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="display_freeze_frame_info" msgid="1425573367248263107">"ディスプレイ フリーズ フレーム情報"</string>
+</resources>
diff --git a/tests/DiagnosticTools/res/values-ka/strings.xml b/tests/DiagnosticTools/res/values-ka/strings.xml
new file mode 100644
index 0000000..cf35524
--- /dev/null
+++ b/tests/DiagnosticTools/res/values-ka/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="display_freeze_frame_info" msgid="1425573367248263107">"სტოპ-კადრის ინფორმაციის ჩვენება"</string>
+</resources>
diff --git a/tests/DiagnosticTools/res/values-kk/strings.xml b/tests/DiagnosticTools/res/values-kk/strings.xml
new file mode 100644
index 0000000..04626ed
--- /dev/null
+++ b/tests/DiagnosticTools/res/values-kk/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="display_freeze_frame_info" msgid="1425573367248263107">"Стоп-кадр ақпаратын көрсету"</string>
+</resources>
diff --git a/tests/DiagnosticTools/res/values-km/strings.xml b/tests/DiagnosticTools/res/values-km/strings.xml
new file mode 100644
index 0000000..37f54f4
--- /dev/null
+++ b/tests/DiagnosticTools/res/values-km/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="display_freeze_frame_info" msgid="1425573367248263107">"បង្ហាញ​ព័ត៌មាន​អំពីស៊ុម​គាំង"</string>
+</resources>
diff --git a/tests/DiagnosticTools/res/values-kn/strings.xml b/tests/DiagnosticTools/res/values-kn/strings.xml
new file mode 100644
index 0000000..cf4e172
--- /dev/null
+++ b/tests/DiagnosticTools/res/values-kn/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="display_freeze_frame_info" msgid="1425573367248263107">"ಫ್ರೀಜ್ ಫ್ರೇಮ್ ಮಾಹಿತಿಯನ್ನು ಡಿಸ್‌ಪ್ಲೇ ಮಾಡಿ"</string>
+</resources>
diff --git a/tests/DiagnosticTools/res/values-ko/strings.xml b/tests/DiagnosticTools/res/values-ko/strings.xml
new file mode 100644
index 0000000..26fea08
--- /dev/null
+++ b/tests/DiagnosticTools/res/values-ko/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="display_freeze_frame_info" msgid="1425573367248263107">"정지 화면 정보 표시"</string>
+</resources>
diff --git a/tests/DiagnosticTools/res/values-ky/strings.xml b/tests/DiagnosticTools/res/values-ky/strings.xml
new file mode 100644
index 0000000..c74ecfc
--- /dev/null
+++ b/tests/DiagnosticTools/res/values-ky/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="display_freeze_frame_info" msgid="1425573367248263107">"Freeze Frame\'дин маалыматын чагылдыруу"</string>
+</resources>
diff --git a/tests/DiagnosticTools/res/values-lo/strings.xml b/tests/DiagnosticTools/res/values-lo/strings.xml
new file mode 100644
index 0000000..34a5849
--- /dev/null
+++ b/tests/DiagnosticTools/res/values-lo/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="display_freeze_frame_info" msgid="1425573367248263107">"ສະແດງຂໍ້ມູນເຟຣມຄ້າງ"</string>
+</resources>
diff --git a/tests/DiagnosticTools/res/values-lt/strings.xml b/tests/DiagnosticTools/res/values-lt/strings.xml
new file mode 100644
index 0000000..1f83787
--- /dev/null
+++ b/tests/DiagnosticTools/res/values-lt/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="display_freeze_frame_info" msgid="1425573367248263107">"Rodyti užfiksuoto kadro informaciją"</string>
+</resources>
diff --git a/tests/DiagnosticTools/res/values-lv/strings.xml b/tests/DiagnosticTools/res/values-lv/strings.xml
new file mode 100644
index 0000000..a835124
--- /dev/null
+++ b/tests/DiagnosticTools/res/values-lv/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="display_freeze_frame_info" msgid="1425573367248263107">"Rādīt fiksētā kadra informāciju"</string>
+</resources>
diff --git a/tests/DiagnosticTools/res/values-mk/strings.xml b/tests/DiagnosticTools/res/values-mk/strings.xml
new file mode 100644
index 0000000..804ab83
--- /dev/null
+++ b/tests/DiagnosticTools/res/values-mk/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="display_freeze_frame_info" msgid="1425573367248263107">"Информации за замрзната рамка на екранот"</string>
+</resources>
diff --git a/tests/DiagnosticTools/res/values-ml/strings.xml b/tests/DiagnosticTools/res/values-ml/strings.xml
new file mode 100644
index 0000000..dc612dd
--- /dev/null
+++ b/tests/DiagnosticTools/res/values-ml/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="display_freeze_frame_info" msgid="1425573367248263107">"ഫ്രീസ് ഫ്രെയിമിനെക്കുറിച്ചുള്ള വിവരം കാണിക്കുക"</string>
+</resources>
diff --git a/tests/DiagnosticTools/res/values-mn/strings.xml b/tests/DiagnosticTools/res/values-mn/strings.xml
new file mode 100644
index 0000000..ded1e9a
--- /dev/null
+++ b/tests/DiagnosticTools/res/values-mn/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="display_freeze_frame_info" msgid="1425573367248263107">"Freeze Frame-н мэдээлэл харуулах"</string>
+</resources>
diff --git a/tests/DiagnosticTools/res/values-mr/strings.xml b/tests/DiagnosticTools/res/values-mr/strings.xml
new file mode 100644
index 0000000..94eea43
--- /dev/null
+++ b/tests/DiagnosticTools/res/values-mr/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="display_freeze_frame_info" msgid="1425573367248263107">"फ्रीझ फ्रेम माहिती प्रदर्शित करा"</string>
+</resources>
diff --git a/tests/DiagnosticTools/res/values-ms/strings.xml b/tests/DiagnosticTools/res/values-ms/strings.xml
new file mode 100644
index 0000000..67d2ba0
--- /dev/null
+++ b/tests/DiagnosticTools/res/values-ms/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="display_freeze_frame_info" msgid="1425573367248263107">"Paparkan Maklumat Bingkai Pegun"</string>
+</resources>
diff --git a/tests/DiagnosticTools/res/values-my/strings.xml b/tests/DiagnosticTools/res/values-my/strings.xml
new file mode 100644
index 0000000..98c8d16
--- /dev/null
+++ b/tests/DiagnosticTools/res/values-my/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="display_freeze_frame_info" msgid="1425573367248263107">"ရပ်နေသည့်ဖရိမ် အချက်အလက်များကို ပြရန်"</string>
+</resources>
diff --git a/tests/DiagnosticTools/res/values-nb/strings.xml b/tests/DiagnosticTools/res/values-nb/strings.xml
new file mode 100644
index 0000000..51af902
--- /dev/null
+++ b/tests/DiagnosticTools/res/values-nb/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="display_freeze_frame_info" msgid="1425573367248263107">"Vis informasjon om øyeblikksbildet"</string>
+</resources>
diff --git a/tests/DiagnosticTools/res/values-ne/strings.xml b/tests/DiagnosticTools/res/values-ne/strings.xml
new file mode 100644
index 0000000..c227447
--- /dev/null
+++ b/tests/DiagnosticTools/res/values-ne/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="display_freeze_frame_info" msgid="1425573367248263107">"डिस्प्ले फ्रिज गर्ने फ्रेमसम्बन्धी जानकारी"</string>
+</resources>
diff --git a/tests/DiagnosticTools/res/values-nl/strings.xml b/tests/DiagnosticTools/res/values-nl/strings.xml
new file mode 100644
index 0000000..a808fd2
--- /dev/null
+++ b/tests/DiagnosticTools/res/values-nl/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="display_freeze_frame_info" msgid="1425573367248263107">"Informatie over freeze frame weergeven"</string>
+</resources>
diff --git a/tests/DiagnosticTools/res/values-or/strings.xml b/tests/DiagnosticTools/res/values-or/strings.xml
new file mode 100644
index 0000000..22df718
--- /dev/null
+++ b/tests/DiagnosticTools/res/values-or/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="display_freeze_frame_info" msgid="1425573367248263107">"ଫ୍ରିଜ୍ ଫ୍ରେମ୍ ସୂଚନା ପ୍ରଦର୍ଶନ କରନ୍ତୁ"</string>
+</resources>
diff --git a/tests/DiagnosticTools/res/values-pa/strings.xml b/tests/DiagnosticTools/res/values-pa/strings.xml
new file mode 100644
index 0000000..a2277c6
--- /dev/null
+++ b/tests/DiagnosticTools/res/values-pa/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="display_freeze_frame_info" msgid="1425573367248263107">"ਫ੍ਰੀਜ਼ ਫ੍ਰੇਮ ਜਾਣਕਾਰੀ ਦਿਖਾਓ"</string>
+</resources>
diff --git a/tests/DiagnosticTools/res/values-pl/strings.xml b/tests/DiagnosticTools/res/values-pl/strings.xml
new file mode 100644
index 0000000..4828319
--- /dev/null
+++ b/tests/DiagnosticTools/res/values-pl/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="display_freeze_frame_info" msgid="1425573367248263107">"Wyświetl informacje o zatrzymanej klatce"</string>
+</resources>
diff --git a/tests/DiagnosticTools/res/values-pt-rPT/strings.xml b/tests/DiagnosticTools/res/values-pt-rPT/strings.xml
new file mode 100644
index 0000000..d768d53
--- /dev/null
+++ b/tests/DiagnosticTools/res/values-pt-rPT/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="display_freeze_frame_info" msgid="1425573367248263107">"Apresentar informações de frame fixo"</string>
+</resources>
diff --git a/tests/DiagnosticTools/res/values-pt/strings.xml b/tests/DiagnosticTools/res/values-pt/strings.xml
new file mode 100644
index 0000000..968bfe1
--- /dev/null
+++ b/tests/DiagnosticTools/res/values-pt/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="display_freeze_frame_info" msgid="1425573367248263107">"Mostrar informação do congelamento do frame"</string>
+</resources>
diff --git a/tests/DiagnosticTools/res/values-ro/strings.xml b/tests/DiagnosticTools/res/values-ro/strings.xml
new file mode 100644
index 0000000..d06e299
--- /dev/null
+++ b/tests/DiagnosticTools/res/values-ro/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="display_freeze_frame_info" msgid="1425573367248263107">"Afișați informații despre cadrul blocat"</string>
+</resources>
diff --git a/tests/DiagnosticTools/res/values-ru/strings.xml b/tests/DiagnosticTools/res/values-ru/strings.xml
new file mode 100644
index 0000000..482ee1b
--- /dev/null
+++ b/tests/DiagnosticTools/res/values-ru/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="display_freeze_frame_info" msgid="1425573367248263107">"Показывать информацию на стоп-кадре"</string>
+</resources>
diff --git a/tests/DiagnosticTools/res/values-si/strings.xml b/tests/DiagnosticTools/res/values-si/strings.xml
new file mode 100644
index 0000000..3304298
--- /dev/null
+++ b/tests/DiagnosticTools/res/values-si/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="display_freeze_frame_info" msgid="1425573367248263107">"ඇවුරුම් රාමු තතු සංදර්ශනය"</string>
+</resources>
diff --git a/tests/DiagnosticTools/res/values-sk/strings.xml b/tests/DiagnosticTools/res/values-sk/strings.xml
new file mode 100644
index 0000000..967abff
--- /dev/null
+++ b/tests/DiagnosticTools/res/values-sk/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="display_freeze_frame_info" msgid="1425573367248263107">"Zobraziť informácie o ukotvenom rámci"</string>
+</resources>
diff --git a/tests/DiagnosticTools/res/values-sl/strings.xml b/tests/DiagnosticTools/res/values-sl/strings.xml
new file mode 100644
index 0000000..c856e68
--- /dev/null
+++ b/tests/DiagnosticTools/res/values-sl/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="display_freeze_frame_info" msgid="1425573367248263107">"Prikaz podatkov o zamrznjenem okviru"</string>
+</resources>
diff --git a/tests/DiagnosticTools/res/values-sq/strings.xml b/tests/DiagnosticTools/res/values-sq/strings.xml
new file mode 100644
index 0000000..a129f8d
--- /dev/null
+++ b/tests/DiagnosticTools/res/values-sq/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="display_freeze_frame_info" msgid="1425573367248263107">"Shfaq informacionin e ngrirjes së kuadrit"</string>
+</resources>
diff --git a/tests/DiagnosticTools/res/values-sr/strings.xml b/tests/DiagnosticTools/res/values-sr/strings.xml
new file mode 100644
index 0000000..abae266
--- /dev/null
+++ b/tests/DiagnosticTools/res/values-sr/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="display_freeze_frame_info" msgid="1425573367248263107">"Прикажи информације о замрзнутом оквиру"</string>
+</resources>
diff --git a/tests/DiagnosticTools/res/values-sv/strings.xml b/tests/DiagnosticTools/res/values-sv/strings.xml
new file mode 100644
index 0000000..ab70663
--- /dev/null
+++ b/tests/DiagnosticTools/res/values-sv/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="display_freeze_frame_info" msgid="1425573367248263107">"Visa information om fryst bildruta"</string>
+</resources>
diff --git a/tests/DiagnosticTools/res/values-sw/strings.xml b/tests/DiagnosticTools/res/values-sw/strings.xml
new file mode 100644
index 0000000..c3f839e
--- /dev/null
+++ b/tests/DiagnosticTools/res/values-sw/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="display_freeze_frame_info" msgid="1425573367248263107">"Onyesha Maelezo kuhusu Fremu ya Kufanya Skrini Isisonge"</string>
+</resources>
diff --git a/tests/DiagnosticTools/res/values-ta/strings.xml b/tests/DiagnosticTools/res/values-ta/strings.xml
new file mode 100644
index 0000000..09e74cc
--- /dev/null
+++ b/tests/DiagnosticTools/res/values-ta/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="display_freeze_frame_info" msgid="1425573367248263107">"ஃப்ரீஸ் ஃப்ரேம் தகவலைக் காட்டு"</string>
+</resources>
diff --git a/tests/DiagnosticTools/res/values-te/strings.xml b/tests/DiagnosticTools/res/values-te/strings.xml
new file mode 100644
index 0000000..dfad13b
--- /dev/null
+++ b/tests/DiagnosticTools/res/values-te/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="display_freeze_frame_info" msgid="1425573367248263107">"స్తంభించిన ఫ్రేమ్ సమాచారాన్ని ప్రదర్శించు"</string>
+</resources>
diff --git a/tests/DiagnosticTools/res/values-th/strings.xml b/tests/DiagnosticTools/res/values-th/strings.xml
new file mode 100644
index 0000000..098229f
--- /dev/null
+++ b/tests/DiagnosticTools/res/values-th/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="display_freeze_frame_info" msgid="1425573367248263107">"แสดงข้อมูลเฟรมค้าง"</string>
+</resources>
diff --git a/tests/DiagnosticTools/res/values-tl/strings.xml b/tests/DiagnosticTools/res/values-tl/strings.xml
new file mode 100644
index 0000000..561b1e8
--- /dev/null
+++ b/tests/DiagnosticTools/res/values-tl/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="display_freeze_frame_info" msgid="1425573367248263107">"Ipakita ang Impormasyon ng Freeze Frame"</string>
+</resources>
diff --git a/tests/DiagnosticTools/res/values-tr/strings.xml b/tests/DiagnosticTools/res/values-tr/strings.xml
new file mode 100644
index 0000000..e7c5340
--- /dev/null
+++ b/tests/DiagnosticTools/res/values-tr/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="display_freeze_frame_info" msgid="1425573367248263107">"Kare Dondurma Bilgilerini Göster"</string>
+</resources>
diff --git a/tests/DiagnosticTools/res/values-uk/strings.xml b/tests/DiagnosticTools/res/values-uk/strings.xml
new file mode 100644
index 0000000..073f940
--- /dev/null
+++ b/tests/DiagnosticTools/res/values-uk/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="display_freeze_frame_info" msgid="1425573367248263107">"Інформація про показ стоп-кадру"</string>
+</resources>
diff --git a/tests/DiagnosticTools/res/values-ur/strings.xml b/tests/DiagnosticTools/res/values-ur/strings.xml
new file mode 100644
index 0000000..07254b9
--- /dev/null
+++ b/tests/DiagnosticTools/res/values-ur/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="display_freeze_frame_info" msgid="1425573367248263107">"فریز کو فریم کرنے کی معلومات دکھائیں"</string>
+</resources>
diff --git a/tests/DiagnosticTools/res/values-uz/strings.xml b/tests/DiagnosticTools/res/values-uz/strings.xml
new file mode 100644
index 0000000..40b606e
--- /dev/null
+++ b/tests/DiagnosticTools/res/values-uz/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="display_freeze_frame_info" msgid="1425573367248263107">"Stop-kadrni chiqarish axboroti"</string>
+</resources>
diff --git a/tests/DiagnosticTools/res/values-vi/strings.xml b/tests/DiagnosticTools/res/values-vi/strings.xml
new file mode 100644
index 0000000..a2b79b0
--- /dev/null
+++ b/tests/DiagnosticTools/res/values-vi/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="display_freeze_frame_info" msgid="1425573367248263107">"Hiển thị thông tin về khung cố định"</string>
+</resources>
diff --git a/tests/DiagnosticTools/res/values-zh-rCN/strings.xml b/tests/DiagnosticTools/res/values-zh-rCN/strings.xml
new file mode 100644
index 0000000..f7bdfea
--- /dev/null
+++ b/tests/DiagnosticTools/res/values-zh-rCN/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="display_freeze_frame_info" msgid="1425573367248263107">"显示冻结帧信息"</string>
+</resources>
diff --git a/tests/DiagnosticTools/res/values-zh-rHK/strings.xml b/tests/DiagnosticTools/res/values-zh-rHK/strings.xml
new file mode 100644
index 0000000..4733dbb
--- /dev/null
+++ b/tests/DiagnosticTools/res/values-zh-rHK/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="display_freeze_frame_info" msgid="1425573367248263107">"顯示頁框凍結資訊"</string>
+</resources>
diff --git a/tests/DiagnosticTools/res/values-zh-rTW/strings.xml b/tests/DiagnosticTools/res/values-zh-rTW/strings.xml
new file mode 100644
index 0000000..103d099
--- /dev/null
+++ b/tests/DiagnosticTools/res/values-zh-rTW/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="display_freeze_frame_info" msgid="1425573367248263107">"顯示凍結頁框資訊"</string>
+</resources>
diff --git a/tests/DiagnosticTools/res/values-zu/strings.xml b/tests/DiagnosticTools/res/values-zu/strings.xml
new file mode 100644
index 0000000..94ff953
--- /dev/null
+++ b/tests/DiagnosticTools/res/values-zu/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="display_freeze_frame_info" msgid="1425573367248263107">"Bonisa ulwazi lokumisa uzimele"</string>
+</resources>
diff --git a/tests/DiagnosticTools/res/values/strings.xml b/tests/DiagnosticTools/res/values/strings.xml
new file mode 100644
index 0000000..fa8fb51
--- /dev/null
+++ b/tests/DiagnosticTools/res/values/strings.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+  -->
+
+<resources>
+    <string name="display_freeze_frame_info">Display Freeze Frame Info</string>
+</resources>
diff --git a/tests/DiagnosticTools/res/values/styles.xml b/tests/DiagnosticTools/res/values/styles.xml
new file mode 100644
index 0000000..66e44ff
--- /dev/null
+++ b/tests/DiagnosticTools/res/values/styles.xml
@@ -0,0 +1,26 @@
+<!--
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+  -->
+
+<resources>
+    <style name="AppTheme" parent="Theme.AppCompat.NoActionBar">
+        <item name="android:colorPrimary">#008577</item>
+        <item name="android:colorPrimaryDark">#00574B</item>
+        <item name="android:colorAccent">#D81B60</item>
+        <item name="android:windowNoTitle">true</item>
+        <item name="android:windowActionBar">false</item>
+    </style>
+
+</resources>
diff --git a/tests/DiagnosticTools/src/com/google/android/car/diagnostictools/DTC.java b/tests/DiagnosticTools/src/com/google/android/car/diagnostictools/DTC.java
new file mode 100644
index 0000000..ca75157
--- /dev/null
+++ b/tests/DiagnosticTools/src/com/google/android/car/diagnostictools/DTC.java
@@ -0,0 +1,183 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.car.diagnostictools;
+
+import android.content.Context;
+import android.os.Parcel;
+import android.text.Html;
+import android.text.Spanned;
+import android.text.SpannedString;
+
+import com.google.android.car.diagnostictools.utils.DTCMetadata;
+import com.google.android.car.diagnostictools.utils.MetadataProcessing;
+import com.google.android.car.diagnostictools.utils.SelectableRowModel;
+
+import java.util.Random;
+
+/**
+ * Model which wraps DTC code and metadata information for display through DTCAdapter. Extends
+ * SelectableRowModel to allow for selection and use with SelectableAdapter
+ */
+public class DTC extends SelectableRowModel implements android.os.Parcelable {
+
+    public static final Creator<DTC> CREATOR =
+            new Creator<DTC>() {
+                @Override
+                public DTC createFromParcel(Parcel source) {
+                    return new DTC(source);
+                }
+
+                @Override
+                public DTC[] newArray(int size) {
+                    return new DTC[size];
+                }
+            };
+    private static final String TAG = "DTC";
+    private static Random sRandomGen;
+    private String mCode;
+    private String mDescription;
+    private long mTimestamp;
+    private Spanned mLongDescription;
+    private String mStringLongDescription; // Used for Parcelable compatibility
+
+    /**
+     * Full constructor for DTC.
+     *
+     * @param code DTC code associated with the DTC
+     * @param description Short description associated with the DTC
+     * @param timestamp Timestamp associated with the DTC
+     * @param longDescription Long descriptions associated with the DTC
+     * @param stringLongDescription String version of longDescription for Parcelable support
+     */
+    private DTC(
+            String code,
+            String description,
+            long timestamp,
+            Spanned longDescription,
+            String stringLongDescription) {
+        this.mCode = code;
+        this.mDescription = description;
+        this.mTimestamp = timestamp;
+        this.mLongDescription = longDescription;
+        this.mStringLongDescription = stringLongDescription;
+    }
+
+    /**
+     * Paired down constructor that utilizes DTC metadata that is preloaded.
+     *
+     * @param code DTC code associated with the DTC
+     * @param timestamp Timestamp associated with the DTC
+     * @param context Context from which this is called. Required to allow MetadataProcessing to be
+     *     loaded if the singleton has not been instantiated
+     */
+    DTC(String code, long timestamp, Context context) {
+        this.mCode = code;
+        this.mTimestamp = timestamp;
+        DTCMetadata dtcMetadata = getDTCMetadata(code, context);
+        if (dtcMetadata != null) {
+            this.mDescription = dtcMetadata.getShortDescription();
+            this.mLongDescription = dtcMetadata.getSpannedLongDescription();
+            this.mStringLongDescription = dtcMetadata.getStringLongDescription();
+        } else {
+            this.mDescription = "No Description Available";
+            this.mLongDescription = SpannedString.valueOf("No Details Available");
+            this.mStringLongDescription = "No Details Available";
+        }
+    }
+
+    protected DTC(Parcel in) {
+        this.mCode = in.readString();
+        this.mDescription = in.readString();
+        this.mTimestamp = in.readLong();
+        this.mStringLongDescription = in.readString();
+        this.mLongDescription = Html.fromHtml(this.mStringLongDescription, 0);
+    }
+
+    public String getCode() {
+        return mCode;
+    }
+
+    public String getDescription() {
+        return mDescription;
+    }
+
+    public long getTimestamp() {
+        return mTimestamp;
+    }
+
+    //Delete when DTCs are implemented
+    public void setTimestamp(long timestamp) {
+        mTimestamp = timestamp;
+    }
+
+    public Spanned getLongDescription() {
+        return mLongDescription;
+    }
+
+
+    /**
+     * Create sample DTC using MetadataProcessing for values. Some numbers will not return actual
+     * values and will be replaced with placeholder information that would be presented if metadata
+     * wasn't found.
+     *
+     * @param number Used to generate the DTC code which will be "P(008+number)"
+     * @param context Context from which this is called. Required to allow MetadataProcessing to be
+     *     loaded if the singleton has not been instantiated
+     * @return New sample DTC based on number and metadata
+     */
+    static DTC createSampleDTC(int number, Context context) {
+        String code = String.format("P%04d", 8 + number);
+        if (sRandomGen == null) {
+            sRandomGen = new Random();
+        }
+        long timestamp = sRandomGen.nextLong();
+        return new DTC(code, timestamp, context);
+    }
+
+    private DTCMetadata getDTCMetadata(String code, Context context) {
+        return MetadataProcessing.getInstance(context).getDTCMetadata(code);
+    }
+
+    /**
+     * Implement method of SelectableRowModel. One element is selected as the DTC is the base child
+     *
+     * @return Returns 1 (as DTC is the based element)
+     */
+    @Override
+    public int numSelected() {
+        return 1;
+    }
+
+    /** TODO: Calls VHAL DTC delete method to allow this DTC to be cleared */
+    @Override
+    public void delete() {
+        // TODO clear DTC
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeString(this.mCode);
+        dest.writeString(this.mDescription);
+        dest.writeLong(this.mTimestamp);
+        dest.writeString(this.mStringLongDescription);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+}
diff --git a/tests/DiagnosticTools/src/com/google/android/car/diagnostictools/DTCAdapter.java b/tests/DiagnosticTools/src/com/google/android/car/diagnostictools/DTCAdapter.java
new file mode 100644
index 0000000..f837666
--- /dev/null
+++ b/tests/DiagnosticTools/src/com/google/android/car/diagnostictools/DTCAdapter.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.car.diagnostictools;
+
+import android.content.Context;
+import android.content.Intent;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+
+import androidx.annotation.NonNull;
+
+import com.google.android.car.diagnostictools.utils.SelectableAdapter;
+
+import java.util.List;
+
+/** Adapter for RecyclerView in DTCListActivity which displays DTCs */
+public class DTCAdapter extends SelectableAdapter<DTC, RowViewHolder> {
+
+    private List<DTC> mDtcs;
+    private Context mContext;
+
+    DTCAdapter(List<DTC> inputDTCs, Context context) {
+        mDtcs = inputDTCs;
+        mContext = context;
+    }
+
+    @NonNull
+    @Override
+    public RowViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
+        LayoutInflater inflater = LayoutInflater.from(parent.getContext());
+
+        View v = inflater.inflate(R.layout.row_layout, parent, false);
+
+        return new RowViewHolder(v, true, false, false);
+    }
+
+    @Override
+    public void onBindViewHolder(@NonNull RowViewHolder holder, int position) {
+        final DTC refDTC = mDtcs.get(position);
+        final RowViewHolder finalHolder = holder;
+        holder.setFields(refDTC.getCode(), refDTC.getDescription(), "", false);
+        refDTC.setColor(finalHolder);
+        holder.layout.setOnClickListener(
+                new OnClickListener() {
+                    @Override
+                    public void onClick(View view) {
+                        if (hasSelected()) {
+                            toggleSelect(refDTC);
+                            refDTC.setColor(finalHolder);
+                        } else {
+                            Intent intent = new Intent(mContext, DTCDetailActivity.class);
+                            intent.putExtra("dtc", refDTC);
+                            mContext.startActivity(intent);
+                        }
+                    }
+                });
+        holder.layout.setOnLongClickListener(
+                new View.OnLongClickListener() {
+                    @Override
+                    public boolean onLongClick(View view) {
+                        toggleSelect(refDTC);
+                        refDTC.setColor(finalHolder);
+                        return true;
+                    }
+                });
+    }
+
+    @Override
+    public int getItemCount() {
+        return mDtcs.size();
+    }
+
+    /**
+     * Overrides method getBaseList of SelectableAdapter to allow selection of elements
+     *
+     * @return base list of DTCs
+     */
+    @Override
+    protected List<DTC> getBaseList() {
+        return mDtcs;
+    }
+}
diff --git a/tests/DiagnosticTools/src/com/google/android/car/diagnostictools/DTCDetailActivity.java b/tests/DiagnosticTools/src/com/google/android/car/diagnostictools/DTCDetailActivity.java
new file mode 100644
index 0000000..7ea03fc
--- /dev/null
+++ b/tests/DiagnosticTools/src/com/google/android/car/diagnostictools/DTCDetailActivity.java
@@ -0,0 +1,218 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.car.diagnostictools;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.car.Car;
+import android.car.diagnostic.CarDiagnosticEvent;
+import android.car.diagnostic.CarDiagnosticManager;
+import android.os.Bundle;
+import android.os.Handler;
+import android.util.Log;
+import android.view.View;
+import android.widget.Button;
+import android.widget.ProgressBar;
+import android.widget.TextView;
+import android.widget.Toolbar;
+
+import androidx.recyclerview.widget.DividerItemDecoration;
+import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.google.android.car.diagnostictools.utils.MetadataProcessing;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Displays detailed information about a specific DTC. Opened through clicking a DTC in
+ * DTCListActivity
+ */
+public class DTCDetailActivity extends Activity {
+
+    private static final String TAG = "DTCDetailActivity";
+    private final Handler mHandler = new Handler();
+    private Toolbar mDTCTitle;
+    private TextView mDTCDetails;
+    private DTC mDTC;
+    private ProgressBar mFreezeFrameLoading;
+    private TextView mFreezeFrameTitle;
+    private Button mFreezeFrameClear;
+    private RecyclerView mFreezeFrameData;
+    private CarDiagnosticManager mCarDiagnosticManager;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_dtc_details);
+
+        mDTCTitle = findViewById(R.id.toolbar);
+        mDTCTitle.setNavigationIcon(R.drawable.ic_arrow_back_black_24dp);
+        mDTCTitle.setNavigationOnClickListener(
+                view -> {
+                    finish();
+                    mHandler.removeCallbacksAndMessages(null);
+                });
+        mDTCDetails = findViewById(R.id.dtc_details);
+        mFreezeFrameLoading = findViewById(R.id.freeze_frame_loading);
+        mFreezeFrameTitle = findViewById(R.id.freeze_frame_title);
+        mFreezeFrameClear = findViewById(R.id.freeze_frame_clear);
+        mFreezeFrameData = findViewById(R.id.freeze_frame_data);
+
+        hideFreezeFrameFields();
+
+        // Set up RecyclerView
+        mFreezeFrameData.setHasFixedSize(false);
+        mFreezeFrameData.setLayoutManager(new LinearLayoutManager(DTCDetailActivity.this));
+        final List<LiveDataAdapter.SensorDataWrapper> input = new ArrayList<>();
+        // Add test data
+        for (int i = 0; i < 100; i++) {
+            input.add(new LiveDataAdapter.SensorDataWrapper("Test " + i, "%", i));
+        }
+        LiveDataAdapter adapter = new LiveDataAdapter(input);
+        mFreezeFrameData.setAdapter(adapter);
+        mFreezeFrameData.addItemDecoration(
+                new DividerItemDecoration(
+                        mFreezeFrameData.getContext(), DividerItemDecoration.VERTICAL));
+
+        loadingFreezeFrame();
+
+        mCarDiagnosticManager =
+                (CarDiagnosticManager) Car.createCar(this).getCarManager(Car.DIAGNOSTIC_SERVICE);
+
+        // Runnable function that checks to see if there is a Freeze Frame available or not.
+        // Repeats
+        // until there is a Freeze Frame available
+        mHandler.postDelayed(
+                new Runnable() {
+                    @Override
+                    public void run() {
+                        long[] timestamps = mCarDiagnosticManager.getFreezeFrameTimestamps();
+                        Log.e(TAG, "onCreate: Number of timestamps" + timestamps.length);
+                        if (timestamps.length > 0) {
+                            CarDiagnosticEvent freezeFrame =
+                                    mCarDiagnosticManager.getFreezeFrame(
+                                            timestamps[timestamps.length - 1]);
+                            adapter.update(
+                                    LiveDataActivity.processSensorInfoIntoWrapper(
+                                            freezeFrame,
+                                            MetadataProcessing.getInstance(
+                                                    DTCDetailActivity.this)));
+                            loadedFreezeFrame();
+                            mDTC.setTimestamp(timestamps[timestamps.length - 1]);
+                        } else {
+                            hideFreezeFrameFields();
+                            mHandler.postDelayed(this, 2000);
+                        }
+                    }
+                },
+                0);
+
+        getIncomingIntent();
+    }
+
+    /** Handle incoming intent to extract extras. */
+    private void getIncomingIntent() {
+        Log.d(TAG, "getIncomingIntent: extras: " + getIntent().toString());
+        if (getIntent().hasExtra("dtc")) {
+            mDTC = getIntent().getParcelableExtra("dtc");
+            setUpDetailPage();
+        }
+    }
+
+    /** Assuming dtc has been set to a DTC. */
+    private void setUpDetailPage() {
+        if (mDTC != null) {
+            mDTCTitle.setTitle(mDTC.getCode() + ": " + mDTC.getDescription());
+            mDTCDetails.setText(mDTC.getLongDescription());
+        } else {
+            mDTCTitle.setTitle("No DTC input");
+            mDTCDetails.setText("No DTC long description");
+        }
+    }
+
+    /** Hide all Freeze Frame associated elements. */
+    private void hideFreezeFrameFields() {
+        mFreezeFrameData.setVisibility(View.INVISIBLE);
+        mFreezeFrameClear.setVisibility(View.INVISIBLE);
+        mFreezeFrameTitle.setVisibility(View.INVISIBLE);
+        mFreezeFrameLoading.setVisibility(View.INVISIBLE);
+    }
+
+    /** Hide most Freeze Frame associated elements and tell user that there isn't one available. */
+    private void noFreezeFrameAvailable() {
+        hideFreezeFrameFields();
+        mFreezeFrameTitle.setVisibility(View.VISIBLE);
+        mFreezeFrameTitle.setText(
+                "No Freeze Frame Data Available Right Now. Data will appear if becomes available");
+    }
+
+    /** Indicate to the user that a freeze frame is being loaded with a spinning progress bar */
+    private void loadingFreezeFrame() {
+        mFreezeFrameTitle.setVisibility(View.VISIBLE);
+        mFreezeFrameTitle.setText("Freeze Frame Loading...");
+        mFreezeFrameLoading.setVisibility(View.VISIBLE);
+    }
+
+    /**
+     * Displays freeze frame and conditionally displays button to clear it based on if functionality
+     * is supported
+     */
+    private void loadedFreezeFrame() {
+        mFreezeFrameLoading.setVisibility(View.INVISIBLE);
+        if (mCarDiagnosticManager.isClearFreezeFramesSupported()
+                && mCarDiagnosticManager.isSelectiveClearFreezeFramesSupported()) {
+            mFreezeFrameClear.setVisibility(View.VISIBLE);
+        }
+        mFreezeFrameData.setVisibility(View.VISIBLE);
+        mFreezeFrameTitle.setText("Freeze Frame");
+    }
+
+    /**
+     * Handles button press of the clear Freeze Frame button. Confirms that the user would like to
+     * do this action and then clears Frames with Manager methods
+     *
+     * @param v View that triggered the function
+     */
+    public void clearFreezeFrameButtonPress(View v) {
+        new AlertDialog.Builder(this)
+                .setTitle("Confirm Freeze Frame Clear")
+                .setMessage(
+                        String.format(
+                                "Do you really want to clear the freeze frame for DTC %s?",
+                                mDTC.getCode()))
+                .setIcon(android.R.drawable.ic_dialog_alert)
+                .setPositiveButton(
+                        android.R.string.yes,
+                        (dialog, whichButton) -> {
+                            mCarDiagnosticManager.clearFreezeFrames(mDTC.getTimestamp());
+                            hideFreezeFrameFields();
+                        })
+                .setNegativeButton(android.R.string.no, null)
+                .show();
+    }
+
+    /** Removes all callbacks from mHandler when DTCDetailActivity is destroyed */
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+        if (mHandler != null) {
+            mHandler.removeCallbacksAndMessages(null);
+        }
+    }
+}
diff --git a/tests/DiagnosticTools/src/com/google/android/car/diagnostictools/DTCListActivity.java b/tests/DiagnosticTools/src/com/google/android/car/diagnostictools/DTCListActivity.java
new file mode 100644
index 0000000..4d7220b
--- /dev/null
+++ b/tests/DiagnosticTools/src/com/google/android/car/diagnostictools/DTCListActivity.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.car.diagnostictools;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.View;
+import android.widget.Toolbar;
+
+import androidx.recyclerview.widget.DividerItemDecoration;
+import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+
+import java.util.List;
+
+/** Displays a list of DTCs associated with an ECU */
+public class DTCListActivity extends Activity {
+
+    private static final String TAG = "DTCListActivity";
+    private String mEcuName;
+    private List<DTC> mDtcs;
+    private Toolbar mEcuTitle;
+    private RecyclerView mDTCList;
+    private DTCAdapter mAdapter;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_dtc_list);
+        mEcuTitle = findViewById(R.id.toolbar);
+        mEcuTitle.setNavigationIcon(R.drawable.ic_arrow_back_black_24dp);
+        mEcuTitle.setNavigationOnClickListener(view -> finish());
+
+        mDTCList = findViewById(R.id.dtc_list);
+        mDTCList.setHasFixedSize(true);
+        mDTCList.setLayoutManager(new LinearLayoutManager(this));
+        getIncomingIntent();
+    }
+
+    /** Handle incoming intent and extras. Extract ECU name and DTC list */
+    private void getIncomingIntent() {
+        Log.d(TAG, "getIncomingIntent: extras: " + getIntent().toString());
+        if (getIntent().hasExtra("name")) {
+            mEcuName = getIntent().getStringExtra("name");
+            mEcuTitle.setTitle(mEcuName);
+        }
+        if (getIntent().hasExtra("dtcs")) {
+            mDtcs = getIntent().getParcelableArrayListExtra("dtcs");
+            mAdapter = new DTCAdapter(mDtcs, this);
+            mDTCList.setAdapter(mAdapter);
+            mDTCList.addItemDecoration(
+                    new DividerItemDecoration(
+                            mDTCList.getContext(), DividerItemDecoration.VERTICAL));
+        }
+    }
+
+    /**
+     * Handle clicks from the ActionButton. Confirms that the user wants to clear selected DTCs and
+     * then deletes them on confirmation.
+     */
+    public void onClickActionButton(View view) {
+        new AlertDialog.Builder(this)
+                .setTitle("Clear DTCs")
+                .setMessage(
+                        String.format(
+                                "Do you really want to clear %d mDtcs?", mAdapter.numSelected()))
+                .setIcon(android.R.drawable.ic_dialog_alert)
+                .setPositiveButton(
+                        android.R.string.yes, (dialog, whichButton) -> mAdapter.deleteSelected())
+                .setNegativeButton(android.R.string.no, null)
+                .show();
+    }
+}
diff --git a/tests/DiagnosticTools/src/com/google/android/car/diagnostictools/ECU.java b/tests/DiagnosticTools/src/com/google/android/car/diagnostictools/ECU.java
new file mode 100644
index 0000000..ca8fe3c
--- /dev/null
+++ b/tests/DiagnosticTools/src/com/google/android/car/diagnostictools/ECU.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.car.diagnostictools;
+
+import android.content.Context;
+
+import com.google.android.car.diagnostictools.utils.MetadataProcessing;
+import com.google.android.car.diagnostictools.utils.SelectableRowModel;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Model which wraps ECU data (address, name, and associated DTCs) and extends SelectableRowModel to
+ * enable the selection of ECU elements.
+ */
+public class ECU extends SelectableRowModel {
+
+    private String mAddress;
+    private String mName;
+    /**
+     * MUST be locked when integrating DTC properties to prevent issues with parallel adding and
+     * deleting.
+     */
+    private List<DTC> mDtcs;
+
+    /**
+     * Full constructor that creates an ECU model with its address, name, and list of associated
+     * DTCs.
+     *
+     * @param address Address for ECU
+     * @param name Human readable name for ECU
+     * @param dtcs List of DTCs that are raised by the ECU
+     */
+    private ECU(String address, String name, List<DTC> dtcs) {
+        mAddress = address;
+        mName = name;
+        this.mDtcs = dtcs;
+    }
+
+    /**
+     * Paired down constructor that creates an ECU based on its address, list of associated DTCs,
+     * and metadata.
+     *
+     * @param address Address for ECU
+     * @param dtcs List of DTCs that are raised by the ECU
+     * @param context Context from which this is called. Required to allow MetadataProcessing to be
+     *     loaded if the singleton has not been instantiated
+     */
+    public ECU(String address, List<DTC> dtcs, Context context) {
+        mAddress = address;
+        this.mDtcs = dtcs;
+        String metadata = MetadataProcessing.getInstance(context).getECUMetadata(address);
+        if (metadata != null) {
+            this.mName = metadata;
+        } else {
+            this.mName = "No Name Available";
+        }
+    }
+
+    public String getAddress() {
+        return mAddress;
+    }
+
+    public String getName() {
+        return mName;
+    }
+
+    public List<DTC> getDtcs() {
+        return mDtcs;
+    }
+
+    /**
+     * Create sample DTC using MetadataProcessing for values. Some numbers will not return actual
+     * values and will be replaced with placeholder information that would be presented if metadata
+     * wasn't found.
+     *
+     * @param number Used to generate the DTC code which will be "P(008+number)"
+     * @param context Context from which this is called. Required to allow MetadataProcessing to be
+     *     loaded if the singleton has not been instantiated
+     * @return New sample DTC based on number and metadata
+     */
+    static ECU createSampleECU(int number, Context context) {
+        List<DTC> dtcs = new ArrayList<>();
+        String address = (number + 123) + "";
+
+        ECU rtrECU = new ECU(address, dtcs, context);
+
+        for (int i = 0; i < number; i++) {
+            dtcs.add(DTC.createSampleDTC(i, context));
+        }
+        return rtrECU;
+    }
+
+    /**
+     * Implement method of SelectableRowModel. The number of elements selected is based on the
+     * number of elements in its children
+     *
+     * @return Returns the number of DTCs that are its children (as DTC is the based element)
+     */
+    @Override
+    public int numSelected() {
+        int count = 0;
+        for (DTC dtc : mDtcs) {
+            count += dtc.numSelected();
+        }
+        return count;
+    }
+
+    /** Runs the implemented delete method in the DTC model to delete records on a VHAL level */
+    @Override
+    public void delete() {
+        for (DTC dtc : mDtcs) {
+            dtc.delete();
+        }
+    }
+}
diff --git a/tests/DiagnosticTools/src/com/google/android/car/diagnostictools/ECUAdapter.java b/tests/DiagnosticTools/src/com/google/android/car/diagnostictools/ECUAdapter.java
new file mode 100644
index 0000000..e8eda50
--- /dev/null
+++ b/tests/DiagnosticTools/src/com/google/android/car/diagnostictools/ECUAdapter.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.car.diagnostictools;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.Parcelable;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.annotation.NonNull;
+
+import com.google.android.car.diagnostictools.utils.SelectableAdapter;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/** Adapter for RecyclerView in ECUListActivity which displays ECUs */
+public class ECUAdapter extends SelectableAdapter<ECU, RowViewHolder> {
+
+    private List<ECU> mEcuOrderedList;
+    private Context mContext;
+
+    public ECUAdapter(List<ECU> ecusIn, Context context) {
+        mEcuOrderedList = ecusIn;
+        mContext = context;
+        mEcuOrderedList.sort((ecu, t1) -> t1.getDtcs().size() - ecu.getDtcs().size());
+    }
+
+    @NonNull
+    @Override
+    public RowViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
+        LayoutInflater inflater = LayoutInflater.from(parent.getContext());
+
+        View v = inflater.inflate(R.layout.row_layout, parent, false);
+
+        return new RowViewHolder(v, true, false, true);
+    }
+
+    @Override
+    public void onBindViewHolder(@NonNull RowViewHolder holder, final int position) {
+        final ECU refECU = mEcuOrderedList.get(position);
+        final RowViewHolder finalHolder = holder;
+        final String name = refECU.getName();
+        refECU.setColor(finalHolder);
+        holder.setFields(
+                refECU.getAddress(), name, String.format("%d DTCs", refECU.getDtcs().size()),
+                false);
+        holder.layout.setOnClickListener(
+                view -> {
+                    if (hasSelected()) {
+                        toggleSelect(refECU);
+                        refECU.setColor(finalHolder);
+                    } else {
+                        Intent intent = new Intent(mContext, DTCListActivity.class);
+                        intent.putExtra("name", name);
+                        intent.putParcelableArrayListExtra(
+                                "dtcs", (ArrayList<? extends Parcelable>) refECU.getDtcs());
+                        mContext.startActivity(intent);
+                    }
+                });
+        holder.layout.setOnLongClickListener(
+                view -> {
+                    toggleSelect(refECU);
+                    refECU.setColor(finalHolder);
+                    return true;
+                });
+    }
+
+    @Override
+    public int getItemCount() {
+        return mEcuOrderedList.size();
+    }
+
+    /**
+     * Overrides method getBaseList of SelectableAdapter to allow selection of elements
+     *
+     * @return base list of DTCs
+     */
+    @Override
+    protected List<ECU> getBaseList() {
+        return mEcuOrderedList;
+    }
+}
diff --git a/tests/DiagnosticTools/src/com/google/android/car/diagnostictools/ECUListActivity.java b/tests/DiagnosticTools/src/com/google/android/car/diagnostictools/ECUListActivity.java
new file mode 100644
index 0000000..a2ee70b
--- /dev/null
+++ b/tests/DiagnosticTools/src/com/google/android/car/diagnostictools/ECUListActivity.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.car.diagnostictools;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.View;
+import android.widget.Toolbar;
+
+import androidx.recyclerview.widget.DividerItemDecoration;
+import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/** Displays a list of ECUs */
+public class ECUListActivity extends Activity {
+
+    private RecyclerView mRecyclerView;
+    private ECUAdapter mAdapter;
+    private RecyclerView.LayoutManager mLayoutManager;
+    private Toolbar mToolbar;
+
+    /** Called with the activity is first created. */
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        View view = getLayoutInflater().inflate(R.layout.diagnostic_tools, null);
+        setContentView(view);
+
+        mRecyclerView = findViewById(R.id.my_recycler_view);
+
+        mRecyclerView.setHasFixedSize(true);
+
+        mLayoutManager = new LinearLayoutManager(this);
+        mRecyclerView.setLayoutManager(mLayoutManager);
+        List<ECU> input = new ArrayList<>();
+        for (int i = 0; i < 100; i++) {
+            input.add(ECU.createSampleECU(i, this));
+        }
+        mAdapter = new ECUAdapter(input, this);
+        mRecyclerView.setAdapter(mAdapter);
+        mRecyclerView.addItemDecoration(
+                new DividerItemDecoration(
+                        mRecyclerView.getContext(), DividerItemDecoration.VERTICAL));
+
+        mToolbar = findViewById(R.id.toolbar);
+        setActionBar(mToolbar);
+        mToolbar.setNavigationIcon(R.drawable.ic_show_chart_black_24dp);
+        mToolbar.setTitle("ECU Overview");
+        final Context mContext = this;
+        mToolbar.setNavigationOnClickListener(
+                view1 -> {
+                    Intent intent = new Intent(mContext, LiveDataActivity.class);
+                    mContext.startActivity(intent);
+                });
+    }
+
+    /**
+     * Handle clicks from the ActionButton. Confirms that the user wants to clear selected DTCs and
+     * then deletes them on confirmation.
+     */
+    public void onClickActionButton(View view) {
+        new AlertDialog.Builder(this)
+                .setTitle("Clear DTCs")
+                .setMessage(
+                        String.format(
+                                "Do you really want to clear %d mDtcs?", mAdapter.numSelected()))
+                .setIcon(android.R.drawable.ic_dialog_alert)
+                .setPositiveButton(
+                        android.R.string.yes, (dialog, whichButton) -> mAdapter.deleteSelected())
+                .setNegativeButton(android.R.string.no, null)
+                .show();
+    }
+}
diff --git a/tests/DiagnosticTools/src/com/google/android/car/diagnostictools/LiveDataActivity.java b/tests/DiagnosticTools/src/com/google/android/car/diagnostictools/LiveDataActivity.java
new file mode 100644
index 0000000..d985603
--- /dev/null
+++ b/tests/DiagnosticTools/src/com/google/android/car/diagnostictools/LiveDataActivity.java
@@ -0,0 +1,164 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.car.diagnostictools;
+
+import android.app.Activity;
+import android.car.Car;
+import android.car.diagnostic.CarDiagnosticEvent;
+import android.car.diagnostic.CarDiagnosticManager;
+import android.car.diagnostic.FloatSensorIndex;
+import android.car.diagnostic.IntegerSensorIndex;
+import android.car.hardware.property.CarPropertyManager;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.View;
+import android.widget.Toast;
+import android.widget.Toolbar;
+
+import androidx.recyclerview.widget.DividerItemDecoration;
+import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+import androidx.recyclerview.widget.RecyclerView.LayoutManager;
+
+import com.google.android.car.diagnostictools.utils.MetadataProcessing;
+import com.google.android.car.diagnostictools.utils.SensorMetadata;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class LiveDataActivity extends Activity {
+    private static final String TAG = "LiveDataActivity";
+
+    private Car mCar;
+    private MetadataProcessing mMetadataProcessing;
+    private RecyclerView mRecyclerView;
+    private LayoutManager mLayoutManager;
+    private LiveDataAdapter mAdapter;
+
+    /**
+     * Convert CarDiagnosticEvent into a list of SensorDataWrapper objects to be displayed
+     *
+     * @param event CarDiagnosticEvent with live(freeze) frame data in it.
+     * @param mMetadataProcessing MetadataProcessing object to associate sensor data with
+     * @return List of LiveDataWrappers to be displayed
+     */
+    static List<LiveDataAdapter.SensorDataWrapper> processSensorInfoIntoWrapper(
+            CarDiagnosticEvent event, MetadataProcessing mMetadataProcessing) {
+        List<LiveDataAdapter.SensorDataWrapper> sensorData = new ArrayList<>();
+        for (int i = 0; i <= FloatSensorIndex.LAST_SYSTEM; i++) {
+            Float sensor_value = event.getSystemFloatSensor(i);
+            if (sensor_value != null) {
+                SensorMetadata metadata = mMetadataProcessing.getFloatMetadata(i);
+                if (metadata != null) {
+                    Log.d(TAG, "Float metadata" + metadata.toString());
+                    sensorData.add(metadata.toLiveDataWrapper(sensor_value));
+                } else {
+                    sensorData.add(
+                            new LiveDataAdapter.SensorDataWrapper(
+                                    "Float Sensor " + i, "", sensor_value));
+                }
+            }
+        }
+        for (int i = 0; i <= IntegerSensorIndex.LAST_SYSTEM; i++) {
+            Integer sensor_value = event.getSystemIntegerSensor(i);
+            if (sensor_value != null) {
+                SensorMetadata metadata = mMetadataProcessing.getIntegerMetadata(i);
+                if (metadata != null) {
+                    Log.d(TAG, "Sensor metadata" + metadata.toString());
+                    sensorData.add(metadata.toLiveDataWrapper(sensor_value));
+                } else {
+                    sensorData.add(
+                            new LiveDataAdapter.SensorDataWrapper(
+                                    "Integer Sensor " + i, "", sensor_value));
+                }
+            }
+        }
+        return sensorData;
+    }
+
+    /**
+     * Overloaded version of processSensorInfoIntoWrapper that uses the metadata member variable
+     *
+     * @param event CarDiagnosticEvent with live(freeze) frame data in it.
+     * @return List of LiveDataWrappers to be displayed
+     */
+    private List<LiveDataAdapter.SensorDataWrapper> processSensorInfoIntoWrapper(
+            CarDiagnosticEvent event) {
+        return processSensorInfoIntoWrapper(event, mMetadataProcessing);
+    }
+
+    /** Called with the activity is first created. */
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        View view = getLayoutInflater().inflate(R.layout.activity_live_data, null);
+        setContentView(view);
+
+        mCar = Car.createCar(this);
+        mMetadataProcessing = MetadataProcessing.getInstance(this);
+
+        CarDiagnosticManager diagnosticManager =
+                (CarDiagnosticManager) mCar.getCarManager(Car.DIAGNOSTIC_SERVICE);
+
+        CarDiagnosticListener listener = new CarDiagnosticListener(diagnosticManager);
+
+        if (diagnosticManager != null && diagnosticManager.isLiveFrameSupported()) {
+            diagnosticManager.registerListener(
+                    listener,
+                    CarDiagnosticManager.FRAME_TYPE_LIVE,
+                    (int) CarPropertyManager.SENSOR_RATE_NORMAL);
+        } else if (diagnosticManager == null) {
+            Toast.makeText(this, "Error reading manager, please reload", Toast.LENGTH_LONG).show();
+        } else if (!diagnosticManager.isLiveFrameSupported()) {
+            Toast.makeText(this, "Live Frame data not supported", Toast.LENGTH_LONG).show();
+        }
+
+        Toolbar toolbar = findViewById(R.id.toolbar);
+        setActionBar(toolbar);
+        toolbar.setNavigationIcon(R.drawable.ic_arrow_back_black_24dp);
+        toolbar.setNavigationOnClickListener(view1 -> finish());
+
+        mRecyclerView = findViewById(R.id.live_data_list);
+
+        mRecyclerView.setHasFixedSize(false);
+
+        mLayoutManager = new LinearLayoutManager(this);
+        mRecyclerView.setLayoutManager(mLayoutManager);
+        mAdapter = new LiveDataAdapter();
+        mRecyclerView.setAdapter(mAdapter);
+        mRecyclerView.addItemDecoration(
+                new DividerItemDecoration(
+                        mRecyclerView.getContext(), DividerItemDecoration.VERTICAL));
+    }
+
+    /** Listener which updates live frame data when it is available */
+    private class CarDiagnosticListener implements CarDiagnosticManager.OnDiagnosticEventListener {
+
+        private final CarDiagnosticManager mManager;
+
+        CarDiagnosticListener(CarDiagnosticManager manager) {
+            this.mManager = manager;
+        }
+
+        @Override
+        public void onDiagnosticEvent(CarDiagnosticEvent event) {
+            if (mManager.isLiveFrameSupported()) {
+                mAdapter.update(processSensorInfoIntoWrapper(event));
+            }
+        }
+    }
+}
diff --git a/tests/DiagnosticTools/src/com/google/android/car/diagnostictools/LiveDataAdapter.java b/tests/DiagnosticTools/src/com/google/android/car/diagnostictools/LiveDataAdapter.java
new file mode 100644
index 0000000..7127052
--- /dev/null
+++ b/tests/DiagnosticTools/src/com/google/android/car/diagnostictools/LiveDataAdapter.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.car.diagnostictools;
+
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.annotation.NonNull;
+import androidx.recyclerview.widget.RecyclerView;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/** Adapter which displays live data */
+public class LiveDataAdapter extends RecyclerView.Adapter<RowViewHolder> {
+
+    private List<SensorDataWrapper> mLiveData;
+
+    public LiveDataAdapter(List<SensorDataWrapper> initialData) {
+        mLiveData = initialData;
+    }
+
+    public LiveDataAdapter() {
+        mLiveData = new ArrayList<>();
+    }
+
+    @NonNull
+    @Override
+    public RowViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
+        LayoutInflater inflater = LayoutInflater.from(parent.getContext());
+
+        View v = inflater.inflate(R.layout.row_layout, parent, false);
+
+        return new RowViewHolder(v, false, false, true);
+    }
+
+    @Override
+    public void onBindViewHolder(@NonNull RowViewHolder holder, int position) {
+        SensorDataWrapper wrapper = mLiveData.get(position);
+        holder.setFields(wrapper.mName, "", "" + wrapper.mNumber + " " + wrapper.mUnit, false);
+    }
+
+    @Override
+    public int getItemCount() {
+        return mLiveData.size();
+    }
+
+    /**
+     * Takes in new data to update the RecyclerView with
+     *
+     * @param data New list of LiveDataWrappers to display
+     */
+    public void update(List<SensorDataWrapper> data) {
+        mLiveData.clear();
+        mLiveData.addAll(data);
+        notifyDataSetChanged();
+    }
+
+    /** Wrapper which holds data about a DataFrame */
+    public static class SensorDataWrapper {
+
+        private String mName;
+        private String mUnit;
+        private String mNumber;
+
+        public SensorDataWrapper(String name, String unit, float number) {
+            this.mName = name;
+            this.mUnit = unit;
+            this.mNumber = String.valueOf(number);
+        }
+
+        public SensorDataWrapper(String name, String unit, String number) {
+            this.mName = name;
+            this.mUnit = unit;
+            this.mNumber = number;
+        }
+    }
+}
diff --git a/tests/DiagnosticTools/src/com/google/android/car/diagnostictools/RowViewHolder.java b/tests/DiagnosticTools/src/com/google/android/car/diagnostictools/RowViewHolder.java
new file mode 100644
index 0000000..3e83f06
--- /dev/null
+++ b/tests/DiagnosticTools/src/com/google/android/car/diagnostictools/RowViewHolder.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.car.diagnostictools;
+
+import android.view.View;
+import android.widget.CheckBox;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.recyclerview.widget.RecyclerView;
+
+/** Generic ViewHolder which is used to represent data in rows. */
+public class RowViewHolder extends RecyclerView.ViewHolder {
+
+    public TextView textHeader;
+    public TextView textDescription;
+    public TextView textValueField;
+    public CheckBox checkBox;
+    public View layout;
+
+    public RowViewHolder(@NonNull View itemView) {
+        super(itemView);
+        layout = itemView;
+        textHeader = itemView.findViewById(R.id.firstLine);
+        textDescription = itemView.findViewById(R.id.secondLine);
+        textValueField = itemView.findViewById(R.id.dtc_number);
+        checkBox = itemView.findViewById(R.id.checkBox);
+    }
+
+    /**
+     * Full constructor which allows selective displaying of description, checkbox, and value
+     * fields.
+     *
+     * @param itemView View to construct on
+     * @param showDescription Display description if true, remove from view (View.GONE) if false
+     * @param showCheckBox Display checkBox if true, remove from view (View.GONE) if false
+     * @param showValue Display value if true, remove from view (View.GONE) if false
+     */
+    public RowViewHolder(
+            @NonNull View itemView,
+            boolean showDescription,
+            boolean showCheckBox,
+            boolean showValue) {
+        this(itemView);
+        checkBox.setVisibility(showCheckBox ? View.VISIBLE : View.GONE);
+        textValueField.setVisibility(showValue ? View.VISIBLE : View.GONE);
+        textDescription.setVisibility(showDescription ? View.VISIBLE : View.GONE);
+    }
+
+    /**
+     * Set values of elements inside the view.
+     *
+     * @param header Header value to display
+     * @param description Description value to display
+     * @param valueField Value to display on the right side
+     * @param chbx Whether or not the checkbox is checked
+     */
+    public void setFields(String header, String description, String valueField, boolean chbx) {
+        textHeader.setText(header);
+        textDescription.setText(description);
+        textValueField.setText(valueField);
+        checkBox.setChecked(chbx);
+    }
+}
diff --git a/tests/DiagnosticTools/src/com/google/android/car/diagnostictools/utils/DTCMetadata.java b/tests/DiagnosticTools/src/com/google/android/car/diagnostictools/utils/DTCMetadata.java
new file mode 100644
index 0000000..8071365
--- /dev/null
+++ b/tests/DiagnosticTools/src/com/google/android/car/diagnostictools/utils/DTCMetadata.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.car.diagnostictools.utils;
+
+import android.text.Html;
+import android.text.Spanned;
+import android.util.Log;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+/** Holds metadata for a DTC. The intended use case is to map this to a DTC code. */
+public class DTCMetadata {
+
+    private static final String TAG = "DTCMetadata";
+    private static final String SHORT_DESCRIPTION_KEY = "short_description";
+    private static final String LONG_DESCRIPTION_KEY = "long_description";
+
+    private String mShortDescription;
+    private String mStringLongDescription;
+    private Spanned mSpannedLongDescription;
+
+    /**
+     * Creates new DTCMeta data object. Both the String and Spanned version of the longDescription
+     * are stored to allow for the object to be parcelable (String is used) and allow only one call
+     * to Html.fromHTML (Spanned is used)
+     *
+     * @param shortDescription Main description of 50 characters or less
+     * @param longDescription Longer description that can be in HTML format to allow for more
+     *     complex formatting.
+     */
+    private DTCMetadata(String shortDescription, String longDescription) {
+        this.mShortDescription = shortDescription;
+        this.mStringLongDescription = longDescription;
+        this.mSpannedLongDescription = Html.fromHtml(longDescription, 0);
+    }
+
+    /**
+     * Build a DTCmeta object from a JSON object. Used by JsonMetadataReader
+     *
+     * @param jsonObject JSONObject that contains both a "short_description" key and a
+     *     "long_description" key
+     * @return New DTCMetadata object if successful or null if not
+     */
+    static DTCMetadata buildFromJson(JSONObject jsonObject) {
+        try {
+            return new DTCMetadata(
+                    jsonObject.getString(SHORT_DESCRIPTION_KEY),
+                    jsonObject.getString(LONG_DESCRIPTION_KEY));
+        } catch (JSONException e) {
+            Log.d(
+                    TAG,
+                    "DTC JSON Object doesn't match expected format: "
+                            + jsonObject.toString()
+                            + " Error: "
+                            + e.toString());
+            return null;
+        }
+    }
+
+    public String getShortDescription() {
+        return mShortDescription;
+    }
+
+    public Spanned getSpannedLongDescription() {
+        return mSpannedLongDescription;
+    }
+
+    public String getStringLongDescription() {
+        return mStringLongDescription;
+    }
+}
diff --git a/tests/DiagnosticTools/src/com/google/android/car/diagnostictools/utils/JsonMetadataReader.java b/tests/DiagnosticTools/src/com/google/android/car/diagnostictools/utils/JsonMetadataReader.java
new file mode 100644
index 0000000..3ce3e66
--- /dev/null
+++ b/tests/DiagnosticTools/src/com/google/android/car/diagnostictools/utils/JsonMetadataReader.java
@@ -0,0 +1,191 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.car.diagnostictools.utils;
+
+import android.car.diagnostic.FloatSensorIndex;
+import android.car.diagnostic.IntegerSensorIndex;
+import android.util.Log;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.lang.reflect.Field;
+import java.nio.charset.StandardCharsets;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Takes in an InputStream of a JSON file and converts it into a mapping from Integer (SensorIndex
+ * IntDef) to a SensorMetadata object
+ *
+ * <p>Two similar methods used are read(Float|Integer)SensorIndexFromJson. These utilize the
+ * (Float|Integer)SensorIndex.class objects to allow the JSON object to utilize @IntDef variable
+ * names.
+ *
+ * <p>Additionally two similar methods are read(DTCs|ECUs)FromJson which take in an input stream and
+ * return a mapping between DTCs/ECUs and their respective metadata (DTCMetadata or String
+ * respectively)
+ */
+class JsonMetadataReader {
+
+    private static final String TAG = "JsonMetadataReader";
+
+    /**
+     * Converts a JSON file with DTC -> DTCMetadata formatting into the corresponding mapping
+     *
+     * @param in InputStream for JSON file
+     * @return Mapping from String to SensorMetadata
+     * @throws IOException Thrown if there is an issue with reading the file from the InputStream
+     * @throws JSONException Thrown if the JSON object is malformed
+     */
+    static Map<String, DTCMetadata> readDTCsFromJson(InputStream in)
+            throws IOException, JSONException {
+        JSONObject metadataMapping = new JSONObject(inputStreamToString(in));
+        Map<String, DTCMetadata> metadata = new HashMap<>();
+        for (String dtc : metadataMapping.keySet()) {
+            try {
+                metadata.put(dtc, DTCMetadata.buildFromJson(metadataMapping.getJSONObject(dtc)));
+            } catch (JSONException e) {
+                Log.d(TAG, "Invalid JSON for DTC: " + dtc);
+            }
+        }
+        return metadata;
+    }
+
+    /**
+     * Converts a JSON file with ECU -> String formatting into the corresponding mapping
+     *
+     * @param in InputStream for JSON file
+     * @return Mapping from String to SensorMetadata
+     * @throws IOException Thrown if there is an issue with reading the file from the InputStream
+     * @throws JSONException Thrown if the JSON object is malformed
+     */
+    static Map<String, String> readECUsFromJson(InputStream in) throws IOException, JSONException {
+        JSONObject metadataMapping = new JSONObject(inputStreamToString(in));
+        Map<String, String> metadata = new HashMap<>();
+        for (String address : metadataMapping.keySet()) {
+            try {
+                metadata.put(address, metadataMapping.getString(address));
+            } catch (JSONException e) {
+                Log.d(TAG, "Invalid JSON for ECU: " + address);
+            }
+        }
+
+        return metadata;
+    }
+
+    /**
+     * Takes an InputStream of JSON and converts it to a mapping from FloatSensorIndex field value
+     * to Sensor Metadata objects
+     *
+     * @param in InputStream of JSON
+     * @return Mapping from FloatSensorIndex field values to SensorMetadata objects
+     * @throws IOException Thrown if there is an issue with reading the file from the InputStream
+     * @throws JSONException Thrown if the JSON object is malformed
+     */
+    static Map<Integer, SensorMetadata> readFloatSensorIndexFromJson(InputStream in)
+            throws IOException, JSONException {
+        return readSensorIndexFromJson(in, FloatSensorIndex.class);
+    }
+
+    /**
+     * Takes an InputStream of JSON and converts it to a mapping from IntegerSensorIndex field value
+     * to Sensor Metadata objects
+     *
+     * @param in InputStream of JSON
+     * @return Mapping from IntegerSensorIndex field values to SensorMetadata objects
+     * @throws IOException Thrown if there is an issue with reading the file from the InputStream
+     * @throws JSONException Thrown if the JSON object is malformed
+     */
+    static Map<Integer, SensorMetadata> readIntegerSensorIndexFromJson(InputStream in)
+            throws IOException, JSONException {
+        return readSensorIndexFromJson(in, IntegerSensorIndex.class);
+    }
+
+    /**
+     * Takes an InputStream and Class to read in as a SensorIndex. The Class is used to connect with
+     * reflections and assert that key names are consistent with the SensorIndex class input
+     *
+     * @param in InputStream of JSON file
+     * @param sensorIndexClass Defines which of the (Float|Integer)SensorIndex classes does the key
+     *     for the output mapping link to
+     * @return Mapping from sensorIndexClass field values to SensorMetadata objects
+     * @throws IOException Thrown if there is an issue with reading the file from the InputStream
+     * @throws JSONException Thrown if the JSON object is malformed
+     */
+    private static Map<Integer, SensorMetadata> readSensorIndexFromJson(
+            InputStream in, Class sensorIndexClass) throws IOException, JSONException {
+        Map<String, SensorMetadata> metadata = JsonMetadataReader.readSensorsFromJson(in);
+        Map<Integer, SensorMetadata> metadataMap = new HashMap<>();
+        // Use Reflection to get all fields of the specified sensorIndexClass
+        Field[] sensorIndexFields = sensorIndexClass.getFields();
+        // For all fields, if the JSON object contains the field then put it into the map
+        for (Field f : sensorIndexFields) {
+            if (metadata.containsKey(f.getName())) {
+                try {
+                    metadataMap.put((Integer) f.get(sensorIndexClass), metadata.get(f.getName()));
+                } catch (IllegalAccessException e) { // Added for safety though should never
+                    // trigger
+                    Log.e(TAG, "Illegal Access Exception throws but should not be thrown");
+                }
+            }
+        }
+        return metadataMap;
+    }
+
+    /**
+     * Helper method that creates an unchecked String to SensorMetadata mapping from an InputStream
+     *
+     * @param in InputStream for JSON file
+     * @return Mapping from String to SensorMetadata
+     * @throws IOException Thrown if there is an issue with reading the file from the InputStream
+     * @throws JSONException Thrown if the JSON object is malformed
+     */
+    private static Map<String, SensorMetadata> readSensorsFromJson(InputStream in)
+            throws IOException, JSONException {
+        JSONObject metadataMapping = new JSONObject(inputStreamToString(in));
+        Map<String, SensorMetadata> metadata = new HashMap<>();
+        for (String sensorIndex : metadataMapping.keySet()) {
+            // Use a static method from SensorMetadata to handle the conversion from
+            // JSON->SensorMetadata
+            metadata.put(
+                    sensorIndex,
+                    SensorMetadata.buildFromJson(metadataMapping.getJSONObject(sensorIndex)));
+        }
+        return metadata;
+    }
+
+    /**
+     * Converts an InputStream into a String
+     *
+     * @param in InputStream
+     * @return InputStream converted to a String
+     * @throws IOException Thrown if there are issues in reading the file
+     */
+    private static String inputStreamToString(InputStream in) throws IOException {
+        StringBuilder builder = new StringBuilder();
+        try (BufferedReader reader =
+                new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8))) {
+            reader.lines().forEach(builder::append);
+        }
+        return builder.toString();
+    }
+}
diff --git a/tests/DiagnosticTools/src/com/google/android/car/diagnostictools/utils/MathEval.java b/tests/DiagnosticTools/src/com/google/android/car/diagnostictools/utils/MathEval.java
new file mode 100644
index 0000000..11910df
--- /dev/null
+++ b/tests/DiagnosticTools/src/com/google/android/car/diagnostictools/utils/MathEval.java
@@ -0,0 +1,201 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.car.diagnostictools.utils;
+
+/**
+ * This class allows for custom formulas to translate input live/freeze frame data into the
+ * appropriate values. Ideally a `conversion` object should be used in the JSON but this allows for
+ * more flexibility if needed.
+ */
+class MathEval {
+
+    /**
+     * This is a sanity check for user generated strings to catch errors once instead of everytime
+     * the data is translated
+     *
+     * @param str Translation string to test
+     * @return True if the string doesn't or won't fail when processing simple inputs
+     */
+    static boolean testTranslation(final String str) {
+        if (str == null || str.length() == 0) {
+            return true;
+        } else if (str.length() > 50) {
+            return false;
+        }
+
+        try {
+            eval(str, (float) 100.0);
+            eval(str, (float) 10.0);
+            eval(str, (float) 1);
+            eval(str, (float) .1);
+            eval(str, (float) 0);
+            eval(str, (float) -100.0);
+            eval(str, (float) -10.0);
+            eval(str, (float) -1);
+            eval(str, (float) -.1);
+        } catch (Exception e) {
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Uses a translation string and applies the formula to a variable From
+     * https://stackoverflow.com/a/26227947 with modifications. String must only use +,-,*,/,^,(,)
+     * or "sqrt", "sin", "cos", "tan" (as a function ie sqrt(4)) and the variable x
+     *
+     * @param translationString Translation string which uses x as the variable
+     * @param variableIn Float that the translation is operating on
+     * @return New Float that has gone through operations defined in "translationString". If
+     *     translationString is non-operable then the variableIn will be returned
+     * @throws TranslationTooLongException Thrown if the translation string is longer than 50 chars
+     *     to prevent long execution times
+     */
+    static double eval(final String translationString, Float variableIn)
+            throws TranslationTooLongException {
+
+        if (translationString == null || translationString.length() == 0) {
+            return variableIn;
+        } else if (translationString.length() > 50) {
+            throw new TranslationTooLongException(
+                    "Translation function " + translationString + " is too long");
+        }
+
+        return new Object() {
+            int mPos = -1, mCh;
+
+            void nextChar() {
+                mCh = (++mPos < translationString.length()) ? translationString.charAt(mPos) : -1;
+            }
+
+            boolean eat(int charToEat) {
+                while (mCh == ' ') {
+                    nextChar();
+                }
+                if (mCh == charToEat) {
+                    nextChar();
+                    return true;
+                }
+                return false;
+            }
+
+            double parse() {
+                nextChar();
+                double x = parseExpression();
+                if (mPos < translationString.length()) {
+                    throw new RuntimeException("Unexpected: " + (char) mCh);
+                }
+                return x;
+            }
+
+            // Grammar:
+            // expression = term | expression `+` term | expression `-` term
+            // term = factor | term `*` factor | term `/` factor
+            // factor = `+` factor | `-` factor | `(` expression `)`
+            //        | number | functionName factor | factor `^` factor
+
+            double parseExpression() {
+                double x = parseTerm();
+                for (;; ) {
+                    if (eat('+')) {
+                        x += parseTerm(); // addition
+                    } else if (eat('-')) {
+                        x -= parseTerm(); // subtraction
+                    } else {
+                        return x;
+                    }
+                }
+            }
+
+            double parseTerm() {
+                double x = parseFactor();
+                for (;; ) {
+                    if (eat('*')) {
+                        x *= parseFactor(); // multiplication
+                    } else if (eat('/')) {
+                        x /= parseFactor(); // division
+                    } else {
+                        return x;
+                    }
+                }
+            }
+
+            double parseFactor() {
+                if (eat('+')) {
+                    return parseFactor(); // unary plus
+                }
+                if (eat('-')) {
+                    return -parseFactor(); // unary minus
+                }
+
+                double x;
+                int startPos = this.mPos;
+                if (eat('(')) { // parentheses
+                    x = parseExpression();
+                    eat(')');
+                } else if ((mCh >= '0' && mCh <= '9') || mCh == '.') { // numbers
+                    while ((mCh >= '0' && mCh <= '9') || mCh == '.') {
+                        nextChar();
+                    }
+                    x = Double.parseDouble(translationString.substring(startPos, this.mPos));
+                } else if (mCh == 'x') {
+                    x = variableIn;
+                    nextChar();
+                    // System.out.println(x);
+                } else if (mCh >= 'a' && mCh <= 'z') { // functions
+                    while (mCh >= 'a' && mCh <= 'z') {
+                        nextChar();
+                    }
+                    String func = translationString.substring(startPos, this.mPos);
+                    x = parseFactor();
+                    switch (func) {
+                        case "sqrt":
+                            x = Math.sqrt(x);
+                            break;
+                        case "sin":
+                            x = Math.sin(Math.toRadians(x));
+                            break;
+                        case "cos":
+                            x = Math.cos(Math.toRadians(x));
+                            break;
+                        case "tan":
+                            x = Math.tan(Math.toRadians(x));
+                            break;
+                        default:
+                            throw new RuntimeException("Unknown function: " + func);
+                    }
+                } else {
+                    throw new RuntimeException("Unexpected: " + (char) mCh);
+                }
+
+                if (eat('^')) {
+                    x = Math.pow(x, parseFactor()); // exponentiation
+                }
+
+                return x;
+            }
+        }.parse();
+    }
+
+    /** Exception thrown if the translation string is too long */
+    static class TranslationTooLongException extends Exception {
+
+        TranslationTooLongException(String errorMsg) {
+            super(errorMsg);
+        }
+    }
+}
diff --git a/tests/DiagnosticTools/src/com/google/android/car/diagnostictools/utils/MetadataProcessing.java b/tests/DiagnosticTools/src/com/google/android/car/diagnostictools/utils/MetadataProcessing.java
new file mode 100644
index 0000000..9f36002
--- /dev/null
+++ b/tests/DiagnosticTools/src/com/google/android/car/diagnostictools/utils/MetadataProcessing.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.car.diagnostictools.utils;
+
+import android.content.Context;
+import android.util.Log;
+
+import com.google.android.car.diagnostictools.R;
+
+import org.json.JSONException;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+
+/** Singleton class that contains all available metadata information */
+public class MetadataProcessing {
+
+    private static final String TAG = "MetadataProcessing";
+    private static MetadataProcessing sInstance = null;
+
+    private Map<Integer, SensorMetadata> mFloatMetadata = new HashMap<>();
+    private Map<Integer, SensorMetadata> mIntegerMetadata = new HashMap<>();
+    private Map<String, DTCMetadata> mDTCMetadataMap = new HashMap<>();
+    private Map<String, String> mECUMetadataMap = new HashMap<>();
+
+    /**
+     * Creates a MetadataProcessing object using a context to access files in res/raw
+     *
+     * @param context Any context object. Used to access files in res/raw
+     */
+    private MetadataProcessing(Context context) {
+        // DTC Metadata
+        try {
+            mDTCMetadataMap =
+                    JsonMetadataReader.readDTCsFromJson(
+                            context.getResources().openRawResource(R.raw.system_dtcs));
+        } catch (IOException | JSONException e) {
+            Log.d(
+                    TAG,
+                    "Error reading in JSON Metadata for system DTCs. More info: " + e.toString());
+        }
+        try {
+            mDTCMetadataMap.putAll(
+                    JsonMetadataReader.readDTCsFromJson(
+                            context.getResources().openRawResource(R.raw.vendor_dtcs)));
+        } catch (IOException | JSONException e) {
+            Log.d(
+                    TAG,
+                    "Error reading in JSON Metadata for vendor DTCs. More info: " + e.toString());
+        }
+
+        // Float Metadata
+        try {
+            mFloatMetadata =
+                    JsonMetadataReader.readFloatSensorIndexFromJson(
+                            context.getResources().openRawResource(R.raw.system_float_sensors));
+        } catch (IOException | JSONException e) {
+            Log.e(
+                    TAG,
+                    "Error reading in JSON Metadata for system float sensors. More info: "
+                            + e.toString());
+        }
+        try {
+            mFloatMetadata.putAll(
+                    JsonMetadataReader.readFloatSensorIndexFromJson(
+                            context.getResources().openRawResource(R.raw.vendor_float_sensors)));
+        } catch (IOException | JSONException e) {
+            Log.e(
+                    TAG,
+                    "Error reading in JSON Metadata for vendor float sensors. More info: "
+                            + e.toString());
+        }
+
+        // Integer Metadata
+        try {
+            mIntegerMetadata =
+                    JsonMetadataReader.readIntegerSensorIndexFromJson(
+                            context.getResources().openRawResource(R.raw.system_integer_sensors));
+        } catch (IOException | JSONException e) {
+            Log.e(
+                    TAG,
+                    "Error reading in JSON Metadata for system integer sensors. More info: "
+                            + e.toString());
+        }
+        try {
+            mIntegerMetadata.putAll(
+                    JsonMetadataReader.readIntegerSensorIndexFromJson(
+                            context.getResources().openRawResource(R.raw.vendor_integer_sensors)));
+        } catch (IOException | JSONException e) {
+            Log.e(
+                    TAG,
+                    "Error reading in JSON Metadata for vendor integer sensors. More info: "
+                            + e.toString());
+        }
+
+        // ECU Metadata
+        try {
+            mECUMetadataMap =
+                    JsonMetadataReader.readECUsFromJson(
+                            context.getResources().openRawResource(R.raw.vendor_ecus));
+        } catch (IOException | JSONException e) {
+            Log.e(TAG, "Error reading in JSON Metadata for ECUs. More info: " + e.toString());
+        }
+    }
+
+    /**
+     * Maintains a singleton object and allows access to it. If no singleton object is created the
+     * method will create one based on passed in context
+     *
+     * @param context Any context that allows access to res/raw
+     * @return Singleton MetadataProcessing object
+     */
+    public static synchronized MetadataProcessing getInstance(Context context) {
+        if (sInstance == null) {
+            sInstance = new MetadataProcessing(context);
+        }
+        return sInstance;
+    }
+
+    public SensorMetadata getFloatMetadata(Integer floatSensorIndex) {
+        return mFloatMetadata.getOrDefault(floatSensorIndex, null);
+    }
+
+    public SensorMetadata getIntegerMetadata(Integer integerSensorIndex) {
+        return mIntegerMetadata.getOrDefault(integerSensorIndex, null);
+    }
+
+    public DTCMetadata getDTCMetadata(String dtcCode) {
+        return mDTCMetadataMap.getOrDefault(dtcCode, null);
+    }
+
+    public String getECUMetadata(String ecuAddress) {
+        return mECUMetadataMap.getOrDefault(ecuAddress, null);
+    }
+}
diff --git a/tests/DiagnosticTools/src/com/google/android/car/diagnostictools/utils/SelectableAdapter.java b/tests/DiagnosticTools/src/com/google/android/car/diagnostictools/utils/SelectableAdapter.java
new file mode 100644
index 0000000..77deca9
--- /dev/null
+++ b/tests/DiagnosticTools/src/com/google/android/car/diagnostictools/utils/SelectableAdapter.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.car.diagnostictools.utils;
+
+import androidx.recyclerview.widget.RecyclerView;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Used to enable a RecyclerView Adapter to select and delete row elements. Designed to be self
+ * contained so that implementing the abstract function will enable selecting behavior.
+ *
+ * @param <M> Base row model used. For example, the `DTC` class is used for the DTCAdapter
+ * @param <VH> Base view holder used. Included so that SelectableAdapter can use
+ *     notifyDataSetChanged
+ */
+public abstract class SelectableAdapter<
+                M extends SelectableRowModel, VH extends RecyclerView.ViewHolder>
+        extends RecyclerView.Adapter<VH> {
+
+    private List<M> mSelected = new ArrayList<>();
+
+    /**
+     * Toggle if an element is selected or not.
+     *
+     * @param model The model object that is related to the row selected
+     */
+    public void toggleSelect(M model) {
+        if (model.isSelected()) {
+            model.setSelected(false);
+            mSelected.remove(model);
+        } else {
+            model.setSelected(true);
+            mSelected.add(model);
+        }
+    }
+
+    /**
+     * Returns the number of elements selected. Uses the model's numSelected method to allow for one
+     * model object to represent multiple element (one ECU holds multiple DTCs). If no elements are
+     * explicitly selected, it is assumed that all elements are selected.
+     *
+     * @return number of elements selected
+     */
+    public int numSelected() {
+        int count = 0;
+        for (M model : mSelected) {
+            count += model.numSelected();
+        }
+        if (count == 0) {
+            for (M model : getBaseList()) {
+                count += model.numSelected();
+            }
+        }
+        return count;
+    }
+
+    /**
+     * Deletes all selected element or all elements if no elements are selected. Additionally, calls
+     * the delete methods for all related models.
+     */
+    public void deleteSelected() {
+        if (mSelected.size() == 0) {
+            for (M model : getBaseList()) {
+                model.delete();
+            }
+            getBaseList().clear();
+        } else {
+            for (M model : mSelected) {
+                model.delete();
+            }
+            getBaseList().removeAll(mSelected);
+            mSelected.clear();
+        }
+        notifyDataSetChanged();
+    }
+
+    /**
+     * If there are any models explicitly selected. Does not take into account there are any
+     * elements in that model (an ECU with 0 DTCs).
+     *
+     * @return if there are any models selected
+     */
+    public boolean hasSelected() {
+        return mSelected.size() > 0;
+    }
+
+    /**
+     * Gets base list that the adapter is built off of. This must be one list which the adapter uses
+     * for both order and elements.
+     *
+     * @return base list that adapter is built off of
+     */
+    protected abstract List<M> getBaseList();
+}
diff --git a/tests/DiagnosticTools/src/com/google/android/car/diagnostictools/utils/SelectableRowModel.java b/tests/DiagnosticTools/src/com/google/android/car/diagnostictools/utils/SelectableRowModel.java
new file mode 100644
index 0000000..e2675bf
--- /dev/null
+++ b/tests/DiagnosticTools/src/com/google/android/car/diagnostictools/utils/SelectableRowModel.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.car.diagnostictools.utils;
+
+import android.graphics.Color;
+
+import com.google.android.car.diagnostictools.RowViewHolder;
+
+/**
+ * Data model class which ECUs and DTCs extend. Allows those classes to be connected with a
+ * SelectableAdapter to enable a RecyclerView with elements getting selected
+ */
+public abstract class SelectableRowModel {
+
+    /** If model is selected */
+    private boolean mIsSelected;
+
+    boolean isSelected() {
+        return mIsSelected;
+    }
+
+    void setSelected(boolean selected) {
+        mIsSelected = selected;
+    }
+
+    /**
+     * Takes in a RowViewHolder object and sets its layout background to its appropriate color based
+     * on if it is selected or not. Uses hardcoded values for the selected and default backgrounds.
+     *
+     * @param holder RowViewHolder object associated with the model object
+     */
+    public void setColor(RowViewHolder holder) {
+        int color = mIsSelected ? Color.GRAY : Color.parseColor("#303030");
+        holder.layout.setBackgroundColor(color);
+    }
+
+    /**
+     * Get the number of elements selected when this model is selected. If the object has children
+     * which are also selected as a result of this select, then those should be counted in that
+     * number.
+     *
+     * @return number of elements selected
+     */
+    public abstract int numSelected();
+
+    /**
+     * Delete this model and all of its children. This allows the model to run special functions to
+     * delete itself in any other representation (DTCs can be deleted from the VHAL).
+     */
+    public abstract void delete();
+}
diff --git a/tests/DiagnosticTools/src/com/google/android/car/diagnostictools/utils/SensorMetadata.java b/tests/DiagnosticTools/src/com/google/android/car/diagnostictools/utils/SensorMetadata.java
new file mode 100644
index 0000000..91425d1
--- /dev/null
+++ b/tests/DiagnosticTools/src/com/google/android/car/diagnostictools/utils/SensorMetadata.java
@@ -0,0 +1,295 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.car.diagnostictools.utils;
+
+import android.util.Log;
+
+import com.google.android.car.diagnostictools.LiveDataAdapter;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+/**
+ * Stores metadata about a sensor and provides methods to better understand the sensor data.
+ * Provides support for arbitrary translation, scaling (conversion) following the format [Scaled
+ * Data Value] = [Offset] + [Scale] x [Raw Decimal Data Value], and mapping from integer to String.
+ */
+public class SensorMetadata {
+
+    private static final String TRANSLATION_KEY = "translation";
+    private static final String NAME_KEY = "name";
+    private static final String UNITS_KEY = "units";
+    private static final String CONVERSION_KEY = "conversion";
+    private static final String MAPPING_KEY = "mapping";
+    private static final String TAG = "SensorMetadata";
+
+    private String mTranslationString;
+    private float mScale;
+    private float mOffset;
+    private String mName;
+    private String mUnits;
+    private Map<Integer, String> mMapping;
+    private boolean mHasConversion;
+    private boolean mHasMapping;
+
+    /**
+     * Takes in arbitrary translation string that should have x in it as a variable, sensor name,
+     * and the sensor data units. For more information view MathEval.eval.
+     *
+     * @param translationString translation string with a 50 character limit. More information on
+     *     usage in MathEval.eval.
+     * @param name Name of sensor.
+     * @param units Units of the sensor's data.
+     */
+    private SensorMetadata(String translationString, String name, String units) {
+        if (MathEval.testTranslation(translationString)) {
+            this.mTranslationString = translationString;
+        } else {
+            Log.e(
+                    "ServiceCenterTools",
+                    String.format(
+                            "Invalid translation string %s for sensor %s because tests failed",
+                            translationString, name));
+            this.mTranslationString = "";
+        }
+        this.mName = name;
+        this.mUnits = units;
+        this.mHasConversion = false;
+        this.mHasMapping = false;
+    }
+
+    /**
+     * Takes in float scale and offset values, sensor name, and the sensor data units. Scaling and
+     * mOffset are used in the following format: [Scaled Data Value] = [Offset] + [Scale] x [Raw
+     * Decimal Data Value].
+     *
+     * @param scale Value to mScale sensor data by.
+     * @param offset Value to shift sensor data by.
+     * @param name Name of sensor.
+     * @param units Units of the sensor's data.
+     */
+    private SensorMetadata(float scale, float offset, String name, String units) {
+        this.mScale = scale;
+        this.mOffset = offset;
+        this.mName = name;
+        this.mUnits = units;
+        this.mHasConversion = true;
+        this.mHasMapping = false;
+    }
+
+    /**
+     * Takes in a Integer -> String mapping and sensor mName.
+     *
+     * @param mapping Mapping between Integer sensor values to their String values
+     * @param name Name of sensor.
+     */
+    private SensorMetadata(Map<Integer, String> mapping, String name) {
+        this.mMapping = mapping;
+        this.mName = name;
+        this.mUnits = "";
+        this.mHasMapping = true;
+        this.mHasConversion = false;
+    }
+
+    /**
+     * Takes in json object which represents the metadata for a single sensor and converts it to a
+     * SensorMetadata object. Supports mappings, structured conversions (mScale and mOffset), and
+     * arbitrary translations. Mappings and conversions are preferred and translation read in if
+     * neither are present.
+     *
+     * @param json JSON object for metadata
+     * @return returns a new SensorMeta data object with the data from the JSON object
+     */
+    static SensorMetadata buildFromJson(JSONObject json) {
+        String name = readStringFromJSON(NAME_KEY, json);
+        String units = readStringFromJSON(UNITS_KEY, json);
+
+        if (json.has(CONVERSION_KEY)) {
+            try {
+                JSONObject conversionObject = json.getJSONObject(CONVERSION_KEY);
+                float scale = readFloatFromJSON("scale", conversionObject);
+                float offset = readFloatFromJSON("offset", conversionObject);
+
+                return new SensorMetadata(scale, offset, name, units);
+            } catch (JSONException e) {
+                Log.d(TAG, "buildFromJson: Error reading conversion for " + name + ": " + e);
+            }
+        } else if (json.has(MAPPING_KEY)) {
+            try {
+                Map<Integer, String> mapping = readMappingFromJSON(MAPPING_KEY, json);
+                return new SensorMetadata(mapping, name);
+            } catch (JSONException e) {
+                Log.d(TAG, "buildFromJson: Error reading mapping: " + e);
+            }
+        }
+        String translationString = readStringFromJSON(TRANSLATION_KEY, json);
+
+        return new SensorMetadata(translationString, name, units);
+    }
+
+    /**
+     * Helper function that reads a string at a certain key from a JSONObject. Function wraps the
+     * JSON functions with appropriate exception handling.
+     *
+     * @param key key at which the desired string value is located
+     * @param json JSONObject that has the string value at the key
+     * @return value at key or null if error or not found
+     */
+    private static String readStringFromJSON(String key, JSONObject json) {
+        try {
+            if (json.has(key)) {
+                return json.getString(key);
+            }
+        } catch (JSONException e) {
+            Log.e(TAG, "Error reading " + key + " for String SensorMetadata");
+        }
+        return "";
+    }
+
+    /**
+     * Helper function that reads a float at a certain key from a JSONObject. Function wraps the
+     * JSON functions with appropriate exception handling.
+     *
+     * @param key key at which the desired float value is located
+     * @param json JSONObject that has the float value at the key
+     * @return value at key or null if error or not found
+     * @throws JSONException Throws JSONException if there is any
+     */
+    private static float readFloatFromJSON(String key, JSONObject json) throws JSONException {
+        try {
+            if (json.has(key)) {
+                return (float) json.getDouble(key);
+            } else {
+                Log.e(TAG, "No tag for " + key + " is found");
+                throw new JSONException(String.format("%s missing from object", key));
+            }
+        } catch (JSONException e) {
+            Log.e(TAG, "Error reading " + key + " for float SensorMetadata");
+            throw e;
+        }
+    }
+
+    /**
+     * Helper function that reads a mapping at a certain key from a JSONObject. Function wraps the
+     * JSON functions with appropriate exception handling.
+     *
+     * @param key key at which the desired float value is located
+     * @param json JSONObject that has the float value at the key
+     * @return value at key or null if error or not found
+     * @throws JSONException Throws JSONException if there is any
+     */
+    private static Map<Integer, String> readMappingFromJSON(String key, JSONObject json)
+            throws JSONException {
+        try {
+            if (json.has(key)) {
+                json = json.getJSONObject(key);
+                Map<Integer, String> map = new HashMap<>();
+                Iterator<String> keys = json.keys();
+
+                while (keys.hasNext()) {
+                    String integer = keys.next();
+                    if (json.get(integer) instanceof String && isInteger(integer)) {
+                        map.put(Integer.parseInt(integer), json.getString(integer));
+                    }
+                }
+                return map;
+            } else {
+                Log.e(TAG, "No tag for " + key + " is found");
+                throw new JSONException(String.format("%s missing from object", key));
+            }
+        } catch (JSONException e) {
+            Log.e(TAG, "Error reading " + key + " for mapping SensorMetadata");
+            throw e;
+        }
+    }
+
+    /**
+     * Helper function to quickly check if a String can be converted to an int. From
+     * https://stackoverflow.com/a/237204
+     *
+     * @param str String to check.
+     * @return true if String can be converted to an int, false if not.
+     */
+    private static boolean isInteger(String str) {
+        if (str == null) {
+            return false;
+        }
+        int length = str.length();
+        if (length == 0) {
+            return false;
+        }
+        int i = 0;
+        if (str.charAt(0) == '-') {
+            if (length == 1) {
+                return false;
+            }
+            i = 1;
+        }
+        for (; i < length; i++) {
+            char c = str.charAt(i);
+            if (c < '0' || c > '9') {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Uses appropriate translation method (mapping, conversion, translation string) to convert
+     * float sensor data into true value
+     *
+     * @param data Float of data to translate
+     * @return Processed data
+     */
+    private String translateData(Float data) {
+        if (mHasConversion) {
+            return String.valueOf(data * mScale + mOffset);
+        } else if (mHasMapping) {
+            return mMapping.getOrDefault(Math.round(data), "No mapping available");
+        }
+        try {
+            return String.valueOf((float) MathEval.eval(mTranslationString, data));
+        } catch (MathEval.TranslationTooLongException e) {
+            Log.e("ServiceCenterTools", "Translation: " + mTranslationString + " too long");
+            return String.valueOf(data);
+        }
+    }
+
+    @Override
+    public String toString() {
+        return "Metadata for "
+                + mName
+                + " with units: "
+                + mUnits
+                + " and translation: "
+                + mTranslationString;
+    }
+
+    /**
+     * Translates and wraps data in a SensorDataWrapper object to allow for it to be displayed
+     *
+     * @param data Sensor data to translate and wrapp
+     * @return SensorDataWrapper that can be displayed in the LiveDataAdapter
+     */
+    public LiveDataAdapter.SensorDataWrapper toLiveDataWrapper(float data) {
+        return new LiveDataAdapter.SensorDataWrapper(mName, mUnits, this.translateData(data));
+    }
+}
diff --git a/tests/DiagnosticTools/tests/Android.mk b/tests/DiagnosticTools/tests/Android.mk
new file mode 100644
index 0000000..8c21035
--- /dev/null
+++ b/tests/DiagnosticTools/tests/Android.mk
@@ -0,0 +1,33 @@
+#
+# Copyright (C) 2019 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(call all-subdir-java-files)
+
+LOCAL_JAVA_LIBRARIES := android.test.runner.stubs android.test.base.stubs
+
+LOCAL_STATIC_JAVA_LIBRARIES := junit
+
+LOCAL_PACKAGE_NAME := DiagnosticToolsTests
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_INSTRUMENTATION_FOR := DiagnosticTools
+
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_PACKAGE)
diff --git a/tests/DiagnosticTools/tests/AndroidManifest.xml b/tests/DiagnosticTools/tests/AndroidManifest.xml
new file mode 100644
index 0000000..ad451f4
--- /dev/null
+++ b/tests/DiagnosticTools/tests/AndroidManifest.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android" 
+      package="com.google.android.car.diagnostictools.tests">
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+  <instrumentation android:name="android.test.InstrumentationTestRunner"
+      android:targetPackage="com.example.android.diagnostictools"
+      android:label="DiagnosticTools sample tests">
+  </instrumentation>  
+  
+</manifest>
diff --git a/tests/DiagnosticTools/tests/build.properties b/tests/DiagnosticTools/tests/build.properties
new file mode 100644
index 0000000..e0c39de
--- /dev/null
+++ b/tests/DiagnosticTools/tests/build.properties
@@ -0,0 +1 @@
+tested.project.dir=..
diff --git a/tests/DiagnosticTools/tests/src/com/google/android/car/diagnostictools/DiagnosticToolsTest.java b/tests/DiagnosticTools/tests/src/com/google/android/car/diagnostictools/DiagnosticToolsTest.java
new file mode 100644
index 0000000..ca26a8f
--- /dev/null
+++ b/tests/DiagnosticTools/tests/src/com/google/android/car/diagnostictools/DiagnosticToolsTest.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.car.diagnostictools;
+
+import android.test.ActivityInstrumentationTestCase2;
+
+/**
+ * Make sure that the main launcher activity opens up properly, which will be
+ * verified by {@link #testActivityTestCaseSetUpProperly}.
+ */
+public class DiagnosticToolsTest extends ActivityInstrumentationTestCase2<ECUListActivity> {
+
+    /**
+     * Creates an {@link ActivityInstrumentationTestCase2} for the {@link ECUListActivity} activity.
+     */
+    public DiagnosticToolsTest() {
+        super(ECUListActivity.class);
+    }
+
+    /**
+     * Verifies that the activity under test can be launched.
+     */
+    public void testActivityTestCaseSetUpProperly() {
+        assertNotNull("activity should be launched successfully", getActivity());
+    }
+}
diff --git a/tests/EmbeddedKitchenSinkApp/Android.mk b/tests/EmbeddedKitchenSinkApp/Android.mk
index f00a14b..b3cff4d 100644
--- a/tests/EmbeddedKitchenSinkApp/Android.mk
+++ b/tests/EmbeddedKitchenSinkApp/Android.mk
@@ -41,17 +41,19 @@
 LOCAL_DEX_PREOPT := false
 
 LOCAL_STATIC_ANDROID_LIBRARIES += \
-    car-service-lib-for-test \
-    androidx.car_car-cluster
+    car-service-test-static-lib \
+    com.google.android.material_material \
+    androidx.appcompat_appcompat \
+    androidx.car_car
 
 LOCAL_STATIC_JAVA_LIBRARIES += \
     android.hidl.base-V1.0-java \
     android.hardware.automotive.vehicle-V2.0-java \
-    vehicle-hal-support-lib \
+    vehicle-hal-support-lib-for-test \
     com.android.car.keventreader-client \
     guava \
-    kitchensink-gson \
-    android.car.cluster.navigation
+    android.car.cluster.navigation \
+    car-experimental-api-static-lib
 
 LOCAL_JAVA_LIBRARIES += android.car
 
@@ -61,9 +63,6 @@
 
 include $(CLEAR_VARS)
 
-LOCAL_PREBUILT_STATIC_JAVA_LIBRARIES := \
-    kitchensink-gson:libs/gson-2.1.jar
-
 include $(BUILD_MULTI_PREBUILT)
 
 include $(CLEAR_VARS)
diff --git a/tests/EmbeddedKitchenSinkApp/AndroidManifest.xml b/tests/EmbeddedKitchenSinkApp/AndroidManifest.xml
index 5612807..19e936b 100644
--- a/tests/EmbeddedKitchenSinkApp/AndroidManifest.xml
+++ b/tests/EmbeddedKitchenSinkApp/AndroidManifest.xml
@@ -15,64 +15,114 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="com.google.android.car.kitchensink"
-        android:sharedUserId="android.uid.system">
-    <uses-sdk
-        android:minSdkVersion="24"
-        android:targetSdkVersion='25'/>
-    <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
+        android:sharedUserId="com.google.android.car.uid.kitchensink"
+        package="com.google.android.car.kitchensink">
+    <uses-permission android:name="android.car.permission.ACCESS_CAR_PROJECTION_STATUS"/>
+    <uses-permission android:name="android.car.permission.CAR_CAMERA"/>
+    <uses-permission android:name="android.car.permission.CAR_CONTROL_AUDIO_SETTINGS"/>
+    <uses-permission android:name="android.car.permission.CAR_CONTROL_AUDIO_VOLUME"/>
+    <uses-permission android:name="android.car.permission.CAR_DIAGNOSTICS"/>
+    <uses-permission android:name="android.car.permission.CAR_DISPLAY_IN_CLUSTER"/>
+    <!-- use for CarServiceTest -->
+    <uses-permission android:name="android.car.permission.CAR_DRIVING_STATE"/>
+    <uses-permission android:name="android.car.permission.CAR_ENERGY"/>
+    <!-- use for AndroidCarApiTest -->
+    <uses-permission android:name="android.car.permission.CAR_INFO"/>
+    <!-- use for AndroidCarApiTest -->
+    <uses-permission android:name="android.car.permission.CAR_PROJECTION"/>
+    <uses-permission android:name="android.car.permission.CAR_INSTRUMENT_CLUSTER_CONTROL"/>
+    <uses-permission android:name="android.car.permission.CAR_MILEAGE"/>
+    <uses-permission android:name="android.car.permission.CAR_MOCK_VEHICLE_HAL"/>
+    <uses-permission android:name="android.car.permission.CAR_NAVIGATION_MANAGER"/>
+    <!-- use for CarServiceTest -->
+    <uses-permission android:name="android.car.permission.CAR_EXTERIOR_ENVIRONMENT"/>
+    <uses-permission android:name="android.car.permission.CAR_POWER"/>
+    <!-- use for CarServiceTest -->
+    <uses-permission android:name="android.car.permission.CAR_POWERTRAIN"/>
+    <uses-permission android:name="android.car.permission.CAR_SPEED"/>
+     <!-- use for CarServiceTest -->
+    <uses-permission android:name="android.car.permission.CAR_TEST_SERVICE"/>
+    <!-- use for CarServiceTest -->
+    <uses-permission android:name="android.car.permission.CAR_VENDOR_EXTENSION"/>
+    <!-- use for CarServiceTest -->
+    <uses-permission android:name="android.car.permission.CAR_UX_RESTRICTIONS_CONFIGURATION"/>
+    <uses-permission android:name="android.car.permission.CONTROL_CAR_CLIMATE"/>
+    <uses-permission android:name="android.car.permission.READ_CAR_STEERING"/>
+    <uses-permission android:name="android.car.permission.STORAGE_MONITORING"/>
+    <uses-permission android:name="android.car.permission.VEHICLE_DYNAMICS_STATE"/>
+    <!-- use for CarServiceTest -->
+    <uses-permission android:name="android.car.permission.VMS_PUBLISHER"/>
+    <!-- use for CarServiceTest -->
+    <uses-permission android:name="android.car.permission.VMS_SUBSCRIBER"/>
     <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
     <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
-    <uses-permission android:name="android.car.permission.CAR_SPEED" />
-    <uses-permission android:name="android.car.permission.CAR_MILEAGE" />
-    <uses-permission android:name="android.car.permission.CAR_ENERGY" />
-    <uses-permission android:name="android.car.permission.CONTROL_CAR_CLIMATE" />
-    <uses-permission android:name="android.car.permission.CAR_MOCK_VEHICLE_HAL" />
-    <uses-permission android:name="android.car.permission.CAR_CAMERA" />
-    <uses-permission android:name="android.car.permission.CAR_NAVIGATION_MANAGER"/>
-    <uses-permission android:name="android.car.permission.CAR_CONTROL_AUDIO_VOLUME"/>
-    <uses-permission android:name="android.car.permission.VEHICLE_DYNAMICS_STATE"/>
-    <uses-permission android:name="android.car.permission.CAR_DISPLAY_IN_CLUSTER"/>
-    <uses-permission android:name="android.car.permission.CAR_INSTRUMENT_CLUSTER_CONTROL"/>
-    <uses-permission android:name="android.car.permission.STORAGE_MONITORING" />
-    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
-    <uses-permission android:name="android.permission.MANAGE_USB" />
-    <uses-permission android:name="android.permission.WRITE_SETTINGS" />
-    <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
-    <uses-permission android:name="android.permission.SEND_SMS" />
+    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
+    <uses-permission android:name="android.permission.ACCESS_VOICE_INTERACTION_SERVICE"/>
+    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
+    <uses-permission android:name="android.permission.ACTIVITY_EMBEDDING"/>
+    <uses-permission android:name="android.permission.BLUETOOTH"/>
+    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
+    <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE"/>
+    <uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>
+    <uses-permission android:name="android.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS"/>
+    <uses-permission android:name="android.permission.INJECT_EVENTS"/>
+    <!-- use for CarServiceUnitTest and CarServiceTest -->
+    <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS"/>
+    <!-- use for CarServiceUnitTest -->
+    <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL"/>
+    <uses-permission android:name="android.permission.INTERNAL_SYSTEM_WINDOW"/>
+    <uses-permission android:name="android.permission.LOCATION_HARDWARE"/>
+    <uses-permission android:name="android.permission.MANAGE_USB"/>
+    <uses-permission android:name="android.permission.MANAGE_USERS"/>
+    <!-- use for CarServiceTest -->
+    <uses-permission android:name="android.permission.MEDIA_CONTENT_CONTROL"/>
+    <uses-permission android:name="android.permission.MODIFY_AUDIO_ROUTING"/>
+    <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>
+    <uses-permission android:name="android.permission.MODIFY_DAY_NIGHT_MODE"/>
+    <uses-permission android:name="android.permission.MODIFY_PHONE_STATE"/>
+    <uses-permission android:name="android.permission.OVERRIDE_WIFI_CONFIG"/>
+    <uses-permission android:name="android.permission.PROVIDE_TRUST_AGENT"/>
+    <uses-permission android:name="android.permission.READ_LOGS"/>
     <uses-permission android:name="android.permission.READ_SMS"/>
-    <uses-permission android:name="android.permission.BLUETOOTH" />
-    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
-    <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
-    <uses-permission android:name="android.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS" />
-    <uses-permission android:name="android.permission.INJECT_EVENTS" />
-    <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
+    <uses-permission android:name="android.permission.REAL_GET_TASKS"/>
+    <uses-permission android:name="android.permission.REBOOT"/>
+    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
+    <uses-permission android:name="android.permission.SEND_SMS"/>
+    <!-- use for CarServiceTest -->
+    <uses-permission android:name="android.permission.SET_ACTIVITY_WATCHER"/>
+    <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS"/>
+    <uses-permission android:name="android.permission.WRITE_SETTINGS"/>
+    <!-- use for vendor properties -->
+    <uses-permission android:name="android.car.permission.CAR_VENDOR_EXTENSION" />
+    <uses-permission android:name="android.car.permission.GET_CAR_VENDOR_CATEGORY_SEAT" />
+    <uses-permission android:name="android.car.permission.GET_CAR_VENDOR_CATEGORY_INFO" />
+    <uses-permission android:name="android.car.permission.SET_CAR_VENDOR_CATEGORY_INFO" />
+
+    <uses-permission android:name="android.car.permission.CONTROL_CAR_FEATURES"/>
 
     <application android:label="@string/app_title"
         android:icon="@drawable/ic_launcher">
-        <uses-library android:name="android.test.runner" />
-
-
+        <uses-library android:name="android.test.runner"/>
         <!-- This is for embedded mode. -->
         <activity android:name=".KitchenSinkActivity"
             android:theme="@style/KitchenSinkActivityTheme"
             android:label="@string/app_title"
             android:launchMode="singleTask">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
 
         <meta-data
             android:name="android.car.application"
-            android:resource="@xml/automotive_app_desc" />
+            android:resource="@xml/automotive_app_desc"/>
 
         <activity android:name=".orientation.LandscapeActivity"
                   android:label="@string/landscpae_activity"
                   android:screenOrientation="landscape">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
+                <action android:name="android.intent.action.MAIN"/>
             </intent-filter>
         </activity>
 
@@ -80,7 +130,7 @@
             android:label="@string/portrait_activity"
             android:screenOrientation="portrait">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
+                <action android:name="android.intent.action.MAIN"/>
             </intent-filter>
         </activity>
 
@@ -97,24 +147,17 @@
 
         <activity android:name=".activityview.ActivityViewTestFragment"/>
 
-        <!-- temporary solution until b/68882625 is fixed. -->
-        <receiver android:name=".touchsound.DisableTouchSoundOnBoot" android:exported="true">
-            <intent-filter>
-                <action android:name="android.intent.action.BOOT_COMPLETED"/>
-            </intent-filter>
-        </receiver>
-
         <service android:name=".vendorservice.LogLifecycleService"
                  android:exported="false" android:directBootAware="true">
         </service>
 
-        <service android:name=".UserNoiticeDemoUiService" android:directBootAware="true" />
+        <service android:name=".UserNoiticeDemoUiService" android:directBootAware="true"/>
 
         <!-- Content provider for images -->
         <provider android:name=".cluster.ClusterContentProvider"
                   android:authorities="com.google.android.car.kitchensink.cluster.clustercontentprovider"
                   android:grantUriPermissions="true"
-                  android:exported="true" />
+                  android:exported="true"/>
 
         <activity android:name=".AlwaysCrashingActivity"
                   android:label="@string/always_crashing_activity">
@@ -139,3 +182,4 @@
 
     </application>
 </manifest>
+
diff --git a/tests/EmbeddedKitchenSinkApp/libs/gson-2.1-sources.jar b/tests/EmbeddedKitchenSinkApp/libs/gson-2.1-sources.jar
deleted file mode 100644
index 09396a0..0000000
--- a/tests/EmbeddedKitchenSinkApp/libs/gson-2.1-sources.jar
+++ /dev/null
Binary files differ
diff --git a/tests/EmbeddedKitchenSinkApp/libs/gson-2.1.jar b/tests/EmbeddedKitchenSinkApp/libs/gson-2.1.jar
deleted file mode 100644
index 83c5c99..0000000
--- a/tests/EmbeddedKitchenSinkApp/libs/gson-2.1.jar
+++ /dev/null
Binary files differ
diff --git a/tests/EmbeddedKitchenSinkApp/res/layout/activity_view_test_fragment.xml b/tests/EmbeddedKitchenSinkApp/res/layout/activity_view_test_fragment.xml
index bd74423..568bad5 100644
--- a/tests/EmbeddedKitchenSinkApp/res/layout/activity_view_test_fragment.xml
+++ b/tests/EmbeddedKitchenSinkApp/res/layout/activity_view_test_fragment.xml
@@ -19,7 +19,7 @@
     <RelativeLayout android:layout_width="wrap_content"
                     android:layout_height="wrap_content"
                     android:padding="20dp">
-        <ActivityView android:id="@+id/av_activityview"
+        <android.car.app.CarActivityView android:id="@+id/av_activityview"
                       android:layout_width="match_parent"
                       android:layout_height="match_parent"/>
     </RelativeLayout>
diff --git a/tests/EmbeddedKitchenSinkApp/res/layout/connectivity_fragment.xml b/tests/EmbeddedKitchenSinkApp/res/layout/connectivity_fragment.xml
index c6e75fe..3d8f94a 100644
--- a/tests/EmbeddedKitchenSinkApp/res/layout/connectivity_fragment.xml
+++ b/tests/EmbeddedKitchenSinkApp/res/layout/connectivity_fragment.xml
@@ -14,122 +14,168 @@
      limitations under the License.
 -->
 
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-              android:orientation="vertical"
-              android:layout_width="match_parent"
-              android:layout_height="match_parent">
+<androidx.viewpager.widget.ViewPager
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/connectivity_pager"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <com.google.android.material.tabs.TabLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_gravity="top" />
 
     <LinearLayout
+        android:tag="WiFi Control"
+        android:orientation="vertical"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent">
+
+        <LinearLayout
             android:orientation="horizontal"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:layout_margin="4dp">
 
-        <TextView
+            <TextView
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
                 android:layout_marginRight="20dp"
                 android:text="Wifi:" />
 
-        <Button android:id="@+id/startWifi"
+            <Button android:id="@+id/startWifi"
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
                 android:layout_marginRight="5dp"
                 android:text="Start" />
 
-        <Button android:id="@+id/stopWifi"
+            <Button android:id="@+id/stopWifi"
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
                 android:layout_marginRight="20dp"
                 android:text="Stop" />
 
-        <TextView
+            <TextView
                 android:id="@+id/wifiStatusPolled"
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content" />
-    </LinearLayout>
+        </LinearLayout>
 
-    <LinearLayout
+        <LinearLayout
             android:orientation="horizontal"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:layout_margin="4dp">
 
-        <TextView
+            <TextView
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
                 android:layout_marginRight="20dp"
                 android:text="Tethering:" />
 
-        <Button android:id="@+id/startTethering"
+            <Button android:id="@+id/startTethering"
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
                 android:layout_marginRight="5dp"
                 android:text="Start" />
 
-        <Button android:id="@+id/stopTethering"
+            <Button android:id="@+id/stopTethering"
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
                 android:layout_marginRight="20dp"
                 android:text="Stop" />
 
-        <TextView
+            <TextView
                 android:id="@+id/tetheringStatus"
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
                 android:layout_marginRight="5dp" />
 
-        <TextView
+            <TextView
                 android:id="@+id/tetheringStatusPolled"
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content" />
-    </LinearLayout>
+        </LinearLayout>
 
-    <LinearLayout
+        <LinearLayout
             android:orientation="horizontal"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:layout_margin="4dp">
 
-        <TextView
+            <TextView
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
                 android:layout_marginRight="20dp"
                 android:text="LocalOnly:" />
 
-        <Button android:id="@+id/startLocalOnly"
+            <Button android:id="@+id/startLocalOnly"
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
                 android:layout_marginRight="5dp"
                 android:text="Start" />
 
-        <Button android:id="@+id/stopLocalOnly"
+            <Button android:id="@+id/stopLocalOnly"
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
                 android:layout_marginRight="20dp"
                 android:text="Stop" />
 
-        <TextView
+            <TextView
                 android:id="@+id/localOnlyStatus"
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content" />
+        </LinearLayout>
+
+        <LinearLayout
+            android:orientation="horizontal"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_margin="4dp">
+            <Button android:id="@+id/networkEnableWifiIntent"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="Enable Wifi Intent"/>
+            <Button android:id="@+id/networkDisableWifiIntent"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="Disable Wifi Intent"/>
+        </LinearLayout>
+
+        <LinearLayout
+            android:orientation="horizontal"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_margin="4dp">
+            <Button android:id="@+id/networkEnableBluetoothIntent"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="Enable Bluetooth Intent"/>
+            <Button android:id="@+id/networkDisableBluetoothIntent"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="Disable Bluetooth Intent"/>
+            <Button android:id="@+id/networkDiscoverableBluetoothIntent"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="Discoverable Bluetooth Intent"/>
+        </LinearLayout>
     </LinearLayout>
 
-    <!-- List(s) of networks (right now only have view all, may do a search UI)
-    -->
     <LinearLayout
-            android:orientation="vertical"
-            android:layout_width="match_parent"
-            android:layout_height="match_parent">
+        android:tag="Network list"
+        android:orientation="vertical"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent">
 
         <!-- Header for table -->
         <LinearLayout
-                android:orientation="horizontal"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:paddingLeft="4dp"
-                android:background="#3C3F41"
-                android:weightSum="12">
+            android:orientation="horizontal"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:paddingLeft="4dp"
+            android:background="#3C3F41"
+            android:weightSum="12">
 
             <TextView
                 android:layout_width="0dp"
@@ -138,8 +184,7 @@
                 android:textSize="32sp"
                 android:textColor="#d4d4d4"
                 android:textStyle="bold"
-                android:text="Net ID">
-            </TextView>
+                android:text="Net ID" />
 
             <TextView
                 android:layout_width="0dp"
@@ -148,8 +193,7 @@
                 android:textSize="32sp"
                 android:textColor="#d4d4d4"
                 android:textStyle="bold"
-                android:text="Details">
-            </TextView>
+                android:text="Details" />
 
             <TextView
                 android:layout_width="0dp"
@@ -158,8 +202,7 @@
                 android:textSize="32sp"
                 android:textColor="#d4d4d4"
                 android:textStyle="bold"
-                android:text="Functions">
-            </TextView>
+                android:text="Functions" />
 
         </LinearLayout>
 
@@ -171,47 +214,12 @@
 
             <!-- Filled in code with network_item.xml -->
             <ListView
-                    android:id="@+id/networks"
-                    android:layout_width="match_parent"
-                    android:layout_height="wrap_content">
-            </ListView>
+                android:id="@+id/networks"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content" />
 
         </androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
 
     </LinearLayout>
 
-    <LinearLayout
-        android:orientation="horizontal"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_margin="4dp">
-        <Button android:id="@+id/networkEnableWifiIntent"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:text="Enable Wifi Intent"/>
-        <Button android:id="@+id/networkDisableWifiIntent"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:text="Disable Wifi Intent"/>
-    </LinearLayout>
-
-    <LinearLayout
-        android:orientation="horizontal"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_margin="4dp">
-        <Button android:id="@+id/networkEnableBluetoothIntent"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:text="Enable Bluetooth Intent"/>
-        <Button android:id="@+id/networkDisableBluetoothIntent"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:text="Disable Bluetooth Intent"/>
-        <Button android:id="@+id/networkDiscoverableBluetoothIntent"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:text="Discoverable Bluetooth Intent"/>
-    </LinearLayout>
-
-</LinearLayout>
+</androidx.viewpager.widget.ViewPager>
diff --git a/tests/EmbeddedKitchenSinkApp/res/layout/experimental_feature_test.xml b/tests/EmbeddedKitchenSinkApp/res/layout/experimental_feature_test.xml
new file mode 100644
index 0000000..6119157
--- /dev/null
+++ b/tests/EmbeddedKitchenSinkApp/res/layout/experimental_feature_test.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<LinearLayout
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent"
+    xmlns:android="http://schemas.android.com/apk/res/android">
+    <ScrollView
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content">
+        <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:orientation="vertical" >
+            <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+                          android:layout_width="match_parent"
+                          android:layout_height="match_parent"
+                          android:orientation="horizontal" >
+                <Button
+                    android:id="@+id/button_experimental_ping"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/experimental_ping" />
+                <TextView
+                    android:id="@+id/experimental_ping_msg"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/empty"
+                    android:layout_weight="1" />
+            </LinearLayout>
+        </LinearLayout>
+     </ScrollView>
+</LinearLayout>
diff --git a/tests/EmbeddedKitchenSinkApp/res/layout/kitchen_activity.xml b/tests/EmbeddedKitchenSinkApp/res/layout/kitchen_activity.xml
index f0439c4..e6de221 100644
--- a/tests/EmbeddedKitchenSinkApp/res/layout/kitchen_activity.xml
+++ b/tests/EmbeddedKitchenSinkApp/res/layout/kitchen_activity.xml
@@ -26,7 +26,6 @@
         android:layout_height="wrap_content"
         android:layout_centerHorizontal="true"
         android:layout_marginBottom="10dp"
-        android:background="@android:color/background_light"
         android:text="Hide KitchenSink Menu"
         android:textSize="30sp"
         android:padding="15dp"/>
diff --git a/tests/EmbeddedKitchenSinkApp/res/layout/notification_fragment.xml b/tests/EmbeddedKitchenSinkApp/res/layout/notification_fragment.xml
index 088aa68..43be213 100644
--- a/tests/EmbeddedKitchenSinkApp/res/layout/notification_fragment.xml
+++ b/tests/EmbeddedKitchenSinkApp/res/layout/notification_fragment.xml
@@ -55,13 +55,23 @@
                 android:textSize="30sp"/>
 
             <Button
-                android:id="@+id/category_message_button"
+                android:id="@+id/category_message_diff_person_button"
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
                 android:layout_margin="10dp"
                 android:background="#ffa9a8"
                 android:foreground="?android:attr/selectableItemBackground"
-                android:text="Message"
+                android:text="Message from diff person"
+                android:textSize="30sp"/>
+
+            <Button
+                android:id="@+id/category_message_same_person_button"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_margin="10dp"
+                android:background="#ffa9a8"
+                android:foreground="?android:attr/selectableItemBackground"
+                android:text="Message from same person"
                 android:textSize="30sp"/>
 
             <Button
@@ -109,6 +119,22 @@
                 android:layout_margin="10dp"
                 android:text="Progress (Can't dismiss)"
                 android:textSize="30sp"/>
+
+            <Button
+                android:id="@+id/custom_group_summary_button"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_margin="10dp"
+                android:text="Custom Group Summary of 6 (Inbox Style, Should behave the same as phone)"
+                android:textSize="30sp"/>
+
+            <Button
+                android:id="@+id/group_without_summary_button"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_margin="10dp"
+                android:text="Custom Group Without Summary of 6 (Should not group)"
+                android:textSize="30sp"/>
         </LinearLayout>
 
         <LinearLayout
diff --git a/tests/EmbeddedKitchenSinkApp/res/layout/package_info_test.xml b/tests/EmbeddedKitchenSinkApp/res/layout/package_info_test.xml
new file mode 100644
index 0000000..23a5765
--- /dev/null
+++ b/tests/EmbeddedKitchenSinkApp/res/layout/package_info_test.xml
@@ -0,0 +1,83 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+          http://www.apache.org/licenses/LICENSE-2.0
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<LinearLayout
+    android:layout_width="fill_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical"
+    xmlns:android="http://schemas.android.com/apk/res/android">
+    <LinearLayout
+        android:id="@+id/header"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_margin="@dimen/header_margin"
+        android:orientation="horizontal">
+        <Spinner
+            android:id = "@+id/spinner"
+            android:layout_width = "wrap_content"
+            android:layout_height = "wrap_content"
+            android:layout_weight = "1"
+            android:paddingLeft="@dimen/header_padding"
+            android:prompt = "@string/select_user"
+            android:textSize = "@dimen/spinner_text_size"
+            />
+        <CheckBox
+            android:id = "@+id/checkbox_activities"
+            android:layout_width = "wrap_content"
+            android:layout_height = "wrap_content"
+            android:layout_weight = "1"
+            android:paddingLeft="@dimen/header_padding"
+            android:text = "@string/checkbox_activities"
+            android:textSize = "@dimen/checkbox_text_size"
+            />
+        <CheckBox
+            android:id = "@+id/checkbox_services"
+            android:layout_width = "wrap_content"
+            android:layout_height = "wrap_content"
+            android:layout_weight = "1"
+            android:text = "@string/checkbox_services"
+            android:textSize = "@dimen/checkbox_text_size"
+            />
+        <CheckBox
+            android:id = "@+id/checkbox_permissions"
+            android:layout_width = "wrap_content"
+            android:layout_height = "wrap_content"
+            android:layout_weight = "1"
+            android:text = "@string/checkbox_permissions"
+            android:textSize = "@dimen/checkbox_text_size"
+            />
+        <CheckBox
+            android:id = "@+id/checkbox_shareduid"
+            android:layout_width = "wrap_content"
+            android:layout_height = "wrap_content"
+            android:layout_weight = "1"
+            android:text = "@string/checkbox_shareduid"
+            android:textSize = "@dimen/checkbox_text_size"
+            />
+        <Button
+            android:id = "@+id/button_show"
+            android:layout_width = "wrap_content"
+            android:layout_height = "wrap_content"
+            android:layout_weight = "1"
+            android:text = "@string/button_show"
+            android:textSize = "@dimen/checkbox_text_size"
+            />
+    </LinearLayout>
+    <ScrollView
+        android:layout_width="match_parent"
+        android:layout_height="match_parent">
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:id="@+id/packages"/>
+     </ScrollView>
+</LinearLayout>
diff --git a/tests/EmbeddedKitchenSinkApp/res/layout/sensors.xml b/tests/EmbeddedKitchenSinkApp/res/layout/sensors.xml
index 6c4d945..3d7f320 100644
--- a/tests/EmbeddedKitchenSinkApp/res/layout/sensors.xml
+++ b/tests/EmbeddedKitchenSinkApp/res/layout/sensors.xml
@@ -23,7 +23,7 @@
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:orientation="vertical"
-            android:layout_centerHorizontal="true">
+            android:layout_marginLeft="30dp">
         <TextView
             android:id="@+id/location_title"
             android:layout_width="wrap_content"
@@ -59,9 +59,8 @@
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:orientation="vertical"
-            android:layout_centerHorizontal="true"
-            android:layout_below="@+id/location_layout"
-            android:layout_alignStart="@+id/location_layout">
+            android:layout_marginLeft="30dp"
+            android:layout_below="@+id/location_layout">
         <TextView
             android:id="@+id/sensor_title"
             android:layout_width="wrap_content"
diff --git a/tests/EmbeddedKitchenSinkApp/res/layout/vehicle_ctrl_fragment.xml b/tests/EmbeddedKitchenSinkApp/res/layout/vehicle_ctrl_fragment.xml
new file mode 100644
index 0000000..0b3ade4
--- /dev/null
+++ b/tests/EmbeddedKitchenSinkApp/res/layout/vehicle_ctrl_fragment.xml
@@ -0,0 +1,169 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<androidx.viewpager.widget.ViewPager
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/vehicle_ctrl_pager"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <com.google.android.material.tabs.TabLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_gravity="top" />
+
+    <LinearLayout
+        android:tag="Doors/windows"
+        android:orientation="vertical"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent">
+
+        <LinearLayout
+            android:orientation="horizontal"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content">
+
+            <TextView
+                android:layout_width="300dp"
+                android:layout_height="wrap_content"
+                android:text="Front left window:" />
+
+            <Button android:id="@+id/winFLOpen"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="Open" />
+
+            <Button android:id="@+id/winFLClose"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="Close" />
+
+            <TextView android:id="@+id/winFLPos"
+                android:layout_marginLeft="20dp"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content" />
+        </LinearLayout>
+
+        <LinearLayout
+            android:orientation="horizontal"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content">
+
+            <TextView
+                android:layout_width="300dp"
+                android:layout_height="wrap_content"
+                android:text="Front right window:" />
+
+            <Button android:id="@+id/winFROpen"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="Open" />
+
+            <Button android:id="@+id/winFRClose"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="Close" />
+
+            <TextView android:id="@+id/winFRPos"
+                android:layout_marginLeft="20dp"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content" />
+        </LinearLayout>
+
+        <LinearLayout
+            android:orientation="horizontal"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content">
+
+            <TextView
+                android:layout_width="300dp"
+                android:layout_height="wrap_content"
+                android:text="Rear left window:" />
+
+            <Button android:id="@+id/winRLOpen"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="Open" />
+
+            <Button android:id="@+id/winRLClose"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="Close" />
+
+            <TextView android:id="@+id/winRLPos"
+                android:layout_marginLeft="20dp"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content" />
+        </LinearLayout>
+
+        <LinearLayout
+            android:orientation="horizontal"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content">
+
+            <TextView
+                android:layout_width="300dp"
+                android:layout_height="wrap_content"
+                android:text="Rear right window:" />
+
+            <Button android:id="@+id/winRROpen"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="Open" />
+
+            <Button android:id="@+id/winRRClose"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="Close" />
+
+            <TextView android:id="@+id/winRRPos"
+                android:layout_marginLeft="20dp"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content" />
+        </LinearLayout>
+    </LinearLayout>
+
+    <LinearLayout
+        android:tag="Prop test"
+        android:orientation="vertical"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent">
+
+        <LinearLayout
+            android:orientation="horizontal"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content">
+
+            <TextView
+                android:layout_width="300dp"
+                android:layout_height="wrap_content"
+                android:text="PROTOCAN_TEST" />
+
+            <Button android:id="@+id/protocan_test_on"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="ON" />
+
+            <Button android:id="@+id/protocan_test_off"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="OFF" />
+
+        </LinearLayout>
+    </LinearLayout>
+
+
+</androidx.viewpager.widget.ViewPager>
diff --git a/tests/EmbeddedKitchenSinkApp/res/values/dimens.xml b/tests/EmbeddedKitchenSinkApp/res/values/dimens.xml
index 437d69d..a649f9c 100644
--- a/tests/EmbeddedKitchenSinkApp/res/values/dimens.xml
+++ b/tests/EmbeddedKitchenSinkApp/res/values/dimens.xml
@@ -45,6 +45,12 @@
     <dimen name="car_card_header_height">96dp</dimen>
     <dimen name="car_card_action_bar_height">96dp</dimen>
 
+    <!-- Package info -->
+    <dimen name="spinner_text_size">16sp</dimen>
+    <dimen name="checkbox_text_size">16sp</dimen>
+    <dimen name="header_margin">8dp</dimen>
+    <dimen name="header_padding">8dp</dimen>
+
     <!-- Paddings -->
     <dimen name="car_padding_1">4dp</dimen>
     <dimen name="car_padding_2">10dp</dimen>
diff --git a/tests/EmbeddedKitchenSinkApp/res/values/strings.xml b/tests/EmbeddedKitchenSinkApp/res/values/strings.xml
index 0d56369..da2d341 100644
--- a/tests/EmbeddedKitchenSinkApp/res/values/strings.xml
+++ b/tests/EmbeddedKitchenSinkApp/res/values/strings.xml
@@ -178,6 +178,14 @@
     <string name="sw_center" translatable="false">Center</string>
     <string name="sw_back" translatable="false">Back</string>
 
+    <!-- package info test -->
+    <string name="select_user" translatable="false">Select user</string>
+    <string name="checkbox_activities" translatable="false">contains only activities</string>
+    <string name="checkbox_services" translatable="false">services are not exported</string>
+    <string name="checkbox_permissions" translatable="false"> does not require selected permissions</string>
+    <string name="checkbox_shareduid" translatable="false">sharedUserId is not system</string>
+    <string name="button_show" translatable="false">Show</string>
+
     <!-- power test -->
     <string name="power_request_shutdown" translatable="false">Request Shutdown</string>
     <string name="power_shutdown" translatable="false">Shutdown</string>
@@ -337,4 +345,7 @@
     <string name="always_crashing_activity" translatable="false">Always Crash Activity</string>
     <string name="no_crash_activity" translatable="false">No Crash Activity</string>
     <string name="empty_activity" translatable="false">Empty Activity</string>
+
+    <!-- ExperimentalFeatureTest -->
+    <string name="experimental_ping" translatable="false">Ping Service</string>
 </resources>
diff --git a/tests/EmbeddedKitchenSinkApp/res/values/styles.xml b/tests/EmbeddedKitchenSinkApp/res/values/styles.xml
index 908c051..7f450ad 100644
--- a/tests/EmbeddedKitchenSinkApp/res/values/styles.xml
+++ b/tests/EmbeddedKitchenSinkApp/res/values/styles.xml
@@ -34,6 +34,7 @@
         <item name="android:clickable">true</item>
     </style>
 
-    <style name="KitchenSinkActivityTheme" parent="android:Theme.DeviceDefault.NoActionBar">
+    <style name="KitchenSinkActivityTheme" parent="Theme.AppCompat.NoActionBar">
+        <item name="android:textSize">24sp</item>
     </style>
 </resources>
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/KitchenSinkActivity.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/KitchenSinkActivity.java
index b4a4d1b..d751a43 100644
--- a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/KitchenSinkActivity.java
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/KitchenSinkActivity.java
@@ -53,10 +53,11 @@
 import com.google.android.car.kitchensink.cube.CubesTestFragment;
 import com.google.android.car.kitchensink.diagnostic.DiagnosticTestFragment;
 import com.google.android.car.kitchensink.displayinfo.DisplayInfoFragment;
+import com.google.android.car.kitchensink.experimental.ExperimentalFeatureTestFragment;
 import com.google.android.car.kitchensink.hvac.HvacTestFragment;
-import com.google.android.car.kitchensink.input.InputTestFragment;
 import com.google.android.car.kitchensink.notification.NotificationFragment;
 import com.google.android.car.kitchensink.orientation.OrientationTestFragment;
+import com.google.android.car.kitchensink.packageinfo.PackageInfoFragment;
 import com.google.android.car.kitchensink.power.PowerTestFragment;
 import com.google.android.car.kitchensink.projection.ProjectionFragment;
 import com.google.android.car.kitchensink.property.PropertyTestFragment;
@@ -65,6 +66,7 @@
 import com.google.android.car.kitchensink.storagevolumes.StorageVolumesFragment;
 import com.google.android.car.kitchensink.touch.TouchTestFragment;
 import com.google.android.car.kitchensink.users.UsersFragment;
+import com.google.android.car.kitchensink.vehiclectrl.VehicleCtrlFragment;
 import com.google.android.car.kitchensink.vhal.VehicleHalFragment;
 import com.google.android.car.kitchensink.volume.VolumeTestFragment;
 import com.google.android.car.kitchensink.weblinks.WebLinksTestFragment;
@@ -162,18 +164,19 @@
             new FragmentMenuEntry("alert window", AlertDialogTestFragment.class),
             new FragmentMenuEntry("assistant", CarAssistantFragment.class),
             new FragmentMenuEntry("audio", AudioTestFragment.class),
-
-            new FragmentMenuEntry("bluetooth headset", BluetoothHeadsetFragment.class),
-            new FragmentMenuEntry("bluetooth messaging test", MapMceTestFragment.class),
+            new FragmentMenuEntry("BT headset", BluetoothHeadsetFragment.class),
+            new FragmentMenuEntry("BT messaging", MapMceTestFragment.class),
             new FragmentMenuEntry("carapi", CarApiTestFragment.class),
             new FragmentMenuEntry("carboard", KeyboardTestFragment.class),
             new FragmentMenuEntry("connectivity", ConnectivityFragment.class),
             new FragmentMenuEntry("cubes test", CubesTestFragment.class),
             new FragmentMenuEntry("diagnostic", DiagnosticTestFragment.class),
             new FragmentMenuEntry("display info", DisplayInfoFragment.class),
+            new FragmentMenuEntry("experimental feature", ExperimentalFeatureTestFragment.class),
             new FragmentMenuEntry("hvac", HvacTestFragment.class),
             new FragmentMenuEntry("inst cluster", InstrumentClusterFragment.class),
-            new FragmentMenuEntry("input test", InputTestFragment.class),
+            // TODO (b/141774865) Enable after b/141635607 is fixed
+            // new FragmentMenuEntry("input test", InputTestFragment.class),
             new FragmentMenuEntry("notification", NotificationFragment.class),
             new FragmentMenuEntry("orientation test", OrientationTestFragment.class),
             new FragmentMenuEntry("power test", PowerTestFragment.class),
@@ -185,8 +188,10 @@
             new FragmentMenuEntry("touch test", TouchTestFragment.class),
             new FragmentMenuEntry("users", UsersFragment.class),
             new FragmentMenuEntry("volume test", VolumeTestFragment.class),
+            new FragmentMenuEntry("vehicle ctrl", VehicleCtrlFragment.class),
             new FragmentMenuEntry("vehicle hal", VehicleHalFragment.class),
-            new FragmentMenuEntry("web links", WebLinksTestFragment.class)
+            new FragmentMenuEntry("web links", WebLinksTestFragment.class),
+            new FragmentMenuEntry("package info", PackageInfoFragment.class)
     );
 
     private Car mCarApi;
@@ -249,7 +254,7 @@
 
         mMenu = findViewById(R.id.menu);
         mMenu.setAdapter(new MenuAdapter(this));
-        mMenu.setLayoutManager(new GridLayoutManager(this, 3));
+        mMenu.setLayoutManager(new GridLayoutManager(this, 4));
 
         mMenuButton = findViewById(R.id.menu_button);
         mMenuButton.setOnClickListener(view -> toggleMenuVisibility());
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/SimplePagerAdapter.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/SimplePagerAdapter.java
new file mode 100644
index 0000000..aedd3dd
--- /dev/null
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/SimplePagerAdapter.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.car.kitchensink;
+
+import android.util.Pair;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.viewpager.widget.PagerAdapter;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class SimplePagerAdapter extends PagerAdapter {
+    private final List<Pair<View, String>> mPages = new ArrayList<>();
+
+    public SimplePagerAdapter(ViewGroup container) {
+        for (int i = 0; i < container.getChildCount(); i++) {
+            final View child = container.getChildAt(i);
+            final Object tag = child.getTag();
+            if (!(tag instanceof String)) continue;
+            mPages.add(new Pair<>(child, (String) tag));
+        }
+    }
+
+    @Override
+    public Object instantiateItem(ViewGroup container, int position) {
+        return mPages.get(position).first;
+    }
+
+    @Override
+    public int getCount() {
+        return mPages.size();
+    }
+
+    @Override
+    public CharSequence getPageTitle(int position) {
+        return mPages.get(position).second;
+    }
+
+    @Override
+    public boolean isViewFromObject(View view, Object object) {
+        return view == object;
+    }
+}
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/activityview/ActivityViewTestFragment.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/activityview/ActivityViewTestFragment.java
index a41427d..40c470d 100644
--- a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/activityview/ActivityViewTestFragment.java
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/activityview/ActivityViewTestFragment.java
@@ -16,7 +16,7 @@
 
 package com.google.android.car.kitchensink.activityview;
 
-import android.app.ActivityView;
+import android.car.app.CarActivityView;
 import android.content.ComponentName;
 import android.content.Intent;
 import android.os.Bundle;
@@ -36,7 +36,7 @@
  * This fragment exercises the capabilities of virtual display.
  */
 public class ActivityViewTestFragment extends Fragment {
-    private ActivityView mActivityView;
+    private CarActivityView mActivityView;
     private ViewGroup mActivityViewParent;
 
     @Override
@@ -62,7 +62,7 @@
 
     private void onLaunchActivityClicked(View v) {
         Intent intent = new Intent();
-        intent.setComponent(ComponentName.createRelative("com.android.settings", ".Settings"));
+        intent.setComponent(ComponentName.createRelative("com.android.car.settings", ".Settings"));
         intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
         mActivityView.startActivity(intent);
     }
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/audio/AudioPlayer.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/audio/AudioPlayer.java
index a16160f..909c44c 100644
--- a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/audio/AudioPlayer.java
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/audio/AudioPlayer.java
@@ -93,6 +93,7 @@
     private final Context mContext;
     private final int mResourceId;
     private final AudioAttributes mAttrib;
+    private final AudioDeviceInfo mPreferredDeviceInfo;
 
     private final AtomicBoolean mPlaying = new AtomicBoolean(false);
 
@@ -101,15 +102,17 @@
 
     private PlayStateListener mListener;
 
+
     public AudioPlayer(Context context, int resourceId, AudioAttributes attrib) {
+        this(context, resourceId, attrib, /* deviceInfo= */ null);
+    }
+
+    public AudioPlayer(Context context, int resourceId, AudioAttributes attrib,
+            @Nullable AudioDeviceInfo preferredDeviceInfo) {
         mContext = context;
         mResourceId = resourceId;
         mAttrib = attrib;
-    }
-
-    public void start(boolean handleFocus, boolean repeat, int focusRequest) {
-        String nullDeviceAddress = null;
-        start(handleFocus, repeat, focusRequest, nullDeviceAddress);
+        mPreferredDeviceInfo = preferredDeviceInfo;
     }
 
     /**
@@ -117,10 +120,8 @@
      * @param handleFocus true to handle focus
      * @param repeat true to repeat track
      * @param focusRequest type of focus to request
-     * @param deviceAddress preferred device to attached to audio
      */
-    public void start(boolean handleFocus, boolean repeat, int focusRequest,
-            @Nullable String deviceAddress) {
+    public void start(boolean handleFocus, boolean repeat, int focusRequest) {
         mHandleFocus = handleFocus;
         mRepeat = repeat;
         mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
@@ -130,12 +131,11 @@
             // exercise the framework's focus logic when faced with a (sloppy) application which
             // might do this.
             Log.i(TAG, "Asking for focus for usage " + mAttrib.getUsage());
-            ret = mAudioManager.requestAudioFocus(mFocusListener, mAttrib,
-                    focusRequest, 0);
+            ret = mAudioManager.requestAudioFocus(mFocusListener, mAttrib, focusRequest, 0);
         }
         if (ret == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
             Log.i(TAG, "MediaPlayer got focus for usage " + mAttrib.getUsage());
-            doStart(deviceAddress);
+            doStart();
         } else {
             Log.i(TAG, "MediaPlayer denied focus for usage " + mAttrib.getUsage());
         }
@@ -147,7 +147,7 @@
         start(handleFocus, repeat, focusRequest);
     }
 
-    private void doStart(String deviceAddress) {
+    private void doStart() {
         if (mPlaying.getAndSet(true)) {
             Log.i(TAG, "already playing");
             return;
@@ -203,14 +203,10 @@
         }
 
         // Search for preferred device
-        if (deviceAddress != null) {
-            AudioDeviceInfo[] devices = mAudioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS);
-            for (AudioDeviceInfo deviceInfo : devices) {
-                if (deviceInfo.getAddress().equals(deviceAddress)) {
-                    mPlayer.setPreferredDevice(deviceInfo);
-                    break;
-                }
-            }
+        if (mPreferredDeviceInfo != null) {
+            mPlayer.setPreferredDevice(mPreferredDeviceInfo);
+            Log.d(TAG, "doStart preferred device address: " + mPreferredDeviceInfo.getAddress());
+
         }
 
         mPlayer.start();
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/audio/AudioTestFragment.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/audio/AudioTestFragment.java
index 84b6bcf..888fa31 100644
--- a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/audio/AudioTestFragment.java
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/audio/AudioTestFragment.java
@@ -30,6 +30,7 @@
 import android.media.AudioFocusRequest;
 import android.media.AudioManager;
 import android.media.HwAudioSource;
+import android.os.Build;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Looper;
@@ -55,13 +56,16 @@
 import com.google.android.car.kitchensink.R;
 
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.List;
 
 public class AudioTestFragment extends Fragment {
     private static final String TAG = "CAR.AUDIO.KS";
     private static final boolean DBG = true;
 
+    // Key for communicating to hall which audio zone has been selected to play
+    private static final String AAE_PARAMETER_KEY_FOR_SELECTED_ZONE =
+            "com.android.car.emulator.selected_zone";
+
     private AudioManager mAudioManager;
     private FocusHandler mAudioFocusHandler;
     private ToggleButton mEnableMocking;
@@ -141,8 +145,8 @@
                     mCarAudioManager = (CarAudioManager) car.getCarManager(Car.AUDIO_SERVICE);
 
                     //take care of zone selection
-                    int[] zoneList = mCarAudioManager.getAudioZoneIds();
-                    Integer[] zoneArray = Arrays.stream(zoneList).boxed().toArray(Integer[]::new);
+                    List<Integer> zoneList = mCarAudioManager.getAudioZoneIds();
+                    Integer[] zoneArray = zoneList.stream().toArray(Integer[]::new);
                     mZoneAdapter = new ArrayAdapter<>(mContext,
                             android.R.layout.simple_spinner_item, zoneArray);
                     mZoneAdapter.setDropDownViewResource(
@@ -391,11 +395,15 @@
             if (mCarAudioManager.setZoneIdForUid(zone, uid)) {
                 Log.d(TAG, "Changed uid " + uid + " sound to zone " + zone);
                 mOldZonePosition = position;
+
+                // For non primary zone set the correct speaker to route
+                if (Build.IS_EMULATOR && zone != CarAudioManager.PRIMARY_AUDIO_ZONE) {
+                    setZoneToPlayOnSpeaker(zone);
+                }
             } else {
                 Log.d(TAG, "Filed to changed uid " + uid + " sound to zone " + zone);
                 mZoneSpinner.setSelection(mOldZonePosition);
             }
-
         } catch (PackageManager.NameNotFoundException e) {
             Log.e(TAG, "handleZoneSelection Failed to find name: " , e);
         }
@@ -555,32 +563,23 @@
                 .addBundle(bundle)
                 .build();
 
+        AudioDeviceInfo audioDeviceInfo =
+                mCarAudioManager.getOutputDeviceForUsage(zoneIdForDisplayId,
+                        AudioAttributes.USAGE_MEDIA);
+
         mMusicPlayerForSelectedDisplay = new AudioPlayer(mContext,
                 R.raw.well_worth_the_wait,
-                mMusicAudioAttribForDisplay);
+                mMusicAudioAttribForDisplay,
+                audioDeviceInfo);
 
         mDisplayLayout.findViewById(R.id.audio_display_layout)
                 .setVisibility(View.VISIBLE);
     }
 
     private void startDisplayAudio() {
-        byte selectedDisplayPortId = mDisplayAdapter.getItem(
-                mDisplaySpinner.getSelectedItemPosition()).byteValue();
-        int zoneIdForDisplayId = mCarAudioManager.getZoneIdForDisplayPortId(selectedDisplayPortId);
-        Log.d(TAG, "Starting display audio in zone " + zoneIdForDisplayId);
-        // Direct audio to the correct source
-        // TODO: Figure out a way to facilitate this for the user
-        // Currently there is no way of distinguishing apps from the same package to different zones
-        // One suggested way would be to create a unique id for each focus requester that is also
-        // share with the audio router
-        if (zoneIdForDisplayId == CarAudioManager.PRIMARY_AUDIO_ZONE) {
-            mMusicPlayerForSelectedDisplay.start(true, false,
-                    AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
-        } else {
-            // Route everything else to rear seat
-            mMusicPlayerForSelectedDisplay.start(true, false,
-                    AudioManager.AUDIOFOCUS_GAIN_TRANSIENT, "bus100_rear_seat");
-        }
+        Log.d(TAG, "Starting display audio");
+        mMusicPlayerForSelectedDisplay.start(true, false,
+                AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
     }
 
     public void handleDisplaySelection() {
@@ -590,6 +589,19 @@
         createDisplayAudioPlayer();
     }
 
+    /**
+     * Sets the left speaker to output sound from zoneId
+     * @param zoneId zone id to set left speakers output
+     * @Note this should only be used with emulator where the zones are separated into right
+     * and left speaker, other platforms would have real devices where audio is routed.
+     */
+    private void setZoneToPlayOnSpeaker(int zoneId) {
+        String selectedZoneKeyValueString = AAE_PARAMETER_KEY_FOR_SELECTED_ZONE + "=" + zoneId;
+        // send key value  parameter list to audio HAL
+        mAudioManager.setParameters(selectedZoneKeyValueString);
+        Log.d(TAG, "setZoneToPlayOnSpeaker : " + zoneId);
+    }
+
 
     private class FocusHandler {
         private static final String AUDIO_FOCUS_STATE_GAIN = "gain";
@@ -649,8 +661,12 @@
             if (DBG) {
                 Log.i(TAG, "abandonAudioFocus");
             }
-            mAudioManager.abandonAudioFocusRequest(mFocusRequest);
-            mFocusRequest = null;
+            if (mFocusRequest != null) {
+                mAudioManager.abandonAudioFocusRequest(mFocusRequest);
+                mFocusRequest = null;
+            } else {
+                Log.i(TAG, "mFocusRequest is already null");
+            }
             setFocusText(AUDIO_FOCUS_STATE_RELEASED_UNKNOWN);
         }
 
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/bluetooth/MapMceTestFragment.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/bluetooth/MapMceTestFragment.java
index 68d1c73..2f2e9fe 100644
--- a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/bluetooth/MapMceTestFragment.java
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/bluetooth/MapMceTestFragment.java
@@ -165,7 +165,11 @@
     }
 
     void disconnectDevice(String device) {
-        mMapProfile.disconnect(mBluetoothAdapter.getRemoteDevice((device)));
+        try {
+            mMapProfile.disconnect(mBluetoothAdapter.getRemoteDevice(device));
+        } catch (IllegalArgumentException e) {
+            Log.e(TAG, "Failed to disconnect from " + device, e);
+        }
     }
 
     @Override
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/connectivity/ConnectivityFragment.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/connectivity/ConnectivityFragment.java
index 3bdfd22..7ec9f78 100644
--- a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/connectivity/ConnectivityFragment.java
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/connectivity/ConnectivityFragment.java
@@ -47,8 +47,10 @@
 
 import androidx.fragment.app.Fragment;
 import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
+import androidx.viewpager.widget.ViewPager;
 
 import com.google.android.car.kitchensink.R;
+import com.google.android.car.kitchensink.SimplePagerAdapter;
 
 import java.net.NetworkInterface;
 import java.net.SocketException;
@@ -470,6 +472,9 @@
             @Nullable Bundle savedInstanceState) {
         View view = inflater.inflate(R.layout.connectivity_fragment, container, false);
 
+        ViewPager pager = view.findViewById(R.id.connectivity_pager);
+        pager.setAdapter(new SimplePagerAdapter(pager));
+
         // Create the ListView of all networks
         ListView networksView = view.findViewById(R.id.networks);
         mNetworksAdapter = new NetworkListAdapter(getContext(), mNetworkItems, this);
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/diagnostic/DiagnosticTestFragment.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/diagnostic/DiagnosticTestFragment.java
index 177d36c..99164df 100644
--- a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/diagnostic/DiagnosticTestFragment.java
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/diagnostic/DiagnosticTestFragment.java
@@ -92,8 +92,15 @@
     }
 
     private void resumeDiagnosticManager() {
+        Car car = mActivity.getCar();
+        if (!car.isFeatureEnabled(Car.DIAGNOSTIC_SERVICE)) {
+            String notSupported = Car.DIAGNOSTIC_SERVICE + " not supported";
+            mLiveDiagnosticInfo.setText(notSupported);
+            mFreezeDiagnosticInfo.setText(notSupported);
+            return;
+        }
         mDiagnosticManager =
-                (CarDiagnosticManager) mActivity.getCar().getCarManager(Car.DIAGNOSTIC_SERVICE);
+                (CarDiagnosticManager) car.getCarManager(Car.DIAGNOSTIC_SERVICE);
         if (mLiveListener != null) {
             mDiagnosticManager.registerListener(mLiveListener,
                     CarDiagnosticManager.FRAME_TYPE_LIVE,
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/experimental/ExperimentalFeatureTestFragment.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/experimental/ExperimentalFeatureTestFragment.java
new file mode 100644
index 0000000..9136a9d
--- /dev/null
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/experimental/ExperimentalFeatureTestFragment.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.car.kitchensink.experimental;
+
+import android.car.Car;
+import android.car.experimental.CarTestDemoExperimentalFeatureManager;
+import android.car.experimental.ExperimentalCar;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.TextView;
+
+import androidx.fragment.app.Fragment;
+
+import com.google.android.car.kitchensink.KitchenSinkActivity;
+import com.google.android.car.kitchensink.R;
+
+public class ExperimentalFeatureTestFragment extends Fragment {
+
+    private static final String TAG = "ExperimentalFeature";
+
+    private static final String[] PING_MSGS = {
+            "Hello, world",
+            "This is 1st experimental feature",
+    };
+
+    private int mCurrentMsgIndex = 0;
+    private TextView mPingMsgTextView;
+    private Button mPingButton;
+
+    private CarTestDemoExperimentalFeatureManager mDemoManager;
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstance) {
+        View view = inflater.inflate(R.layout.experimental_feature_test, container, false);
+        mPingMsgTextView = view.findViewById(R.id.experimental_ping_msg);
+        mPingButton = view.findViewById(R.id.button_experimental_ping);
+        Car car = ((KitchenSinkActivity) getHost()).getCar();
+        if (car.isFeatureEnabled(ExperimentalCar.TEST_EXPERIMENTAL_FEATURE_SERVICE)) {
+            mDemoManager = (CarTestDemoExperimentalFeatureManager) car.getCarManager(
+                    ExperimentalCar.TEST_EXPERIMENTAL_FEATURE_SERVICE);
+            mPingMsgTextView.setText("feature enabled");
+        } else {
+            Log.w(TAG, "ExperimentalCar.TEST_EXPERIMENTAL_FEATURE_SERVICE not enabled");
+            mPingButton.setActivated(false);
+            mPingMsgTextView.setText("feature disabled");
+        }
+        view.findViewById(R.id.button_experimental_ping).setOnClickListener(
+                (View v) -> {
+                    if (mDemoManager == null) {
+                        return;
+                    }
+                    String msg = pickMsg();
+                    mPingMsgTextView.setText(mDemoManager.ping(msg));
+                });
+        return view;
+    }
+
+    private String pickMsg() {
+        String msg = PING_MSGS[mCurrentMsgIndex];
+        mCurrentMsgIndex++;
+        if (mCurrentMsgIndex >= PING_MSGS.length) {
+            mCurrentMsgIndex = 0;
+        }
+        return msg;
+    }
+}
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/notification/NotificationFragment.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/notification/NotificationFragment.java
index a6d81c9..e14b25f 100644
--- a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/notification/NotificationFragment.java
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/notification/NotificationFragment.java
@@ -42,6 +42,7 @@
     private NotificationManager mManager;
     private Context mContext;
     private Handler mHandler = new Handler();
+    private int mCount = 0;
     private HashMap<Integer, Runnable> mUpdateRunnables = new HashMap<>();
 
     @Override
@@ -89,12 +90,15 @@
         initImportanceMinButton(view);
 
         initOngoingButton(view);
-        initMessagingStyleButton(view);
+        initMessagingStyleButtonForDiffPerson(view);
+        initMessagingStyleButtonForSamePerson(view);
         initTestMessagesButton(view);
         initProgressButton(view);
         initNavigationButton(view);
         initMediaButton(view);
         initCallButton(view);
+        initCustomGroupSummaryButton(view);
+        initGroupWithoutSummaryButton(view);
 
         return view;
     }
@@ -238,8 +242,8 @@
         });
     }
 
-    private void initMessagingStyleButton(View view) {
-        view.findViewById(R.id.category_message_button).setOnClickListener(v -> {
+    private void initMessagingStyleButtonForDiffPerson(View view) {
+        view.findViewById(R.id.category_message_diff_person_button).setOnClickListener(v -> {
             int id = mCurrentNotificationId++;
 
             PendingIntent replyIntent = createServiceIntent(id, "reply");
@@ -307,6 +311,47 @@
         });
     }
 
+    private void initMessagingStyleButtonForSamePerson(View view) {
+        view.findViewById(R.id.category_message_same_person_button).setOnClickListener(v -> {
+            int id = mCurrentNotificationId++;
+
+            PendingIntent replyIntent = createServiceIntent(id, "reply");
+            PendingIntent markAsReadIntent = createServiceIntent(id, "read");
+
+            Person person = new Person.Builder().setName("John Doe").build();
+            MessagingStyle messagingStyle =
+                    new MessagingStyle(person).setConversationTitle("Hello!");
+            NotificationCompat.Builder builder = new NotificationCompat
+                    .Builder(mContext, IMPORTANCE_HIGH_ID)
+                    .setContentTitle("Message from someone")
+                    .setContentText("hi")
+                    .setShowWhen(true)
+                    .setCategory(Notification.CATEGORY_MESSAGE)
+                    .setSmallIcon(R.drawable.car_ic_mode)
+                    .setAutoCancel(true)
+                    .setColor(mContext.getColor(android.R.color.holo_green_light))
+                    .addAction(
+                            new Action.Builder(R.drawable.ic_check_box, "read", markAsReadIntent)
+                                    .setSemanticAction(Action.SEMANTIC_ACTION_MARK_AS_READ)
+                                    .setShowsUserInterface(false)
+                                    .build())
+                    .addAction(
+                            new Action.Builder(R.drawable.ic_check_box, "reply", replyIntent)
+                                    .setSemanticAction(Action.SEMANTIC_ACTION_REPLY)
+                                    .setShowsUserInterface(false)
+                                    .addRemoteInput(new RemoteInput.Builder("input").build())
+                                    .build());
+
+            NotificationCompat.Builder updateNotification =
+                    builder.setStyle(messagingStyle.addMessage(
+                            new MessagingStyle.Message(
+                                    "Message " + id,
+                                    System.currentTimeMillis(),
+                                    person)));
+            mManager.notify(12345, updateNotification.build());
+        });
+    }
+
     private void initTestMessagesButton(View view) {
         view.findViewById(R.id.test_message_button).setOnClickListener(v -> {
             int id = mCurrentNotificationId++;
@@ -314,7 +359,7 @@
             PendingIntent replyIntent = createServiceIntent(id, "reply");
             PendingIntent markAsReadIntent = createServiceIntent(id, "read");
 
-            Person person = new Person.Builder().setName("John Doe").build();
+            Person person = new Person.Builder().setName("John Doe " + id).build();
             MessagingStyle messagingStyle =
                     new MessagingStyle(person).setConversationTitle("Hello!");
             NotificationCompat.Builder builder = new NotificationCompat
@@ -515,4 +560,60 @@
             mManager.notify(mCurrentNotificationId++, notification);
         });
     }
+
+    private void initCustomGroupSummaryButton(View view) {
+        view.findViewById(R.id.custom_group_summary_button).setOnClickListener(v -> {
+            String groupKey = "GROUP_KEY" + mCurrentNotificationId++;
+            int delay = 500;
+
+            Notification summaryNotification = new Notification
+                    .Builder(mContext, IMPORTANCE_HIGH_ID)
+                    .setContentTitle("6 New mails")
+                    .setContentText("this is some summary")
+                    .setSmallIcon(R.drawable.thumb_up)
+                    .setLargeIcon(Icon.createWithResource(mContext, R.drawable.avatar1))
+                    .setGroup(groupKey)
+                    .setGroupSummary(true)
+                    .setStyle(new Notification.InboxStyle()
+                            .addLine("line 1")
+                            .addLine("line 2")
+                            .addLine("line 3")
+                            .addLine("line 4")
+                            .addLine("line 5")
+                            .setBigContentTitle("You've received 6 messages")
+                            .setSummaryText("From Alice, Bob, Claire, Douglas.."))
+                    .build();
+
+            mHandler.postDelayed(
+                    () -> mManager.notify(mCurrentNotificationId++, summaryNotification), delay);
+            for (int i = 1; i <= 6; i++) {
+                Notification notification = new Notification
+                        .Builder(mContext, IMPORTANCE_HIGH_ID)
+                        .setContentTitle("Group child " + i)
+                        .setSmallIcon(R.drawable.car_ic_mode)
+                        .setGroup(groupKey)
+                        .setSortKey(Integer.toString(6 - i))
+                        .build();
+                mHandler.postDelayed(() -> mManager.notify(mCurrentNotificationId++, notification),
+                        delay += 5000);
+            }
+        });
+    }
+
+    private void initGroupWithoutSummaryButton(View view) {
+        view.findViewById(R.id.group_without_summary_button).setOnClickListener(v -> {
+            String groupKey = "GROUP_KEY" + mCurrentNotificationId++;
+
+            for (int i = 1; i <= 6; i++) {
+                Notification notification = new Notification
+                        .Builder(mContext, IMPORTANCE_DEFAULT_ID)
+                        .setContentTitle("This notification should not group " + i)
+                        .setSmallIcon(R.drawable.car_ic_mode)
+                        .setGroup(groupKey)
+                        .setSortKey(Integer.toString(i))
+                        .build();
+                mHandler.post(() -> mManager.notify(mCurrentNotificationId++, notification));
+            }
+        });
+    }
 }
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/packageinfo/PackageInfoFragment.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/packageinfo/PackageInfoFragment.java
new file mode 100644
index 0000000..a43c510
--- /dev/null
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/packageinfo/PackageInfoFragment.java
@@ -0,0 +1,213 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.car.kitchensink.packageinfo;
+
+import android.content.Context;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ServiceInfo;
+import android.content.pm.UserInfo;
+import android.os.Bundle;
+import android.os.UserManager;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
+import android.widget.Button;
+import android.widget.CheckBox;
+import android.widget.Spinner;
+import android.widget.TextView;
+
+import androidx.fragment.app.Fragment;
+
+import com.google.android.car.kitchensink.R;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Test fragment to check packages installed for each user
+ * <p>
+ * Options to apply conditions to filter out packages, if package
+ * <ul>
+ * <li>only have activities.
+ * <li>have service but not exported or for single user.
+ * <li>does not require any key permission
+ * (INTERACT_ACROSS_USERS, INTERACT_ACROSS_USERS_FULL, WRITE_DEVICE_CONFIG).
+ * <li>does not have sharedUserId or shardUserId is not system uid.
+ * </ul>
+ */
+public final class PackageInfoFragment extends Fragment{
+    private static final String TAG = "PackageInfoTest";
+    private static final boolean DEBUG = true;
+    private static final int PACKAGE_FLAGS = PackageManager.GET_META_DATA
+            | PackageManager.GET_ACTIVITIES | PackageManager.GET_SERVICES
+            | PackageManager.GET_PERMISSIONS | PackageManager.GET_SIGNATURES;
+    private static final List<String> IMPORTANT_PERMISSIONS = Arrays.asList(
+            "android.permission.INTERACT_ACROSS_USERS",
+            "android.permission.INTERACT_ACROSS_USERS_FULL",
+            "android.permission.WRITE_DEVICE_CONFIG");
+    private static final String SYSTEM_UID = "android.uid.system";
+
+    private final List<PackageInfo> mPackagesToDisableForSystemUser = new ArrayList<>();
+
+    private UserManager mUserManager;
+    private PackageManager mPackageManager;
+    private UserInfo mUserToShow;
+    private int mShowFlags;
+    private TextView mPackageView;
+    private boolean mFilterActivities;
+    private boolean mFilterServices;
+    private boolean mFilterPermissions;
+    private boolean mFilterSharedUid;
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        mUserManager = (UserManager) getContext().getSystemService(Context.USER_SERVICE);
+        mPackageManager = getActivity().getPackageManager();
+    }
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstance) {
+        View view = inflater.inflate(R.layout.package_info_test, container, false);
+        setListener(view);
+
+        return view;
+    }
+
+    private void refreshPackages() {
+        List<PackageInfo> packages = new ArrayList<PackageInfo>();
+        try {
+            packages = mPackageManager.getInstalledPackagesAsUser(PackageManager.GET_META_DATA
+                    | PackageManager.GET_ACTIVITIES | PackageManager.GET_SERVICES
+                    | PackageManager.GET_PERMISSIONS | PackageManager.GET_SIGNATURES,
+                    mUserToShow.id);
+            if (DEBUG) {
+                Log.d(TAG, "total packages found: " + packages.size());
+            }
+        } catch (Exception e) {
+            if (DEBUG) {
+                Log.e(TAG, "failed to get packages for given user: " + mUserToShow);
+            }
+            mPackageView.setText("Cannot retrieve packages for this user..");
+            return;
+        }
+
+        mPackagesToDisableForSystemUser.clear();
+
+        for (PackageInfo packageInfo : packages) {
+            Log.d(TAG, "checking package: " + packageInfo);
+            boolean toBlacklist = true;
+            // check share user id, show package does not have sharedUserId or not system uid
+            if (mFilterSharedUid) {
+                if (DEBUG) {
+                    Log.d(TAG, "sharedUid flagged: " + (packageInfo.sharedUserId == null
+                                || !packageInfo.sharedUserId.equals(SYSTEM_UID)));
+                }
+
+                toBlacklist &= (packageInfo.sharedUserId == null
+                        || !packageInfo.sharedUserId.equals(SYSTEM_UID));
+            }
+
+            // check permissions, show package does not require selected permissions
+            if (mFilterPermissions && packageInfo.requestedPermissions != null) {
+                if (DEBUG) {
+                    for (String info : Arrays.asList(packageInfo.requestedPermissions)) {
+                        Log.d(TAG, info + " flagged: " + (!IMPORTANT_PERMISSIONS.contains(info)));
+                    }
+                }
+
+                toBlacklist &= !(Arrays.asList(packageInfo.requestedPermissions).stream().anyMatch(
+                        info -> IMPORTANT_PERMISSIONS.contains(info)));
+            }
+            // check services, w/o service or service not exported and w/o single user flag
+            if (mFilterServices && packageInfo.services != null) {
+                if (DEBUG) {
+                    for (ServiceInfo info : Arrays.asList(packageInfo.services)) {
+                        Log.d(TAG, info + " flagged: " + (!info.exported
+                                && (info.flags & ServiceInfo.FLAG_SINGLE_USER) == 0));
+                    }
+                }
+
+                toBlacklist &= Arrays.asList(packageInfo.services).stream().anyMatch(info ->
+                    !info.exported && (info.flags & ServiceInfo.FLAG_SINGLE_USER) == 0);
+            }
+            // check activities
+            if (mFilterActivities) {
+                if (DEBUG) {
+                    Log.d(TAG, packageInfo + " contain activities only, flagged: " + (
+                            packageInfo.activities != null
+                            && packageInfo.services == null
+                            && packageInfo.providers == null));
+                }
+                toBlacklist &= (packageInfo.activities != null
+                    && packageInfo.services == null && packageInfo.providers == null);
+            }
+
+            if (toBlacklist) {
+                mPackagesToDisableForSystemUser.add(packageInfo);
+            }
+        }
+    }
+
+    private void showPackagesOnView(TextView tv) {
+        refreshPackages();
+
+        tv.setText(mPackagesToDisableForSystemUser.size() + " Packages found ...\n");
+
+        for (PackageInfo info : mPackagesToDisableForSystemUser) {
+            tv.append(info.packageName.toString() + "\n");
+        }
+    }
+
+    private void setListener(View v) {
+        List<UserInfo> users = mUserManager.getUsers();
+        mUserToShow = users.get(0);
+        Spinner spinner = v.findViewById(R.id.spinner);
+        ArrayAdapter<UserInfo> userArrayAdapter = new ArrayAdapter<UserInfo>(
+                    getContext(), android.R.layout.simple_spinner_item, users);
+        userArrayAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+        spinner.setAdapter(userArrayAdapter);
+        spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
+            @Override
+            public void onItemSelected(AdapterView<?> parent, View currentView,
+                    int position, long id) {
+                mUserToShow = (UserInfo) parent.getItemAtPosition(position);
+            }
+            @Override
+            public void onNothingSelected(AdapterView<?> parent) {
+            }
+        });
+
+        CheckBox activities = v.findViewById(R.id.checkbox_activities);
+        CheckBox services = v.findViewById(R.id.checkbox_services);
+        CheckBox permissions = v.findViewById(R.id.checkbox_permissions);
+        CheckBox shareduid = v.findViewById(R.id.checkbox_shareduid);
+        Button showButton = v.findViewById(R.id.button_show);
+        TextView packageView = v.findViewById(R.id.packages);
+
+        activities.setOnClickListener(view -> mFilterActivities = ((CheckBox) view).isChecked());
+        services.setOnClickListener(view -> mFilterServices = ((CheckBox) view).isChecked());
+        permissions.setOnClickListener(view -> mFilterPermissions = ((CheckBox) view).isChecked());
+        shareduid.setOnClickListener(view -> mFilterSharedUid = ((CheckBox) view).isChecked());
+        showButton.setOnClickListener(view -> showPackagesOnView(packageView));
+    }
+}
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/storagelifetime/StorageLifetimeFragment.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/storagelifetime/StorageLifetimeFragment.java
index 481820b..b76cd24 100644
--- a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/storagelifetime/StorageLifetimeFragment.java
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/storagelifetime/StorageLifetimeFragment.java
@@ -213,13 +213,19 @@
     @Override
     public void onResume() {
         super.onResume();
+        if (!mActivity.getCar().isFeatureEnabled(Car.STORAGE_MONITORING_SERVICE)) {
+            Log.w(TAG, "STORAGE_MONITORING_SERVICE not supported");
+            return;
+        }
         reloadInfo();
         registerListener();
     }
 
     @Override
     public void onPause() {
-        unregisterListener();
+        if (mActivity.getCar().isFeatureEnabled(Car.STORAGE_MONITORING_SERVICE)) {
+            unregisterListener();
+        }
         super.onPause();
     }
 }
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/touchsound/DisableTouchSoundOnBoot.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/touchsound/DisableTouchSoundOnBoot.java
deleted file mode 100644
index 2c81bda..0000000
--- a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/touchsound/DisableTouchSoundOnBoot.java
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.google.android.car.kitchensink.touchsound;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.provider.Settings;
-import android.util.Log;
-
-/**
- * Temp solution until b/68882625 is fixed
- */
-public class DisableTouchSoundOnBoot extends BroadcastReceiver {
-
-    private static final String KEY_TOUCH_SOUNDS = "sound_effects_enabled";
-
-    @Override
-    public void onReceive(Context context, Intent intent) {
-        Log.w("DisableTouchSoundOnBoot", "disabling touch sound");
-        Settings.System.putInt(context.getContentResolver(), KEY_TOUCH_SOUNDS, 0);
-    }
-}
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/vehiclectrl/VehicleCtrlFragment.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/vehiclectrl/VehicleCtrlFragment.java
new file mode 100644
index 0000000..0040fe2
--- /dev/null
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/vehiclectrl/VehicleCtrlFragment.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.car.kitchensink.vehiclectrl;
+
+import android.annotation.IdRes;
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.car.VehicleAreaWindow;
+import android.car.VehiclePropertyIds;
+import android.car.hardware.CarPropertyValue;
+import android.car.hardware.property.CarPropertyManager;
+import android.hardware.automotive.vehicle.V2_0.VehicleArea;
+import android.hardware.automotive.vehicle.V2_0.VehiclePropertyGroup;
+import android.hardware.automotive.vehicle.V2_0.VehiclePropertyType;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import androidx.fragment.app.Fragment;
+import androidx.viewpager.widget.ViewPager;
+
+import com.google.android.car.kitchensink.KitchenSinkActivity;
+import com.google.android.car.kitchensink.R;
+import com.google.android.car.kitchensink.SimplePagerAdapter;
+
+import java.util.HashMap;
+import java.util.Map;
+
+@SuppressLint("SetTextI18n")
+public final class VehicleCtrlFragment extends Fragment {
+    private static final String TAG = VehicleCtrlFragment.class.getSimpleName();
+
+    public static final class CustomVehicleProperty {
+        public static final int PROTOCAN_TEST = (
+                0x0ABC
+                | VehiclePropertyGroup.VENDOR
+                | VehiclePropertyType.BOOLEAN
+                | VehicleArea.GLOBAL);
+
+        private CustomVehicleProperty() {}
+    };
+
+    private CarPropertyManager mPropMgr;
+    private final Map<Integer, TextView> mWindowPosWidgets = new HashMap<>();
+
+    private final CarPropertyManager.CarPropertyEventCallback mPropCb =
+            new CarPropertyManager.CarPropertyEventCallback() {
+        @Override
+        public void onChangeEvent(CarPropertyValue value) {
+            onPropertyEvent(value);
+        }
+
+        @Override
+        public void onErrorEvent(int propId, int zone) {}
+    };
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
+            @Nullable Bundle savedInstanceState) {
+        View view = inflater.inflate(R.layout.vehicle_ctrl_fragment, container, false);
+
+        ViewPager pager = view.findViewById(R.id.vehicle_ctrl_pager);
+        pager.setAdapter(new SimplePagerAdapter(pager));
+
+        KitchenSinkActivity activity = (KitchenSinkActivity) getHost();
+        mPropMgr = activity.getPropertyManager();
+
+        subscribeProps();
+
+        initWindowBtns(view, VehicleAreaWindow.WINDOW_ROW_1_LEFT,
+                R.id.winFLOpen, R.id.winFLClose, R.id.winFLPos);
+        initWindowBtns(view, VehicleAreaWindow.WINDOW_ROW_1_RIGHT,
+                R.id.winFROpen, R.id.winFRClose, R.id.winFRPos);
+        initWindowBtns(view, VehicleAreaWindow.WINDOW_ROW_2_LEFT,
+                R.id.winRLOpen, R.id.winRLClose, R.id.winRLPos);
+        initWindowBtns(view, VehicleAreaWindow.WINDOW_ROW_2_RIGHT,
+                R.id.winRROpen, R.id.winRRClose, R.id.winRRPos);
+
+        initTestBtn(view, R.id.protocan_test_on, true);
+        initTestBtn(view, R.id.protocan_test_off, false);
+
+        return view;
+    }
+
+    @Override
+    public void onDestroyView() {
+        super.onDestroyView();
+        unsubscribeProps();
+    }
+
+    private void subscribeProps() {
+        mPropMgr.registerCallback(mPropCb, VehiclePropertyIds.WINDOW_POS, 10);
+    }
+
+    private void unsubscribeProps() {
+        mPropMgr.unregisterCallback(mPropCb);
+    }
+
+    public void onPropertyEvent(CarPropertyValue prop) {
+        if (prop.getPropertyId() == VehiclePropertyIds.WINDOW_POS) {
+            Log.i(TAG, "Window pos: " + prop.getValue());
+            updateWindowPos(prop.getAreaId(), (int) prop.getValue());
+        }
+    }
+
+    private void initWindowBtns(View view, int winId, @IdRes int openId, @IdRes int closeId,
+            @IdRes int posId) {
+        // TODO(twasilczyk): fetch the actual min/max values
+        view.findViewById(openId).setOnClickListener(v -> moveWindow(winId, 1));
+        view.findViewById(closeId).setOnClickListener(v -> moveWindow(winId, -1));
+        mWindowPosWidgets.put(winId, view.findViewById(posId));
+    }
+
+    private void moveWindow(int windowId, int speed) {
+        Log.i(TAG, "Moving window " + windowId + " with speed " + speed);
+        mPropMgr.setIntProperty(VehiclePropertyIds.WINDOW_MOVE, windowId, speed);
+    }
+
+    private void updateWindowPos(int windowId, int pos) {
+        TextView view = mWindowPosWidgets.get(windowId);
+        view.post(() -> view.setText(pos + "%"));
+    }
+
+    private void initTestBtn(View view, @IdRes int btnId, boolean on) {
+        view.findViewById(btnId).setOnClickListener(v -> onTestBtnClicked(on));
+    }
+
+    private void onTestBtnClicked(boolean on) {
+        Log.i(TAG, "onTestBtnClicked " + on);
+        mPropMgr.setBooleanProperty(CustomVehicleProperty.PROTOCAN_TEST, VehicleArea.GLOBAL, on);
+    }
+}
diff --git a/tests/MultiDisplayTest/Android.mk b/tests/MultiDisplayTest/Android.mk
index 779c646..dcee7b4 100644
--- a/tests/MultiDisplayTest/Android.mk
+++ b/tests/MultiDisplayTest/Android.mk
@@ -33,7 +33,7 @@
 LOCAL_STATIC_ANDROID_LIBRARIES += \
     androidx.lifecycle_lifecycle-livedata \
     androidx.lifecycle_lifecycle-viewmodel \
-    androidx.car_car-cluster
+    androidx.car_car
 
 include $(BUILD_PACKAGE)
 
diff --git a/tests/OWNERS b/tests/OWNERS
new file mode 100644
index 0000000..e515908
--- /dev/null
+++ b/tests/OWNERS
@@ -0,0 +1,6 @@
+# Test team
+vshah@google.com
+
+# AAE TLs
+pirozzoj@google.com
+twasilczyk@google.com
diff --git a/tests/OccupantAwareness/Android.bp b/tests/OccupantAwareness/Android.bp
new file mode 100644
index 0000000..2160572
--- /dev/null
+++ b/tests/OccupantAwareness/Android.bp
@@ -0,0 +1,51 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+android_test {
+    name: "OccupantAwarenessSystemTests",
+
+    srcs: ["src/**/*.java"],
+
+    instrumentation_for: "CarService",
+
+    optimize: {
+        enabled: false,
+    },
+
+    certificate: "platform",
+    platform_apis: true,
+    privileged: true,
+
+    libs: [
+        "android.car",
+        "android.test.base",
+        "android.test.mock",
+        "android.test.runner",
+        // After here
+        "android.car-test-stubs",
+        "android.car.testapi",
+    ],
+
+    static_libs: [
+        "android-support-test",
+        "truth-prebuilt",
+        "android.hardware.automotive.occupant_awareness-java",
+        "android.test.base.stubs",
+        "car-service-test-static-lib",
+        "androidx.test.core",
+        "androidx.test.ext.junit",
+        "androidx.test.rules",
+        "car-frameworks-service",
+    ],
+}
diff --git a/tests/OccupantAwareness/AndroidManifest.xml b/tests/OccupantAwareness/AndroidManifest.xml
new file mode 100644
index 0000000..72085ed
--- /dev/null
+++ b/tests/OccupantAwareness/AndroidManifest.xml
@@ -0,0 +1,32 @@
+<!--
+  ~ Copyright (C) 2020 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.car.tests">
+
+    <!--  Needed for OAS testing -->
+    <uses-permission android:name="android.car.permission.READ_CAR_OCCUPANT_AWARENESS_STATE" />
+    <uses-permission android:name="android.car.permission.CONTROL_CAR_OCCUPANT_AWARENESS_SYSTEM" />
+
+    <application
+                 android:debuggable="true">
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="com.android.car.tests"
+                     android:label="Occupant Awareness System Tests" />
+</manifest>
diff --git a/tests/OccupantAwareness/src/com/android/car/test/OccupantAwarenessSystemServiceTest.java b/tests/OccupantAwareness/src/com/android/car/test/OccupantAwarenessSystemServiceTest.java
new file mode 100644
index 0000000..daa7ac7
--- /dev/null
+++ b/tests/OccupantAwareness/src/com/android/car/test/OccupantAwarenessSystemServiceTest.java
@@ -0,0 +1,302 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.car;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.car.occupantawareness.IOccupantAwarenessEventCallback;
+import android.car.occupantawareness.OccupantAwarenessDetection;
+import android.car.occupantawareness.SystemStatusEvent;
+import android.content.Context;
+import android.hardware.automotive.occupant_awareness.IOccupantAwareness;
+import android.hardware.automotive.occupant_awareness.IOccupantAwarenessClientCallback;
+import android.hardware.automotive.occupant_awareness.OccupantAwarenessStatus;
+import android.hardware.automotive.occupant_awareness.OccupantDetection;
+import android.hardware.automotive.occupant_awareness.OccupantDetections;
+import android.hardware.automotive.occupant_awareness.Role;
+import android.os.RemoteException;
+import android.test.suitebuilder.annotation.MediumTest;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.runner.AndroidJUnit4;
+
+import junit.framework.TestCase;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
+
+@RunWith(AndroidJUnit4.class)
+@MediumTest
+public class OccupantAwarenessSystemServiceTest extends TestCase {
+    private static final int TIMESTAMP = 1234; // In milliseconds.
+
+    /**
+     * Mock implementation of {@link
+     * android.hardware.automotive.occupant_awareness.IOccupantAwareness} for testing the service
+     * and manager.
+     */
+    private class MockOasHal
+            extends android.hardware.automotive.occupant_awareness.IOccupantAwareness.Stub {
+        private IOccupantAwarenessClientCallback mCallback;
+        private boolean mGraphIsRunning;
+
+        MockOasHal() {}
+
+        /** Returns whether the mock graph is running. */
+        public boolean isGraphRunning() {
+            return mGraphIsRunning;
+        }
+
+        @Override
+        public void getLatestDetection(OccupantDetections detections) {}
+
+        @Override
+        public void setCallback(IOccupantAwarenessClientCallback callback) {
+            mCallback = callback;
+        }
+
+        @Override
+        public @OccupantAwarenessStatus byte getState(int occupantRole, int detectionCapability) {
+            return OccupantAwarenessStatus.READY;
+        }
+
+        @Override
+        public @OccupantAwarenessStatus byte startDetection() {
+            mGraphIsRunning = true;
+            return OccupantAwarenessStatus.READY;
+        }
+
+        @Override
+        public @OccupantAwarenessStatus byte stopDetection() {
+            mGraphIsRunning = false;
+            return OccupantAwarenessStatus.READY;
+        }
+
+        @Override
+        public int getCapabilityForRole(@Role int occupantRole) {
+            if (occupantRole == OccupantAwarenessDetection.VEHICLE_OCCUPANT_DRIVER) {
+                return SystemStatusEvent.DETECTION_TYPE_PRESENCE
+                        | SystemStatusEvent.DETECTION_TYPE_GAZE
+                        | SystemStatusEvent.DETECTION_TYPE_DRIVER_MONITORING;
+            } else if (occupantRole
+                    == OccupantAwarenessDetection.VEHICLE_OCCUPANT_FRONT_PASSENGER) {
+                return SystemStatusEvent.DETECTION_TYPE_PRESENCE;
+            } else {
+                return SystemStatusEvent.DETECTION_TYPE_NONE;
+            }
+        }
+
+        /** Causes a status event to be generated with the specified flags. */
+        public void fireStatusEvent(int detectionFlags, @OccupantAwarenessStatus byte status)
+                throws RemoteException {
+            if (mCallback != null) {
+                mCallback.onSystemStatusChanged(detectionFlags, status);
+            }
+        }
+
+        /** Causes a status event to be generated with the specified detection event data. */
+        public void fireDetectionEvent(OccupantAwarenessDetection detectionEvent)
+                throws RemoteException {
+            if (mCallback != null) {
+                OccupantDetection detection = new OccupantDetection();
+
+                OccupantDetections detections = new OccupantDetections();
+                detections.timeStampMillis = TIMESTAMP;
+                detections.detections = new OccupantDetection[] {detection};
+                mCallback.onDetectionEvent(detections);
+            }
+        }
+    }
+
+    private MockOasHal mMockHal;
+    private com.android.car.OccupantAwarenessService mOasService;
+
+    private CompletableFuture<SystemStatusEvent> mFutureStatus;
+    private CompletableFuture<OccupantAwarenessDetection> mFutureDetection;
+
+    @Before
+    public void setUp() {
+        Context context = ApplicationProvider.getApplicationContext();
+        mMockHal = new MockOasHal();
+        mOasService = new com.android.car.OccupantAwarenessService(context, mMockHal);
+        mOasService.init();
+
+        resetFutures();
+    }
+
+    @After
+    public void tearDown() {
+        mOasService.release();
+    }
+
+    @Test
+    public void testWithNoRegisteredListeners() throws Exception {
+        // Verify operation when no listeners are registered.
+        mMockHal.fireStatusEvent(IOccupantAwareness.CAP_NONE, OccupantAwarenessStatus.READY);
+
+        // Nothing should have been received.
+        assertThat(mFutureStatus.isDone()).isFalse();
+        assertThat(mFutureDetection.isDone()).isFalse();
+    }
+
+    @Test
+    public void testStatusEventsWithRegisteredListeners() throws Exception {
+        // Verify correct operation when a listener has been registered.
+        registerCallbackToService();
+        SystemStatusEvent result;
+
+        // Fire a status event and ensure it is received.
+        // "Presence status is ready"
+        resetFutures();
+        mMockHal.fireStatusEvent(
+                IOccupantAwareness.CAP_PRESENCE_DETECTION, OccupantAwarenessStatus.READY);
+
+        result = mFutureStatus.get(1, TimeUnit.SECONDS);
+        assertEquals(result.detectionType, SystemStatusEvent.DETECTION_TYPE_PRESENCE);
+        assertEquals(result.systemStatus, SystemStatusEvent.SYSTEM_STATUS_READY);
+
+        // "Gaze status is failed"
+        resetFutures();
+        mMockHal.fireStatusEvent(
+                IOccupantAwareness.CAP_GAZE_DETECTION, OccupantAwarenessStatus.FAILURE);
+
+        result = mFutureStatus.get(1, TimeUnit.SECONDS);
+        assertEquals(result.detectionType, SystemStatusEvent.DETECTION_TYPE_GAZE);
+        assertEquals(result.systemStatus, SystemStatusEvent.SYSTEM_STATUS_SYSTEM_FAILURE);
+
+        // "Driver monitoring status is not-ready"
+        resetFutures();
+        mMockHal.fireStatusEvent(
+                IOccupantAwareness.CAP_DRIVER_MONITORING_DETECTION,
+                OccupantAwarenessStatus.NOT_INITIALIZED);
+
+        result = mFutureStatus.get(1, TimeUnit.SECONDS);
+        assertEquals(result.detectionType, SystemStatusEvent.DETECTION_TYPE_DRIVER_MONITORING);
+        assertEquals(result.systemStatus, SystemStatusEvent.SYSTEM_STATUS_NOT_READY);
+
+        // "None is non-supported"
+        resetFutures();
+        mMockHal.fireStatusEvent(
+                IOccupantAwareness.CAP_NONE, OccupantAwarenessStatus.NOT_SUPPORTED);
+
+        result = mFutureStatus.get(1, TimeUnit.SECONDS);
+        assertEquals(result.detectionType, SystemStatusEvent.DETECTION_TYPE_NONE);
+        assertEquals(result.systemStatus, SystemStatusEvent.SYSTEM_STATUS_NOT_SUPPORTED);
+    }
+
+    @Test
+    public void test_unregisteredListeners() throws Exception {
+        // Verify that listeners are successfully unregistered.
+        IOccupantAwarenessEventCallback callback = registerCallbackToService();
+
+        // Unregister the registered listener.
+        mOasService.unregisterEventListener(callback);
+
+        // Fire some events.
+        mMockHal.fireStatusEvent(IOccupantAwareness.CAP_NONE, OccupantAwarenessStatus.READY);
+        mMockHal.fireStatusEvent(
+                IOccupantAwareness.CAP_GAZE_DETECTION, OccupantAwarenessStatus.READY);
+        mMockHal.fireStatusEvent(
+                IOccupantAwareness.CAP_DRIVER_MONITORING_DETECTION, OccupantAwarenessStatus.READY);
+
+        // Nothing should have been received.
+        assertThat(mFutureStatus.isDone()).isFalse();
+        assertThat(mFutureDetection.isDone()).isFalse();
+
+        // Unregister a second time should log an error, but otherwise not cause any action.
+        mOasService.unregisterEventListener(callback);
+    }
+
+    @Test
+    public void test_getCapabilityForRole() throws Exception {
+        assertThat(
+                        mOasService.getCapabilityForRole(
+                                OccupantAwarenessDetection.VEHICLE_OCCUPANT_DRIVER))
+                .isEqualTo(
+                        SystemStatusEvent.DETECTION_TYPE_PRESENCE
+                                | SystemStatusEvent.DETECTION_TYPE_GAZE
+                                | SystemStatusEvent.DETECTION_TYPE_DRIVER_MONITORING);
+
+        assertThat(
+                        mOasService.getCapabilityForRole(
+                                OccupantAwarenessDetection.VEHICLE_OCCUPANT_FRONT_PASSENGER))
+                .isEqualTo(SystemStatusEvent.DETECTION_TYPE_PRESENCE);
+
+        assertThat(
+                        mOasService.getCapabilityForRole(
+                                OccupantAwarenessDetection.VEHICLE_OCCUPANT_ROW_2_PASSENGER_RIGHT))
+                .isEqualTo(SystemStatusEvent.DETECTION_TYPE_NONE);
+
+        assertThat(
+                        mOasService.getCapabilityForRole(
+                                OccupantAwarenessDetection.VEHICLE_OCCUPANT_NONE))
+                .isEqualTo(SystemStatusEvent.DETECTION_TYPE_NONE);
+    }
+
+    @Test
+    public void test_serviceStartsAndStopGraphWithListeners() throws Exception {
+        // Verify that the service starts the detection graph when the first client connects, and
+        // stop when the last client disconnects.
+
+        // Should be not running on start (no clients are yet connected).
+        assertThat(mMockHal.isGraphRunning()).isFalse();
+
+        // Connect a client. Graph should be running.
+        IOccupantAwarenessEventCallback first_client = registerCallbackToService();
+        assertThat(mMockHal.isGraphRunning()).isTrue();
+
+        // Connect a second client. Graph should continue running.
+        IOccupantAwarenessEventCallback second_client = registerCallbackToService();
+        assertThat(mMockHal.isGraphRunning()).isTrue();
+
+        // Remove the first client. Graph should continue to run since a client still remains.
+        mOasService.unregisterEventListener(first_client);
+        assertThat(mMockHal.isGraphRunning()).isTrue();
+
+        // Remove the second client. Graph should now stop since all clients have now closed.
+        mOasService.unregisterEventListener(second_client);
+        assertThat(mMockHal.isGraphRunning()).isFalse();
+    }
+
+    /** Registers a listener to the service. */
+    private IOccupantAwarenessEventCallback registerCallbackToService() {
+        IOccupantAwarenessEventCallback callback =
+                new IOccupantAwarenessEventCallback.Stub() {
+                    @Override
+                    public void onStatusChanged(SystemStatusEvent systemStatusEvent) {
+                        mFutureStatus.complete(systemStatusEvent);
+                    }
+
+                    public void onDetectionEvent(OccupantAwarenessDetection detectionEvent) {
+                        mFutureDetection.complete(detectionEvent);
+                    }
+                };
+
+        mOasService.registerEventListener(callback);
+        return callback;
+    }
+
+    /** Resets futures for testing. */
+    private void resetFutures() {
+        mFutureStatus = new CompletableFuture<>();
+        mFutureDetection = new CompletableFuture<>();
+    }
+}
diff --git a/tests/OccupantAwareness/src/com/android/car/test/OccupantAwarenessUtilsTest.java b/tests/OccupantAwareness/src/com/android/car/test/OccupantAwarenessUtilsTest.java
new file mode 100644
index 0000000..fc6fa06
--- /dev/null
+++ b/tests/OccupantAwareness/src/com/android/car/test/OccupantAwarenessUtilsTest.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.car;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.car.occupantawareness.OccupantAwarenessDetection;
+import android.car.occupantawareness.SystemStatusEvent;
+import android.hardware.automotive.occupant_awareness.IOccupantAwareness;
+import android.hardware.automotive.occupant_awareness.OccupantAwarenessStatus;
+import android.test.suitebuilder.annotation.MediumTest;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import junit.framework.TestCase;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@MediumTest
+public class OccupantAwarenessUtilsTest extends TestCase {
+
+    @Test
+    public void test_convertToStatusEvent() {
+        SystemStatusEvent event;
+
+        event =
+                OccupantAwarenessUtils.convertToStatusEvent(
+                        IOccupantAwareness.CAP_NONE, OccupantAwarenessStatus.READY);
+        assertThat(event.systemStatus).isEqualTo(SystemStatusEvent.SYSTEM_STATUS_READY);
+        assertThat(event.detectionType).isEqualTo(SystemStatusEvent.DETECTION_TYPE_NONE);
+
+        event =
+                OccupantAwarenessUtils.convertToStatusEvent(
+                        IOccupantAwareness.CAP_PRESENCE_DETECTION,
+                        OccupantAwarenessStatus.NOT_SUPPORTED);
+        assertThat(event.systemStatus).isEqualTo(SystemStatusEvent.SYSTEM_STATUS_NOT_SUPPORTED);
+        assertThat(event.detectionType).isEqualTo(SystemStatusEvent.DETECTION_TYPE_PRESENCE);
+
+        event =
+                OccupantAwarenessUtils.convertToStatusEvent(
+                        IOccupantAwareness.CAP_GAZE_DETECTION,
+                        OccupantAwarenessStatus.NOT_INITIALIZED);
+        assertThat(event.systemStatus).isEqualTo(SystemStatusEvent.SYSTEM_STATUS_NOT_READY);
+        assertThat(event.detectionType).isEqualTo(SystemStatusEvent.DETECTION_TYPE_GAZE);
+
+        event =
+                OccupantAwarenessUtils.convertToStatusEvent(
+                        IOccupantAwareness.CAP_DRIVER_MONITORING_DETECTION,
+                        OccupantAwarenessStatus.FAILURE);
+        assertThat(event.systemStatus).isEqualTo(SystemStatusEvent.SYSTEM_STATUS_SYSTEM_FAILURE);
+        assertThat(event.detectionType)
+                .isEqualTo(SystemStatusEvent.DETECTION_TYPE_DRIVER_MONITORING);
+    }
+
+    @Test
+    public void test_convertToConfidenceScore() {
+        assertThat(
+                        OccupantAwarenessUtils.convertToConfidenceScore(
+                                android.hardware.automotive.occupant_awareness.ConfidenceLevel.MAX))
+                .isEqualTo(OccupantAwarenessDetection.CONFIDENCE_LEVEL_MAX);
+
+        assertThat(
+                        OccupantAwarenessUtils.convertToConfidenceScore(
+                                android.hardware.automotive.occupant_awareness.ConfidenceLevel
+                                        .HIGH))
+                .isEqualTo(OccupantAwarenessDetection.CONFIDENCE_LEVEL_HIGH);
+
+        assertThat(
+                        OccupantAwarenessUtils.convertToConfidenceScore(
+                                android.hardware.automotive.occupant_awareness.ConfidenceLevel.LOW))
+                .isEqualTo(OccupantAwarenessDetection.CONFIDENCE_LEVEL_LOW);
+
+        assertThat(
+                        OccupantAwarenessUtils.convertToConfidenceScore(
+                                android.hardware.automotive.occupant_awareness.ConfidenceLevel
+                                        .NONE))
+                .isEqualTo(OccupantAwarenessDetection.CONFIDENCE_LEVEL_NONE);
+    }
+
+    @Test
+    public void test_convertToPoint3D() {
+        assertThat(OccupantAwarenessUtils.convertToPoint3D(null)).isNull();
+        assertThat(OccupantAwarenessUtils.convertToPoint3D(new double[0])).isNull();
+        assertThat(OccupantAwarenessUtils.convertToPoint3D(new double[2])).isNull();
+        assertThat(OccupantAwarenessUtils.convertToPoint3D(new double[] {1, 2, 3})).isNotNull();
+    }
+
+    @Test
+    public void test_convertToRole() {
+        assertThat(
+                        OccupantAwarenessUtils.convertToRole(
+                                android.hardware.automotive.occupant_awareness.Role.INVALID))
+                .isEqualTo(OccupantAwarenessDetection.VEHICLE_OCCUPANT_NONE);
+
+        assertThat(
+                        OccupantAwarenessUtils.convertToRole(
+                                android.hardware.automotive.occupant_awareness.Role.UNKNOWN))
+                .isEqualTo(OccupantAwarenessDetection.VEHICLE_OCCUPANT_NONE);
+
+        assertThat(
+                        OccupantAwarenessUtils.convertToRole(
+                                android.hardware.automotive.occupant_awareness.Role.DRIVER
+                                        | android.hardware.automotive.occupant_awareness.Role
+                                                .FRONT_PASSENGER
+                                        | android.hardware.automotive.occupant_awareness.Role
+                                                .ROW_2_PASSENGER_CENTER))
+                .isEqualTo(
+                        OccupantAwarenessDetection.VEHICLE_OCCUPANT_DRIVER
+                                | OccupantAwarenessDetection.VEHICLE_OCCUPANT_FRONT_PASSENGER
+                                | OccupantAwarenessDetection
+                                        .VEHICLE_OCCUPANT_ROW_2_PASSENGER_CENTER);
+
+        assertThat(
+                        OccupantAwarenessUtils.convertToRole(
+                                android.hardware.automotive.occupant_awareness.Role.ALL_OCCUPANTS))
+                .isEqualTo(OccupantAwarenessDetection.VEHICLE_OCCUPANT_ALL_OCCUPANTS);
+    }
+}
diff --git a/tests/PowerTestService/Android.mk b/tests/PowerTestService/Android.mk
index 6ea33f2..aeca2e9 100644
--- a/tests/PowerTestService/Android.mk
+++ b/tests/PowerTestService/Android.mk
@@ -28,7 +28,6 @@
     -Wno-unused-parameter
 
 LOCAL_C_INCLUDES += \
-    frameworks/base/include \
     packages/services/Car/car-lib/native/include
 
 LOCAL_SHARED_LIBRARIES := \
diff --git a/tests/SecondaryHomeApp/Android.bp b/tests/SecondaryHomeApp/Android.bp
new file mode 100644
index 0000000..ce8e763
--- /dev/null
+++ b/tests/SecondaryHomeApp/Android.bp
@@ -0,0 +1,49 @@
+//
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+android_app {
+    name: "SecondaryHomeApp",
+
+    srcs: [
+        "src/**/*.java",
+        "src/**/I*.aidl",
+    ],
+
+    static_libs: [
+        "android.car.userlib",
+        "androidx.appcompat_appcompat",
+        "androidx.recyclerview_recyclerview",
+        "androidx.legacy_legacy-support-v4",
+        "androidx.lifecycle_lifecycle-extensions",
+        "com.google.android.material_material",
+        "CarNotificationLib",
+        "car-ui-lib"
+    ],
+
+    libs: [
+        "android.car",
+    ],
+
+    manifest: "AndroidManifest.xml",
+
+    platform_apis: true,
+    certificate: "platform",
+    privileged: true,
+    required: ["privapp_whitelist_com.android.car.secondaryhome"],
+
+    resource_dirs: [
+        "res",
+    ],
+}
diff --git a/tests/SecondaryHomeApp/AndroidManifest.xml b/tests/SecondaryHomeApp/AndroidManifest.xml
new file mode 100644
index 0000000..0a84531
--- /dev/null
+++ b/tests/SecondaryHomeApp/AndroidManifest.xml
@@ -0,0 +1,60 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    package="com.android.car.secondaryhome">
+    <!-- System permission to host maps activity -->
+    <uses-permission android:name="android.permission.ACTIVITY_EMBEDDING"/>
+    <!-- System permission to send events to hosted maps activity -->
+    <uses-permission android:name="android.permission.INJECT_EVENTS"/>
+    <!-- System permission to use internal system windows -->
+    <uses-permission android:name="android.permission.INTERNAL_SYSTEM_WINDOW"/>
+    <!-- System permissions to bring hosted activity to front on current display -->
+    <uses-permission android:name="android.permission.MANAGE_ACTIVITY_STACKS"/>
+    <uses-permission android:name="android.permission.REORDER_TASKS"/>
+    <!-- System permissions to querry user -->
+    <uses-permission android:name="android.permission.MANAGE_USERS"/>
+
+    <application
+        android:label="@string/app_name"
+        tools:replace="android:label">
+
+        <activity
+            android:name=".launcher.LauncherActivity"
+            android:label="@string/app_launcher"
+            android:theme="@style/LauncherTheme"
+            android:configChanges="orientation|screenSize|smallestScreenSize|
+                screenLayout|colorMode|density"
+            android:documentLaunchMode="always"
+            android:multiprocess="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+        </activity>
+        <service android:name=".launcher.NotificationListener"
+              android:label="@string/notification_service_label"
+              android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE"
+              android:directBootAware="true">
+            <intent-filter>
+                <action android:name="android.service.notification.NotificationListenerService"/>
+            </intent-filter>
+        </service>
+    </application>
+</manifest>
+
diff --git a/tests/SecondaryHomeApp/CleanSpec.mk b/tests/SecondaryHomeApp/CleanSpec.mk
new file mode 100644
index 0000000..93662b6
--- /dev/null
+++ b/tests/SecondaryHomeApp/CleanSpec.mk
@@ -0,0 +1,52 @@
+# Copyright (C) 2019 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+# If you don't need to do a full clean build but would like to touch
+# a file or delete some intermediate files, add a clean step to the end
+# of the list.  These steps will only be run once, if they haven't been
+# run before.
+#
+# E.g.:
+#     $(call add-clean-step, touch -c external/sqlite/sqlite3.h)
+#     $(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/STATIC_LIBRARIES/libz_intermediates)
+#
+# Always use "touch -c" and "rm -f" or "rm -rf" to gracefully deal with
+# files that are missing or have been moved.
+#
+# Use $(PRODUCT_OUT) to get to the "out/target/product/blah/" directory.
+# Use $(OUT_DIR) to refer to the "out" directory.
+#
+# If you need to re-do something that's already mentioned, just copy
+# the command and add it to the bottom of the list.  E.g., if a change
+# that you made last week required touching a file and a change you
+# made today requires touching the same file, just copy the old
+# touch step and add it to the end of the list.
+#
+# ************************************************
+# NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST
+# ************************************************
+
+# For example:
+#$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/APPS/AndroidTests_intermediates)
+#$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/core_intermediates)
+#$(call add-clean-step, find $(OUT_DIR) -type f -name "IGTalkSession*" -print0 | xargs -0 rm -f)
+#$(call add-clean-step, rm -rf $(PRODUCT_OUT)/data/*)
+
+$(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/product/priv-app/SecondaryHomeApp)
+$(call add-clean-step, rm -rf $(PRODUCT_OUT)/product/priv-app/SecondaryHomeApp)
+
+# ************************************************
+# NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST
+# ************************************************
diff --git a/tests/SecondaryHomeApp/res/drawable/ic_arrow_back.xml b/tests/SecondaryHomeApp/res/drawable/ic_arrow_back.xml
new file mode 100644
index 0000000..658a068
--- /dev/null
+++ b/tests/SecondaryHomeApp/res/drawable/ic_arrow_back.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:viewportWidth="24"
+    android:viewportHeight="24"
+    android:width="48dp"
+    android:height="48dp">
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M15.41 16.09l-4.58-4.59 4.58-4.59L14 5.5l-6 6 6 6z"/>
+</vector>
diff --git a/tests/SecondaryHomeApp/res/drawable/ic_home.xml b/tests/SecondaryHomeApp/res/drawable/ic_home.xml
new file mode 100644
index 0000000..18c26ad
--- /dev/null
+++ b/tests/SecondaryHomeApp/res/drawable/ic_home.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:viewportWidth="48"
+    android:viewportHeight="48"
+    android:width="48dp"
+    android:height="48dp">
+    <path
+        android:pathData="M7.33333333 14.6666667L14.6666667 14.6666667L14.6666667 7.33333333L7.33333333 7.33333333L7.33333333 14.6666667ZM18.3333333 36.6666667L25.6666667 36.6666667L25.6666667 29.3333333L18.3333333 29.3333333L18.3333333 36.6666667ZM7.33333333 36.6666667L14.6666667 36.6666667L14.6666667 29.3333333L7.33333333 29.3333333L7.33333333 36.6666667ZM7.33333333 25.6666667L14.6666667 25.6666667L14.6666667 18.3333333L7.33333333 18.3333333L7.33333333 25.6666667ZM18.3333333 25.6666667L25.6666667 25.6666667L25.6666667 18.3333333L18.3333333 18.3333333L18.3333333 25.6666667ZM29.3333333 7.33333333L29.3333333 14.6666667L36.6666667 14.6666667L36.6666667 7.33333333L29.3333333 7.33333333ZM18.3333333 14.6666667L25.6666667 14.6666667L25.6666667 7.33333333L18.3333333 7.33333333L18.3333333 14.6666667ZM29.3333333 25.6666667L36.6666667 25.6666667L36.6666667 18.3333333L29.3333333 18.3333333L29.3333333 25.6666667ZM29.3333333 36.6666667L36.6666667 36.6666667L36.6666667 29.3333333L29.3333333 29.3333333L29.3333333 36.6666667Z"
+        android:fillColor="@color/nav_icon_fill_color" />
+</vector>
diff --git a/tests/SecondaryHomeApp/res/drawable/ic_notification.xml b/tests/SecondaryHomeApp/res/drawable/ic_notification.xml
new file mode 100644
index 0000000..ab55431
--- /dev/null
+++ b/tests/SecondaryHomeApp/res/drawable/ic_notification.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2020 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:viewportWidth="48"
+    android:viewportHeight="48"
+    android:width="48dp"
+    android:height="48dp">
+    <path
+        android:pathData="M22 39.125C23.925 39.125 25.5 37.55 25.5 35.625L18.5 35.625C18.5 37.55 20.0575 39.125 22 39.125ZM32.5 28.625L32.5 19.875C32.5 14.5025 29.63 10.005 24.625 8.815L24.625 7.625C24.625 6.1725 23.4525 5 22 5C20.5475 5 19.375 6.1725 19.375 7.625L19.375 8.815C14.3525 10.005 11.5 14.485 11.5 19.875L11.5 28.625L8 32.125L8 33.875L36 33.875L36 32.125L32.5 28.625Z"
+        android:fillColor="@color/nav_icon_fill_color" />
+</vector>
diff --git a/tests/SecondaryHomeApp/res/drawable/ic_notification_unseen.xml b/tests/SecondaryHomeApp/res/drawable/ic_notification_unseen.xml
new file mode 100644
index 0000000..4b33825
--- /dev/null
+++ b/tests/SecondaryHomeApp/res/drawable/ic_notification_unseen.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:viewportWidth="44"
+        android:viewportHeight="44"
+        android:width="44dp"
+        android:height="44dp">
+  <path
+      android:pathData="M22 39.125C23.925 39.125 25.5 37.55 25.5 35.625L18.5 35.625C18.5 37.55 20.0575 39.125 22 39.125ZM32.5 28.625L32.5 19.875C32.5 14.5025 29.63 10.005 24.625 8.815L24.625 7.625C24.625 6.1725 23.4525 5 22 5C20.5475 5 19.375 6.1725 19.375 7.625L19.375 8.815C14.3525 10.005 11.5 14.485 11.5 19.875L11.5 28.625L8 32.125L8 33.875L36 33.875L36 32.125L32.5 28.625Z"
+      android:fillColor="@color/nav_icon_fill_color" />
+  <group
+      android:translateX="30"
+      android:translateY="2">
+    <path
+        android:fillColor="@color/notification_unseen_color"
+        android:strokeWidth="1"
+        android:pathData="M 6 0 C 9.31370849898 0 12 2.68629150102 12 6 C 12 9.31370849898 9.31370849898 12 6 12 C 2.68629150102 12 0 9.31370849898 0 6 C 0 2.68629150102 2.68629150102 0 6 0 Z" />
+  </group>
+</vector>
diff --git a/tests/SecondaryHomeApp/res/drawable/nav_button_background.xml b/tests/SecondaryHomeApp/res/drawable/nav_button_background.xml
new file mode 100644
index 0000000..0c1bd93
--- /dev/null
+++ b/tests/SecondaryHomeApp/res/drawable/nav_button_background.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+  -->
+
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+    android:color="@color/nav_bar_ripple_background_color">
+    <item android:id="@android:id/mask">
+        <shape android:shape="rectangle">
+            <solid android:color="?android:colorAccent"/>
+            <corners android:radius="6dp"/>
+        </shape>
+    </item>
+</ripple>
diff --git a/tests/SecondaryHomeApp/res/layout/activity_main.xml b/tests/SecondaryHomeApp/res/layout/activity_main.xml
new file mode 100644
index 0000000..1156f56
--- /dev/null
+++ b/tests/SecondaryHomeApp/res/layout/activity_main.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/RootView"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:background="@color/background_color"
+    android:fitsSystemWindows="true"
+    android:orientation="vertical" >
+
+    <FrameLayout
+        android:id="@+id/container"
+        android:layout_width="match_parent"
+        android:layout_height="0dp"
+        android:layout_weight="1">
+    </FrameLayout>
+
+    <FrameLayout
+        android:id="@+id/navigation_bar"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_gravity="bottom">
+      <include layout="@layout/car_navigation_bar"/>
+    </FrameLayout>
+</LinearLayout>
diff --git a/tests/SecondaryHomeApp/res/layout/app_fragment.xml b/tests/SecondaryHomeApp/res/layout/app_fragment.xml
new file mode 100644
index 0000000..3f778f5
--- /dev/null
+++ b/tests/SecondaryHomeApp/res/layout/app_fragment.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<FrameLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/app_fragment"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <ActivityView
+        android:id="@+id/app_view"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"/>
+</FrameLayout>
diff --git a/tests/SecondaryHomeApp/res/layout/app_grid_item.xml b/tests/SecondaryHomeApp/res/layout/app_grid_item.xml
new file mode 100644
index 0000000..df2b723
--- /dev/null
+++ b/tests/SecondaryHomeApp/res/layout/app_grid_item.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="vertical"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:gravity="center">
+
+    <ImageView
+        android:id="@+id/app_icon"
+        android:layout_width="@dimen/app_icon_width"
+        android:layout_height="@dimen/app_icon_height" />
+
+    <TextView
+        android:id="@+id/app_name"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:gravity="center"
+        android:maxLines="1" />
+</LinearLayout>
diff --git a/tests/SecondaryHomeApp/res/layout/car_navigation_bar.xml b/tests/SecondaryHomeApp/res/layout/car_navigation_bar.xml
new file mode 100644
index 0000000..b494c3a
--- /dev/null
+++ b/tests/SecondaryHomeApp/res/layout/car_navigation_bar.xml
@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+  -->
+
+<FrameLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:orientation="vertical">
+
+    <LinearLayout
+        android:id="@+id/nav_bar"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_weight="1"
+        android:gravity="center">
+
+        <ImageButton
+            android:layout_width="0dp"
+            android:layout_height="match_parent"
+            android:layout_weight="1"
+            android:id="@+id/nav_back"
+            android:src="@drawable/ic_arrow_back"
+            android:gravity="center"
+          />
+
+        <ImageButton
+            android:layout_width="0dp"
+            android:layout_height="match_parent"
+            android:layout_weight="1"
+            android:id="@+id/nav_home"
+            android:src="@drawable/ic_home"
+            android:gravity="center"
+          />
+
+          <ImageButton
+            android:layout_width="0dp"
+            android:layout_height="match_parent"
+            android:layout_weight="1"
+            android:id="@+id/nav_notification"
+            android:src="@drawable/ic_notification"
+            android:gravity="center"
+          />
+    </LinearLayout>
+</FrameLayout>
diff --git a/tests/SecondaryHomeApp/res/layout/home_fragment.xml b/tests/SecondaryHomeApp/res/layout/home_fragment.xml
new file mode 100644
index 0000000..6fe0c79
--- /dev/null
+++ b/tests/SecondaryHomeApp/res/layout/home_fragment.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<FrameLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/home_fragment"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:background="@color/background_color"
+    android:layout_margin="@dimen/app_grid_margin_top">
+
+    <GridView
+        android:id="@+id/app_grid"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:columnWidth="@dimen/app_list_col_width"
+        android:verticalSpacing="@dimen/app_list_horizontal_spacing"
+        android:horizontalSpacing="@dimen/app_list_vertical_spacing"
+        android:numColumns="auto_fit"
+        android:name="app_grid" />
+</FrameLayout>
diff --git a/tests/SecondaryHomeApp/res/layout/notification_fragment.xml b/tests/SecondaryHomeApp/res/layout/notification_fragment.xml
new file mode 100644
index 0000000..217d6cc
--- /dev/null
+++ b/tests/SecondaryHomeApp/res/layout/notification_fragment.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2020 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<FrameLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:id="@+id/notification_fragment"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <Button
+        android:id="@+id/clear_all_button"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="@string/clear_all"
+        style="@style/ClearButton"/>
+
+    <ScrollView
+        android:id="@+id/debug_container"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:layout_marginTop="50dp"
+        android:gravity="center_vertical">
+
+        <TextView
+            android:id="@+id/debug"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:textSize="32sp"
+            android:gravity="center_vertical"/>
+    </ScrollView>
+</FrameLayout>
diff --git a/tests/SecondaryHomeApp/res/values-af/strings.xml b/tests/SecondaryHomeApp/res/values-af/strings.xml
new file mode 100644
index 0000000..31ac308
--- /dev/null
+++ b/tests/SecondaryHomeApp/res/values-af/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="1232190134475133062">"SecondaryHomeApp"</string>
+    <string name="app_launcher" msgid="3285007106222765396">"SecHome-lanseerder"</string>
+    <string name="launch_fail_msg" msgid="2935729218024647088">"Kon nie begin nie"</string>
+    <string name="clear_all" msgid="7403807937179450743">"Vee alles uit"</string>
+</resources>
diff --git a/tests/SecondaryHomeApp/res/values-am/strings.xml b/tests/SecondaryHomeApp/res/values-am/strings.xml
new file mode 100644
index 0000000..e873c1e
--- /dev/null
+++ b/tests/SecondaryHomeApp/res/values-am/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="1232190134475133062">"SecondaryHomeApp"</string>
+    <string name="app_launcher" msgid="3285007106222765396">"የSecHome ማስጀመሪያ"</string>
+    <string name="launch_fail_msg" msgid="2935729218024647088">"ማስጀመር አልተቻለም"</string>
+    <string name="clear_all" msgid="7403807937179450743">"ሁሉንም አጽዳ"</string>
+</resources>
diff --git a/tests/SecondaryHomeApp/res/values-ar/strings.xml b/tests/SecondaryHomeApp/res/values-ar/strings.xml
new file mode 100644
index 0000000..9920b3c
--- /dev/null
+++ b/tests/SecondaryHomeApp/res/values-ar/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="1232190134475133062">"SecondaryHomeApp"</string>
+    <string name="app_launcher" msgid="3285007106222765396">"مشغّل التطبيقات SecHome"</string>
+    <string name="launch_fail_msg" msgid="2935729218024647088">"تعذّر فتح"</string>
+    <string name="clear_all" msgid="7403807937179450743">"محو الكل"</string>
+</resources>
diff --git a/tests/SecondaryHomeApp/res/values-as/strings.xml b/tests/SecondaryHomeApp/res/values-as/strings.xml
new file mode 100644
index 0000000..99bd16b
--- /dev/null
+++ b/tests/SecondaryHomeApp/res/values-as/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="1232190134475133062">"SecondaryHomeApp"</string>
+    <string name="app_launcher" msgid="3285007106222765396">"SecHome লঞ্চাৰ"</string>
+    <string name="launch_fail_msg" msgid="2935729218024647088">"লঞ্চ কৰিব পৰা নগ’ল"</string>
+    <string name="clear_all" msgid="7403807937179450743">"সকলো মচক"</string>
+</resources>
diff --git a/tests/SecondaryHomeApp/res/values-az/strings.xml b/tests/SecondaryHomeApp/res/values-az/strings.xml
new file mode 100644
index 0000000..78b6841
--- /dev/null
+++ b/tests/SecondaryHomeApp/res/values-az/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="1232190134475133062">"SecondaryHomeApp"</string>
+    <string name="app_launcher" msgid="3285007106222765396">"SecHome Başladıcısı"</string>
+    <string name="launch_fail_msg" msgid="2935729218024647088">"İşə salmaq alınmadı"</string>
+    <string name="clear_all" msgid="7403807937179450743">"Hamısını silin"</string>
+</resources>
diff --git a/tests/SecondaryHomeApp/res/values-b+sr+Latn/strings.xml b/tests/SecondaryHomeApp/res/values-b+sr+Latn/strings.xml
new file mode 100644
index 0000000..f4db301
--- /dev/null
+++ b/tests/SecondaryHomeApp/res/values-b+sr+Latn/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="1232190134475133062">"SecondaryHomeApp"</string>
+    <string name="app_launcher" msgid="3285007106222765396">"Pokretač aplikacije SecHome"</string>
+    <string name="launch_fail_msg" msgid="2935729218024647088">"Pokretanje nije uspelo"</string>
+    <string name="clear_all" msgid="7403807937179450743">"Obriši sve"</string>
+</resources>
diff --git a/tests/SecondaryHomeApp/res/values-be/strings.xml b/tests/SecondaryHomeApp/res/values-be/strings.xml
new file mode 100644
index 0000000..526ae17
--- /dev/null
+++ b/tests/SecondaryHomeApp/res/values-be/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="1232190134475133062">"SecondaryHomeApp"</string>
+    <string name="app_launcher" msgid="3285007106222765396">"Праграма запуску SecHome"</string>
+    <string name="launch_fail_msg" msgid="2935729218024647088">"Не ўдалося запусціць"</string>
+    <string name="clear_all" msgid="7403807937179450743">"Ачысціць усё"</string>
+</resources>
diff --git a/tests/SecondaryHomeApp/res/values-bg/strings.xml b/tests/SecondaryHomeApp/res/values-bg/strings.xml
new file mode 100644
index 0000000..e100da1
--- /dev/null
+++ b/tests/SecondaryHomeApp/res/values-bg/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="1232190134475133062">"SecondaryHomeApp"</string>
+    <string name="app_launcher" msgid="3285007106222765396">"Стартов панел на SecHome"</string>
+    <string name="launch_fail_msg" msgid="2935729218024647088">"Не можа да се стартира"</string>
+    <string name="clear_all" msgid="7403807937179450743">"Изчистване на всичко"</string>
+</resources>
diff --git a/tests/SecondaryHomeApp/res/values-bn/strings.xml b/tests/SecondaryHomeApp/res/values-bn/strings.xml
new file mode 100644
index 0000000..7b0cc75
--- /dev/null
+++ b/tests/SecondaryHomeApp/res/values-bn/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="1232190134475133062">"SecondaryHomeApp"</string>
+    <string name="app_launcher" msgid="3285007106222765396">"SecHome লঞ্চার"</string>
+    <string name="launch_fail_msg" msgid="2935729218024647088">"শুরু করা যায়নি"</string>
+    <string name="clear_all" msgid="7403807937179450743">"সব মুছে দিন"</string>
+</resources>
diff --git a/tests/SecondaryHomeApp/res/values-bs/strings.xml b/tests/SecondaryHomeApp/res/values-bs/strings.xml
new file mode 100644
index 0000000..54b9ab3
--- /dev/null
+++ b/tests/SecondaryHomeApp/res/values-bs/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="1232190134475133062">"SecondaryHomeApp"</string>
+    <string name="app_launcher" msgid="3285007106222765396">"Pokretač za SecHome"</string>
+    <string name="launch_fail_msg" msgid="2935729218024647088">"Pokretanje nije uspjelo"</string>
+    <string name="clear_all" msgid="7403807937179450743">"Obriši sve"</string>
+</resources>
diff --git a/tests/SecondaryHomeApp/res/values-ca/strings.xml b/tests/SecondaryHomeApp/res/values-ca/strings.xml
new file mode 100644
index 0000000..17f2514
--- /dev/null
+++ b/tests/SecondaryHomeApp/res/values-ca/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="1232190134475133062">"SecondaryHomeApp"</string>
+    <string name="app_launcher" msgid="3285007106222765396">"SecHome Launcher"</string>
+    <string name="launch_fail_msg" msgid="2935729218024647088">"No s\'ha pogut iniciar"</string>
+    <string name="clear_all" msgid="7403807937179450743">"Esborra-ho tot"</string>
+</resources>
diff --git a/tests/SecondaryHomeApp/res/values-cs/strings.xml b/tests/SecondaryHomeApp/res/values-cs/strings.xml
new file mode 100644
index 0000000..2a7a8bc
--- /dev/null
+++ b/tests/SecondaryHomeApp/res/values-cs/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="1232190134475133062">"SecondaryHomeApp"</string>
+    <string name="app_launcher" msgid="3285007106222765396">"SecHome Launcher"</string>
+    <string name="launch_fail_msg" msgid="2935729218024647088">"Nelze spustit"</string>
+    <string name="clear_all" msgid="7403807937179450743">"Vymazat vše"</string>
+</resources>
diff --git a/tests/SecondaryHomeApp/res/values-da/strings.xml b/tests/SecondaryHomeApp/res/values-da/strings.xml
new file mode 100644
index 0000000..17df754
--- /dev/null
+++ b/tests/SecondaryHomeApp/res/values-da/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="1232190134475133062">"SecondaryHomeApp"</string>
+    <string name="app_launcher" msgid="3285007106222765396">"SecHome-starter"</string>
+    <string name="launch_fail_msg" msgid="2935729218024647088">"Kunne ikke åbnes"</string>
+    <string name="clear_all" msgid="7403807937179450743">"Ryd alt"</string>
+</resources>
diff --git a/tests/SecondaryHomeApp/res/values-de/strings.xml b/tests/SecondaryHomeApp/res/values-de/strings.xml
new file mode 100644
index 0000000..76c249f
--- /dev/null
+++ b/tests/SecondaryHomeApp/res/values-de/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="1232190134475133062">"SecondaryHomeApp"</string>
+    <string name="app_launcher" msgid="3285007106222765396">"SecHome-Launcher"</string>
+    <string name="launch_fail_msg" msgid="2935729218024647088">"Konnte nicht gestartet werden"</string>
+    <string name="clear_all" msgid="7403807937179450743">"Alle löschen"</string>
+</resources>
diff --git a/tests/SecondaryHomeApp/res/values-el/strings.xml b/tests/SecondaryHomeApp/res/values-el/strings.xml
new file mode 100644
index 0000000..8f9b5e9
--- /dev/null
+++ b/tests/SecondaryHomeApp/res/values-el/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="1232190134475133062">"SecondaryHomeApp"</string>
+    <string name="app_launcher" msgid="3285007106222765396">"Εφαρμογή εκκίνησης SecHome"</string>
+    <string name="launch_fail_msg" msgid="2935729218024647088">"Δεν ήταν δυνατή η εκκίνηση"</string>
+    <string name="clear_all" msgid="7403807937179450743">"Διαγραφή όλων"</string>
+</resources>
diff --git a/tests/SecondaryHomeApp/res/values-en-rAU/strings.xml b/tests/SecondaryHomeApp/res/values-en-rAU/strings.xml
new file mode 100644
index 0000000..b4aaaf6
--- /dev/null
+++ b/tests/SecondaryHomeApp/res/values-en-rAU/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="1232190134475133062">"SecondaryHomeApp"</string>
+    <string name="app_launcher" msgid="3285007106222765396">"SecHome Launcher"</string>
+    <string name="launch_fail_msg" msgid="2935729218024647088">"Couldn\'t launch"</string>
+    <string name="clear_all" msgid="7403807937179450743">"Clear all"</string>
+</resources>
diff --git a/tests/SecondaryHomeApp/res/values-en-rCA/strings.xml b/tests/SecondaryHomeApp/res/values-en-rCA/strings.xml
new file mode 100644
index 0000000..b4aaaf6
--- /dev/null
+++ b/tests/SecondaryHomeApp/res/values-en-rCA/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="1232190134475133062">"SecondaryHomeApp"</string>
+    <string name="app_launcher" msgid="3285007106222765396">"SecHome Launcher"</string>
+    <string name="launch_fail_msg" msgid="2935729218024647088">"Couldn\'t launch"</string>
+    <string name="clear_all" msgid="7403807937179450743">"Clear all"</string>
+</resources>
diff --git a/tests/SecondaryHomeApp/res/values-en-rGB/strings.xml b/tests/SecondaryHomeApp/res/values-en-rGB/strings.xml
new file mode 100644
index 0000000..b4aaaf6
--- /dev/null
+++ b/tests/SecondaryHomeApp/res/values-en-rGB/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="1232190134475133062">"SecondaryHomeApp"</string>
+    <string name="app_launcher" msgid="3285007106222765396">"SecHome Launcher"</string>
+    <string name="launch_fail_msg" msgid="2935729218024647088">"Couldn\'t launch"</string>
+    <string name="clear_all" msgid="7403807937179450743">"Clear all"</string>
+</resources>
diff --git a/tests/SecondaryHomeApp/res/values-en-rIN/strings.xml b/tests/SecondaryHomeApp/res/values-en-rIN/strings.xml
new file mode 100644
index 0000000..b4aaaf6
--- /dev/null
+++ b/tests/SecondaryHomeApp/res/values-en-rIN/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="1232190134475133062">"SecondaryHomeApp"</string>
+    <string name="app_launcher" msgid="3285007106222765396">"SecHome Launcher"</string>
+    <string name="launch_fail_msg" msgid="2935729218024647088">"Couldn\'t launch"</string>
+    <string name="clear_all" msgid="7403807937179450743">"Clear all"</string>
+</resources>
diff --git a/tests/SecondaryHomeApp/res/values-en-rXC/strings.xml b/tests/SecondaryHomeApp/res/values-en-rXC/strings.xml
new file mode 100644
index 0000000..92ad83a
--- /dev/null
+++ b/tests/SecondaryHomeApp/res/values-en-rXC/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="1232190134475133062">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‎‏‏‎‎‎‏‎‎‎‏‏‎‎‏‏‎‎‏‏‏‏‎‎‏‏‎‎‏‏‎‎‎‎‏‏‏‎‏‎‎‏‎‎‎‎‏‏‎‎‏‎‎‎‎‏‎‎‎‎‏‏‎‎SecondaryHomeApp‎‏‎‎‏‎"</string>
+    <string name="app_launcher" msgid="3285007106222765396">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‎‏‎‏‏‎‏‏‎‎‏‎‏‏‎‏‎‏‏‎‎‎‎‏‎‎‎‏‏‏‎‏‏‏‏‎‎‏‏‎‎‎‎‏‎‏‎‏‎‏‏‎‏‎‏‎‏‎‏‎‏‎‎‎SecHome Launcher‎‏‎‎‏‎"</string>
+    <string name="launch_fail_msg" msgid="2935729218024647088">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‎‏‎‏‎‎‎‏‎‏‏‏‏‎‏‏‏‎‎‏‏‏‎‎‎‏‎‏‎‏‏‎‏‏‎‏‏‎‏‎‎‎‏‏‎‎‎‎‎‏‎‏‎‎‏‏‎‏‏‎‎‎‎‎Couldn\'t launch‎‏‎‎‏‎"</string>
+    <string name="clear_all" msgid="7403807937179450743">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‎‎‏‏‎‏‎‏‏‏‏‏‏‏‎‎‏‏‏‎‎‎‎‎‏‎‏‏‏‏‎‎‎‎‎‎‎‏‏‎‎‏‏‎‎‎‏‏‏‏‏‎‏‎‏‏‏‎‏‏‏‎Clear all‎‏‎‎‏‎"</string>
+</resources>
diff --git a/tests/SecondaryHomeApp/res/values-es-rUS/strings.xml b/tests/SecondaryHomeApp/res/values-es-rUS/strings.xml
new file mode 100644
index 0000000..fe9bd48
--- /dev/null
+++ b/tests/SecondaryHomeApp/res/values-es-rUS/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="1232190134475133062">"SecondaryHomeApp"</string>
+    <string name="app_launcher" msgid="3285007106222765396">"SecHome Launcher"</string>
+    <string name="launch_fail_msg" msgid="2935729218024647088">"No se pudo iniciar"</string>
+    <string name="clear_all" msgid="7403807937179450743">"Borrar todo"</string>
+</resources>
diff --git a/tests/SecondaryHomeApp/res/values-es/strings.xml b/tests/SecondaryHomeApp/res/values-es/strings.xml
new file mode 100644
index 0000000..450e6b1
--- /dev/null
+++ b/tests/SecondaryHomeApp/res/values-es/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="1232190134475133062">"SecondaryHomeApp"</string>
+    <string name="app_launcher" msgid="3285007106222765396">"SecHome Launcher"</string>
+    <string name="launch_fail_msg" msgid="2935729218024647088">"No se ha podido iniciar"</string>
+    <string name="clear_all" msgid="7403807937179450743">"Borrar todo"</string>
+</resources>
diff --git a/tests/SecondaryHomeApp/res/values-et/strings.xml b/tests/SecondaryHomeApp/res/values-et/strings.xml
new file mode 100644
index 0000000..2f2fa0e
--- /dev/null
+++ b/tests/SecondaryHomeApp/res/values-et/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="1232190134475133062">"SecondaryHomeApp"</string>
+    <string name="app_launcher" msgid="3285007106222765396">"SecHome\'i käivitusprogramm"</string>
+    <string name="launch_fail_msg" msgid="2935729218024647088">"Ei õnnestunud käivitada"</string>
+    <string name="clear_all" msgid="7403807937179450743">"Kustuta kõik"</string>
+</resources>
diff --git a/tests/SecondaryHomeApp/res/values-eu/strings.xml b/tests/SecondaryHomeApp/res/values-eu/strings.xml
new file mode 100644
index 0000000..c2039c3
--- /dev/null
+++ b/tests/SecondaryHomeApp/res/values-eu/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="1232190134475133062">"SecondaryHomeApp"</string>
+    <string name="app_launcher" msgid="3285007106222765396">"SecHome Launcher"</string>
+    <string name="launch_fail_msg" msgid="2935729218024647088">"Ezin izan da abiarazi"</string>
+    <string name="clear_all" msgid="7403807937179450743">"Garbitu guztiak"</string>
+</resources>
diff --git a/tests/SecondaryHomeApp/res/values-fa/strings.xml b/tests/SecondaryHomeApp/res/values-fa/strings.xml
new file mode 100644
index 0000000..45ded85
--- /dev/null
+++ b/tests/SecondaryHomeApp/res/values-fa/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="1232190134475133062">"SecondaryHomeApp"</string>
+    <string name="app_launcher" msgid="3285007106222765396">"راه‌انداز SecHome"</string>
+    <string name="launch_fail_msg" msgid="2935729218024647088">"راه‌اندازی نشد"</string>
+    <string name="clear_all" msgid="7403807937179450743">"پاک کردن همه"</string>
+</resources>
diff --git a/tests/SecondaryHomeApp/res/values-fi/strings.xml b/tests/SecondaryHomeApp/res/values-fi/strings.xml
new file mode 100644
index 0000000..2868300
--- /dev/null
+++ b/tests/SecondaryHomeApp/res/values-fi/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="1232190134475133062">"SecondaryHomeApp"</string>
+    <string name="app_launcher" msgid="3285007106222765396">"SecHome-käynnistysohjelma"</string>
+    <string name="launch_fail_msg" msgid="2935729218024647088">"Käynnistys epäonnistui"</string>
+    <string name="clear_all" msgid="7403807937179450743">"Tyhjennä kaikki"</string>
+</resources>
diff --git a/tests/SecondaryHomeApp/res/values-fr-rCA/strings.xml b/tests/SecondaryHomeApp/res/values-fr-rCA/strings.xml
new file mode 100644
index 0000000..e3dce12
--- /dev/null
+++ b/tests/SecondaryHomeApp/res/values-fr-rCA/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="1232190134475133062">"SecondaryHomeApp"</string>
+    <string name="app_launcher" msgid="3285007106222765396">"Lanceur d\'applications SecHome"</string>
+    <string name="launch_fail_msg" msgid="2935729218024647088">"Impossible d\'effectuer le lancement"</string>
+    <string name="clear_all" msgid="7403807937179450743">"Tout effacer"</string>
+</resources>
diff --git a/tests/SecondaryHomeApp/res/values-fr/strings.xml b/tests/SecondaryHomeApp/res/values-fr/strings.xml
new file mode 100644
index 0000000..e3dce12
--- /dev/null
+++ b/tests/SecondaryHomeApp/res/values-fr/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="1232190134475133062">"SecondaryHomeApp"</string>
+    <string name="app_launcher" msgid="3285007106222765396">"Lanceur d\'applications SecHome"</string>
+    <string name="launch_fail_msg" msgid="2935729218024647088">"Impossible d\'effectuer le lancement"</string>
+    <string name="clear_all" msgid="7403807937179450743">"Tout effacer"</string>
+</resources>
diff --git a/tests/SecondaryHomeApp/res/values-gl/strings.xml b/tests/SecondaryHomeApp/res/values-gl/strings.xml
new file mode 100644
index 0000000..9dfbcb6
--- /dev/null
+++ b/tests/SecondaryHomeApp/res/values-gl/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="1232190134475133062">"SecondaryHomeApp"</string>
+    <string name="app_launcher" msgid="3285007106222765396">"Launcher de SecHom"</string>
+    <string name="launch_fail_msg" msgid="2935729218024647088">"Non se puido iniciar"</string>
+    <string name="clear_all" msgid="7403807937179450743">"Borrar todo"</string>
+</resources>
diff --git a/tests/SecondaryHomeApp/res/values-gu/strings.xml b/tests/SecondaryHomeApp/res/values-gu/strings.xml
new file mode 100644
index 0000000..9797bec
--- /dev/null
+++ b/tests/SecondaryHomeApp/res/values-gu/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="1232190134475133062">"SecondaryHomeApp"</string>
+    <string name="app_launcher" msgid="3285007106222765396">"SecHome લૉન્ચર"</string>
+    <string name="launch_fail_msg" msgid="2935729218024647088">"લૉન્ચ કરી શકાઈ નથી"</string>
+    <string name="clear_all" msgid="7403807937179450743">"બધું સાફ કરો"</string>
+</resources>
diff --git a/tests/SecondaryHomeApp/res/values-hi/strings.xml b/tests/SecondaryHomeApp/res/values-hi/strings.xml
new file mode 100644
index 0000000..ad89c0e
--- /dev/null
+++ b/tests/SecondaryHomeApp/res/values-hi/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="1232190134475133062">"SecondaryHomeApp"</string>
+    <string name="app_launcher" msgid="3285007106222765396">"सेकेंडरी होम ऐप्लिकेशन लॉन्चर"</string>
+    <string name="launch_fail_msg" msgid="2935729218024647088">"ऐप्लिकेशन नहीं खुल सका"</string>
+    <string name="clear_all" msgid="7403807937179450743">"सभी सूचनाएं हटाएं"</string>
+</resources>
diff --git a/tests/SecondaryHomeApp/res/values-hr/strings.xml b/tests/SecondaryHomeApp/res/values-hr/strings.xml
new file mode 100644
index 0000000..d1e736f
--- /dev/null
+++ b/tests/SecondaryHomeApp/res/values-hr/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="1232190134475133062">"SecondaryHomeApp"</string>
+    <string name="app_launcher" msgid="3285007106222765396">"Pokretač za SecHome"</string>
+    <string name="launch_fail_msg" msgid="2935729218024647088">"Pokretanje nije uspjelo"</string>
+    <string name="clear_all" msgid="7403807937179450743">"Izbriši sve"</string>
+</resources>
diff --git a/tests/SecondaryHomeApp/res/values-hu/strings.xml b/tests/SecondaryHomeApp/res/values-hu/strings.xml
new file mode 100644
index 0000000..654ca24
--- /dev/null
+++ b/tests/SecondaryHomeApp/res/values-hu/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="1232190134475133062">"SecondaryHomeApp"</string>
+    <string name="app_launcher" msgid="3285007106222765396">"SecHome-indító"</string>
+    <string name="launch_fail_msg" msgid="2935729218024647088">"Nem sikerült elindítani"</string>
+    <string name="clear_all" msgid="7403807937179450743">"Összes törlése"</string>
+</resources>
diff --git a/tests/SecondaryHomeApp/res/values-hy/strings.xml b/tests/SecondaryHomeApp/res/values-hy/strings.xml
new file mode 100644
index 0000000..bf7be1d
--- /dev/null
+++ b/tests/SecondaryHomeApp/res/values-hy/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="1232190134475133062">"SecondaryHomeApp"</string>
+    <string name="app_launcher" msgid="3285007106222765396">"SecHome Launcher"</string>
+    <string name="launch_fail_msg" msgid="2935729218024647088">"Չհաջողվեց գործարկել հավելվածը"</string>
+    <string name="clear_all" msgid="7403807937179450743">"Ջնջել բոլորը"</string>
+</resources>
diff --git a/tests/SecondaryHomeApp/res/values-in/strings.xml b/tests/SecondaryHomeApp/res/values-in/strings.xml
new file mode 100644
index 0000000..1d1ed9b
--- /dev/null
+++ b/tests/SecondaryHomeApp/res/values-in/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="1232190134475133062">"SecondaryHomeApp"</string>
+    <string name="app_launcher" msgid="3285007106222765396">"Peluncur SecHome"</string>
+    <string name="launch_fail_msg" msgid="2935729218024647088">"Tidak dapat meluncurkan"</string>
+    <string name="clear_all" msgid="7403807937179450743">"Hapus semua"</string>
+</resources>
diff --git a/tests/SecondaryHomeApp/res/values-is/strings.xml b/tests/SecondaryHomeApp/res/values-is/strings.xml
new file mode 100644
index 0000000..c077fe0
--- /dev/null
+++ b/tests/SecondaryHomeApp/res/values-is/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="1232190134475133062">"SecondaryHomeApp"</string>
+    <string name="app_launcher" msgid="3285007106222765396">"SecHome Launcher"</string>
+    <string name="launch_fail_msg" msgid="2935729218024647088">"Ekki tókst að ræsa"</string>
+    <string name="clear_all" msgid="7403807937179450743">"Hreinsa allt"</string>
+</resources>
diff --git a/tests/SecondaryHomeApp/res/values-it/strings.xml b/tests/SecondaryHomeApp/res/values-it/strings.xml
new file mode 100644
index 0000000..c20aea8
--- /dev/null
+++ b/tests/SecondaryHomeApp/res/values-it/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="1232190134475133062">"SecondaryHomeApp"</string>
+    <string name="app_launcher" msgid="3285007106222765396">"Avvio app SecHome"</string>
+    <string name="launch_fail_msg" msgid="2935729218024647088">"Impossibile avviare"</string>
+    <string name="clear_all" msgid="7403807937179450743">"Elimina tutto"</string>
+</resources>
diff --git a/tests/SecondaryHomeApp/res/values-iw/strings.xml b/tests/SecondaryHomeApp/res/values-iw/strings.xml
new file mode 100644
index 0000000..1ff23a1
--- /dev/null
+++ b/tests/SecondaryHomeApp/res/values-iw/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="1232190134475133062">"SecondaryHomeApp"</string>
+    <string name="app_launcher" msgid="3285007106222765396">"מרכז האפליקציות של SecHome"</string>
+    <string name="launch_fail_msg" msgid="2935729218024647088">"לא ניתן היה להפעיל"</string>
+    <string name="clear_all" msgid="7403807937179450743">"ניקוי הכול"</string>
+</resources>
diff --git a/tests/SecondaryHomeApp/res/values-ja/strings.xml b/tests/SecondaryHomeApp/res/values-ja/strings.xml
new file mode 100644
index 0000000..6b7c7bd
--- /dev/null
+++ b/tests/SecondaryHomeApp/res/values-ja/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="1232190134475133062">"SecondaryHomeApp"</string>
+    <string name="app_launcher" msgid="3285007106222765396">"SecHome Launcher"</string>
+    <string name="launch_fail_msg" msgid="2935729218024647088">"起動できませんでした"</string>
+    <string name="clear_all" msgid="7403807937179450743">"すべて消去"</string>
+</resources>
diff --git a/tests/SecondaryHomeApp/res/values-ka/strings.xml b/tests/SecondaryHomeApp/res/values-ka/strings.xml
new file mode 100644
index 0000000..2dc6bcc
--- /dev/null
+++ b/tests/SecondaryHomeApp/res/values-ka/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="1232190134475133062">"SecondaryHomeApp"</string>
+    <string name="app_launcher" msgid="3285007106222765396">"SecHome-ის გამშვები"</string>
+    <string name="launch_fail_msg" msgid="2935729218024647088">"გაშვება ვერ მოხერხდა"</string>
+    <string name="clear_all" msgid="7403807937179450743">"ყველას გასუფთავება"</string>
+</resources>
diff --git a/tests/SecondaryHomeApp/res/values-kk/strings.xml b/tests/SecondaryHomeApp/res/values-kk/strings.xml
new file mode 100644
index 0000000..c85a83d
--- /dev/null
+++ b/tests/SecondaryHomeApp/res/values-kk/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="1232190134475133062">"SecondaryHomeApp"</string>
+    <string name="app_launcher" msgid="3285007106222765396">"SecHome іске қосқышы"</string>
+    <string name="launch_fail_msg" msgid="2935729218024647088">"Іске қосылмады."</string>
+    <string name="clear_all" msgid="7403807937179450743">"Бәрін өшіру"</string>
+</resources>
diff --git a/tests/SecondaryHomeApp/res/values-km/strings.xml b/tests/SecondaryHomeApp/res/values-km/strings.xml
new file mode 100644
index 0000000..d7f4fc3
--- /dev/null
+++ b/tests/SecondaryHomeApp/res/values-km/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="1232190134475133062">"SecondaryHomeApp"</string>
+    <string name="app_launcher" msgid="3285007106222765396">"កម្មវិធីចាប់ផ្ដើម SecHome"</string>
+    <string name="launch_fail_msg" msgid="2935729218024647088">"មិនអាច​ចាប់ផ្ដើមបានទេ"</string>
+    <string name="clear_all" msgid="7403807937179450743">"សម្អាត​ទាំងអស់"</string>
+</resources>
diff --git a/tests/SecondaryHomeApp/res/values-kn/strings.xml b/tests/SecondaryHomeApp/res/values-kn/strings.xml
new file mode 100644
index 0000000..baa1b0b
--- /dev/null
+++ b/tests/SecondaryHomeApp/res/values-kn/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="1232190134475133062">"SecondaryHomeApp"</string>
+    <string name="app_launcher" msgid="3285007106222765396">"SecHome ಲಾಂಚರ್"</string>
+    <string name="launch_fail_msg" msgid="2935729218024647088">"ಪ್ರಾರಂಭಿಸಲು ಸಾಧ್ಯವಾಗಲಿಲ್ಲ"</string>
+    <string name="clear_all" msgid="7403807937179450743">"ಎಲ್ಲ ತೆಗೆ"</string>
+</resources>
diff --git a/tests/SecondaryHomeApp/res/values-ko/strings.xml b/tests/SecondaryHomeApp/res/values-ko/strings.xml
new file mode 100644
index 0000000..dd0ba23
--- /dev/null
+++ b/tests/SecondaryHomeApp/res/values-ko/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="1232190134475133062">"보조 Home 앱"</string>
+    <string name="app_launcher" msgid="3285007106222765396">"SecHome 런처"</string>
+    <string name="launch_fail_msg" msgid="2935729218024647088">"실행할 수 없음"</string>
+    <string name="clear_all" msgid="7403807937179450743">"모두 삭제"</string>
+</resources>
diff --git a/tests/SecondaryHomeApp/res/values-ky/strings.xml b/tests/SecondaryHomeApp/res/values-ky/strings.xml
new file mode 100644
index 0000000..8c8b6f3
--- /dev/null
+++ b/tests/SecondaryHomeApp/res/values-ky/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="1232190134475133062">"SecondaryHomeApp"</string>
+    <string name="app_launcher" msgid="3285007106222765396">"SecHome Жүргүзгүчү"</string>
+    <string name="launch_fail_msg" msgid="2935729218024647088">"Иштетилген жок"</string>
+    <string name="clear_all" msgid="7403807937179450743">"Баарын тазалоо"</string>
+</resources>
diff --git a/tests/SecondaryHomeApp/res/values-lo/strings.xml b/tests/SecondaryHomeApp/res/values-lo/strings.xml
new file mode 100644
index 0000000..771a7a7
--- /dev/null
+++ b/tests/SecondaryHomeApp/res/values-lo/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="1232190134475133062">"SecondaryHomeApp"</string>
+    <string name="app_launcher" msgid="3285007106222765396">"ຕົວເປີດໃຊ້ SecHome"</string>
+    <string name="launch_fail_msg" msgid="2935729218024647088">"ບໍ່ສາມາດເປີດໃຊ້ໄດ້"</string>
+    <string name="clear_all" msgid="7403807937179450743">"ລຶບລ້າງທັງໝົດ"</string>
+</resources>
diff --git a/tests/SecondaryHomeApp/res/values-lt/strings.xml b/tests/SecondaryHomeApp/res/values-lt/strings.xml
new file mode 100644
index 0000000..1313ab6
--- /dev/null
+++ b/tests/SecondaryHomeApp/res/values-lt/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="1232190134475133062">"SecondaryHomeApp"</string>
+    <string name="app_launcher" msgid="3285007106222765396">"„SecHome“ paleidimo priemonė"</string>
+    <string name="launch_fail_msg" msgid="2935729218024647088">"Nepavyko paleisti"</string>
+    <string name="clear_all" msgid="7403807937179450743">"Išvalyti viską"</string>
+</resources>
diff --git a/tests/SecondaryHomeApp/res/values-lv/strings.xml b/tests/SecondaryHomeApp/res/values-lv/strings.xml
new file mode 100644
index 0000000..be82a66
--- /dev/null
+++ b/tests/SecondaryHomeApp/res/values-lv/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="1232190134475133062">"SecondaryHomeApp"</string>
+    <string name="app_launcher" msgid="3285007106222765396">"SecHome palaišanas programma"</string>
+    <string name="launch_fail_msg" msgid="2935729218024647088">"Nevarēja palaist"</string>
+    <string name="clear_all" msgid="7403807937179450743">"Notīrīt visu"</string>
+</resources>
diff --git a/tests/SecondaryHomeApp/res/values-mk/strings.xml b/tests/SecondaryHomeApp/res/values-mk/strings.xml
new file mode 100644
index 0000000..645fed4
--- /dev/null
+++ b/tests/SecondaryHomeApp/res/values-mk/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="1232190134475133062">"SecondaryHomeApp"</string>
+    <string name="app_launcher" msgid="3285007106222765396">"Стартер на SecHome"</string>
+    <string name="launch_fail_msg" msgid="2935729218024647088">"Не можеше да се стартува"</string>
+    <string name="clear_all" msgid="7403807937179450743">"Исчисти сѐ"</string>
+</resources>
diff --git a/tests/SecondaryHomeApp/res/values-ml/strings.xml b/tests/SecondaryHomeApp/res/values-ml/strings.xml
new file mode 100644
index 0000000..7642ab3
--- /dev/null
+++ b/tests/SecondaryHomeApp/res/values-ml/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="1232190134475133062">"SecondaryHomeApp"</string>
+    <string name="app_launcher" msgid="3285007106222765396">"SecHome ലോഞ്ചർ"</string>
+    <string name="launch_fail_msg" msgid="2935729218024647088">"ലോഞ്ച് ചെയ്യാനായില്ല"</string>
+    <string name="clear_all" msgid="7403807937179450743">"എല്ലാം മായ്‌ക്കുക"</string>
+</resources>
diff --git a/tests/SecondaryHomeApp/res/values-mn/strings.xml b/tests/SecondaryHomeApp/res/values-mn/strings.xml
new file mode 100644
index 0000000..dcf260e
--- /dev/null
+++ b/tests/SecondaryHomeApp/res/values-mn/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="1232190134475133062">"SecondaryHomeApp"</string>
+    <string name="app_launcher" msgid="3285007106222765396">"SecHome эхлүүлэгч"</string>
+    <string name="launch_fail_msg" msgid="2935729218024647088">"Эхлүүлж чадсангүй"</string>
+    <string name="clear_all" msgid="7403807937179450743">"Бүгдийг арилгах"</string>
+</resources>
diff --git a/tests/SecondaryHomeApp/res/values-mr/strings.xml b/tests/SecondaryHomeApp/res/values-mr/strings.xml
new file mode 100644
index 0000000..e4bcaab
--- /dev/null
+++ b/tests/SecondaryHomeApp/res/values-mr/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="1232190134475133062">"SecondaryHomeApp"</string>
+    <string name="app_launcher" msgid="3285007106222765396">"SecHome लाँचर"</string>
+    <string name="launch_fail_msg" msgid="2935729218024647088">"लाँच करू शकलो नाही"</string>
+    <string name="clear_all" msgid="7403807937179450743">"सर्व साफ करा"</string>
+</resources>
diff --git a/tests/SecondaryHomeApp/res/values-ms/strings.xml b/tests/SecondaryHomeApp/res/values-ms/strings.xml
new file mode 100644
index 0000000..c90eeea
--- /dev/null
+++ b/tests/SecondaryHomeApp/res/values-ms/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="1232190134475133062">"SecondaryHomeApp"</string>
+    <string name="app_launcher" msgid="3285007106222765396">"Pelancar SecHome"</string>
+    <string name="launch_fail_msg" msgid="2935729218024647088">"Tidak dapat dilancarkan"</string>
+    <string name="clear_all" msgid="7403807937179450743">"Kosongkan semua"</string>
+</resources>
diff --git a/tests/SecondaryHomeApp/res/values-my/strings.xml b/tests/SecondaryHomeApp/res/values-my/strings.xml
new file mode 100644
index 0000000..251d857
--- /dev/null
+++ b/tests/SecondaryHomeApp/res/values-my/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="1232190134475133062">"SecondaryHomeApp"</string>
+    <string name="app_launcher" msgid="3285007106222765396">"SecHome Launcher"</string>
+    <string name="launch_fail_msg" msgid="2935729218024647088">"စတင်၍မရပါ"</string>
+    <string name="clear_all" msgid="7403807937179450743">"အားလုံးရှင်းရန်"</string>
+</resources>
diff --git a/tests/SecondaryHomeApp/res/values-nb/strings.xml b/tests/SecondaryHomeApp/res/values-nb/strings.xml
new file mode 100644
index 0000000..2476553
--- /dev/null
+++ b/tests/SecondaryHomeApp/res/values-nb/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="1232190134475133062">"SecondaryHomeApp"</string>
+    <string name="app_launcher" msgid="3285007106222765396">"SecHome-starter"</string>
+    <string name="launch_fail_msg" msgid="2935729218024647088">"Kunne ikke starte"</string>
+    <string name="clear_all" msgid="7403807937179450743">"Fjern alle"</string>
+</resources>
diff --git a/tests/SecondaryHomeApp/res/values-ne/strings.xml b/tests/SecondaryHomeApp/res/values-ne/strings.xml
new file mode 100644
index 0000000..73d5498
--- /dev/null
+++ b/tests/SecondaryHomeApp/res/values-ne/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="1232190134475133062">"SecondaryHomeApp"</string>
+    <string name="app_launcher" msgid="3285007106222765396">"SecHome लन्चर"</string>
+    <string name="launch_fail_msg" msgid="2935729218024647088">"सुरु गर्न सकिएन"</string>
+    <string name="clear_all" msgid="7403807937179450743">"सबै हटाउनुहोस्"</string>
+</resources>
diff --git a/tests/SecondaryHomeApp/res/values-nl/strings.xml b/tests/SecondaryHomeApp/res/values-nl/strings.xml
new file mode 100644
index 0000000..53776b7
--- /dev/null
+++ b/tests/SecondaryHomeApp/res/values-nl/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="1232190134475133062">"SecondaryHomeApp"</string>
+    <string name="app_launcher" msgid="3285007106222765396">"SecHome Launcher"</string>
+    <string name="launch_fail_msg" msgid="2935729218024647088">"Kan niet starten"</string>
+    <string name="clear_all" msgid="7403807937179450743">"Alles wissen"</string>
+</resources>
diff --git a/tests/SecondaryHomeApp/res/values-or/strings.xml b/tests/SecondaryHomeApp/res/values-or/strings.xml
new file mode 100644
index 0000000..4af4f6d
--- /dev/null
+++ b/tests/SecondaryHomeApp/res/values-or/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="1232190134475133062">"SecondaryHomeApp"</string>
+    <string name="app_launcher" msgid="3285007106222765396">"SecHome ଲଞ୍ଚର୍"</string>
+    <string name="launch_fail_msg" msgid="2935729218024647088">"ଲଞ୍ଚ କରାଯାଇପାରିଲା ନାହିଁ"</string>
+    <string name="clear_all" msgid="7403807937179450743">"ସବୁ ଖାଲି କରନ୍ତୁ"</string>
+</resources>
diff --git a/tests/SecondaryHomeApp/res/values-pa/strings.xml b/tests/SecondaryHomeApp/res/values-pa/strings.xml
new file mode 100644
index 0000000..e1f8b06
--- /dev/null
+++ b/tests/SecondaryHomeApp/res/values-pa/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="1232190134475133062">"SecondaryHomeApp"</string>
+    <string name="app_launcher" msgid="3285007106222765396">"SecHome ਲਾਂਚਰ"</string>
+    <string name="launch_fail_msg" msgid="2935729218024647088">"ਲਾਂਚ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕਿਆ"</string>
+    <string name="clear_all" msgid="7403807937179450743">"ਸਭ ਕਲੀਅਰ ਕਰੋ"</string>
+</resources>
diff --git a/tests/SecondaryHomeApp/res/values-pl/strings.xml b/tests/SecondaryHomeApp/res/values-pl/strings.xml
new file mode 100644
index 0000000..bcee38e
--- /dev/null
+++ b/tests/SecondaryHomeApp/res/values-pl/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="1232190134475133062">"SecondaryHomeApp"</string>
+    <string name="app_launcher" msgid="3285007106222765396">"Program uruchamiający aplikację SecHome"</string>
+    <string name="launch_fail_msg" msgid="2935729218024647088">"Nie udało się uruchomić"</string>
+    <string name="clear_all" msgid="7403807937179450743">"Wyczyść wszystko"</string>
+</resources>
diff --git a/tests/SecondaryHomeApp/res/values-pt-rPT/strings.xml b/tests/SecondaryHomeApp/res/values-pt-rPT/strings.xml
new file mode 100644
index 0000000..1c071da
--- /dev/null
+++ b/tests/SecondaryHomeApp/res/values-pt-rPT/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="1232190134475133062">"SecondaryHomeApp"</string>
+    <string name="app_launcher" msgid="3285007106222765396">"Launcher SecHome"</string>
+    <string name="launch_fail_msg" msgid="2935729218024647088">"Não foi possível iniciar."</string>
+    <string name="clear_all" msgid="7403807937179450743">"Limpar tudo"</string>
+</resources>
diff --git a/tests/SecondaryHomeApp/res/values-pt/strings.xml b/tests/SecondaryHomeApp/res/values-pt/strings.xml
new file mode 100644
index 0000000..a6fc9f1
--- /dev/null
+++ b/tests/SecondaryHomeApp/res/values-pt/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="1232190134475133062">"SecondaryHomeApp"</string>
+    <string name="app_launcher" msgid="3285007106222765396">"Tela de início do SecHome"</string>
+    <string name="launch_fail_msg" msgid="2935729218024647088">"Não foi possível iniciar"</string>
+    <string name="clear_all" msgid="7403807937179450743">"Limpar tudo"</string>
+</resources>
diff --git a/tests/SecondaryHomeApp/res/values-ro/strings.xml b/tests/SecondaryHomeApp/res/values-ro/strings.xml
new file mode 100644
index 0000000..abf6176
--- /dev/null
+++ b/tests/SecondaryHomeApp/res/values-ro/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="1232190134475133062">"SecondaryHomeApp"</string>
+    <string name="app_launcher" msgid="3285007106222765396">"Lansator SecHome"</string>
+    <string name="launch_fail_msg" msgid="2935729218024647088">"Nu s-a putut lansa"</string>
+    <string name="clear_all" msgid="7403807937179450743">"Ștergeți tot"</string>
+</resources>
diff --git a/tests/SecondaryHomeApp/res/values-ru/strings.xml b/tests/SecondaryHomeApp/res/values-ru/strings.xml
new file mode 100644
index 0000000..b9ae187
--- /dev/null
+++ b/tests/SecondaryHomeApp/res/values-ru/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="1232190134475133062">"SecondaryHomeApp"</string>
+    <string name="app_launcher" msgid="3285007106222765396">"Запуск SecHome"</string>
+    <string name="launch_fail_msg" msgid="2935729218024647088">"Не удалось запустить приложение."</string>
+    <string name="clear_all" msgid="7403807937179450743">"Очистить"</string>
+</resources>
diff --git a/tests/SecondaryHomeApp/res/values-si/strings.xml b/tests/SecondaryHomeApp/res/values-si/strings.xml
new file mode 100644
index 0000000..8b43e23
--- /dev/null
+++ b/tests/SecondaryHomeApp/res/values-si/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="1232190134475133062">"SecondaryHomeApp"</string>
+    <string name="app_launcher" msgid="3285007106222765396">"SecHome දියත්කරණය"</string>
+    <string name="launch_fail_msg" msgid="2935729218024647088">"දියත් කිරීමට නොහැකි විය"</string>
+    <string name="clear_all" msgid="7403807937179450743">"සියල්ල හිස් කරන්න"</string>
+</resources>
diff --git a/tests/SecondaryHomeApp/res/values-sk/strings.xml b/tests/SecondaryHomeApp/res/values-sk/strings.xml
new file mode 100644
index 0000000..f92de0d
--- /dev/null
+++ b/tests/SecondaryHomeApp/res/values-sk/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="1232190134475133062">"SecondaryHomeApp"</string>
+    <string name="app_launcher" msgid="3285007106222765396">"SecHome Launcher"</string>
+    <string name="launch_fail_msg" msgid="2935729218024647088">"Nie je možné spustiť"</string>
+    <string name="clear_all" msgid="7403807937179450743">"Vymazať všetko"</string>
+</resources>
diff --git a/tests/SecondaryHomeApp/res/values-sl/strings.xml b/tests/SecondaryHomeApp/res/values-sl/strings.xml
new file mode 100644
index 0000000..3688d59
--- /dev/null
+++ b/tests/SecondaryHomeApp/res/values-sl/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="1232190134475133062">"SecondaryHomeApp"</string>
+    <string name="app_launcher" msgid="3285007106222765396">"Zaganjalnik za SecHome"</string>
+    <string name="launch_fail_msg" msgid="2935729218024647088">"Ni bilo mogoče zagnati"</string>
+    <string name="clear_all" msgid="7403807937179450743">"Izbriši vse"</string>
+</resources>
diff --git a/tests/SecondaryHomeApp/res/values-sq/strings.xml b/tests/SecondaryHomeApp/res/values-sq/strings.xml
new file mode 100644
index 0000000..65f930b
--- /dev/null
+++ b/tests/SecondaryHomeApp/res/values-sq/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="1232190134475133062">"SecondaryHomeApp"</string>
+    <string name="app_launcher" msgid="3285007106222765396">"Nisësi i SecHome"</string>
+    <string name="launch_fail_msg" msgid="2935729218024647088">"Nuk mund të nisej"</string>
+    <string name="clear_all" msgid="7403807937179450743">"Pastro të gjitha"</string>
+</resources>
diff --git a/tests/SecondaryHomeApp/res/values-sr/strings.xml b/tests/SecondaryHomeApp/res/values-sr/strings.xml
new file mode 100644
index 0000000..5841c55
--- /dev/null
+++ b/tests/SecondaryHomeApp/res/values-sr/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="1232190134475133062">"SecondaryHomeApp"</string>
+    <string name="app_launcher" msgid="3285007106222765396">"Покретач апликације SecHome"</string>
+    <string name="launch_fail_msg" msgid="2935729218024647088">"Покретање није успело"</string>
+    <string name="clear_all" msgid="7403807937179450743">"Обриши све"</string>
+</resources>
diff --git a/tests/SecondaryHomeApp/res/values-sv/strings.xml b/tests/SecondaryHomeApp/res/values-sv/strings.xml
new file mode 100644
index 0000000..dc94e44
--- /dev/null
+++ b/tests/SecondaryHomeApp/res/values-sv/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="1232190134475133062">"SecondaryHomeApp"</string>
+    <string name="app_launcher" msgid="3285007106222765396">"SecHome Launcher"</string>
+    <string name="launch_fail_msg" msgid="2935729218024647088">"Starten misslyckades"</string>
+    <string name="clear_all" msgid="7403807937179450743">"Rensa alla"</string>
+</resources>
diff --git a/tests/SecondaryHomeApp/res/values-sw/strings.xml b/tests/SecondaryHomeApp/res/values-sw/strings.xml
new file mode 100644
index 0000000..3a55603
--- /dev/null
+++ b/tests/SecondaryHomeApp/res/values-sw/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="1232190134475133062">"SecondaryHomeApp"</string>
+    <string name="app_launcher" msgid="3285007106222765396">"Kifungua Programu cha SecHome"</string>
+    <string name="launch_fail_msg" msgid="2935729218024647088">"Imeshindwa kufungua programu"</string>
+    <string name="clear_all" msgid="7403807937179450743">"Futa yote"</string>
+</resources>
diff --git a/tests/SecondaryHomeApp/res/values-ta/strings.xml b/tests/SecondaryHomeApp/res/values-ta/strings.xml
new file mode 100644
index 0000000..42b81d9
--- /dev/null
+++ b/tests/SecondaryHomeApp/res/values-ta/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="1232190134475133062">"SecondaryHomeApp"</string>
+    <string name="app_launcher" msgid="3285007106222765396">"SecHome தொடங்கி"</string>
+    <string name="launch_fail_msg" msgid="2935729218024647088">"தொடங்க முடியவில்லை"</string>
+    <string name="clear_all" msgid="7403807937179450743">"அனைத்தையும் அழி"</string>
+</resources>
diff --git a/tests/SecondaryHomeApp/res/values-te/strings.xml b/tests/SecondaryHomeApp/res/values-te/strings.xml
new file mode 100644
index 0000000..43e09f6
--- /dev/null
+++ b/tests/SecondaryHomeApp/res/values-te/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="1232190134475133062">"SecondaryHomeApp"</string>
+    <string name="app_launcher" msgid="3285007106222765396">"SecHome లాంచర్"</string>
+    <string name="launch_fail_msg" msgid="2935729218024647088">"ప్రారంభించడం సాధ్యపడలేదు"</string>
+    <string name="clear_all" msgid="7403807937179450743">"అన్నింటినీ క్లియర్ చేయి"</string>
+</resources>
diff --git a/tests/SecondaryHomeApp/res/values-th/strings.xml b/tests/SecondaryHomeApp/res/values-th/strings.xml
new file mode 100644
index 0000000..7f4f300
--- /dev/null
+++ b/tests/SecondaryHomeApp/res/values-th/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="1232190134475133062">"SecondaryHomeApp"</string>
+    <string name="app_launcher" msgid="3285007106222765396">"Launcher ของ SecHome"</string>
+    <string name="launch_fail_msg" msgid="2935729218024647088">"เปิดไม่ได้"</string>
+    <string name="clear_all" msgid="7403807937179450743">"ล้างทั้งหมด"</string>
+</resources>
diff --git a/tests/SecondaryHomeApp/res/values-tl/strings.xml b/tests/SecondaryHomeApp/res/values-tl/strings.xml
new file mode 100644
index 0000000..3464112
--- /dev/null
+++ b/tests/SecondaryHomeApp/res/values-tl/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="1232190134475133062">"SecondaryHomeApp"</string>
+    <string name="app_launcher" msgid="3285007106222765396">"SecHome Launcher"</string>
+    <string name="launch_fail_msg" msgid="2935729218024647088">"Hindi mailunsad"</string>
+    <string name="clear_all" msgid="7403807937179450743">"I-clear lahat"</string>
+</resources>
diff --git a/tests/SecondaryHomeApp/res/values-tr/strings.xml b/tests/SecondaryHomeApp/res/values-tr/strings.xml
new file mode 100644
index 0000000..8d60d4e
--- /dev/null
+++ b/tests/SecondaryHomeApp/res/values-tr/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="1232190134475133062">"SecondaryHomeApp"</string>
+    <string name="app_launcher" msgid="3285007106222765396">"SecHome Başlatıcı"</string>
+    <string name="launch_fail_msg" msgid="2935729218024647088">"Başlatılamadı"</string>
+    <string name="clear_all" msgid="7403807937179450743">"Tümünü temizle"</string>
+</resources>
diff --git a/tests/SecondaryHomeApp/res/values-uk/strings.xml b/tests/SecondaryHomeApp/res/values-uk/strings.xml
new file mode 100644
index 0000000..e0a65b5
--- /dev/null
+++ b/tests/SecondaryHomeApp/res/values-uk/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="1232190134475133062">"SecondaryHomeApp"</string>
+    <string name="app_launcher" msgid="3285007106222765396">"SecHome Launcher"</string>
+    <string name="launch_fail_msg" msgid="2935729218024647088">"Не вдалося запустити"</string>
+    <string name="clear_all" msgid="7403807937179450743">"Очистити все"</string>
+</resources>
diff --git a/tests/SecondaryHomeApp/res/values-ur/strings.xml b/tests/SecondaryHomeApp/res/values-ur/strings.xml
new file mode 100644
index 0000000..1816a6a
--- /dev/null
+++ b/tests/SecondaryHomeApp/res/values-ur/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="1232190134475133062">"SecondaryHomeApp"</string>
+    <string name="app_launcher" msgid="3285007106222765396">"SecHome لانچر"</string>
+    <string name="launch_fail_msg" msgid="2935729218024647088">"لانچ نہیں ہو سکا"</string>
+    <string name="clear_all" msgid="7403807937179450743">"سبھی کو ہٹائیں"</string>
+</resources>
diff --git a/tests/SecondaryHomeApp/res/values-uz/strings.xml b/tests/SecondaryHomeApp/res/values-uz/strings.xml
new file mode 100644
index 0000000..4ace080
--- /dev/null
+++ b/tests/SecondaryHomeApp/res/values-uz/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="1232190134475133062">"SecondaryHomeApp"</string>
+    <string name="app_launcher" msgid="3285007106222765396">"SecHome Launcher"</string>
+    <string name="launch_fail_msg" msgid="2935729218024647088">"Ishga tushmadi"</string>
+    <string name="clear_all" msgid="7403807937179450743">"Hammasini tozalash"</string>
+</resources>
diff --git a/tests/SecondaryHomeApp/res/values-vi/strings.xml b/tests/SecondaryHomeApp/res/values-vi/strings.xml
new file mode 100644
index 0000000..64cc1c1
--- /dev/null
+++ b/tests/SecondaryHomeApp/res/values-vi/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="1232190134475133062">"SecondaryHomeApp"</string>
+    <string name="app_launcher" msgid="3285007106222765396">"Trình khởi chạy SecHome"</string>
+    <string name="launch_fail_msg" msgid="2935729218024647088">"Không thể khởi chạy"</string>
+    <string name="clear_all" msgid="7403807937179450743">"Xóa tất cả"</string>
+</resources>
diff --git a/tests/SecondaryHomeApp/res/values-zh-rCN/strings.xml b/tests/SecondaryHomeApp/res/values-zh-rCN/strings.xml
new file mode 100644
index 0000000..e91a39b
--- /dev/null
+++ b/tests/SecondaryHomeApp/res/values-zh-rCN/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="1232190134475133062">"SecondaryHomeApp"</string>
+    <string name="app_launcher" msgid="3285007106222765396">"SecHome 启动器"</string>
+    <string name="launch_fail_msg" msgid="2935729218024647088">"无法启动"</string>
+    <string name="clear_all" msgid="7403807937179450743">"全部清除"</string>
+</resources>
diff --git a/tests/SecondaryHomeApp/res/values-zh-rHK/strings.xml b/tests/SecondaryHomeApp/res/values-zh-rHK/strings.xml
new file mode 100644
index 0000000..2aea956
--- /dev/null
+++ b/tests/SecondaryHomeApp/res/values-zh-rHK/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="1232190134475133062">"SecondaryHomeApp"</string>
+    <string name="app_launcher" msgid="3285007106222765396">"SecHome 啟動器"</string>
+    <string name="launch_fail_msg" msgid="2935729218024647088">"無法啟動"</string>
+    <string name="clear_all" msgid="7403807937179450743">"全部清除"</string>
+</resources>
diff --git a/tests/SecondaryHomeApp/res/values-zh-rTW/strings.xml b/tests/SecondaryHomeApp/res/values-zh-rTW/strings.xml
new file mode 100644
index 0000000..2aea956
--- /dev/null
+++ b/tests/SecondaryHomeApp/res/values-zh-rTW/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="1232190134475133062">"SecondaryHomeApp"</string>
+    <string name="app_launcher" msgid="3285007106222765396">"SecHome 啟動器"</string>
+    <string name="launch_fail_msg" msgid="2935729218024647088">"無法啟動"</string>
+    <string name="clear_all" msgid="7403807937179450743">"全部清除"</string>
+</resources>
diff --git a/tests/SecondaryHomeApp/res/values-zu/strings.xml b/tests/SecondaryHomeApp/res/values-zu/strings.xml
new file mode 100644
index 0000000..07af15e
--- /dev/null
+++ b/tests/SecondaryHomeApp/res/values-zu/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="1232190134475133062">"SecondaryHomeApp"</string>
+    <string name="app_launcher" msgid="3285007106222765396">"I-SecHome Launcher"</string>
+    <string name="launch_fail_msg" msgid="2935729218024647088">"Ayikwazanga ukuqalisa"</string>
+    <string name="clear_all" msgid="7403807937179450743">"Sula konke"</string>
+</resources>
diff --git a/tests/SecondaryHomeApp/res/values/colors.xml b/tests/SecondaryHomeApp/res/values/colors.xml
new file mode 100644
index 0000000..7d5b424
--- /dev/null
+++ b/tests/SecondaryHomeApp/res/values/colors.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<resources>
+    <color name="background_color">#263238</color>
+    <color name="nav_icon_fill_color">#8Fffffff</color>
+    <color name="nav_bar_ripple_background_color">#40ffffff</color>
+    <color name="notification_unseen_color">#e25142</color>
+</resources>
diff --git a/tests/SecondaryHomeApp/res/values/dimens.xml b/tests/SecondaryHomeApp/res/values/dimens.xml
new file mode 100644
index 0000000..84ba801
--- /dev/null
+++ b/tests/SecondaryHomeApp/res/values/dimens.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<resources>
+    <dimen name="app_list_col_width">72dp</dimen>
+    <dimen name="app_list_horizontal_spacing">24dp</dimen>
+    <dimen name="app_list_vertical_spacing">24dp</dimen>
+    <dimen name="app_icon_width">64dp</dimen>
+    <dimen name="app_icon_height">64dp</dimen>
+    <dimen name="app_grid_margin_top">24dp</dimen>
+    <dimen name="app_grid_margin_left">8dp</dimen>
+    <dimen name="app_grid_margin_right">8dp</dimen>
+    <dimen name="notification_title_bar_height">8dp</dimen>
+    <dimen name="notification_primary_icon_size">64dp</dimen>
+</resources>
diff --git a/tests/SecondaryHomeApp/res/values/strings.xml b/tests/SecondaryHomeApp/res/values/strings.xml
new file mode 100644
index 0000000..9fb8e87
--- /dev/null
+++ b/tests/SecondaryHomeApp/res/values/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<resources>
+    <string name="app_name">SecondaryHomeApp</string>
+    <string name="app_launcher">SecHome Launcher</string>
+    <string name="launch_fail_msg">Couldn\'t launch</string>
+    <string-array name="hidden_apps" translatable="false">
+        <item>com.android.car.secondaryhome</item>
+    </string-array>
+    <string name="clear_all">Clear all</string>
+</resources>
diff --git a/tests/SecondaryHomeApp/res/values/styles.xml b/tests/SecondaryHomeApp/res/values/styles.xml
new file mode 100644
index 0000000..03ef3dc
--- /dev/null
+++ b/tests/SecondaryHomeApp/res/values/styles.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<resources>
+    <style name="LauncherTheme" parent="Theme.MaterialComponents.NoActionBar" >
+        <item name="android:windowShowWallpaper">true</item>
+        <item name="android:windowNoTitle">true</item>
+        <item name="android:windowIsTranslucent">true</item>
+        <item name="android:windowTranslucentStatus">true</item>
+        <item name="android:windowTranslucentNavigation">true</item>
+        <item name="android:colorBackgroundCacheHint">@null</item>
+        <item name="android:windowBackground">@android:color/transparent</item>
+    </style>
+
+    <style name="NavigationBarButton">
+        <item name="android:layout_height">96dp</item>
+        <item name="android:layout_width">96dp</item>
+        <item name="android:background">@drawable/nav_button_background</item>
+    </style>
+        <style name="ClearButton">
+        <item name="android:layout_height">96dp</item>
+        <item name="android:layout_width">96dp</item>
+        <item name="android:background">@drawable/nav_button_background</item>
+    </style>
+</resources>
diff --git a/tests/SecondaryHomeApp/src/com/android/car/secondaryhome/launcher/AppEntry.java b/tests/SecondaryHomeApp/src/com/android/car/secondaryhome/launcher/AppEntry.java
new file mode 100644
index 0000000..f77b6c9
--- /dev/null
+++ b/tests/SecondaryHomeApp/src/com/android/car/secondaryhome/launcher/AppEntry.java
@@ -0,0 +1,68 @@
+/**
+ * Copyright (c) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.secondaryhome.launcher;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.graphics.drawable.Drawable;
+
+import java.util.Comparator;
+
+/** An entry that represents a single activity that can be launched. */
+public final class AppEntry {
+    @NonNull
+    private final Intent mLaunchIntent;
+    @NonNull
+    private final String mLabel;
+    @Nullable
+    private final Drawable mIcon;
+
+    AppEntry(@NonNull ResolveInfo info, @NonNull PackageManager packageManager) {
+        mLabel = info.loadLabel(packageManager).toString();
+        mIcon = info.loadIcon(packageManager);
+        mLaunchIntent = new Intent().setComponent(new ComponentName(
+                info.activityInfo.packageName, info.activityInfo.name));
+    }
+
+    @NonNull String getLabel() {
+        return mLabel;
+    }
+
+    @Nullable Drawable getIcon() {
+        return mIcon;
+    }
+
+    @NonNull Intent getLaunchIntent() {
+        return mLaunchIntent;
+    }
+
+    @NonNull ComponentName getComponentName() {
+        return mLaunchIntent.getComponent();
+    }
+
+    @Override
+    public String toString() {
+        return mLabel;
+    }
+
+    public static final Comparator<AppEntry> AppNameComparator =
+            Comparator.comparing(AppEntry::getLabel, String::compareToIgnoreCase);
+}
diff --git a/tests/SecondaryHomeApp/src/com/android/car/secondaryhome/launcher/AppFragment.java b/tests/SecondaryHomeApp/src/com/android/car/secondaryhome/launcher/AppFragment.java
new file mode 100644
index 0000000..a05beee
--- /dev/null
+++ b/tests/SecondaryHomeApp/src/com/android/car/secondaryhome/launcher/AppFragment.java
@@ -0,0 +1,262 @@
+/**
+ * Copyright (c) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.secondaryhome.launcher;
+
+import android.app.Activity;
+import android.app.ActivityManager;
+import android.app.ActivityManager.StackInfo;
+import android.app.ActivityView;
+import android.app.IActivityManager;
+import android.app.TaskStackBuilder;
+import android.app.TaskStackListener;
+import android.content.ActivityNotFoundException;
+import android.content.Intent;
+import android.hardware.input.InputManager;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.util.Log;
+import android.view.Display;
+import android.view.InputDevice;
+import android.view.KeyCharacterMap;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.fragment.app.Fragment;
+
+import com.android.car.secondaryhome.R;
+import com.android.internal.annotations.GuardedBy;
+
+/**
+ * {@link Fragment} that contains an ActivityView to run embedded application
+ */
+public final class AppFragment extends Fragment {
+    public static final int INVALID_TASK_STACK_ID = -1;
+
+    private static final String TAG = "SecHome.AppFragment";
+
+    private final IActivityManager mAm;
+    private final Object mLock = new Object();
+    @GuardedBy("mLock")
+    private int mVirtualDisplayId = Display.INVALID_DISPLAY;
+    @GuardedBy("mLock")
+    private int mTaskStackId = INVALID_TASK_STACK_ID;
+    private boolean mActivityViewReady;
+    private Intent mLaunchIntent;
+    private TaskListener mTaskListener;
+    private TaskStackBuilder mTaskStackBuilder;
+
+    private Activity mActivity;
+    private ActivityView mActivityView;
+    private int mHomeDisplayId = Display.INVALID_DISPLAY;
+
+    private final ActivityView.StateCallback mActivityViewCallback =
+            new ActivityView.StateCallback() {
+                @Override
+                public void onActivityViewReady(ActivityView view) {
+                    mActivityViewReady = true;
+                    view.startActivity(mLaunchIntent);
+                    synchronized (mLock) {
+                        try {
+                            mTaskStackId = mAm.getFocusedStackInfo().stackId;
+                        } catch (RemoteException e) {
+                            Log.e(TAG, "cannot get new taskstackid in ActivityView.StateCallback");
+                        }
+                        mVirtualDisplayId = view.getVirtualDisplayId();
+                    }
+                }
+
+                @Override
+                public void onActivityViewDestroyed(ActivityView view) {
+                    mActivityViewReady = false;
+                }
+            };
+
+    public AppFragment() {
+        mAm = ActivityManager.getService();
+    }
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        mActivity = getActivity();
+        mHomeDisplayId = mActivity.getWindowManager().getDefaultDisplay().getDisplayId();
+
+        mTaskListener = new TaskListener();
+        mTaskStackBuilder = TaskStackBuilder.create(mActivity);
+
+        try {
+            mAm.registerTaskStackListener(mTaskListener);
+        } catch (RemoteException e) {
+            mTaskListener = null;
+        }
+    }
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+            Bundle savedInstanceState) {
+        View view = inflater.inflate(R.layout.app_fragment, container, false);
+        mActivityView = view.findViewById(R.id.app_view);
+        return view;
+    }
+
+    @Override
+    public void onDestroy() {
+        super.onDestroy();
+        if (mActivityView != null && mActivityViewReady) {
+            try {
+                mActivityView.release();
+                mActivityView = null;
+                mActivityViewReady = false;
+            } catch (Exception e) {
+                Log.e(TAG, "Fail to release ActivityView");
+            }
+        }
+
+        if (mTaskListener != null) {
+            mLaunchIntent = null;
+            mTaskListener = null;
+            synchronized (mLock) {
+                mTaskStackId = INVALID_TASK_STACK_ID;
+                mVirtualDisplayId = Display.INVALID_DISPLAY;
+            }
+        }
+    }
+
+    @Override
+    public void onResume() {
+        super.onResume();
+        // If task stack is not empty, launch the top intent
+        if (mTaskStackBuilder.getIntentCount() > 0) {
+            launchTopAppInActivityView();
+        }
+    }
+
+    private void launchTopAppInActivityView() {
+        try {
+            if (mTaskStackBuilder.getIntentCount() == 0) {
+                return;
+            }
+            mLaunchIntent = mTaskStackBuilder
+                    .editIntentAt(mTaskStackBuilder.getIntentCount() - 1);
+
+            if (mActivityView != null) {
+                // Set callback to launch the app when ActivityView is ready
+                mActivityView.setCallback(mActivityViewCallback);
+            } else if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG, "ActivityView is null ");
+            }
+        } catch (ActivityNotFoundException e) {
+            Log.e(TAG, "App activity not found ..", e);
+        }
+    }
+
+    public void addLaunchIntentToStack(Intent launchIntent) {
+        mTaskStackBuilder.addNextIntent(launchIntent);
+        launchTopAppInActivityView();
+    }
+
+    public void onBackButtonPressed() {
+        if (Log.isLoggable(TAG, Log.DEBUG)) {
+            Log.d(TAG, "onBackButtonPressed..");
+        }
+        if (mActivityView != null) {
+            performBackPress();
+        }
+    }
+
+    public int getTaskStackId() {
+        synchronized (mLock) {
+            return mTaskStackId;
+        }
+    }
+
+    private void performBackPress() {
+        InputManager im = mActivity.getSystemService(InputManager.class);
+        int displayId;
+        synchronized (mLock) {
+            displayId = mVirtualDisplayId;
+        }
+
+        im.injectInputEvent(createKeyEvent(KeyEvent.ACTION_DOWN,
+                KeyEvent.KEYCODE_BACK, displayId),
+                InputManager.INJECT_INPUT_EVENT_MODE_ASYNC);
+        im.injectInputEvent(createKeyEvent(KeyEvent.ACTION_UP,
+                KeyEvent.KEYCODE_BACK, displayId),
+                InputManager.INJECT_INPUT_EVENT_MODE_ASYNC);
+    }
+
+    private static KeyEvent createKeyEvent(int action, int code, int displayId) {
+        long when = SystemClock.uptimeMillis();
+        KeyEvent ev = new KeyEvent(when, when, action, code, /* repeat= */ 0,
+                /* metaState= */ 0, KeyCharacterMap.VIRTUAL_KEYBOARD, /* scancode= */ 0,
+                KeyEvent.FLAG_FROM_SYSTEM | KeyEvent.FLAG_VIRTUAL_HARD_KEY,
+                InputDevice.SOURCE_KEYBOARD);
+        ev.setDisplayId(displayId);
+        return ev;
+    }
+
+    private boolean isTaskStackEmpty() {
+        synchronized (mLock) {
+            try {
+                return mAm.getAllStackInfos().stream().anyMatch(info
+                        -> (info.stackId == mTaskStackId && info.topActivity == null));
+            } catch (RemoteException e) {
+                Log.e(TAG, "cannot getFocusedStackInfos", e);
+                return true;
+            }
+        }
+    }
+
+    private final class TaskListener extends TaskStackListener {
+        @Override
+        public void onTaskStackChanged() {
+            StackInfo focusedStackInfo;
+            try {
+                focusedStackInfo = mAm.getFocusedStackInfo();
+            } catch (RemoteException e) {
+                Log.e(TAG, "cannot getFocusedStackInfo", e);
+                return;
+            }
+
+            // App could be exited in two ways, and HomeFragment should be shown
+            if (isTaskStackEmpty()) {
+                ((LauncherActivity) mActivity).navigateHome();
+                synchronized (mLock) {
+                    mTaskStackId = INVALID_TASK_STACK_ID;
+                }
+            }
+
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG, "OnTaskStackChanged: virtual display: "
+                        + mVirtualDisplayId + " homeDisplay: " + mHomeDisplayId
+                        + "\nFocused stack: " + focusedStackInfo);
+                try {
+                    for (StackInfo info: mAm.getAllStackInfos()) {
+                        Log.d(TAG, "    stackId: " + info.stackId + " displayId: "
+                                + info.displayId + " component: " + info.topActivity);
+                    }
+                } catch (RemoteException e) {
+                    Log.e(TAG, "cannot getFocusedStackInfos", e);
+                }
+            }
+        }
+    }
+}
diff --git a/tests/SecondaryHomeApp/src/com/android/car/secondaryhome/launcher/AppListAdapter.java b/tests/SecondaryHomeApp/src/com/android/car/secondaryhome/launcher/AppListAdapter.java
new file mode 100644
index 0000000..a3f4ed3
--- /dev/null
+++ b/tests/SecondaryHomeApp/src/com/android/car/secondaryhome/launcher/AppListAdapter.java
@@ -0,0 +1,75 @@
+/**
+ * Copyright (c) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.secondaryhome.launcher;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.android.car.secondaryhome.R;
+
+import java.util.List;
+import java.util.Set;
+
+/** Adapter for available apps list. */
+public final class AppListAdapter extends ArrayAdapter<AppEntry> {
+    private final LayoutInflater mInflater;
+
+    AppListAdapter(@NonNull Context context) {
+        super(context, android.R.layout.simple_list_item_2);
+        mInflater = LayoutInflater.from(context);
+    }
+
+    /**
+     * Sets data for AppListAdaptor and exclude the app from  blackList
+     * @param data        List of {@link AppEntry}
+     * @param blackList   A (possibly empty but not null) list of apps (package names) to hide
+     */
+    void setData(@NonNull List<AppEntry> data, @NonNull Set<String> blackList) {
+        clear();
+
+        data.stream().filter(app -> !blackList.contains(app.getComponentName().getPackageName()))
+                .forEach(app -> add(app));
+
+        sort(AppEntry.AppNameComparator);
+    }
+
+    @Override
+    public View getView(@NonNull int position,
+            @Nullable View convertView,
+            @NonNull ViewGroup parent) {
+        View view;
+        if (convertView == null) {
+            view = mInflater.inflate(R.layout.app_grid_item, parent, false);
+        } else {
+            view = convertView;
+        }
+
+        AppEntry item = getItem(position);
+        if (item != null) {
+            ((ImageView) view.findViewById(R.id.app_icon)).setImageDrawable(item.getIcon());
+            ((TextView) view.findViewById(R.id.app_name)).setText(item.getLabel());
+        }
+        return view;
+    }
+}
diff --git a/tests/SecondaryHomeApp/src/com/android/car/secondaryhome/launcher/AppListLiveData.java b/tests/SecondaryHomeApp/src/com/android/car/secondaryhome/launcher/AppListLiveData.java
new file mode 100644
index 0000000..e1540ae
--- /dev/null
+++ b/tests/SecondaryHomeApp/src/com/android/car/secondaryhome/launcher/AppListLiveData.java
@@ -0,0 +1,71 @@
+/**
+ * Copyright (c) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.secondaryhome.launcher;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.os.AsyncTask;
+
+import androidx.lifecycle.LiveData;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+public final class AppListLiveData extends LiveData<List<AppEntry>> {
+
+    private final PackageManager mPackageManager;
+    private int mCurrentDataVersion;
+
+    AppListLiveData(@NonNull Context context) {
+        mPackageManager = context.getPackageManager();
+        loadData();
+    }
+
+    protected void loadData() {
+        int loadDataVersion = ++mCurrentDataVersion;
+
+        new AsyncTask<Void, Void, List<AppEntry>>() {
+            @Override
+            protected List<AppEntry> doInBackground(Void... voids) {
+                Intent mainIntent = new Intent(Intent.ACTION_MAIN, null);
+                mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);
+
+                List<ResolveInfo> apps = mPackageManager.queryIntentActivities(mainIntent,
+                        PackageManager.GET_META_DATA);
+
+                if (apps == null) return Collections.emptyList();
+
+                List<AppEntry> entries = new ArrayList(apps.size());
+                apps.stream().forEach(app -> entries.add(new AppEntry(app, mPackageManager)));
+
+                return entries;
+            }
+
+            @Override
+            protected void onPostExecute(List<AppEntry> data) {
+                if (mCurrentDataVersion == loadDataVersion) {
+                    setValue(data);
+                }
+            }
+        }.execute();
+    }
+}
+
diff --git a/tests/SecondaryHomeApp/src/com/android/car/secondaryhome/launcher/AppListViewModel.java b/tests/SecondaryHomeApp/src/com/android/car/secondaryhome/launcher/AppListViewModel.java
new file mode 100644
index 0000000..f94b96d
--- /dev/null
+++ b/tests/SecondaryHomeApp/src/com/android/car/secondaryhome/launcher/AppListViewModel.java
@@ -0,0 +1,51 @@
+/**
+ * Copyright (c) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.secondaryhome.launcher;
+
+import android.annotation.NonNull;
+import android.app.Application;
+
+import androidx.lifecycle.AndroidViewModel;
+import androidx.lifecycle.LiveData;
+
+import java.util.List;
+
+/**
+ * A view model that provides a list of activities that can be launched.
+ */
+public final class AppListViewModel extends AndroidViewModel {
+
+    @NonNull
+    private final AppListLiveData mAppList;
+    @NonNull
+    private final PackageIntentReceiver mPackageIntentReceiver;
+
+    public AppListViewModel(Application application) {
+        super(application);
+        mAppList = new AppListLiveData(application);
+        mPackageIntentReceiver = new PackageIntentReceiver(mAppList, application);
+    }
+
+    public LiveData<List<AppEntry>> getAppList() {
+        return mAppList;
+    }
+
+    @Override
+    protected void onCleared() {
+        getApplication().unregisterReceiver(mPackageIntentReceiver);
+    }
+}
diff --git a/tests/SecondaryHomeApp/src/com/android/car/secondaryhome/launcher/AppPickedCallback.java b/tests/SecondaryHomeApp/src/com/android/car/secondaryhome/launcher/AppPickedCallback.java
new file mode 100644
index 0000000..fb06350
--- /dev/null
+++ b/tests/SecondaryHomeApp/src/com/android/car/secondaryhome/launcher/AppPickedCallback.java
@@ -0,0 +1,27 @@
+/**
+ * Copyright (c) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.secondaryhome.launcher;
+
+import android.annotation.NonNull;
+
+/**
+ * Callback to be invoked when an app was picked.
+ */
+interface AppPickedCallback {
+    void onAppPicked(@NonNull AppEntry appEntry);
+}
+
diff --git a/tests/SecondaryHomeApp/src/com/android/car/secondaryhome/launcher/HomeFragment.java b/tests/SecondaryHomeApp/src/com/android/car/secondaryhome/launcher/HomeFragment.java
new file mode 100644
index 0000000..1a8ef85
--- /dev/null
+++ b/tests/SecondaryHomeApp/src/com/android/car/secondaryhome/launcher/HomeFragment.java
@@ -0,0 +1,90 @@
+/**
+ * Copyright (c) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.secondaryhome.launcher;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.GridView;
+
+import androidx.fragment.app.Fragment;
+import androidx.fragment.app.FragmentManager;
+import androidx.lifecycle.ViewModelProviders;
+
+import com.android.car.secondaryhome.R;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * {@link Fragment} that shows a grid of app installed.
+ * It will launch app into AppFragment.
+ * Note: a new task will be launched every time for app to run in multiple display.
+ */
+public final class HomeFragment extends Fragment {
+    private static final String TAG = "SecHome.HomeFragment";
+
+    private final Set<String> mHiddenApps = new HashSet<>();
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+    }
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+            Bundle savedInstanceState) {
+
+        View view = inflater.inflate(R.layout.home_fragment, container, false);
+        GridView gridView = view.findViewById(R.id.app_grid);
+
+        FragmentManager fragmentManager = getActivity().getSupportFragmentManager();
+        AppListAdapter appListAdapter = new AppListAdapter(getActivity());
+
+        gridView.setAdapter(appListAdapter);
+        gridView.setOnItemClickListener((adapterView, v, position, id) -> {
+            AppEntry entry = appListAdapter.getItem(position);
+            AppFragment mAppFragment = (AppFragment) fragmentManager
+                    .findFragmentByTag(((LauncherActivity) getActivity()).APP_FRAGMENT_TAG);
+
+            if (mAppFragment != null) {
+                // Equivalent to setting in AndroidManifest: documentLaunchMode="always"
+                Intent newIntent = entry.getLaunchIntent()
+                        .setFlags(~Intent.FLAG_ACTIVITY_SINGLE_TOP)
+                        .addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT
+                                | Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
+                mAppFragment.addLaunchIntentToStack(newIntent);
+                ((LauncherActivity) getActivity()).navigateApp();
+
+            } else if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG, "AppFragment is not found...");
+            }
+        });
+
+        AppListViewModel appListViewModel = ViewModelProviders.of(this)
+                .get(AppListViewModel.class);
+
+        mHiddenApps.addAll(Arrays.asList(getResources().getStringArray(R.array.hidden_apps)));
+        appListViewModel.getAppList().observe(this, data ->
+                appListAdapter.setData(data, mHiddenApps));
+        return view;
+    }
+}
diff --git a/tests/SecondaryHomeApp/src/com/android/car/secondaryhome/launcher/LauncherActivity.java b/tests/SecondaryHomeApp/src/com/android/car/secondaryhome/launcher/LauncherActivity.java
new file mode 100644
index 0000000..89f013f
--- /dev/null
+++ b/tests/SecondaryHomeApp/src/com/android/car/secondaryhome/launcher/LauncherActivity.java
@@ -0,0 +1,140 @@
+/**
+ * Copyright (c) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.secondaryhome.launcher;
+
+import android.os.Bundle;
+import android.util.Log;
+import android.widget.ImageButton;
+
+import androidx.appcompat.app.AppCompatActivity;
+
+import com.android.car.secondaryhome.R;
+
+/**
+ * Main launcher activity.
+ * It contains a navigation bar with BACK and HOME buttons.
+ */
+public final class LauncherActivity extends AppCompatActivity {
+    public static final String APP_FRAGMENT_TAG = "app";
+    public static final String HOME_FRAGMENT_TAG = "home";
+    public static final String NOTIFICATION_FRAGMENT_TAG = "notification";
+    private static final String TAG = "SecHome.LauncherActivity";
+
+    private final AppFragment mAppFragment = new AppFragment();
+    private final HomeFragment mHomeFragment = new HomeFragment();
+    private final NotificationFragment mNotificationFragment =
+            new NotificationFragment();
+
+    private String mLastFragment = HOME_FRAGMENT_TAG;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setContentView(R.layout.activity_main);
+
+        if (Log.isLoggable(TAG, Log.DEBUG)) {
+            Log.d(TAG, "Secondary home activity created...");
+        }
+
+        getSupportFragmentManager().beginTransaction()
+            .add(R.id.container, mAppFragment, APP_FRAGMENT_TAG)
+            .commit();
+
+        getSupportFragmentManager().beginTransaction()
+            .add(R.id.container, mHomeFragment, HOME_FRAGMENT_TAG)
+            .commit();
+
+        getSupportFragmentManager().beginTransaction()
+            .add(R.id.container, mNotificationFragment, NOTIFICATION_FRAGMENT_TAG)
+            .commit();
+
+        ImageButton backButton = findViewById(R.id.nav_back);
+        backButton.setOnClickListener(view -> onBackButtonPressed());
+        ImageButton homeButton = findViewById(R.id.nav_home);
+        homeButton.setOnClickListener(view -> navigateHome());
+        ImageButton notificationButton = findViewById(R.id.nav_notification);
+        notificationButton.setOnClickListener(view -> toggleNotification());
+    }
+
+    @Override
+    protected void onStart() {
+        super.onStart();
+        navigateHome();
+    }
+
+    private void onBackButtonPressed() {
+        // When BACK is pressed, if HomeFragment is shown
+        // and AppFragment's has valid and non-empty task stack, navigate to AppFragment;
+        // if AppFragment is shown, pop from stack on AppFragment;
+        // if NotificationFragment is shown, navigate to previous fragment.
+        if (mHomeFragment.isVisible()
+                && mAppFragment.getTaskStackId() != AppFragment.INVALID_TASK_STACK_ID) {
+            navigateApp();
+        } else if (mAppFragment.isVisible()) {
+            mAppFragment.onBackButtonPressed();
+        } else if (mNotificationFragment.isVisible()) {
+            toggleNotification();
+        }
+    }
+
+    public void navigateHome() {
+        getSupportFragmentManager().beginTransaction()
+            .show(mHomeFragment)
+            .commit();
+        getSupportFragmentManager().beginTransaction()
+            .hide(mAppFragment)
+            .commit();
+        getSupportFragmentManager().beginTransaction()
+            .hide(mNotificationFragment)
+            .commit();
+
+        mLastFragment = HOME_FRAGMENT_TAG;
+    }
+
+    public void navigateApp() {
+        getSupportFragmentManager().beginTransaction()
+            .hide(mHomeFragment)
+            .commit();
+        getSupportFragmentManager().beginTransaction()
+            .show(mAppFragment)
+            .commit();
+        getSupportFragmentManager().beginTransaction()
+            .hide(mNotificationFragment)
+            .commit();
+
+        mLastFragment = APP_FRAGMENT_TAG;
+    }
+
+    public void toggleNotification() {
+        if (!mNotificationFragment.isVisible()) {
+            getSupportFragmentManager().beginTransaction()
+                .hide(mHomeFragment)
+                .commit();
+            getSupportFragmentManager().beginTransaction()
+                .hide(mAppFragment)
+                .commit();
+            getSupportFragmentManager().beginTransaction()
+                .show(mNotificationFragment)
+                .commit();
+        } else if (mLastFragment.equals(HOME_FRAGMENT_TAG)) {
+            navigateHome();
+        } else {
+            navigateApp();
+        }
+    }
+}
diff --git a/tests/SecondaryHomeApp/src/com/android/car/secondaryhome/launcher/NotificationFragment.java b/tests/SecondaryHomeApp/src/com/android/car/secondaryhome/launcher/NotificationFragment.java
new file mode 100644
index 0000000..64c20d2
--- /dev/null
+++ b/tests/SecondaryHomeApp/src/com/android/car/secondaryhome/launcher/NotificationFragment.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.secondaryhome.launcher;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+
+import androidx.fragment.app.Fragment;
+
+import com.android.car.notification.PreprocessingManager;
+import com.android.car.secondaryhome.R;
+
+/**
+ * {@link Fragment} that shows list of notifications.
+ */
+public final class NotificationFragment extends Fragment {
+
+    private static final String TAG = "SecHome.NotificationFragment";
+
+    private NotificationListener mNotificationListener;
+    private PreprocessingManager mPreprocessingManager;
+    private NotificationViewController mNotificationViewController;
+    private Context mContext;
+    private View mNotificationView;
+    private Button mClearAllButton;
+
+    private final ServiceConnection mNotificationListenerConnection = new ServiceConnection() {
+        public void onServiceConnected(ComponentName className, IBinder binder) {
+            Log.d(TAG, "onServiceConnected");
+            mNotificationListener = ((NotificationListener.LocalBinder) binder).getService();
+            mNotificationListener.registerAsSystemService(mContext);
+            mNotificationViewController =
+                    new NotificationViewController(mNotificationView,
+                            mPreprocessingManager,
+                            mNotificationListener);
+            mNotificationViewController.enable();
+        }
+
+        public void onServiceDisconnected(ComponentName className) {
+            Log.d(TAG, "onServiceDisconnected");
+            mNotificationViewController.disable();
+            mNotificationViewController = null;
+            mNotificationListener = null;
+        }
+    };
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        mContext = getActivity().getApplicationContext();
+        mPreprocessingManager = PreprocessingManager.getInstance(mContext);
+        Intent intent = new Intent(mContext, NotificationListener.class);
+        intent.setAction(NotificationListener.ACTION_LOCAL_BINDING);
+
+        mContext.bindService(intent, mNotificationListenerConnection,
+                Context.BIND_AUTO_CREATE);
+    }
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+            Bundle savedInstanceState) {
+        View view = inflater.inflate(R.layout.notification_fragment, container, false);
+        mNotificationView = view.findViewById(R.id.notification_fragment);
+        mClearAllButton = view.findViewById(R.id.clear_all_button);
+        mClearAllButton.setOnClickListener(v -> mNotificationViewController.resetNotifications());
+
+        return view;
+    }
+
+    @Override
+    public void onDestroy() {
+        super.onDestroy();
+        // Unbind notification listener
+        mNotificationViewController.disable();
+        mNotificationViewController = null;
+        mNotificationListener = null;
+        mContext.unbindService(mNotificationListenerConnection);
+    }
+
+    @Override
+    public void onResume() {
+        super.onResume();
+        Log.d(TAG, "Resuming notification fragment");
+    }
+
+    @Override
+    public void onPause() {
+        super.onPause();
+        Log.d(TAG, "Pausing notification fragment");
+    }
+}
diff --git a/tests/SecondaryHomeApp/src/com/android/car/secondaryhome/launcher/NotificationListener.java b/tests/SecondaryHomeApp/src/com/android/car/secondaryhome/launcher/NotificationListener.java
new file mode 100644
index 0000000..fb106e1
--- /dev/null
+++ b/tests/SecondaryHomeApp/src/com/android/car/secondaryhome/launcher/NotificationListener.java
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.secondaryhome.launcher;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.service.notification.NotificationListenerService;
+import android.service.notification.StatusBarNotification;
+import android.util.Log;
+
+import com.android.car.notification.AlertEntry;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+/**
+ * NotificationListenerService that fetches all notifications from system.
+ * Copied from {@link CarNotificationListener} since {@link CarNotificationListener}
+ * cannot be used outside that package.
+*/
+public class NotificationListener extends NotificationListenerService {
+    private static final String TAG = "SecHome.NotificationListener";
+    public static final String ACTION_LOCAL_BINDING = "local_binding";
+    public static final int NOTIFY_NOTIFICATION_POSTED = 1;
+    public static final int NOTIFY_NOTIFICATION_REMOVED = 2;
+
+    private final int mUserId = Process.myUserHandle().getIdentifier();
+
+    private Handler mHandler;
+
+    /**
+     * Map that contains all the active notifications. They will be removed from the map
+     * when the {@link NotificationListenerService} calls the onNotificationRemoved method.
+     */
+    private Map<String, AlertEntry> mActiveNotifications = new HashMap<>();
+
+    /**
+     * Call this to register this service as a system service.
+     *
+     * @param context Context required for registering the service.
+     */
+
+    public void registerAsSystemService(Context context) {
+        try {
+            Log.d(TAG, "register as system service for: " + mUserId);
+            registerAsSystemService(context,
+                    new ComponentName(context.getPackageName(),
+                    getClass().getCanonicalName()),
+                    Process.myUid());
+        } catch (RemoteException e) {
+            Log.e(TAG, "Unable to register notification listener", e);
+        }
+    }
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        Log.d("TAG", "onBind");
+        return ACTION_LOCAL_BINDING.equals(intent.getAction())
+                ? new LocalBinder() : super.onBind(intent);
+    }
+
+    @Override
+    public void onNotificationPosted(StatusBarNotification sbn, RankingMap rankingMap) {
+        Log.d(TAG, "onNotificationPosted: " + sbn);
+        if (!isNotificationForCurrentUser(sbn)) {
+            return;
+        }
+        AlertEntry alertEntry = new AlertEntry(sbn);
+        notifyNotificationPosted(alertEntry);
+    }
+
+    @Override
+    public void onNotificationRemoved(StatusBarNotification sbn) {
+        Log.d(TAG, "onNotificationRemoved: " + sbn);
+        AlertEntry alertEntry = mActiveNotifications.get(sbn.getKey());
+        if (alertEntry != null) {
+            removeNotification(alertEntry);
+        }
+    }
+
+    /**
+     * Get all active notifications
+     *
+     * @return a map of all active notifications with key being the notification key.
+     */
+    Map<String, AlertEntry> getNotifications() {
+        return mActiveNotifications.entrySet().stream()
+                .filter(x -> (isNotificationForCurrentUser(
+                        x.getValue().getStatusBarNotification())))
+                .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
+    }
+
+    @Override
+    public void onListenerConnected() {
+        mActiveNotifications = Stream.of(getActiveNotifications()).collect(
+                Collectors.toMap(StatusBarNotification::getKey, sbn -> new AlertEntry(sbn)));
+    }
+
+    @Override
+    public void onListenerDisconnected() {
+    }
+
+    public void setHandler(Handler handler) {
+        mHandler = handler;
+    }
+
+    private void notifyNotificationPosted(AlertEntry alertEntry) {
+        postNewNotification(alertEntry);
+    }
+
+    private boolean isNotificationForCurrentUser(StatusBarNotification sbn) {
+        return (sbn.getUser().getIdentifier() == mUserId
+                || sbn.getUser().getIdentifier() == UserHandle.USER_ALL);
+    }
+
+    class LocalBinder extends Binder {
+        public NotificationListener getService() {
+            return NotificationListener.this;
+        }
+    }
+
+    private void postNewNotification(AlertEntry alertEntry) {
+        mActiveNotifications.put(alertEntry.getKey(), alertEntry);
+        sendNotificationEventToHandler(alertEntry, NOTIFY_NOTIFICATION_POSTED);
+    }
+
+    private void removeNotification(AlertEntry alertEntry) {
+        mActiveNotifications.remove(alertEntry.getKey());
+        sendNotificationEventToHandler(alertEntry, NOTIFY_NOTIFICATION_REMOVED);
+    }
+
+    private void sendNotificationEventToHandler(AlertEntry alertEntry, int eventType) {
+        if (mHandler == null) {
+            return;
+        }
+        Message msg = Message.obtain(mHandler);
+        msg.what = eventType;
+        msg.obj = alertEntry;
+        mHandler.sendMessage(msg);
+    }
+}
diff --git a/tests/SecondaryHomeApp/src/com/android/car/secondaryhome/launcher/NotificationViewController.java b/tests/SecondaryHomeApp/src/com/android/car/secondaryhome/launcher/NotificationViewController.java
new file mode 100644
index 0000000..17f3b3c
--- /dev/null
+++ b/tests/SecondaryHomeApp/src/com/android/car/secondaryhome/launcher/NotificationViewController.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.car.secondaryhome.launcher;
+
+import android.app.Notification;
+import android.os.Handler;
+import android.os.Message;
+import android.view.View;
+import android.widget.TextView;
+
+import com.android.car.notification.AlertEntry;
+import com.android.car.notification.PreprocessingManager;
+import com.android.car.secondaryhome.R;
+
+/**
+ * This object will handle updating notification view.
+ * Each Notification should be shown on CarUiRecyclerView.
+ */
+public class NotificationViewController {
+    private static final String TAG = "NotificationViewControl";
+    private final View mCarNotificationView;
+    private final NotificationListener mCarNotificationListener;
+    private final NotificationUpdateHandler mNotificationUpdateHandler =
+            new NotificationUpdateHandler();
+    private final PreprocessingManager mPreprocessingManager;
+    private final TextView mTextBox;
+
+    public NotificationViewController(View carNotificationView,
+            PreprocessingManager preprocessingManager,
+            NotificationListener carNotificationListener) {
+        mCarNotificationView = carNotificationView;
+        mCarNotificationListener = carNotificationListener;
+        mPreprocessingManager = preprocessingManager;
+        mTextBox = mCarNotificationView.findViewById(R.id.debug);
+        resetNotifications();
+    }
+
+    /**
+     * Set handler to NotificationListener
+     */
+    public void enable() {
+        mCarNotificationListener.setHandler(mNotificationUpdateHandler);
+    }
+
+    /**
+     * Remove handler
+     */
+    public void disable() {
+        mCarNotificationListener.setHandler(null);
+    }
+
+    /**
+     * Update notifications: print to text box
+     */
+    private void updateNotifications(int what, AlertEntry alertEntry) {
+        // filter out navigation notification, otherwise it will spam
+        if (Notification.CATEGORY_NAVIGATION.equals(
+                    alertEntry.getNotification().category)) {
+            return;
+        }
+
+        mTextBox.append("New: " + alertEntry.getStatusBarNotification() + "\n");
+    }
+
+    /**
+    * Reset notifications to the latest state.
+    */
+    public void resetNotifications() {
+        mPreprocessingManager.init(
+                mCarNotificationListener.getNotifications(),
+                mCarNotificationListener.getCurrentRanking());
+
+        mPreprocessingManager.process(
+                true /* showLessImportantNotifications */,
+                mCarNotificationListener.getNotifications(),
+                mCarNotificationListener.getCurrentRanking());
+        mTextBox.setText("");
+    }
+
+    private class NotificationUpdateHandler extends Handler {
+        @Override
+        public void handleMessage(Message message) {
+            updateNotifications(message.what, (AlertEntry) message.obj);
+        }
+    }
+}
diff --git a/tests/SecondaryHomeApp/src/com/android/car/secondaryhome/launcher/PackageIntentReceiver.java b/tests/SecondaryHomeApp/src/com/android/car/secondaryhome/launcher/PackageIntentReceiver.java
new file mode 100644
index 0000000..cca2fec
--- /dev/null
+++ b/tests/SecondaryHomeApp/src/com/android/car/secondaryhome/launcher/PackageIntentReceiver.java
@@ -0,0 +1,50 @@
+/**
+ * Copyright (c) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.secondaryhome.launcher;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+
+/**
+ * Receiver used to notify live data about app list changes.
+ */
+public final class PackageIntentReceiver extends BroadcastReceiver {
+
+    private final AppListLiveData mLiveData;
+
+    PackageIntentReceiver(AppListLiveData liveData, Context context) {
+        mLiveData = liveData;
+        IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
+        filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
+        filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
+        filter.addDataScheme("package");
+        context.registerReceiver(this, filter);
+
+        // Register for events related to sdcard installation.
+        IntentFilter sdFilter = new IntentFilter();
+        sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE);
+        sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);
+        context.registerReceiver(this, sdFilter);
+    }
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        mLiveData.loadData();
+    }
+}
diff --git a/tests/SecurityPermissionTest/Android.bp b/tests/SecurityPermissionTest/Android.bp
new file mode 100644
index 0000000..f77d151
--- /dev/null
+++ b/tests/SecurityPermissionTest/Android.bp
@@ -0,0 +1,40 @@
+//
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+android_test {
+    name: "SecurityPermissionTest",
+
+    srcs: ["src/**/*.java"],
+
+    libs: [
+        "android.car",
+        "android.test.runner",
+        "android.test.base",
+        "android.test.mock",
+    ],
+
+    static_libs: [
+        "androidx.test.core",
+        "androidx.test.ext.junit",
+        "androidx.test.rules",
+        "car-frameworks-service",
+        "testng",
+    ],
+
+    platform_apis: true,
+
+    certificate: "platform",
+}
\ No newline at end of file
diff --git a/tests/SecurityPermissionTest/AndroidManifest.xml b/tests/SecurityPermissionTest/AndroidManifest.xml
new file mode 100644
index 0000000..c0c6ecc
--- /dev/null
+++ b/tests/SecurityPermissionTest/AndroidManifest.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2020 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="com.android.car.securitypermissiontest">
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="com.android.car.securitypermissiontest"
+                     android:label="Unit Tests for Car APIs"/>
+    <application android:label="SecurityPermissionTest"
+                 android:debuggable="true">
+        <uses-library android:name="android.test.runner" />
+    </application>
+</manifest>
\ No newline at end of file
diff --git a/tests/SecurityPermissionTest/src/com/android/car/CarPublicTest.java b/tests/SecurityPermissionTest/src/com/android/car/CarPublicTest.java
new file mode 100644
index 0000000..af1867b
--- /dev/null
+++ b/tests/SecurityPermissionTest/src/com/android/car/CarPublicTest.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.car;
+
+import static org.testng.Assert.assertThrows;
+
+import android.car.Car;
+import android.os.Handler;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * This class contains security permission tests for the {@link CarTest}'s public APIs.
+ */
+@RunWith(AndroidJUnit4.class)
+public class CarPublicTest {
+    private Car mCar = null;
+
+    @Before
+    public void setUp() throws Exception {
+        mCar = Car.createCar(
+                InstrumentationRegistry.getInstrumentation().getTargetContext(), (Handler) null);
+    }
+
+    @After
+    public void tearDown() {
+        mCar.disconnect();
+    }
+
+    @Test
+    public void testGetCarManagerPermissions() throws Exception {
+        assertThrows(SecurityException.class, () -> mCar.getCarManager(
+                Car.CAR_DRIVING_STATE_SERVICE));
+        assertThrows(SecurityException.class, () -> mCar.getCarManager(
+                Car.CAR_INSTRUMENT_CLUSTER_SERVICE));
+        assertThrows(SecurityException.class, () -> mCar.getCarManager(Car.CAR_NAVIGATION_SERVICE));
+        assertThrows(SecurityException.class, () -> mCar.getCarManager(
+                Car.CAR_TRUST_AGENT_ENROLLMENT_SERVICE));
+        if (mCar.isFeatureEnabled(Car.DIAGNOSTIC_SERVICE)) {
+            assertThrows(SecurityException.class, () -> mCar.getCarManager(Car.DIAGNOSTIC_SERVICE));
+        }
+        assertThrows(SecurityException.class, () -> mCar.getCarManager(Car.POWER_SERVICE));
+        if (mCar.isFeatureEnabled(Car.VMS_SUBSCRIBER_SERVICE)) {
+            assertThrows(SecurityException.class, () -> mCar.getCarManager(
+                    Car.VMS_SUBSCRIBER_SERVICE));
+        }
+        assertThrows(SecurityException.class, () -> mCar.getCarManager(Car.TEST_SERVICE));
+        if (mCar.isFeatureEnabled(Car.STORAGE_MONITORING_SERVICE)) {
+            assertThrows(SecurityException.class, () -> mCar.getCarManager(
+                    Car.STORAGE_MONITORING_SERVICE));
+        }
+    }
+}
diff --git a/tests/SecurityPermissionTest/src/com/android/car/CarTest.java b/tests/SecurityPermissionTest/src/com/android/car/CarTest.java
new file mode 100644
index 0000000..90378a8
--- /dev/null
+++ b/tests/SecurityPermissionTest/src/com/android/car/CarTest.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.car;
+
+import static org.testng.Assert.assertThrows;
+
+import android.car.Car;
+import android.os.Handler;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * This class contains security permission tests for the {@link CarTest}'s system APIs.
+ */
+@RunWith(AndroidJUnit4.class)
+public class CarTest {
+    private Car mCar = null;
+
+    @Before
+    public void setUp() throws Exception {
+        mCar = Car.createCar(
+                InstrumentationRegistry.getInstrumentation().getTargetContext(), (Handler) null);
+    }
+
+    @After
+    public void tearDown() {
+        mCar.disconnect();
+    }
+
+    @Test
+    public void testEnableFeaturePermission() throws Exception {
+        assertThrows(SecurityException.class, () -> mCar.enableFeature("some feature"));
+    }
+
+    @Test
+    public void testDisableFeaturePermission() throws Exception {
+        assertThrows(SecurityException.class, () -> mCar.disableFeature("some feature"));
+    }
+
+    @Test
+    public void testGetAllEnabledFeaturesPermission() throws Exception {
+        assertThrows(SecurityException.class, () -> mCar.getAllEnabledFeatures());
+    }
+
+    @Test
+    public void testGetAllPendingDisabledFeaturesPermission() throws Exception {
+        assertThrows(SecurityException.class, () -> mCar.getAllPendingDisabledFeatures());
+    }
+
+    @Test
+    public void testGetAllPendingEnabledFeaturesPermission() throws Exception {
+        assertThrows(SecurityException.class, () -> mCar.getAllPendingEnabledFeatures());
+    }
+}
diff --git a/tests/ThemePlayground/Android.bp b/tests/ThemePlayground/Android.bp
new file mode 100644
index 0000000..09677dd
--- /dev/null
+++ b/tests/ThemePlayground/Android.bp
@@ -0,0 +1,46 @@
+//
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+android_app {
+    name: "ThemePlayground",
+
+    aaptflags: ["--auto-add-overlay"],
+
+    srcs: ["src/**/*.java"],
+    resource_dirs: ["res"],
+
+    static_libs: [
+        "androidx.recyclerview_recyclerview",
+        "androidx.transition_transition",
+        "androidx.legacy_legacy-support-v4",
+        "androidx-constraintlayout_constraintlayout",
+        "androidx-constraintlayout_constraintlayout-solver",
+        "car-apps-common",
+    ],
+
+    owner: "google",
+    platform_apis: true,
+    certificate: "platform",
+    privileged: true,
+
+    optimize: {
+        enabled: false,
+    },
+
+    libs: ["android.car"],
+
+    required: ["privapp_whitelist_com.android.car.themeplayground"],
+}
diff --git a/tests/ThemePlayground/Android.mk b/tests/ThemePlayground/Android.mk
deleted file mode 100644
index 1247765..0000000
--- a/tests/ThemePlayground/Android.mk
+++ /dev/null
@@ -1,49 +0,0 @@
-#
-# Copyright (C) 2018 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-LOCAL_PATH := $(call my-dir)
-
-include $(CLEAR_VARS)
-
-LOCAL_USE_AAPT2 := true
-
-LOCAL_MODULE_TAGS := optional
-LOCAL_AAPT_FLAGS := --auto-add-overlay
-
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
-
-LOCAL_STATIC_ANDROID_LIBRARIES += \
-    androidx.recyclerview_recyclerview \
-    androidx.transition_transition \
-    androidx.legacy_legacy-support-v4 \
-    androidx-constraintlayout_constraintlayout
-
-LOCAL_MODULE_OWNER := google
-LOCAL_PACKAGE_NAME := ThemePlayground
-LOCAL_PRIVATE_PLATFORM_APIS := true
-LOCAL_CERTIFICATE := platform
-LOCAL_PRIVILEGED_MODULE := true
-LOCAL_STATIC_JAVA_LIBRARIES := \
-         androidx-constraintlayout_constraintlayout-solver
-
-LOCAL_PROGUARD_ENABLED := disabled
-
-LOCAL_JAVA_LIBRARIES += android.car
-
-LOCAL_REQUIRED_MODULES := privapp_whitelist_com.android.car.themeplayground
-
-include $(BUILD_PACKAGE)
diff --git a/tests/ThemePlayground/AndroidManifest.xml b/tests/ThemePlayground/AndroidManifest.xml
index efaf6e9..5492704 100644
--- a/tests/ThemePlayground/AndroidManifest.xml
+++ b/tests/ThemePlayground/AndroidManifest.xml
@@ -58,6 +58,10 @@
                   android:label="@string/recycler_view"
                   android:parentActivityName="com.android.car.themeplayground.TextSamples">
         </activity>
+        <activity android:name=".PagedRecyclerViewSamples"
+            android:label="@string/paged_recycler_view"
+            android:parentActivityName="com.android.car.themeplayground.TextSamples">
+        </activity>
         <activity android:name=".DefaultThemeSamples"
                   android:label="@string/default_themes"
                   android:parentActivityName="com.android.car.themeplayground.TextSamples">
diff --git a/tests/ThemePlayground/res/layout/paged_recycler_view_samples.xml b/tests/ThemePlayground/res/layout/paged_recycler_view_samples.xml
new file mode 100644
index 0000000..4037545
--- /dev/null
+++ b/tests/ThemePlayground/res/layout/paged_recycler_view_samples.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+  -->
+
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical">
+
+  <com.android.car.apps.common.widget.PagedRecyclerView
+      android:id="@+id/list"
+      android:layout_width="match_parent"
+      android:layout_height="match_parent"/>
+  <include layout="@layout/menu_button"/>
+</LinearLayout>
diff --git a/tests/ThemePlayground/res/menu/menu_main.xml b/tests/ThemePlayground/res/menu/menu_main.xml
index 08168c0..3e2a360 100644
--- a/tests/ThemePlayground/res/menu/menu_main.xml
+++ b/tests/ThemePlayground/res/menu/menu_main.xml
@@ -42,6 +42,10 @@
         android:orderInCategory="100"
         android:title="@string/recycler_view"/>
     <item
+        android:id="@+id/paged_recycler_view"
+        android:orderInCategory="100"
+        android:title="@string/paged_recycler_view"/>
+    <item
         android:id="@+id/default_themes"
         android:orderInCategory="100"
         android:title="@string/default_themes"/>
diff --git a/tests/ThemePlayground/res/values/strings.xml b/tests/ThemePlayground/res/values/strings.xml
index 0a329b5..932f899 100644
--- a/tests/ThemePlayground/res/values/strings.xml
+++ b/tests/ThemePlayground/res/values/strings.xml
@@ -22,7 +22,8 @@
     <string name="toggle_theme" translatable="false">Change configuration(Day/Night)</string>
     <string name="apply" translatable="false">Apply</string>
     <string name="widgets" translatable="false">Widgets</string>
-    <string name="recycler_view" translatable="false">Recycler List View</string>
+    <string name="recycler_view" translatable="false">Recycler View</string>
+    <string name="paged_recycler_view" translatable="false">Paged Recycler View</string>
     <string name="widget_checkbox" translatable="false">Checkbox</string>
     <string name="toggle_switch" translatable="false">Toggle Switch</string>
     <string name="reset" translatable="false">Reset</string>
diff --git a/tests/ThemePlayground/src/com/android/car/themeplayground/AbstractSampleActivity.java b/tests/ThemePlayground/src/com/android/car/themeplayground/AbstractSampleActivity.java
index a55bbbf..df0c80b 100644
--- a/tests/ThemePlayground/src/com/android/car/themeplayground/AbstractSampleActivity.java
+++ b/tests/ThemePlayground/src/com/android/car/themeplayground/AbstractSampleActivity.java
@@ -62,6 +62,8 @@
                 return startSampleActivity(WidgetsSamples.class);
             case R.id.recycler_view:
                 return startSampleActivity(RecyclerViewSamples.class);
+            case R.id.paged_recycler_view:
+                return startSampleActivity(PagedRecyclerViewSamples.class);
             case R.id.default_themes:
                 return startSampleActivity(DefaultThemeSamples.class);
             case R.id.multiple_intent:
diff --git a/tests/ThemePlayground/src/com/android/car/themeplayground/PagedRecyclerViewSamples.java b/tests/ThemePlayground/src/com/android/car/themeplayground/PagedRecyclerViewSamples.java
new file mode 100644
index 0000000..a331cab
--- /dev/null
+++ b/tests/ThemePlayground/src/com/android/car/themeplayground/PagedRecyclerViewSamples.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.themeplayground;
+
+import android.os.Bundle;
+
+import androidx.recyclerview.widget.LinearLayoutManager;
+
+import com.android.car.apps.common.widget.PagedRecyclerView;
+
+import java.util.ArrayList;
+
+/**
+ * Activity that shows PagedRecyclerView example with dummy data.
+ */
+public class PagedRecyclerViewSamples extends AbstractSampleActivity {
+
+    private final ArrayList<String> mData = new ArrayList<>();
+    private final int mDataToGenerate = 15;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        Utils.onActivityCreateSetTheme(this);
+        setContentView(R.layout.paged_recycler_view_samples);
+        PagedRecyclerView recyclerView = findViewById(R.id.list);
+        recyclerView.setLayoutManager(new LinearLayoutManager(this));
+
+        RecyclerViewAdapter adapter = new RecyclerViewAdapter(generateDummyData());
+        recyclerView.setAdapter(adapter);
+    }
+
+    private ArrayList<String> generateDummyData() {
+        for (int i = 0; i <= mDataToGenerate; i++) {
+            mData.add("data" + i);
+        }
+        return mData;
+    }
+}
diff --git a/tests/UxRestrictionsSample/Android.mk b/tests/UxRestrictionsSample/Android.mk
index 3092876..b763290 100644
--- a/tests/UxRestrictionsSample/Android.mk
+++ b/tests/UxRestrictionsSample/Android.mk
@@ -39,7 +39,9 @@
 
 LOCAL_CERTIFICATE := platform
 
-LOCAL_STATIC_JAVA_LIBRARIES += vehicle-hal-support-lib
+LOCAL_STATIC_JAVA_LIBRARIES += \
+    vehicle-hal-support-lib \
+    car-experimental-api-static-lib
 
 LOCAL_STATIC_ANDROID_LIBRARIES += \
     com.google.android.material_material \
diff --git a/tests/UxRestrictionsSample/res/layout/main_activity.xml b/tests/UxRestrictionsSample/res/layout/main_activity.xml
index e8634ee..66a3ea2 100644
--- a/tests/UxRestrictionsSample/res/layout/main_activity.xml
+++ b/tests/UxRestrictionsSample/res/layout/main_activity.xml
@@ -65,6 +65,15 @@
         android:textSize="@dimen/info_text_size"
         android:textAppearance="?android:textAppearanceLarge"/>
 
+    <TextView
+        android:id="@+id/current_driver_distraction"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:text="@string/current_driver_distraction"
+        android:padding="@dimen/section_padding"
+        android:textSize="@dimen/info_text_size"
+        android:textAppearance="?android:textAppearanceLarge"/>
+
     <!-- Section: Available Actions -->
     <View
         android:layout_width="match_parent"
diff --git a/tests/UxRestrictionsSample/res/values/strings.xml b/tests/UxRestrictionsSample/res/values/strings.xml
index c789067..ec97bb7 100644
--- a/tests/UxRestrictionsSample/res/values/strings.xml
+++ b/tests/UxRestrictionsSample/res/values/strings.xml
@@ -38,4 +38,5 @@
     <string name="prod_config_title" translatable="false">Production Config</string>
     <string name="tab_baseline" translatable="false">Baseline</string>
     <string name="tab_passenger" translatable="false">Passenger</string>
+    <string name="current_driver_distraction" translatable="false">Driver Awareness Percentage: %1$s</string>
 </resources>
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 f4eff56..7393673 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
@@ -29,10 +29,14 @@
 import android.car.drivingstate.CarUxRestrictionsConfiguration;
 import android.car.drivingstate.CarUxRestrictionsConfiguration.DrivingStateRestrictions;
 import android.car.drivingstate.CarUxRestrictionsManager;
+import android.car.experimental.CarDriverDistractionManager;
+import android.car.experimental.DriverDistractionChangeEvent;
+import android.car.experimental.ExperimentalCar;
 import android.os.Bundle;
 import android.util.JsonWriter;
 import android.widget.Button;
 import android.widget.TextView;
+import android.widget.Toast;
 
 import androidx.appcompat.app.AppCompatActivity;
 import androidx.fragment.app.DialogFragment;
@@ -55,15 +59,19 @@
     private CarDrivingStateManager mCarDrivingStateManager;
     private CarUxRestrictionsManager mCarUxRestrictionsManager;
     private CarPackageManager mCarPackageManager;
+    private CarDriverDistractionManager mDistractionManager;
 
     private CarUxRestrictionsManager.OnUxRestrictionsChangedListener mUxRChangeListener =
             this::updateUxRText;
     private CarDrivingStateManager.CarDrivingStateEventListener mDrvStateChangeListener =
             this::updateDrivingStateText;
+    private CarDriverDistractionManager.OnDriverDistractionChangeListener
+            mDistractionChangedListener = this::onDriverDistractionChange;
 
     private TextView mDrvStatus;
     private TextView mDistractionOptStatus;
     private TextView mUxrStatus;
+    private TextView mDistractionStatus;
     private Button mToggleButton;
     private Button mSaveUxrConfigButton;
     private Button mShowStagedConfig;
@@ -81,6 +89,7 @@
         mDrvStatus = findViewById(R.id.driving_state);
         mDistractionOptStatus = findViewById(R.id.do_status);
         mUxrStatus = findViewById(R.id.uxr_status);
+        mDistractionStatus = findViewById(R.id.current_driver_distraction);
 
         mToggleButton = findViewById(R.id.toggle_status);
         mToggleButton.setOnClickListener(v -> updateToggleUxREnable());
@@ -114,6 +123,18 @@
             mCarUxRestrictionsManager.registerListener(mUxRChangeListener);
             updateUxRText(mCarUxRestrictionsManager.getCurrentCarUxRestrictions());
         }
+
+        if (mCar.isFeatureEnabled(
+                ExperimentalCar.DRIVER_DISTRACTION_EXPERIMENTAL_FEATURE_SERVICE)) {
+            mDistractionManager = (CarDriverDistractionManager) mCar.getCarManager(
+                    ExperimentalCar.DRIVER_DISTRACTION_EXPERIMENTAL_FEATURE_SERVICE);
+            if (mDistractionManager != null) {
+                mDistractionManager.addDriverDistractionChangeListener(mDistractionChangedListener);
+            }
+        } else {
+            mDistractionStatus.setText(
+                    getString(R.string.current_driver_distraction, "feature disabled"));
+        }
     }
 
     @Override
@@ -125,6 +146,9 @@
         if (mCarDrivingStateManager != null) {
             mCarDrivingStateManager.unregisterListener();
         }
+        if (mDistractionManager != null) {
+            mDistractionManager.removeDriverDistractionChangeListener(mDistractionChangedListener);
+        }
         if (mCar != null) {
             mCar.disconnect();
         }
@@ -145,6 +169,11 @@
         mUxrStatus.requestLayout();
     }
 
+    private void onDriverDistractionChange(DriverDistractionChangeEvent event) {
+        mDistractionStatus.setText(
+                getString(R.string.current_driver_distraction, event.getAwarenessPercentage()));
+    }
+
     private void updateToggleUxREnable() {
         if (mCarPackageManager == null) {
             return;
@@ -232,7 +261,11 @@
                                 .setRestrictions(passenger))
                 .build();
 
-        mCarUxRestrictionsManager.saveUxRestrictionsConfigurationForNextBoot(config);
+        if (mCarUxRestrictionsManager.saveUxRestrictionsConfigurationForNextBoot(config)) {
+            Toast.makeText(this, "Config saved successfully", Toast.LENGTH_SHORT).show();
+        } else {
+            Toast.makeText(this, "Config failed to save", Toast.LENGTH_SHORT).show();
+        }
     }
 
     private void showStagedUxRestrictionsConfig() {
diff --git a/tests/VmsSubscriberClientSample/src/com/google/android/car/vms/subscriber/VmsSubscriberClientSampleActivity.java b/tests/VmsSubscriberClientSample/src/com/google/android/car/vms/subscriber/VmsSubscriberClientSampleActivity.java
index 3730104..3faa93f 100644
--- a/tests/VmsSubscriberClientSample/src/com/google/android/car/vms/subscriber/VmsSubscriberClientSampleActivity.java
+++ b/tests/VmsSubscriberClientSample/src/com/google/android/car/vms/subscriber/VmsSubscriberClientSampleActivity.java
@@ -80,6 +80,10 @@
         @Override
         public void onServiceConnected(ComponentName name, IBinder service) {
             Log.d(TAG, "Connected to Car Service");
+            if (!mCarApi.isFeatureEnabled(Car.VMS_SUBSCRIBER_SERVICE)) {
+                Log.e(TAG, "VMS not supported");
+                finish();
+            }
             mVmsSubscriberManager = getVmsSubscriberManager();
             configureSubscriptions(mVmsSubscriberManager);
         }
diff --git a/tests/android_car_api_test/Android.mk b/tests/android_car_api_test/Android.mk
index b88ca78..48001f9 100644
--- a/tests/android_car_api_test/Android.mk
+++ b/tests/android_car_api_test/Android.mk
@@ -39,7 +39,13 @@
         android.hidl.base-V1.0-java \
         android.hardware.automotive.vehicle-V2.0-java \
         android.car.cluster.navigation \
+        compatibility-device-util-axt \
+        testng \
+        truth-prebuilt \
+        androidx.test.runner \
 
 LOCAL_JAVA_LIBRARIES := android.car android.test.runner android.test.base
 
+LOCAL_COMPATIBILITY_SUITE := general-tests
+
 include $(BUILD_PACKAGE)
diff --git a/tests/android_car_api_test/AndroidManifest.xml b/tests/android_car_api_test/AndroidManifest.xml
index e4c5bbb..9f22d11 100644
--- a/tests/android_car_api_test/AndroidManifest.xml
+++ b/tests/android_car_api_test/AndroidManifest.xml
@@ -17,11 +17,10 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
         xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
         package="android.car.apitest"
-        android:sharedUserId="android.uid.system"
-        coreApp="true"
+        android:sharedUserId="com.google.android.car.uid.kitchensink"
         android:debuggable="true" >
 
-    <instrumentation android:name="android.test.InstrumentationTestRunner"
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
             android:targetPackage="android.car.apitest"
             android:label="Tests for Car APIs"
             android:debuggable="true" />
@@ -36,5 +35,9 @@
         </activity>
         <service android:name=".CarProjectionManagerTest$TestService"
                  android:exported="true" />
+        <activity android:name=".CarActivityViewDisplayIdTest$ActivityInActivityView"/>
+        <activity android:name=".CarActivityViewDisplayIdTest$ActivityViewTestActivity"/>
+        <activity android:name=".CarActivityViewDisplayIdTest$MultiProcessActivityViewTestActivity"
+                  android:exported="true" android:process=":activity_view_test"/>
     </application>
 </manifest>
diff --git a/tests/android_car_api_test/src/android/car/apitest/AppBlockingPackageInfoTest.java b/tests/android_car_api_test/src/android/car/apitest/AppBlockingPackageInfoTest.java
index c260c0e..346f0fa 100644
--- a/tests/android_car_api_test/src/android/car/apitest/AppBlockingPackageInfoTest.java
+++ b/tests/android_car_api_test/src/android/car/apitest/AppBlockingPackageInfoTest.java
@@ -18,19 +18,28 @@
 import android.car.content.pm.AppBlockingPackageInfo;
 import android.content.Context;
 import android.content.pm.PackageManager;
-import android.content.pm.Signature;
 import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.Signature;
 import android.os.Parcel;
 import android.test.AndroidTestCase;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.util.Log;
 
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
 @SmallTest
 public class AppBlockingPackageInfoTest extends AndroidTestCase {
     private static final String TAG = AppBlockingPackageInfoTest.class.getSimpleName();
 
+    @Test
     public void testParcellingSystemInfo() throws Exception {
-        AppBlockingPackageInfo carServiceInfo = createInfoCarService(getContext());
+        AppBlockingPackageInfo carServiceInfo = createInfoCarService(
+                InstrumentationRegistry.getInstrumentation().getContext());
         Parcel dest = Parcel.obtain();
         carServiceInfo.writeToParcel(dest, 0);
         dest.setDataPosition(0);
@@ -39,8 +48,10 @@
         assertEquals(carServiceInfo, carServiceInfoRead);
     }
 
+    @Test
     public void testParcellingNonSystemInfo() throws Exception {
-        AppBlockingPackageInfo selfInfo = createInfoSelf(getContext());
+        AppBlockingPackageInfo selfInfo = createInfoSelf(
+                InstrumentationRegistry.getInstrumentation().getContext());
         Parcel dest = Parcel.obtain();
         selfInfo.writeToParcel(dest, 0);
         dest.setDataPosition(0);
diff --git a/tests/android_car_api_test/src/android/car/apitest/CarActivityViewDisplayIdTest.java b/tests/android_car_api_test/src/android/car/apitest/CarActivityViewDisplayIdTest.java
new file mode 100644
index 0000000..b5adb35
--- /dev/null
+++ b/tests/android_car_api_test/src/android/car/apitest/CarActivityViewDisplayIdTest.java
@@ -0,0 +1,372 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.car.apitest;
+
+import static android.app.ActivityManager.RunningAppProcessInfo;
+import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.Display.INVALID_DISPLAY;
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assume.assumeTrue;
+import static org.testng.Assert.assertThrows;
+
+import android.app.Activity;
+import android.app.ActivityManager;
+import android.app.ActivityOptions;
+import android.app.ActivityView;
+import android.app.Instrumentation;
+import android.car.Car;
+import android.car.app.CarActivityView;
+import android.car.drivingstate.CarUxRestrictionsManager;
+import android.content.Context;
+import android.content.Intent;
+import android.hardware.display.DisplayManager;
+import android.os.Bundle;
+import android.os.Process;
+import android.os.SystemClock;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.view.Display;
+import android.view.ViewGroup;
+
+import androidx.test.filters.FlakyTest;
+
+import org.junit.Before;
+import org.junit.FixMethodOrder;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.junit.runners.MethodSorters;
+
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+/**
+ * Build/Install/Run:
+ *  atest AndroidCarApiTest:CarActivityViewDisplayIdTest
+ */
+@RunWith(JUnit4.class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@MediumTest
+public class CarActivityViewDisplayIdTest extends CarApiTestBase {
+    private static final String CAR_LAUNCHER_PKG_NAME = "com.android.car.carlauncher";
+    private static final String ACTIVITY_VIEW_TEST_PKG_NAME = "android.car.apitest";
+    private static final String ACTIVITY_VIEW_TEST_PROCESS_NAME =
+            ACTIVITY_VIEW_TEST_PKG_NAME + ":activity_view_test";
+    private static final String ACTIVITY_VIEW_DISPLAY_NAME = "TaskVirtualDisplay";
+    private static final int NONEXISTENT_DISPLAY_ID = Integer.MAX_VALUE;
+    private static final int TEST_TIMEOUT_SEC = 5;
+    private static final int TEST_TIMEOUT_MS = TEST_TIMEOUT_SEC * 1000;
+    private static final int TEST_POLL_MS = 50;
+    private static final int INVALID_PID = -1;
+
+    private DisplayManager mDisplayManager;
+    private ActivityManager mActivityManager;
+    private CarUxRestrictionsManager mCarUxRestrictionsManager;
+
+    @Before
+    public void setUp() throws Exception {
+        super.setUp();
+        mDisplayManager = getContext().getSystemService(DisplayManager.class);
+        mActivityManager = getContext().getSystemService(ActivityManager.class);
+        mCarUxRestrictionsManager = (CarUxRestrictionsManager)
+                getCar().getCarManager(Car.CAR_UX_RESTRICTION_SERVICE);
+    }
+
+    private int getMappedPhysicalDisplayOfVirtualDisplay(int displayId) {
+        return mCarUxRestrictionsManager.getMappedPhysicalDisplayOfVirtualDisplay(displayId);
+    }
+
+    @Test
+    @FlakyTest
+    public void testSingleActivityView() throws Exception {
+        ActivityViewTestActivity activity = startActivityViewTestActivity(DEFAULT_DISPLAY);
+        activity.waitForActivityViewReady();
+        int virtualDisplayId = activity.getActivityView().getVirtualDisplayId();
+
+        startTestActivity(ActivityInActivityView.class, virtualDisplayId);
+
+        assertThat(getMappedPhysicalDisplayOfVirtualDisplay(virtualDisplayId))
+                .isEqualTo(DEFAULT_DISPLAY);
+        assertThat(getMappedPhysicalDisplayOfVirtualDisplay(DEFAULT_DISPLAY))
+                .isEqualTo(INVALID_DISPLAY);
+
+        activity.finish();
+        activity.waitForActivityViewDestroyed();
+
+        assertThat(getMappedPhysicalDisplayOfVirtualDisplay(virtualDisplayId))
+                .isEqualTo(INVALID_DISPLAY);
+        assertThat(getMappedPhysicalDisplayOfVirtualDisplay(DEFAULT_DISPLAY))
+                .isEqualTo(INVALID_DISPLAY);
+    }
+
+    @Test
+    @FlakyTest
+    public void testDoubleActivityView() throws Exception {
+        ActivityViewTestActivity activity1 = startActivityViewTestActivity(DEFAULT_DISPLAY);
+        activity1.waitForActivityViewReady();
+        int virtualDisplayId1 = activity1.getActivityView().getVirtualDisplayId();
+
+        ActivityViewTestActivity activity2 = startActivityViewTestActivity(virtualDisplayId1);
+        activity2.waitForActivityViewReady();
+        int virtualDisplayId2 = activity2.getActivityView().getVirtualDisplayId();
+
+        startTestActivity(ActivityInActivityView.class, virtualDisplayId2);
+
+        assertThat(virtualDisplayId1).isNotEqualTo(virtualDisplayId2);
+        assertThat(getMappedPhysicalDisplayOfVirtualDisplay(virtualDisplayId1))
+                .isEqualTo(DEFAULT_DISPLAY);
+        assertThat(getMappedPhysicalDisplayOfVirtualDisplay(virtualDisplayId2))
+                .isEqualTo(DEFAULT_DISPLAY);
+        assertThat(getMappedPhysicalDisplayOfVirtualDisplay(DEFAULT_DISPLAY))
+                .isEqualTo(INVALID_DISPLAY);
+
+        activity2.finish();
+        activity1.finish();
+
+        activity2.waitForActivityViewDestroyed();
+        activity1.waitForActivityViewDestroyed();
+
+        assertThat(getMappedPhysicalDisplayOfVirtualDisplay(virtualDisplayId1))
+                .isEqualTo(INVALID_DISPLAY);
+        assertThat(getMappedPhysicalDisplayOfVirtualDisplay(virtualDisplayId2))
+                .isEqualTo(INVALID_DISPLAY);
+        assertThat(getMappedPhysicalDisplayOfVirtualDisplay(DEFAULT_DISPLAY))
+                .isEqualTo(INVALID_DISPLAY);
+    }
+
+    @Test
+    @FlakyTest
+    public void testThrowsExceptionOnReportingNonExistingDisplay() throws Exception {
+        ActivityViewTestActivity activity = startActivityViewTestActivity(DEFAULT_DISPLAY);
+        activity.waitForActivityViewReady();
+        int virtualDisplayId = activity.getActivityView().getVirtualDisplayId();
+
+        // This will pass since the test owns the display.
+        mCarUxRestrictionsManager.reportVirtualDisplayToPhysicalDisplay(virtualDisplayId,
+                NONEXISTENT_DISPLAY_ID);
+
+        assertThat(getMappedPhysicalDisplayOfVirtualDisplay(virtualDisplayId))
+                .isEqualTo(NONEXISTENT_DISPLAY_ID);
+
+        activity.finish();
+        activity.waitForActivityViewDestroyed();
+
+        // Now the display was released, so expect to throw an Exception.
+        assertThrows(
+                java.lang.IllegalArgumentException.class,
+                () -> mCarUxRestrictionsManager.reportVirtualDisplayToPhysicalDisplay(
+                        virtualDisplayId, NONEXISTENT_DISPLAY_ID));
+    }
+
+    // TODO(b/143353546): Make the following tests not to rely on CarLauncher.
+    @Test
+    @FlakyTest
+    public void testThrowsExceptionOnReportingNonOwningDisplay() throws Exception {
+        int displayIdOfCarLauncher = waitForActivityViewDisplayReady(CAR_LAUNCHER_PKG_NAME);
+        assumeTrue(INVALID_DISPLAY != displayIdOfCarLauncher);
+
+        // CarLauncher owns the display, so expect to throw an Exception.
+        assertThrows(
+                java.lang.SecurityException.class,
+                () -> mCarUxRestrictionsManager.reportVirtualDisplayToPhysicalDisplay(
+                        displayIdOfCarLauncher, DEFAULT_DISPLAY + 1));
+
+    }
+
+    // The test name starts with 'testz' to run it at the last among the tests, since killing
+    // TestActivity forcefully causes the system unstable for a while.
+    @Test
+    @FlakyTest
+    public void testzCleanUpAfterClientIsCrashed() throws Exception {
+        Intent intent = new Intent(getContext(), MultiProcessActivityViewTestActivity.class);
+        getContext().startActivity(intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
+        int pidOfTestActivity = waitForTestActivityReady();
+        int displayIdOfTestActivity = waitForActivityViewDisplayReady(ACTIVITY_VIEW_TEST_PKG_NAME);
+
+        assertThat(getMappedPhysicalDisplayOfVirtualDisplay(displayIdOfTestActivity))
+                .isEqualTo(DEFAULT_DISPLAY);
+
+        Process.killProcess(pidOfTestActivity);
+
+        assertThat(waitForMappedPhysicalDisplayOfVirtualDisplayCleared(displayIdOfTestActivity))
+                .isEqualTo(INVALID_DISPLAY);
+    }
+
+    private int waitForActivityViewDisplayReady(String packageName) {
+        for (int i = 0; i < TEST_TIMEOUT_MS / TEST_POLL_MS; ++i) {
+            for (Display display : mDisplayManager.getDisplays()) {
+                if (display.getName().contains(ACTIVITY_VIEW_DISPLAY_NAME)
+                        && display.getOwnerPackageName().equals(packageName)
+                        && display.getState() == Display.STATE_ON) {
+                    return display.getDisplayId();
+                }
+            }
+            SystemClock.sleep(TEST_POLL_MS);
+        }
+        return INVALID_DISPLAY;
+    }
+
+    private int waitForMappedPhysicalDisplayOfVirtualDisplayCleared(int displayId) {
+        // Initialized with a random number which is not DEFAULT_DISPLAY nor INVALID_DISPLAY.
+        int physicalDisplayId = 999;
+        for (int i = 0; i < TEST_TIMEOUT_MS / TEST_POLL_MS; ++i) {
+            physicalDisplayId = getMappedPhysicalDisplayOfVirtualDisplay(displayId);
+            if (physicalDisplayId == INVALID_DISPLAY) {
+                return physicalDisplayId;
+            }
+            SystemClock.sleep(TEST_POLL_MS);
+        }
+        return physicalDisplayId;
+    }
+
+    private int waitForTestActivityReady() {
+        for (int i = 0; i < TEST_TIMEOUT_MS / TEST_POLL_MS; ++i) {
+            List<RunningAppProcessInfo> appProcesses = mActivityManager.getRunningAppProcesses();
+            for (RunningAppProcessInfo info : appProcesses) {
+                if (info.processName.equals(ACTIVITY_VIEW_TEST_PROCESS_NAME)
+                        && info.importance == RunningAppProcessInfo.IMPORTANCE_FOREGROUND) {
+                    return info.pid;
+                }
+            }
+            SystemClock.sleep(TEST_POLL_MS);
+        }
+        return INVALID_PID;
+    }
+
+    private static class TestActivity extends Activity {
+        private final CountDownLatch mResumed = new CountDownLatch(1);
+
+        @Override
+        protected void onPostResume() {
+            super.onPostResume();
+            mResumed.countDown();
+        }
+
+        void waitForResumeStateChange() throws Exception {
+            waitForLatch(mResumed);
+        }
+    }
+
+    private static void waitForLatch(CountDownLatch latch) throws Exception {
+        boolean result = latch.await(TEST_TIMEOUT_SEC, TimeUnit.SECONDS);
+        if (!result) {
+            throw new TimeoutException("Timed out waiting for task stack change notification");
+        }
+    }
+
+    /**
+     * Starts the provided activity and returns the started instance.
+     */
+    private TestActivity startTestActivity(Class<?> activityClass, int displayId) throws Exception {
+        Instrumentation.ActivityMonitor monitor = new Instrumentation.ActivityMonitor(
+                activityClass.getName(), null, false);
+        getInstrumentation().addMonitor(monitor);
+
+        Context context = getContext();
+        Intent intent = new Intent(context, activityClass).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        ActivityOptions options = ActivityOptions.makeBasic();
+        if (displayId != DEFAULT_DISPLAY) {
+            intent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
+            options.setLaunchDisplayId(displayId);
+        }
+        context.startActivity(intent, options.toBundle());
+
+        TestActivity activity = (TestActivity) monitor.waitForActivityWithTimeout(TEST_TIMEOUT_MS);
+        if (activity == null) {
+            throw new TimeoutException("Timed out waiting for Activity");
+        }
+        activity.waitForResumeStateChange();
+        return activity;
+    }
+
+    public static class ActivityViewTestActivity extends TestActivity {
+        private static final class ActivityViewStateCallback extends ActivityView.StateCallback {
+            private final CountDownLatch mActivityViewReadyLatch = new CountDownLatch(1);
+            private final CountDownLatch mActivityViewDestroyedLatch = new CountDownLatch(1);
+
+            @Override
+            public void onActivityViewReady(ActivityView view) {
+                mActivityViewReadyLatch.countDown();
+            }
+
+            @Override
+            public void onActivityViewDestroyed(ActivityView view) {
+                mActivityViewDestroyedLatch.countDown();
+            }
+        }
+
+        private CarActivityView mActivityView;
+        private final ActivityViewStateCallback mCallback = new ActivityViewStateCallback();
+
+        @Override
+        public void onCreate(Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+
+            mActivityView = new CarActivityView(this, /*attrs=*/null , /*defStyle=*/0 ,
+                    /*singleTaskInstance=*/true);
+            mActivityView.setCallback(mCallback);
+            setContentView(mActivityView);
+
+            ViewGroup.LayoutParams layoutParams = mActivityView.getLayoutParams();
+            layoutParams.width = MATCH_PARENT;
+            layoutParams.height = MATCH_PARENT;
+            mActivityView.requestLayout();
+        }
+
+        @Override
+        protected void onStop() {
+            super.onStop();
+            // Moved the release of the view from onDestroy to onStop since onDestroy was called
+            // in non-deterministic timing.
+            mActivityView.release();
+        }
+
+        ActivityView getActivityView() {
+            return mActivityView;
+        }
+
+        void waitForActivityViewReady() throws Exception {
+            waitForLatch(mCallback.mActivityViewReadyLatch);
+        }
+
+        void waitForActivityViewDestroyed() throws Exception {
+            waitForLatch(mCallback.mActivityViewDestroyedLatch);
+        }
+    }
+
+    public static final class MultiProcessActivityViewTestActivity extends
+            ActivityViewTestActivity {
+    }
+
+    private ActivityViewTestActivity startActivityViewTestActivity(int displayId) throws Exception {
+        return (ActivityViewTestActivity) startTestActivity(ActivityViewTestActivity.class,
+                displayId);
+    }
+
+    // Activity that has {@link android.R.attr#resizeableActivity} attribute set to {@code true}
+    public static class ActivityInActivityView extends TestActivity {}
+
+    private ActivityInActivityView startActivityInActivityView(int displayId) throws Exception {
+        return (ActivityInActivityView) startTestActivity(ActivityInActivityView.class, displayId);
+    }
+}
diff --git a/tests/android_car_api_test/src/android/car/apitest/CarApiTestBase.java b/tests/android_car_api_test/src/android/car/apitest/CarApiTestBase.java
index dc167b1..594a1a8 100644
--- a/tests/android_car_api_test/src/android/car/apitest/CarApiTestBase.java
+++ b/tests/android_car_api_test/src/android/car/apitest/CarApiTestBase.java
@@ -16,13 +16,18 @@
 
 package android.car.apitest;
 
+import android.car.Car;
 import android.content.ComponentName;
 import android.content.ServiceConnection;
 import android.os.IBinder;
 import android.os.Looper;
-import android.car.Car;
 import android.test.AndroidTestCase;
 
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.junit.After;
+import org.junit.Before;
+
 import java.util.concurrent.Semaphore;
 import java.util.concurrent.TimeUnit;
 
@@ -38,16 +43,20 @@
         assertTrue(Looper.getMainLooper().isCurrentThread());
     }
 
+    @Before
     @Override
-    protected void setUp() throws Exception {
+    public void setUp() throws Exception {
         super.setUp();
-        mCar = Car.createCar(getContext(), mConnectionListener);
+        setContext(InstrumentationRegistry.getInstrumentation().getContext());
+        mCar = Car.createCar(
+            InstrumentationRegistry.getInstrumentation().getContext(), mConnectionListener);
         mCar.connect();
         mConnectionListener.waitForConnection(DEFAULT_WAIT_TIMEOUT_MS);
     }
 
+    @After
     @Override
-    protected void tearDown() throws Exception {
+    public void tearDown() throws Exception {
         super.tearDown();
         mCar.disconnect();
     }
diff --git a/tests/android_car_api_test/src/android/car/apitest/CarAppBlockingPolicyTest.java b/tests/android_car_api_test/src/android/car/apitest/CarAppBlockingPolicyTest.java
index c8690e0..b364387 100644
--- a/tests/android_car_api_test/src/android/car/apitest/CarAppBlockingPolicyTest.java
+++ b/tests/android_car_api_test/src/android/car/apitest/CarAppBlockingPolicyTest.java
@@ -22,15 +22,24 @@
 import android.test.suitebuilder.annotation.SmallTest;
 import android.util.Log;
 
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
 @SmallTest
 public class CarAppBlockingPolicyTest extends AndroidTestCase {
     private static final String TAG = AppBlockingPackageInfoTest.class.getSimpleName();
 
+    @Test
     public void testParcelling() throws Exception {
         AppBlockingPackageInfo carServiceInfo =
                 AppBlockingPackageInfoTest.createInfoCarService(getContext());
         AppBlockingPackageInfo selfInfo =
-                AppBlockingPackageInfoTest.createInfoSelf(getContext());
+                AppBlockingPackageInfoTest.createInfoSelf(
+                    InstrumentationRegistry.getInstrumentation().getContext());
         // this is only for testing parcelling. contents has nothing to do with actual app blocking.
         AppBlockingPackageInfo[] whitelists = new AppBlockingPackageInfo[] { carServiceInfo,
                 selfInfo };
diff --git a/tests/android_car_api_test/src/android/car/apitest/CarAppFocusManagerTest.java b/tests/android_car_api_test/src/android/car/apitest/CarAppFocusManagerTest.java
index f1a25a5..44b4062 100644
--- a/tests/android_car_api_test/src/android/car/apitest/CarAppFocusManagerTest.java
+++ b/tests/android_car_api_test/src/android/car/apitest/CarAppFocusManagerTest.java
@@ -26,11 +26,20 @@
 import android.test.suitebuilder.annotation.MediumTest;
 import android.util.Log;
 
+import androidx.test.filters.FlakyTest;
+import androidx.test.filters.RequiresDevice;
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
 import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
 
 import java.util.concurrent.Semaphore;
 import java.util.concurrent.TimeUnit;
 
+@RunWith(AndroidJUnit4.class)
 @MediumTest
 public class CarAppFocusManagerTest extends CarApiTestBase {
     private static final String TAG = CarAppFocusManagerTest.class.getSimpleName();
@@ -38,8 +47,9 @@
 
     private final LooperThread mEventThread = new LooperThread();
 
+    @Before
     @Override
-    protected void setUp() throws Exception {
+    public void setUp() throws Exception {
         super.setUp();
         mManager = (CarAppFocusManager) getCar().getCarManager(Car.APP_FOCUS_SERVICE);
         assertNotNull(mManager);
@@ -59,6 +69,7 @@
         mEventThread.waitForReadyState();
     }
 
+    @Test
     public void testSetActiveNullListener() throws Exception {
         try {
             mManager.requestAppFocus(APP_FOCUS_TYPE_NAVIGATION, null);
@@ -68,6 +79,7 @@
         }
     }
 
+    @Test
     public void testRegisterNull() throws Exception {
         try {
             mManager.addFocusListener(null, 0);
@@ -77,6 +89,7 @@
         }
     }
 
+    @Test
     public void testRegisterUnregister() throws Exception {
         FocusChangedListener listener = new FocusChangedListener();
         FocusChangedListener listener2 = new FocusChangedListener();
@@ -87,6 +100,7 @@
         mManager.removeFocusListener(listener2);  // Double-unregister is OK
     }
 
+    @Test
     public void testRegisterUnregisterSpecificApp() throws Exception {
         FocusChangedListener listener1 = new FocusChangedListener();
         FocusChangedListener listener2 = new FocusChangedListener();
@@ -116,6 +130,8 @@
         manager.removeFocusListener(listener2, 2);    // Double-unregister is OK
     }
 
+    @Test
+    @FlakyTest
     public void testFocusChange() throws Exception {
         CarAppFocusManager manager1 = createManager();
         CarAppFocusManager manager2 = createManager();
@@ -212,9 +228,13 @@
         manager2.removeFocusListener(change2);
     }
 
+    @RequiresDevice
+    @Test
     public void testFilter() throws Exception {
-        CarAppFocusManager manager1 = createManager(getContext(), mEventThread);
-        CarAppFocusManager manager2 = createManager(getContext(), mEventThread);
+        CarAppFocusManager manager1 = createManager(
+                InstrumentationRegistry.getInstrumentation().getContext(), mEventThread);
+        CarAppFocusManager manager2 = createManager(
+                InstrumentationRegistry.getInstrumentation().getContext(), mEventThread);
 
         Assert.assertArrayEquals(new int[0], manager1.getActiveAppTypes());
         Assert.assertArrayEquals(new int[0], manager2.getActiveAppTypes());
@@ -245,7 +265,8 @@
     }
 
     private CarAppFocusManager createManager() throws InterruptedException {
-        return createManager(getContext(), mEventThread);
+        return createManager(
+            InstrumentationRegistry.getInstrumentation().getContext(), mEventThread);
     }
 
     private static CarAppFocusManager createManager(Context context,
@@ -267,6 +288,8 @@
         return car;
     }
 
+    @RequiresDevice
+    @Test
     public void testMultipleChangeListenersPerManager() throws Exception {
         CarAppFocusManager manager = createManager();
         FocusChangedListener listener = new FocusChangedListener();
diff --git a/tests/android_car_api_test/src/android/car/apitest/CarCabinManagerTest.java b/tests/android_car_api_test/src/android/car/apitest/CarCabinManagerTest.java
index 2526ecf..43b4e62 100644
--- a/tests/android_car_api_test/src/android/car/apitest/CarCabinManagerTest.java
+++ b/tests/android_car_api_test/src/android/car/apitest/CarCabinManagerTest.java
@@ -21,24 +21,33 @@
 import android.test.suitebuilder.annotation.MediumTest;
 import android.util.Log;
 
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
 import java.util.Arrays;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
 
+@RunWith(AndroidJUnit4.class)
 @MediumTest
 public class CarCabinManagerTest extends CarApiTestBase {
     private static final String TAG = CarCabinManagerTest.class.getSimpleName();
 
     private CarCabinManager mCabinManager;
 
+    @Before
     @Override
-    protected void setUp() throws Exception {
+    public void setUp() throws Exception {
         super.setUp();
         mCabinManager = (CarCabinManager) getCar().getCarManager(Car.CABIN_SERVICE);
         assertNotNull(mCabinManager);
     }
 
+    @Test
     public void testAllCabinProperties() throws Exception {
         List<CarPropertyConfig> properties = mCabinManager.getPropertyList();
         Set<Class> supportedTypes = new HashSet<>(Arrays.asList(
diff --git a/tests/android_car_api_test/src/android/car/apitest/CarDiagnosticManagerTest.java b/tests/android_car_api_test/src/android/car/apitest/CarDiagnosticManagerTest.java
index 8783341..12030bb 100644
--- a/tests/android_car_api_test/src/android/car/apitest/CarDiagnosticManagerTest.java
+++ b/tests/android_car_api_test/src/android/car/apitest/CarDiagnosticManagerTest.java
@@ -16,6 +16,8 @@
 
 package android.car.apitest;
 
+import static org.junit.Assume.assumeTrue;
+
 import android.car.Car;
 import android.car.diagnostic.CarDiagnosticEvent;
 import android.car.diagnostic.CarDiagnosticManager;
@@ -25,9 +27,19 @@
 import android.os.Looper;
 import android.test.AndroidTestCase;
 import android.test.suitebuilder.annotation.MediumTest;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
 import java.util.concurrent.Semaphore;
 import java.util.concurrent.TimeUnit;
 
+@RunWith(AndroidJUnit4.class)
 @MediumTest
 public class CarDiagnosticManagerTest extends AndroidTestCase {
     private static final long DEFAULT_WAIT_TIMEOUT_MS = 5000;
@@ -60,18 +72,22 @@
         mConnectionWait.tryAcquire(timeoutMs, TimeUnit.MILLISECONDS);
     }
 
+    @Before
     @Override
-    protected void setUp() throws Exception {
+    public void setUp() throws Exception {
         super.setUp();
-        mCar = Car.createCar(getContext(), mConnectionListener);
+        mCar = Car.createCar(
+            InstrumentationRegistry.getInstrumentation().getContext(), mConnectionListener);
         mCar.connect();
         waitForConnection(DEFAULT_WAIT_TIMEOUT_MS);
+        assumeTrue(mCar.isFeatureEnabled(Car.DIAGNOSTIC_SERVICE));
         mCarDiagnosticManager = (CarDiagnosticManager) mCar.getCarManager(Car.DIAGNOSTIC_SERVICE);
         assertNotNull(mCarDiagnosticManager);
     }
 
+    @After
     @Override
-    protected void tearDown() throws Exception {
+    public void tearDown() throws Exception {
         super.tearDown();
         mCar.disconnect();
     }
@@ -81,6 +97,7 @@
      *
      * @throws Exception
      */
+    @Test
     public void testLiveFrame() throws Exception {
         CarDiagnosticEvent liveFrame = mCarDiagnosticManager.getLatestLiveFrame();
         if (null != liveFrame) {
@@ -94,6 +111,7 @@
      *
      * @throws Exception
      */
+    @Test
     public void testFreezeFrames() throws Exception {
         long[] timestamps = mCarDiagnosticManager.getFreezeFrameTimestamps();
         if (null != timestamps) {
diff --git a/tests/android_car_api_test/src/android/car/apitest/CarFeatureTest.java b/tests/android_car_api_test/src/android/car/apitest/CarFeatureTest.java
new file mode 100644
index 0000000..4516953
--- /dev/null
+++ b/tests/android_car_api_test/src/android/car/apitest/CarFeatureTest.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.car.apitest;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.car.Car;
+import android.car.CarFeatures;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Arrays;
+import java.util.List;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class CarFeatureTest extends CarApiTestBase  {
+    // List in CarFeatureController should be inline with this.
+    private static final List<String> MANDATORY_FEATURES = Arrays.asList(
+            Car.APP_FOCUS_SERVICE,
+            Car.AUDIO_SERVICE,
+            Car.BLUETOOTH_SERVICE,
+            Car.CAR_BUGREPORT_SERVICE,
+            Car.CAR_CONFIGURATION_SERVICE,
+            Car.CAR_DRIVING_STATE_SERVICE,
+            Car.CAR_MEDIA_SERVICE,
+            Car.CAR_NAVIGATION_SERVICE,
+            Car.CAR_OCCUPANT_ZONE_SERVICE,
+            Car.CAR_TRUST_AGENT_ENROLLMENT_SERVICE,
+            Car.CAR_USER_SERVICE,
+            Car.CAR_UX_RESTRICTION_SERVICE,
+            Car.INFO_SERVICE,
+            Car.PACKAGE_SERVICE,
+            Car.POWER_SERVICE,
+            Car.PROJECTION_SERVICE,
+            Car.PROPERTY_SERVICE,
+            Car.TEST_SERVICE,
+            // All items below here are deprecated, but still should be supported
+            Car.CAR_INSTRUMENT_CLUSTER_SERVICE,
+            Car.CABIN_SERVICE,
+            Car.HVAC_SERVICE,
+            Car.SENSOR_SERVICE,
+            Car.VENDOR_EXTENSION_SERVICE
+    );
+
+    private static final List<String> OPTIONAL_FEATURES = Arrays.asList(
+            CarFeatures.FEATURE_CAR_USER_NOTICE_SERVICE,
+            Car.DIAGNOSTIC_SERVICE,
+            Car.STORAGE_MONITORING_SERVICE,
+            Car.VEHICLE_MAP_SERVICE,
+            Car.VMS_SUBSCRIBER_SERVICE
+    );
+
+    private static final String NON_EXISTING_FEATURE = "ThisFeatureDoesNotExist";
+
+    @Test
+    public void checkMandatoryFeatures() {
+        Car car = getCar();
+        assertThat(car).isNotNull();
+        for (String feature : MANDATORY_FEATURES) {
+            assertThat(car.isFeatureEnabled(feature)).isTrue();
+        }
+    }
+
+    @Test
+    public void toggleOptionalFeature() {
+        Car car = getCar();
+        assertThat(car).isNotNull();
+        for (String feature : OPTIONAL_FEATURES) {
+            boolean enabled = getCar().isFeatureEnabled(feature);
+            toggleOptionalFeature(feature, !enabled, enabled);
+            toggleOptionalFeature(feature, enabled, enabled);
+        }
+    }
+
+    @Test
+    public void testGetAllEnabledFeatures() {
+        Car car = getCar();
+        assertThat(car).isNotNull();
+        List<String> allEnabledFeatures = car.getAllEnabledFeatures();
+        assertThat(allEnabledFeatures).isNotEmpty();
+        for (String feature : MANDATORY_FEATURES) {
+            assertThat(allEnabledFeatures).contains(feature);
+        }
+    }
+
+    @Test
+    public void testEnableDisableForMandatoryFeatures() {
+        for (String feature : MANDATORY_FEATURES) {
+            assertThat(getCar().enableFeature(feature)).isEqualTo(Car.FEATURE_REQUEST_MANDATORY);
+            assertThat(getCar().disableFeature(feature)).isEqualTo(Car.FEATURE_REQUEST_MANDATORY);
+        }
+    }
+
+    @Test
+    public void testEnableDisableForNonExistingFeature() {
+        assertThat(getCar().enableFeature(NON_EXISTING_FEATURE)).isEqualTo(
+                Car.FEATURE_REQUEST_NOT_EXISTING);
+        assertThat(getCar().disableFeature(NON_EXISTING_FEATURE)).isEqualTo(
+                Car.FEATURE_REQUEST_NOT_EXISTING);
+    }
+
+    private void toggleOptionalFeature(String feature, boolean enable, boolean originallyEnabled) {
+        if (enable) {
+            if (originallyEnabled) {
+                assertThat(getCar().enableFeature(feature)).isEqualTo(
+                        Car.FEATURE_REQUEST_ALREADY_IN_THE_STATE);
+            } else {
+                assertThat(getCar().enableFeature(feature)).isEqualTo(Car.FEATURE_REQUEST_SUCCESS);
+                assertThat(getCar().getAllPendingEnabledFeatures()).contains(feature);
+            }
+            assertThat(getCar().getAllPendingDisabledFeatures()).doesNotContain(feature);
+        } else {
+            if (originallyEnabled) {
+                assertThat(getCar().disableFeature(feature)).isEqualTo(Car.FEATURE_REQUEST_SUCCESS);
+                assertThat(getCar().getAllPendingDisabledFeatures()).contains(feature);
+            } else {
+                assertThat(getCar().disableFeature(feature)).isEqualTo(
+                        Car.FEATURE_REQUEST_ALREADY_IN_THE_STATE);
+            }
+            assertThat(getCar().getAllPendingEnabledFeatures()).doesNotContain(feature);
+        }
+    }
+}
diff --git a/tests/android_car_api_test/src/android/car/apitest/CarHvacManagerTest.java b/tests/android_car_api_test/src/android/car/apitest/CarHvacManagerTest.java
index 7a3b7b5..740684d 100644
--- a/tests/android_car_api_test/src/android/car/apitest/CarHvacManagerTest.java
+++ b/tests/android_car_api_test/src/android/car/apitest/CarHvacManagerTest.java
@@ -22,24 +22,33 @@
 import android.test.suitebuilder.annotation.MediumTest;
 import android.util.Log;
 
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
 import java.util.Arrays;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
 
+@RunWith(AndroidJUnit4.class)
 @MediumTest
 public class CarHvacManagerTest extends CarApiTestBase {
     private static final String TAG = CarHvacManagerTest.class.getSimpleName();
 
     private CarHvacManager mHvacManager;
 
+    @Before
     @Override
-    protected void setUp() throws Exception {
+    public void setUp() throws Exception {
         super.setUp();
         mHvacManager = (CarHvacManager) getCar().getCarManager(Car.HVAC_SERVICE);
         assertNotNull(mHvacManager);
     }
 
+    @Test
     public void testAllHvacProperties() throws Exception {
         List<CarPropertyConfig> properties = mHvacManager.getPropertyList();
         Set<Class> supportedTypes = new HashSet<>(Arrays.asList(
@@ -54,6 +63,7 @@
         }
     }
 
+    @Test
     public void testHvacPosition() {
         assertEquals(CarHvacManager.FAN_DIRECTION_FACE, VehicleHvacFanDirection.FACE);
         assertEquals(CarHvacManager.FAN_DIRECTION_FLOOR, VehicleHvacFanDirection.FLOOR);
diff --git a/tests/android_car_api_test/src/android/car/apitest/CarInfoManagerTest.java b/tests/android_car_api_test/src/android/car/apitest/CarInfoManagerTest.java
index eb2e9b3..94b9432 100644
--- a/tests/android_car_api_test/src/android/car/apitest/CarInfoManagerTest.java
+++ b/tests/android_car_api_test/src/android/car/apitest/CarInfoManagerTest.java
@@ -20,22 +20,32 @@
 import android.car.CarInfoManager;
 import android.test.suitebuilder.annotation.SmallTest;
 
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
 @SmallTest
 public class CarInfoManagerTest extends CarApiTestBase {
 
     private CarInfoManager mInfoManager;
 
+    @Before
     @Override
-    protected void setUp() throws Exception {
+    public void setUp() throws Exception {
         super.setUp();
         mInfoManager = (CarInfoManager) getCar().getCarManager(Car.INFO_SERVICE);
         assertNotNull(mInfoManager);
     }
 
+    @Test
     public void testVehicleId() throws Exception {
         assertNotNull(mInfoManager.getVehicleId());
     }
 
+    @Test
     public void testNotNullItems() throws Exception {
         // call and check if it throws exception.
         assertNotNull(mInfoManager.getManufacturer());
diff --git a/tests/android_car_api_test/src/android/car/apitest/CarNavigationManagerTest.java b/tests/android_car_api_test/src/android/car/apitest/CarNavigationManagerTest.java
index 193886e..eccfbad 100644
--- a/tests/android_car_api_test/src/android/car/apitest/CarNavigationManagerTest.java
+++ b/tests/android_car_api_test/src/android/car/apitest/CarNavigationManagerTest.java
@@ -36,8 +36,16 @@
 import android.test.suitebuilder.annotation.MediumTest;
 import android.util.Log;
 
+import androidx.test.filters.FlakyTest;
+import androidx.test.runner.AndroidJUnit4;
+
 import com.google.android.collect.Lists;
 
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
 /**
  * Unit tests for {@link CarNavigationStatusManager}
  */
@@ -49,8 +57,9 @@
     private CarNavigationStatusManager mCarNavigationManager;
     private CarAppFocusManager mCarAppFocusManager;
 
+    @Before
     @Override
-    protected void setUp() throws Exception {
+    public void setUp() throws Exception {
         super.setUp();
         mCarNavigationManager =
                 (CarNavigationStatusManager) getCar().getCarManager(Car.CAR_NAVIGATION_SERVICE);
@@ -59,6 +68,7 @@
         assertNotNull(mCarAppFocusManager);
     }
 
+    @Test
     public void testSerializeAndDeserializeProto() throws Exception {
         ImageReference imageReference = ImageReference.newBuilder().build();
         Distance distance = Distance.newBuilder().build();
@@ -103,6 +113,8 @@
         assertNotNull(NavigationStateProto.parseFrom(navigationStateProto.toByteArray()));
     }
 
+    @Test
+    @FlakyTest
     public void testSendEvent() throws Exception {
         if (mCarNavigationManager == null) {
             Log.w(TAG, "Unable to run the test: "
diff --git a/tests/android_car_api_test/src/android/car/apitest/CarPackageManagerTest.java b/tests/android_car_api_test/src/android/car/apitest/CarPackageManagerTest.java
index 622a2af..959f3db 100644
--- a/tests/android_car_api_test/src/android/car/apitest/CarPackageManagerTest.java
+++ b/tests/android_car_api_test/src/android/car/apitest/CarPackageManagerTest.java
@@ -16,18 +16,27 @@
 
 package android.car.apitest;
 
+import android.car.Car;
+import android.car.content.pm.CarPackageManager;
 import android.content.ComponentName;
 import android.content.ServiceConnection;
 import android.os.IBinder;
 import android.os.Looper;
-import android.car.Car;
-import android.car.content.pm.CarPackageManager;
 import android.test.AndroidTestCase;
 import android.test.suitebuilder.annotation.MediumTest;
 
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
 import java.util.concurrent.Semaphore;
 import java.util.concurrent.TimeUnit;
 
+@RunWith(AndroidJUnit4.class)
 @MediumTest
 public class CarPackageManagerTest extends AndroidTestCase {
     private static final long DEFAULT_WAIT_TIMEOUT_MS = 3000;
@@ -59,22 +68,26 @@
         mConnectionWait.tryAcquire(timeoutMs, TimeUnit.MILLISECONDS);
     }
 
+    @Before
     @Override
-    protected void setUp() throws Exception {
+    public void setUp() throws Exception {
         super.setUp();
-        mCar = Car.createCar(getContext(), mConnectionListener);
+        mCar = Car.createCar(
+            InstrumentationRegistry.getInstrumentation().getContext(), mConnectionListener);
         mCar.connect();
         waitForConnection(DEFAULT_WAIT_TIMEOUT_MS);
         mCarPackageManager = (CarPackageManager) mCar.getCarManager(Car.PACKAGE_SERVICE);
         assertNotNull(mCarPackageManager);
     }
 
+    @After
     @Override
-    protected void tearDown() throws Exception {
+    public void tearDown() throws Exception {
         super.tearDown();
         mCar.disconnect();
     }
 
+    @Test
     public void testCreate() throws Exception {
         //nothing to do for now
     }
diff --git a/tests/android_car_api_test/src/android/car/apitest/CarProjectionManagerTest.java b/tests/android_car_api_test/src/android/car/apitest/CarProjectionManagerTest.java
index db04c24..d0a061d 100644
--- a/tests/android_car_api_test/src/android/car/apitest/CarProjectionManagerTest.java
+++ b/tests/android_car_api_test/src/android/car/apitest/CarProjectionManagerTest.java
@@ -20,17 +20,24 @@
 import android.car.CarProjectionManager;
 import android.car.CarProjectionManager.ProjectionAccessPointCallback;
 import android.content.Intent;
-import android.net.wifi.WifiConfiguration;
+import android.net.wifi.SoftApConfiguration;
 import android.os.Binder;
 import android.os.IBinder;
 import android.test.suitebuilder.annotation.LargeTest;
 import android.test.suitebuilder.annotation.Suppress;
 
 import androidx.test.filters.RequiresDevice;
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
 
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 
+@RunWith(AndroidJUnit4.class)
 @LargeTest
 public class CarProjectionManagerTest extends CarApiTestBase {
     private static final String TAG = CarProjectionManagerTest.class.getSimpleName();
@@ -68,19 +75,22 @@
         }
     }
 
+    @Before
     @Override
-    protected void setUp() throws Exception {
+    public void setUp() throws Exception {
         super.setUp();
         mManager = (CarProjectionManager) getCar().getCarManager(Car.PROJECTION_SERVICE);
         assertNotNull(mManager);
     }
 
+    @Test
     public void testSetUnsetListeners() throws Exception {
         mManager.registerProjectionListener(
                 mListener, CarProjectionManager.PROJECTION_VOICE_SEARCH);
         mManager.unregisterProjectionListener();
     }
 
+    @Test
     public void testRegisterListenersHandleBadInput() throws Exception {
         try {
             mManager.registerProjectionListener(null, CarProjectionManager.PROJECTION_VOICE_SEARCH);
@@ -91,7 +101,8 @@
     }
 
     public void testRegisterProjectionRunner() throws Exception {
-        Intent intent = new Intent(getContext(), TestService.class);
+        Intent intent = new Intent(
+                InstrumentationRegistry.getInstrumentation().getContext(), TestService.class);
         assertFalse(TestService.getBound());
         mManager.registerProjectionRunner(intent);
         synchronized (TestService.mLock) {
@@ -113,7 +124,7 @@
 
         mManager.startProjectionAccessPoint(new ProjectionAccessPointCallback() {
             @Override
-            public void onStarted(WifiConfiguration wifiConfiguration) {
+            public void onStarted(SoftApConfiguration softApConfiguration) {
                 startedLatch.countDown();
             }
         });
diff --git a/tests/android_car_api_test/src/android/car/apitest/CarPropertyConfigTest.java b/tests/android_car_api_test/src/android/car/apitest/CarPropertyConfigTest.java
index b869191..5de7504 100644
--- a/tests/android_car_api_test/src/android/car/apitest/CarPropertyConfigTest.java
+++ b/tests/android_car_api_test/src/android/car/apitest/CarPropertyConfigTest.java
@@ -16,15 +16,29 @@
 
 package android.car.apitest;
 
+import android.car.VehicleAreaType;
 import android.car.hardware.CarPropertyConfig;
 import android.test.suitebuilder.annotation.MediumTest;
 
+import androidx.test.runner.AndroidJUnit4;
+
+import static org.junit.Assert.assertArrayEquals;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
 /**
  * Unit tests for {@link CarPropertyConfig}
  */
+@RunWith(AndroidJUnit4.class)
 @MediumTest
 public class CarPropertyConfigTest extends CarPropertyTestBase {
 
+    @Test
     public void testCarPropertyConfigBuilder() {
         createFloatPropertyConfig();
     }
@@ -51,6 +65,7 @@
         return config;
     }
 
+    @Test
     public void testWriteReadFloat() {
         CarPropertyConfig<Float> config = createFloatPropertyConfig();
 
@@ -70,6 +85,7 @@
         assertEquals(20f, configRead.getMaxValue(WINDOW_PASSENGER));
     }
 
+    @Test
     public void testWriteReadIntegerValue() {
         Integer expectedMinValue = 1;
         Integer expectedMaxValue = 20;
@@ -93,6 +109,7 @@
         assertEquals(expectedMaxValue, configRead.getMaxValue(WINDOW_PASSENGER));
     }
 
+    @Test
     public void testWriteReadLongValue() {
         Long expectedMinValue = 0L;
         Long expectedMaxValue = 100L;
@@ -116,6 +133,7 @@
         assertEquals(expectedMaxValue, configRead.getMaxValue(WINDOW_PASSENGER));
     }
 
+    @Test
     public void testWriteReadIntegerArray() {
         CarPropertyConfig<Integer[]> config = CarPropertyConfig
                 .newBuilder(Integer[].class, INT_ARRAY_PROPERTY_ID, CAR_AREA_TYPE)
@@ -139,6 +157,7 @@
         assertNull(configRead.getMaxValue(WINDOW_DRIVER));
     }
 
+    @Test
     public void testWriteReadUnexpectedType() {
         CarPropertyConfig<Float> config = createFloatPropertyConfig();
 
@@ -163,4 +182,26 @@
             // Expected. Wrote float, attempted to read integer.
         }
     }
+
+    @Test
+    public void testWriteReadMixedType() {
+        List<Integer> configArray = Arrays.asList(1, 0, 1, 0, 1, 0, 0, 0, 0);
+
+        CarPropertyConfig<Object> mixedTypePropertyConfig = CarPropertyConfig
+                .newBuilder(Object.class, MIXED_TYPE_PROPERTY_ID,
+                        VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL)
+                .addArea(VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL)
+                .setConfigArray(new ArrayList<Integer>(configArray))
+                .build();
+        writeToParcel(mixedTypePropertyConfig);
+
+        CarPropertyConfig<Object> configRead = readFromParcel();
+
+        assertEquals(MIXED_TYPE_PROPERTY_ID, configRead.getPropertyId());
+        assertEquals(VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL, configRead.getAreaType());
+        assertEquals(Object.class, configRead.getPropertyType());
+        assertEquals(1, configRead.getAreaCount());
+        assertArrayEquals(configArray.toArray(), configRead.getConfigArray().toArray());
+
+    }
 }
diff --git a/tests/android_car_api_test/src/android/car/apitest/CarPropertyTestBase.java b/tests/android_car_api_test/src/android/car/apitest/CarPropertyTestBase.java
index 266a386..c8020ea 100644
--- a/tests/android_car_api_test/src/android/car/apitest/CarPropertyTestBase.java
+++ b/tests/android_car_api_test/src/android/car/apitest/CarPropertyTestBase.java
@@ -22,6 +22,9 @@
 import android.os.Parcelable;
 import android.test.AndroidTestCase;
 
+import org.junit.After;
+import org.junit.Before;
+
 /**
  * Base class to test {@link CarPropertyConfig} and {@link CarPropertyValue}.
  */
@@ -29,8 +32,9 @@
 
     protected static final int FLOAT_PROPERTY_ID        = 0x1160BEEF;
     protected static final int INT_ARRAY_PROPERTY_ID    = 0x0041BEEF;
-    protected static final int INT_PROPERTY_ID          = 0x0040BEFF;
-    protected static final int LONG_PROPERTY_ID         = 0x0050BEFF;
+    protected static final int INT_PROPERTY_ID          = 0x0040BEEF;
+    protected static final int LONG_PROPERTY_ID         = 0x0050BEEF;
+    protected static final int MIXED_TYPE_PROPERTY_ID   = 0x01e0BEEF;
 
     protected static final int CAR_AREA_TYPE    = 0xDEADBEEF;
     protected static final int WINDOW_DRIVER    = 0x00000001;
@@ -38,14 +42,16 @@
 
     private Parcel mParcel;
 
+    @Before
     @Override
-    protected void setUp() throws Exception {
+    public void setUp() throws Exception {
         super.setUp();
         mParcel = Parcel.obtain();
     }
 
+    @After
     @Override
-    protected void tearDown() throws Exception {
+    public void tearDown() throws Exception {
         mParcel.recycle();
         super.tearDown();
     }
diff --git a/tests/android_car_api_test/src/android/car/apitest/CarPropertyValueTest.java b/tests/android_car_api_test/src/android/car/apitest/CarPropertyValueTest.java
index 5dd41de..30f6685 100644
--- a/tests/android_car_api_test/src/android/car/apitest/CarPropertyValueTest.java
+++ b/tests/android_car_api_test/src/android/car/apitest/CarPropertyValueTest.java
@@ -16,15 +16,26 @@
 
 package android.car.apitest;
 
+import android.car.VehicleAreaType;
 import android.car.hardware.CarPropertyValue;
+
+import static org.junit.Assert.assertArrayEquals;
+
 import android.test.suitebuilder.annotation.MediumTest;
 
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
 /**
  * Unit tests for {@link CarPropertyValue}
  */
+@RunWith(AndroidJUnit4.class)
 @MediumTest
-public class CarPropertyValueTest extends CarPropertyConfigTest {
+public class CarPropertyValueTest extends CarPropertyTestBase {
 
+    @Test
     public void testSimpleFloatValue() {
         CarPropertyValue<Float> floatValue =
                 new CarPropertyValue<>(FLOAT_PROPERTY_ID, WINDOW_DRIVER, 10f);
@@ -35,4 +46,17 @@
         assertEquals(10f, valueRead.getValue());
     }
 
+    @Test
+    public void testMixedValue() {
+        Object[] values = {"android", 1, 2.0};
+        CarPropertyValue<Object> mixedValue =
+                new CarPropertyValue<>(MIXED_TYPE_PROPERTY_ID,
+                        VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL,
+                        values);
+        writeToParcel(mixedValue);
+        CarPropertyValue<Object[]> valueRead = readFromParcel();
+        assertArrayEquals(values, valueRead.getValue());
+        assertEquals(MIXED_TYPE_PROPERTY_ID, valueRead.getPropertyId());
+        assertEquals(VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL, valueRead.getAreaId());
+    }
 }
diff --git a/tests/android_car_api_test/src/android/car/apitest/CarSensorManagerTest.java b/tests/android_car_api_test/src/android/car/apitest/CarSensorManagerTest.java
index f19f33d..0d3a93c 100644
--- a/tests/android_car_api_test/src/android/car/apitest/CarSensorManagerTest.java
+++ b/tests/android_car_api_test/src/android/car/apitest/CarSensorManagerTest.java
@@ -25,6 +25,9 @@
 import android.test.AndroidTestCase;
 import android.test.suitebuilder.annotation.MediumTest;
 
+import org.junit.After;
+import org.junit.Before;
+
 import java.util.concurrent.Semaphore;
 import java.util.concurrent.TimeUnit;
 
@@ -58,6 +61,7 @@
         mConnectionWait.tryAcquire(timeoutMs, TimeUnit.MILLISECONDS);
     }
 
+    @Before
     @Override
     protected void setUp() throws Exception {
         super.setUp();
@@ -68,6 +72,7 @@
                 (CarSensorManager) mCar.getCarManager(Car.SENSOR_SERVICE);
     }
 
+    @After
     @Override
     protected void tearDown() throws Exception {
         super.tearDown();
diff --git a/tests/android_car_api_test/src/android/car/apitest/CarTest.java b/tests/android_car_api_test/src/android/car/apitest/CarTest.java
index 38e566a..d483b72 100644
--- a/tests/android_car_api_test/src/android/car/apitest/CarTest.java
+++ b/tests/android_car_api_test/src/android/car/apitest/CarTest.java
@@ -26,9 +26,16 @@
 import android.test.AndroidTestCase;
 import android.test.suitebuilder.annotation.SmallTest;
 
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
 import java.util.concurrent.Semaphore;
 import java.util.concurrent.TimeUnit;
 
+@RunWith(AndroidJUnit4.class)
 @SmallTest
 public class CarTest extends AndroidTestCase {
     private static final long DEFAULT_WAIT_TIMEOUT_MS = 3000;
@@ -60,8 +67,10 @@
         mConnectionWait.tryAcquire(timeoutMs, TimeUnit.MILLISECONDS);
     }
 
+    @Test
     public void testCarConnection() throws Exception {
-        Car car = Car.createCar(getContext(), mConnectionListener);
+        Car car = Car.createCar(
+                InstrumentationRegistry.getInstrumentation().getContext(), mConnectionListener);
         assertFalse(car.isConnected());
         assertFalse(car.isConnecting());
         car.connect();
@@ -85,8 +94,10 @@
         assertFalse(car.isConnecting());
     }
 
+    @Test
     public void testDoubleConnect() throws Exception {
-        Car car = Car.createCar(getContext(), mConnectionListener);
+        Car car = Car.createCar(
+                InstrumentationRegistry.getInstrumentation().getContext(), mConnectionListener);
         assertFalse(car.isConnected());
         assertFalse(car.isConnecting());
         car.connect();
@@ -99,12 +110,14 @@
         car.disconnect();
     }
 
+    @Test
     public void testConstructorWithICar() throws Exception {
-        Car car = Car.createCar(getContext(), mConnectionListener);
+        Car car = Car.createCar(
+                InstrumentationRegistry.getInstrumentation().getContext(), mConnectionListener);
         car.connect();
         waitForConnection(DEFAULT_WAIT_TIMEOUT_MS);
         assertNotNull(mICar);
-        Car car2 = new Car(getContext(), mICar, null);
+        Car car2 = new Car(InstrumentationRegistry.getInstrumentation().getContext(), mICar, null);
         assertTrue(car2.isConnected());
     }
 }
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 030b133..6ca16b9 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
@@ -35,10 +35,12 @@
 import android.util.JsonWriter;
 
 import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
 
 import junit.framework.TestCase;
 
 import org.junit.Test;
+import org.junit.runner.RunWith;
 
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
@@ -50,6 +52,7 @@
 /**
  * Unit test for UXR config and its subclasses.
  */
+@RunWith(AndroidJUnit4.class)
 @SmallTest
 public class CarUxRestrictionsConfigurationTest extends TestCase {
 
@@ -91,6 +94,7 @@
                 .build();
     }
 
+    @Test
     public void testUnspecifiedDrivingStateUsesDefaultRestriction() {
         CarUxRestrictionsConfiguration config = new Builder().build();
 
@@ -103,6 +107,7 @@
         assertEquals(movingRestrictions.getActiveRestrictions(), UX_RESTRICTIONS_FULLY_RESTRICTED);
     }
 
+    @Test
     public void testBuilderValidation_UnspecifiedStateUsesRestrictiveDefault() {
         CarUxRestrictionsConfiguration config = new Builder()
                 .setUxRestrictions(DRIVING_STATE_MOVING, true, UX_RESTRICTIONS_FULLY_RESTRICTED)
@@ -113,6 +118,7 @@
                 .isRequiresDistractionOptimization());
     }
 
+    @Test
     public void testBuilderValidation_NonMovingStateHasOneRestriction() {
         Builder builder = new Builder();
         builder.setUxRestrictions(DRIVING_STATE_IDLING,
@@ -127,6 +133,7 @@
         }
     }
 
+    @Test
     public void testBuilderValidation_PassengerModeNoSpeedRangeOverlap() {
         Builder builder = new Builder();
         builder.setUxRestrictions(DRIVING_STATE_MOVING, new DrivingStateRestrictions()
@@ -145,6 +152,7 @@
         }
     }
 
+    @Test
     public void testBuilderValidation_PassengerModeCanSpecifySubsetOfSpeedRange() {
         CarUxRestrictionsConfiguration config = new Builder()
                 .setUxRestrictions(DRIVING_STATE_MOVING, new DrivingStateRestrictions()
@@ -158,6 +166,7 @@
                 .isRequiresDistractionOptimization());
     }
 
+    @Test
     public void testBuilderValidation_MultipleSpeedRange_NonZeroStart() {
         Builder builder = new Builder();
         builder.setUxRestrictions(DRIVING_STATE_MOVING,
@@ -174,6 +183,7 @@
         }
     }
 
+    @Test
     public void testBuilderValidation_SpeedRange_NonZeroStart() {
         Builder builder = new Builder();
         builder.setUxRestrictions(DRIVING_STATE_MOVING,
@@ -187,6 +197,7 @@
         }
     }
 
+    @Test
     public void testBuilderValidation_SpeedRange_Overlap() {
         Builder builder = new Builder();
         builder.setUxRestrictions(DRIVING_STATE_MOVING,
@@ -203,6 +214,7 @@
         }
     }
 
+    @Test
     public void testBuilderValidation_SpeedRange_Gap() {
         Builder builder = new Builder();
         builder.setUxRestrictions(DRIVING_STATE_MOVING,
@@ -219,6 +231,7 @@
         }
     }
 
+    @Test
     public void testBuilderValidation_NonMovingStateCannotUseSpeedRange() {
         Builder builder = new Builder();
         try {
@@ -231,6 +244,7 @@
         }
     }
 
+    @Test
     public void testBuilderValidation_MultipleMovingRestrictionsShouldAllContainSpeedRange() {
         Builder builder = new Builder();
         builder.setUxRestrictions(DRIVING_STATE_MOVING, new DrivingStateRestrictions()
@@ -248,12 +262,14 @@
         }
     }
 
+    @Test
     public void testSpeedRange_Construction() {
         new Builder.SpeedRange(0f);
         new Builder.SpeedRange(0f, 1f);
         new Builder.SpeedRange(0f, MAX_SPEED);
     }
 
+    @Test
     public void testSpeedRange_NoNegativeMin() {
         try {
             new Builder.SpeedRange(-2f, 1f);
@@ -262,6 +278,7 @@
         }
     }
 
+    @Test
     public void testSpeedRange_NoNegativeMax() {
         try {
             new Builder.SpeedRange(2f, -1f);
@@ -270,6 +287,7 @@
         }
     }
 
+    @Test
     public void testSpeedRange_MinCannotBeMaxSpeed() {
         try {
             new Builder.SpeedRange(MAX_SPEED, 1f);
@@ -278,6 +296,7 @@
         }
     }
 
+    @Test
     public void testSpeedRange_MinGreaterThanMax() {
         try {
             new Builder.SpeedRange(5f, 2f);
@@ -286,6 +305,7 @@
         }
     }
 
+    @Test
     public void testSpeedRangeComparison_DifferentMin() {
         Builder.SpeedRange s1 =
                 new Builder.SpeedRange(1f);
@@ -295,6 +315,7 @@
         assertTrue(s2.compareTo(s1) > 0);
     }
 
+    @Test
     public void testSpeedRangeComparison_SameMin() {
         Builder.SpeedRange s1 =
                 new Builder.SpeedRange(1f);
@@ -303,6 +324,7 @@
         assertEquals(0, s1.compareTo(s2));
     }
 
+    @Test
     public void testSpeedRangeComparison_SameMinDifferentMax() {
         Builder.SpeedRange s1 =
                 new Builder.SpeedRange(0f, 1f);
@@ -312,6 +334,7 @@
         assertTrue(s2.compareTo(s1) > 0);
     }
 
+    @Test
     public void testSpeedRangeComparison_MaxSpeed() {
         Builder.SpeedRange s1 =
                 new Builder.SpeedRange(0f, 1f);
@@ -321,6 +344,7 @@
         assertTrue(s2.compareTo(s1) > 0);
     }
 
+    @Test
     public void testSpeedRangeEquals() {
         Builder.SpeedRange s1, s2;
 
@@ -349,6 +373,7 @@
         assertFalse(s1.equals(s2));
     }
 
+    @Test
     public void testJsonSerialization_DefaultConstructor() {
         CarUxRestrictionsConfiguration config =
                 new Builder().build();
@@ -356,6 +381,7 @@
         verifyConfigThroughJsonSerialization(config, /* schemaVersion= */ 2);
     }
 
+    @Test
     public void testJsonSerialization_RestrictionParameters() {
         CarUxRestrictionsConfiguration config = new Builder()
                 .setMaxStringLength(1)
@@ -366,6 +392,7 @@
         verifyConfigThroughJsonSerialization(config, /* schemaVersion= */ 2);
     }
 
+    @Test
     public void testJsonSerialization_NonMovingStateRestrictions() {
         CarUxRestrictionsConfiguration config = new Builder()
                 .setUxRestrictions(DRIVING_STATE_PARKED, false, UX_RESTRICTIONS_BASELINE)
@@ -374,6 +401,7 @@
         verifyConfigThroughJsonSerialization(config, /* schemaVersion= */ 2);
     }
 
+    @Test
     public void testJsonSerialization_MovingStateNoSpeedRange() {
         CarUxRestrictionsConfiguration config = new Builder()
                 .setUxRestrictions(DRIVING_STATE_MOVING, true, UX_RESTRICTIONS_FULLY_RESTRICTED)
@@ -382,6 +410,7 @@
         verifyConfigThroughJsonSerialization(config, /* schemaVersion= */ 2);
     }
 
+    @Test
     public void testJsonSerialization_MovingStateWithSpeedRange() {
         CarUxRestrictionsConfiguration config = new Builder()
                 .setUxRestrictions(DRIVING_STATE_MOVING, new DrivingStateRestrictions()
@@ -397,6 +426,7 @@
         verifyConfigThroughJsonSerialization(config, /* schemaVersion= */ 2);
     }
 
+    @Test
     public void testJsonSerialization_UxRestrictionMode() {
         CarUxRestrictionsConfiguration config = new Builder()
                 // Passenger mode
@@ -475,17 +505,14 @@
     }
 
 
-    @Test
+    @Test(expected = IllegalArgumentException.class)
     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
-        }
+        CarUxRestrictionsConfiguration deserialized = CarUxRestrictionsConfiguration.readJson(
+                new JsonReader(new StringReader("")), unsupportedVersion);
     }
 
+    @Test
     public void testDump() {
         CarUxRestrictionsConfiguration[] configs = new CarUxRestrictionsConfiguration[]{
                 // Driving state with no speed range
@@ -536,6 +563,7 @@
         }
     }
 
+    @Test
     public void testDumpContainsNecessaryInfo() {
         CarUxRestrictionsConfiguration config = new Builder()
                 .setUxRestrictions(DRIVING_STATE_MOVING, new DrivingStateRestrictions()
@@ -568,6 +596,7 @@
         assertTrue(dump.contains("baseline mode"));
     }
 
+    @Test
     public void testSetUxRestrictions_UnspecifiedModeDefaultsToBaseline() {
         CarUxRestrictionsConfiguration config = new Builder()
                 .setUxRestrictions(DRIVING_STATE_PARKED, new DrivingStateRestrictions()
@@ -583,6 +612,7 @@
                 config.getUxRestrictions(DRIVING_STATE_PARKED, 0f, UX_RESTRICTION_MODE_BASELINE)));
     }
 
+    @Test
     public void testSetUxRestrictions_PassengerMode() {
         CarUxRestrictionsConfiguration config = new Builder()
                 .setUxRestrictions(DRIVING_STATE_PARKED, new DrivingStateRestrictions()
@@ -662,6 +692,7 @@
         assertEquals(UX_RESTRICTIONS_NO_VIDEO, baseline.getActiveRestrictions());
     }
 
+    @Test
     public void testHasSameParameters_SameParameters() {
         CarUxRestrictionsConfiguration one = new CarUxRestrictionsConfiguration.Builder()
                 .setMaxStringLength(1)
@@ -678,6 +709,7 @@
         assertTrue(one.hasSameParameters(other));
     }
 
+    @Test
     public void testHasSameParameters_DifferentParameters() {
         CarUxRestrictionsConfiguration one = new CarUxRestrictionsConfiguration.Builder()
                 .setMaxStringLength(2)
@@ -694,6 +726,7 @@
         assertFalse(one.hasSameParameters(other));
     }
 
+    @Test
     public void testConfigurationEquals() {
         CarUxRestrictionsConfiguration one = new CarUxRestrictionsConfiguration.Builder()
                 .setMaxStringLength(2)
@@ -717,6 +750,7 @@
         assertEquals(one.hashCode(), other.hashCode());
     }
 
+    @Test
     public void testConfigurationEquals_DifferentRestrictions() {
         CarUxRestrictionsConfiguration one = new CarUxRestrictionsConfiguration.Builder()
                 .setMaxStringLength(2)
@@ -740,6 +774,7 @@
         assertFalse(one.equals(other));
     }
 
+    @Test
     public void testParcelableConfiguration() {
         CarUxRestrictionsConfiguration config = new CarUxRestrictionsConfiguration.Builder()
                 .setPhysicalPort((byte) 1)
@@ -769,6 +804,7 @@
         assertEquals(deserialized, config);
     }
 
+    @Test
     public void testParcelableConfiguration_serializeNullPhysicalPort() {
         // Not setting physical port leaves it null.
         CarUxRestrictionsConfiguration config = new CarUxRestrictionsConfiguration.Builder()
diff --git a/tests/android_car_api_test/src/android/car/apitest/FuelTypeTest.java b/tests/android_car_api_test/src/android/car/apitest/FuelTypeTest.java
new file mode 100644
index 0000000..179c1da
--- /dev/null
+++ b/tests/android_car_api_test/src/android/car/apitest/FuelTypeTest.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.car.apitest;
+
+import android.car.FuelType;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public final class FuelTypeTest extends AndroidTestCase {
+    @Test
+    public void testMatchWithVehicleHal() {
+        assertEquals(android.hardware.automotive.vehicle.V2_0.FuelType.FUEL_TYPE_UNKNOWN,
+                FuelType.UNKNOWN);
+
+        assertEquals(android.hardware.automotive.vehicle.V2_0.FuelType.FUEL_TYPE_UNLEADED,
+                FuelType.UNLEADED);
+
+        assertEquals(android.hardware.automotive.vehicle.V2_0.FuelType.FUEL_TYPE_LEADED,
+                FuelType.LEADED);
+
+        assertEquals(android.hardware.automotive.vehicle.V2_0.FuelType.FUEL_TYPE_DIESEL_1,
+                FuelType.DIESEL_1);
+
+        assertEquals(android.hardware.automotive.vehicle.V2_0.FuelType.FUEL_TYPE_DIESEL_2,
+                FuelType.DIESEL_2);
+
+        assertEquals(android.hardware.automotive.vehicle.V2_0.FuelType.FUEL_TYPE_BIODIESEL,
+                FuelType.BIODIESEL);
+
+        assertEquals(android.hardware.automotive.vehicle.V2_0.FuelType.FUEL_TYPE_E85,
+                FuelType.E85);
+
+        assertEquals(android.hardware.automotive.vehicle.V2_0.FuelType.FUEL_TYPE_LPG,
+                FuelType.LPG);
+
+        assertEquals(android.hardware.automotive.vehicle.V2_0.FuelType.FUEL_TYPE_CNG,
+                FuelType.CNG);
+
+        assertEquals(android.hardware.automotive.vehicle.V2_0.FuelType.FUEL_TYPE_LNG,
+                FuelType.LNG);
+
+        assertEquals(android.hardware.automotive.vehicle.V2_0.FuelType.FUEL_TYPE_ELECTRIC,
+                FuelType.ELECTRIC);
+
+        assertEquals(android.hardware.automotive.vehicle.V2_0.FuelType.FUEL_TYPE_HYDROGEN,
+                FuelType.HYDROGEN);
+
+        assertEquals(android.hardware.automotive.vehicle.V2_0.FuelType.FUEL_TYPE_OTHER,
+                FuelType.OTHER);
+    }
+}
diff --git a/tests/android_car_api_test/src/android/car/apitest/PortLocationTypeTest.java b/tests/android_car_api_test/src/android/car/apitest/PortLocationTypeTest.java
new file mode 100644
index 0000000..a39cae2
--- /dev/null
+++ b/tests/android_car_api_test/src/android/car/apitest/PortLocationTypeTest.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.car.apitest;
+
+import android.car.PortLocationType;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public final class PortLocationTypeTest extends AndroidTestCase {
+    @Test
+    public void testMatchWithVehicleHal() {
+        assertEquals(android.hardware.automotive.vehicle.V2_0.PortLocationType.UNKNOWN,
+                PortLocationType.UNKNOWN);
+
+        assertEquals(android.hardware.automotive.vehicle.V2_0.PortLocationType.FRONT_LEFT,
+                PortLocationType.FRONT_LEFT);
+
+        assertEquals(android.hardware.automotive.vehicle.V2_0.PortLocationType.FRONT_RIGHT,
+                PortLocationType.FRONT_RIGHT);
+
+        assertEquals(android.hardware.automotive.vehicle.V2_0.PortLocationType.REAR_RIGHT,
+                PortLocationType.REAR_RIGHT);
+
+        assertEquals(android.hardware.automotive.vehicle.V2_0.PortLocationType.REAR_LEFT,
+                PortLocationType.REAR_LEFT);
+
+        assertEquals(android.hardware.automotive.vehicle.V2_0.PortLocationType.FRONT,
+                PortLocationType.FRONT);
+
+        assertEquals(android.hardware.automotive.vehicle.V2_0.PortLocationType.REAR,
+                PortLocationType.REAR);
+
+    }
+}
diff --git a/tests/android_car_api_test/src/android/car/apitest/VehicleAreaMirrorTest.java b/tests/android_car_api_test/src/android/car/apitest/VehicleAreaMirrorTest.java
index 7eb7eb8..ff50809 100644
--- a/tests/android_car_api_test/src/android/car/apitest/VehicleAreaMirrorTest.java
+++ b/tests/android_car_api_test/src/android/car/apitest/VehicleAreaMirrorTest.java
@@ -19,9 +19,16 @@
 import android.test.AndroidTestCase;
 import android.test.suitebuilder.annotation.SmallTest;
 
-@SmallTest
-public class VehicleAreaMirrorTest extends AndroidTestCase{
+import androidx.test.runner.AndroidJUnit4;
 
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class VehicleAreaMirrorTest extends AndroidTestCase {
+
+    @Test
     public void testMatchWithVehicleHal() {
         assertEquals(android.hardware.automotive.vehicle.V2_0.VehicleAreaMirror.DRIVER_CENTER,
                 VehicleAreaMirror.MIRROR_DRIVER_CENTER);
diff --git a/tests/android_car_api_test/src/android/car/apitest/VehicleDoorTest.java b/tests/android_car_api_test/src/android/car/apitest/VehicleDoorTest.java
index d1b35c0..1d00de7 100644
--- a/tests/android_car_api_test/src/android/car/apitest/VehicleDoorTest.java
+++ b/tests/android_car_api_test/src/android/car/apitest/VehicleDoorTest.java
@@ -19,9 +19,16 @@
 import android.test.AndroidTestCase;
 import android.test.suitebuilder.annotation.SmallTest;
 
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
 @SmallTest
 public class VehicleDoorTest extends AndroidTestCase {
 
+    @Test
     public void testMatchWithVehicleHal() {
         assertEquals(android.hardware.automotive.vehicle.V2_0.VehicleAreaDoor.HOOD,
                 VehicleAreaDoor.DOOR_HOOD);
diff --git a/tests/android_car_api_test/src/android/car/apitest/VehicleGearTest.java b/tests/android_car_api_test/src/android/car/apitest/VehicleGearTest.java
new file mode 100644
index 0000000..ca60fc4
--- /dev/null
+++ b/tests/android_car_api_test/src/android/car/apitest/VehicleGearTest.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.car.apitest;
+
+import android.car.VehicleGear;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class VehicleGearTest extends AndroidTestCase {
+
+    @Test
+    public void testMatchWithVehicleHal() {
+        assertEquals(android.hardware.automotive.vehicle.V2_0.VehicleGear.GEAR_UNKNOWN,
+                VehicleGear.GEAR_UNKNOWN);
+
+        assertEquals(android.hardware.automotive.vehicle.V2_0.VehicleGear.GEAR_NEUTRAL,
+                VehicleGear.GEAR_NEUTRAL);
+
+        assertEquals(android.hardware.automotive.vehicle.V2_0.VehicleGear.GEAR_REVERSE,
+                VehicleGear.GEAR_REVERSE);
+
+        assertEquals(android.hardware.automotive.vehicle.V2_0.VehicleGear.GEAR_PARK,
+                VehicleGear.GEAR_PARK);
+
+        assertEquals(android.hardware.automotive.vehicle.V2_0.VehicleGear.GEAR_DRIVE,
+                VehicleGear.GEAR_DRIVE);
+
+        assertEquals(android.hardware.automotive.vehicle.V2_0.VehicleGear.GEAR_1,
+                VehicleGear.GEAR_FIRST);
+
+        assertEquals(android.hardware.automotive.vehicle.V2_0.VehicleGear.GEAR_2,
+                VehicleGear.GEAR_SECOND);
+
+        assertEquals(android.hardware.automotive.vehicle.V2_0.VehicleGear.GEAR_3,
+                VehicleGear.GEAR_THIRD);
+
+        assertEquals(android.hardware.automotive.vehicle.V2_0.VehicleGear.GEAR_4,
+                VehicleGear.GEAR_FOURTH);
+
+        assertEquals(android.hardware.automotive.vehicle.V2_0.VehicleGear.GEAR_5,
+                VehicleGear.GEAR_FIFTH);
+
+        assertEquals(android.hardware.automotive.vehicle.V2_0.VehicleGear.GEAR_6,
+                VehicleGear.GEAR_SIXTH);
+
+        assertEquals(android.hardware.automotive.vehicle.V2_0.VehicleGear.GEAR_7,
+                VehicleGear.GEAR_SEVENTH);
+
+        assertEquals(android.hardware.automotive.vehicle.V2_0.VehicleGear.GEAR_8,
+                VehicleGear.GEAR_EIGHTH);
+
+        assertEquals(android.hardware.automotive.vehicle.V2_0.VehicleGear.GEAR_9,
+                VehicleGear.GEAR_NINTH);
+    }
+
+    @Test
+    public void testToString() {
+        assertEquals("GEAR_UNKNOWN", VehicleGear.toString(VehicleGear.GEAR_UNKNOWN));
+
+        assertEquals("GEAR_NEUTRAL", VehicleGear.toString(VehicleGear.GEAR_NEUTRAL));
+
+        assertEquals("GEAR_REVERSE", VehicleGear.toString(VehicleGear.GEAR_REVERSE));
+
+        assertEquals("GEAR_PARK", VehicleGear.toString(VehicleGear.GEAR_PARK));
+
+        assertEquals("GEAR_DRIVE", VehicleGear.toString(VehicleGear.GEAR_DRIVE));
+
+        assertEquals("GEAR_FIRST", VehicleGear.toString(VehicleGear.GEAR_FIRST));
+
+        assertEquals("GEAR_SECOND", VehicleGear.toString(VehicleGear.GEAR_SECOND));
+
+        assertEquals("GEAR_THIRD", VehicleGear.toString(VehicleGear.GEAR_THIRD));
+
+        assertEquals("GEAR_FOURTH", VehicleGear.toString(VehicleGear.GEAR_FOURTH));
+
+        assertEquals("GEAR_FIFTH", VehicleGear.toString(VehicleGear.GEAR_FIFTH));
+
+        assertEquals("GEAR_SIXTH", VehicleGear.toString(VehicleGear.GEAR_SIXTH));
+
+        assertEquals("GEAR_SEVENTH", VehicleGear.toString(VehicleGear.GEAR_SEVENTH));
+
+        assertEquals("GEAR_EIGHTH", VehicleGear.toString(VehicleGear.GEAR_EIGHTH));
+
+        assertEquals("GEAR_NINTH", VehicleGear.toString(VehicleGear.GEAR_NINTH));
+
+        assertEquals("0x3", VehicleGear.toString(3));
+
+        assertEquals("0xc", VehicleGear.toString(12));
+    }
+}
diff --git a/tests/android_car_api_test/src/android/car/apitest/VehicleSeatTest.java b/tests/android_car_api_test/src/android/car/apitest/VehicleSeatTest.java
index 841c66e..1b5ba5a 100644
--- a/tests/android_car_api_test/src/android/car/apitest/VehicleSeatTest.java
+++ b/tests/android_car_api_test/src/android/car/apitest/VehicleSeatTest.java
@@ -19,9 +19,16 @@
 import android.test.AndroidTestCase;
 import android.test.suitebuilder.annotation.SmallTest;
 
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
 @SmallTest
 public class VehicleSeatTest extends AndroidTestCase {
 
+    @Test
     public void testMatchWithVehicleHal() {
         assertEquals(android.hardware.automotive.vehicle.V2_0.VehicleAreaSeat.ROW_1_LEFT,
                 VehicleAreaSeat.SEAT_ROW_1_LEFT);
@@ -42,4 +49,49 @@
         assertEquals(android.hardware.automotive.vehicle.V2_0.VehicleAreaSeat.ROW_3_RIGHT,
                 VehicleAreaSeat.SEAT_ROW_3_RIGHT);
     }
+
+    @Test
+    public void testFromRowAndSide() {
+        assertEquals(VehicleAreaSeat.fromRowAndSide(-1, VehicleAreaSeat.SIDE_LEFT),
+                VehicleAreaSeat.SEAT_UNKNOWN);
+        assertEquals(VehicleAreaSeat.fromRowAndSide(-1, VehicleAreaSeat.SIDE_CENTER),
+                VehicleAreaSeat.SEAT_UNKNOWN);
+        assertEquals(VehicleAreaSeat.fromRowAndSide(-1, VehicleAreaSeat.SIDE_RIGHT),
+                VehicleAreaSeat.SEAT_UNKNOWN);
+
+        assertEquals(VehicleAreaSeat.fromRowAndSide(0, VehicleAreaSeat.SIDE_LEFT),
+                VehicleAreaSeat.SEAT_UNKNOWN);
+        assertEquals(VehicleAreaSeat.fromRowAndSide(0, VehicleAreaSeat.SIDE_CENTER),
+                VehicleAreaSeat.SEAT_UNKNOWN);
+        assertEquals(VehicleAreaSeat.fromRowAndSide(0, VehicleAreaSeat.SIDE_RIGHT),
+                VehicleAreaSeat.SEAT_UNKNOWN);
+
+        assertEquals(VehicleAreaSeat.fromRowAndSide(1, VehicleAreaSeat.SIDE_LEFT),
+                VehicleAreaSeat.SEAT_ROW_1_LEFT);
+        assertEquals(VehicleAreaSeat.fromRowAndSide(1, VehicleAreaSeat.SIDE_CENTER),
+                VehicleAreaSeat.SEAT_ROW_1_CENTER);
+        assertEquals(VehicleAreaSeat.fromRowAndSide(1, VehicleAreaSeat.SIDE_RIGHT),
+                VehicleAreaSeat.SEAT_ROW_1_RIGHT);
+
+        assertEquals(VehicleAreaSeat.fromRowAndSide(2, VehicleAreaSeat.SIDE_LEFT),
+                VehicleAreaSeat.SEAT_ROW_2_LEFT);
+        assertEquals(VehicleAreaSeat.fromRowAndSide(2, VehicleAreaSeat.SIDE_CENTER),
+                VehicleAreaSeat.SEAT_ROW_2_CENTER);
+        assertEquals(VehicleAreaSeat.fromRowAndSide(2, VehicleAreaSeat.SIDE_RIGHT),
+                VehicleAreaSeat.SEAT_ROW_2_RIGHT);
+
+        assertEquals(VehicleAreaSeat.fromRowAndSide(3, VehicleAreaSeat.SIDE_LEFT),
+                VehicleAreaSeat.SEAT_ROW_3_LEFT);
+        assertEquals(VehicleAreaSeat.fromRowAndSide(3, VehicleAreaSeat.SIDE_CENTER),
+                VehicleAreaSeat.SEAT_ROW_3_CENTER);
+        assertEquals(VehicleAreaSeat.fromRowAndSide(3, VehicleAreaSeat.SIDE_RIGHT),
+                VehicleAreaSeat.SEAT_ROW_3_RIGHT);
+
+        assertEquals(VehicleAreaSeat.fromRowAndSide(4, VehicleAreaSeat.SIDE_LEFT),
+                VehicleAreaSeat.SEAT_UNKNOWN);
+        assertEquals(VehicleAreaSeat.fromRowAndSide(4, VehicleAreaSeat.SIDE_CENTER),
+                VehicleAreaSeat.SEAT_UNKNOWN);
+        assertEquals(VehicleAreaSeat.fromRowAndSide(4, VehicleAreaSeat.SIDE_RIGHT),
+                VehicleAreaSeat.SEAT_UNKNOWN);
+    }
 }
diff --git a/tests/android_car_api_test/src/android/car/apitest/VehicleWindowTest.java b/tests/android_car_api_test/src/android/car/apitest/VehicleWindowTest.java
index 331d3ef..9fa69e4 100644
--- a/tests/android_car_api_test/src/android/car/apitest/VehicleWindowTest.java
+++ b/tests/android_car_api_test/src/android/car/apitest/VehicleWindowTest.java
@@ -19,9 +19,16 @@
 import android.test.AndroidTestCase;
 import android.test.suitebuilder.annotation.SmallTest;
 
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
 @SmallTest
 public class VehicleWindowTest extends AndroidTestCase {
 
+    @Test
     public void testMatchWithVehicleHal() {
         assertEquals(android.hardware.automotive.vehicle.V2_0.VehicleAreaWindow.FRONT_WINDSHIELD,
                 VehicleAreaWindow.WINDOW_FRONT_WINDSHIELD);
diff --git a/tests/carservice_test/Android.mk b/tests/carservice_test/Android.mk
index 63f1a4e..10884f9 100644
--- a/tests/carservice_test/Android.mk
+++ b/tests/carservice_test/Android.mk
@@ -39,17 +39,20 @@
 LOCAL_PROGUARD_ENABLED := disabled
 
 LOCAL_STATIC_JAVA_LIBRARIES := junit
+# testng imported to use assertThrows, we can remove it once it's ported to JUnit's.
 LOCAL_STATIC_JAVA_LIBRARIES += \
     androidx.test.ext.junit \
     androidx.test.rules \
     android.hardware.automotive.vehicle-V2.0-java \
     car-frameworks-service \
-    car-service-lib-for-test \
+    car-service-test-static-lib \
     car-systemtest \
     com.android.car.test.utils \
     mockito-target-extended \
+    testng \
     truth-prebuilt \
-    vehicle-hal-support-lib
+    vehicle-hal-support-lib-for-test \
+    compatibility-device-util-axt
 
 
 LOCAL_JAVA_LIBRARIES := \
@@ -60,4 +63,6 @@
 
 LOCAL_JNI_SHARED_LIBRARIES := libdexmakerjvmtiagent
 
+LOCAL_COMPATIBILITY_SUITE := general-tests
+
 include $(BUILD_PACKAGE)
diff --git a/tests/carservice_test/AndroidManifest.xml b/tests/carservice_test/AndroidManifest.xml
index 297754d..d960a72 100644
--- a/tests/carservice_test/AndroidManifest.xml
+++ b/tests/carservice_test/AndroidManifest.xml
@@ -15,7 +15,8 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="com.android.car.test" android:sharedUserId="android.uid.system">
+        package="com.android.car.test"
+        android:sharedUserId="com.google.android.car.uid.kitchensink">
 
     <uses-permission android:name="android.Manifest.permission.MODIFY_AUDIO_ROUTING" />
     <uses-permission android:name="android.car.permission.CONTROL_CAR_CLIMATE" />
@@ -28,6 +29,7 @@
     <uses-permission android:name="android.car.permission.CAR_CONTROL_AUDIO_VOLUME" />
     <uses-permission android:name="android.car.permission.CAR_CONTROL_AUDIO_SETTINGS" />
     <uses-permission android:name="android.car.permission.STORAGE_MONITORING" />
+    <uses-permission android:name="android.car.permission.READ_CAR_OCCUPANT_AWARENESS_STATE" />
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
             android:targetPackage="com.android.car.test"
@@ -42,18 +44,6 @@
                 <action android:name="android.car.content.pm.CarAppBlockingPolicyService"/>
             </intent-filter>
         </service>
-        <service android:name="com.android.car.MockedVmsTestBase$MockPublisherClient"
-             android:exported="true"
-             android:permission="android.car.permission.BIND_VMS_CLIENT"/>
-
-        <service android:name="com.android.car.VmsPublisherClientPermissionTest$PublisherClientExpectedPermission"
-                 android:exported="true"
-                 android:permission="android.car.permission.BIND_VMS_CLIENT"/>
-        <service android:name="com.android.car.VmsPublisherClientPermissionTest$PublisherClientWrongPermission"
-                 android:exported="true"
-                 android:permission="android.car.permission.VMS_PUBLISHER"/>
-        <service android:name="com.android.car.VmsPublisherClientPermissionTest$PublisherClientMissingPermission"
-                 android:exported="true"/>
 
         <activity android:name="com.android.car.SystemActivityMonitoringServiceTest$ActivityA"/>
         <activity android:name="com.android.car.SystemActivityMonitoringServiceTest$ActivityB"/>
@@ -61,6 +51,14 @@
         <activity android:name="com.android.car.SystemActivityMonitoringServiceTest$ActivityThatFinishesImmediately"/>
         <activity android:name="com.android.car.SystemActivityMonitoringServiceTest$BlockingActivity"
                   android:taskAffinity="com.android.car.carservicetest.block"/>
+        <activity android:name="com.android.car.CarUxRestrictionsManagerServiceTest$ActivityViewTestActivity"/>
+        <activity android:name="com.android.car.pm.ActivityBlockingActivityTest$NonDoNoHistoryActivity"
+                  android:noHistory="true"/>
+        <activity android:name="com.android.car.pm.ActivityBlockingActivityTest$NonDoActivity"/>
+        <activity android:name="com.android.car.pm.ActivityBlockingActivityTest$DoActivity"
+            android:label="DoActivity">
+            <meta-data android:name="distractionOptimized" android:value="true"/>
+        </activity>
 
         <receiver android:name="com.android.car.CarStorageMonitoringBroadcastReceiver"
             android:exported="true"
diff --git a/tests/carservice_test/res/raw/car_audio_configuration.xml b/tests/carservice_test/res/raw/car_audio_configuration.xml
index 4ae5217..2d7c609 100644
--- a/tests/carservice_test/res/raw/car_audio_configuration.xml
+++ b/tests/carservice_test/res/raw/car_audio_configuration.xml
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="utf-8"?>
-<carAudioConfiguration version="1">
+<carAudioConfiguration version="2">
     <zones>
-        <zone name="primary zone" isPrimary="true">
+        <zone name="primary zone" isPrimary="true" audioZoneId="0" occupantZoneId="1">
             <volumeGroups>
                 <group>
                     <device address="bus0_media_out">
@@ -22,7 +22,7 @@
                 <display port="2"/>
             </displays>
         </zone>
-        <zone name="rear seat zone">
+        <zone name="rear seat zone" audioZoneId="2">
             <volumeGroups>
                 <group>
                     <device address="bus100_rear_seat">
diff --git a/tests/carservice_test/res/raw/car_audio_configuration_V1.xml b/tests/carservice_test/res/raw/car_audio_configuration_V1.xml
new file mode 100644
index 0000000..4aaaa6c
--- /dev/null
+++ b/tests/carservice_test/res/raw/car_audio_configuration_V1.xml
@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2020 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<carAudioConfiguration version="1">
+    <zones>
+        <zone name="primary zone" isPrimary="true">
+            <volumeGroups>
+                <group>
+                    <device address="bus0_media_out">
+                        <context context="music"/>
+                    </device>
+                    <device address="bus3_call_ring_out">
+                        <context context="call_ring"/>
+                    </device>
+                </group>
+                <group>
+                    <device address="bus1_navigation_out">
+                        <context context="navigation"/>
+                    </device>
+                </group>
+            </volumeGroups>
+            <displays>
+                <display port="1"/>
+                <display port="2"/>
+            </displays>
+        </zone>
+        <zone name="rear seat zone">
+            <volumeGroups>
+                <group>
+                    <device address="bus100_rear_seat">
+                        <context context="music"/>
+                        <context context="navigation"/>
+                        <context context="voice_command"/>
+                        <context context="call_ring"/>
+                        <context context="call"/>
+                        <context context="alarm"/>
+                        <context context="notification"/>
+                        <context context="system_sound"/>
+                    </device>
+                </group>
+            </volumeGroups>
+        </zone>
+    </zones>
+</carAudioConfiguration>
diff --git a/tests/carservice_test/res/raw/car_audio_configuration_V1_with_audio_zone_id.xml b/tests/carservice_test/res/raw/car_audio_configuration_V1_with_audio_zone_id.xml
new file mode 100644
index 0000000..5793ab7
--- /dev/null
+++ b/tests/carservice_test/res/raw/car_audio_configuration_V1_with_audio_zone_id.xml
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2020 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<carAudioConfiguration version="1">
+    <zones>
+        <zone name="primary zone" isPrimary="true" audioZoneId="0">
+            <volumeGroups>
+                <group>
+                    <device address="bus0_media_out">
+                        <context context="music"/>
+                    </device>
+                    <device address="bus3_call_ring_out">
+                        <context context="call_ring"/>
+                    </device>
+                </group>
+                <group>
+                    <device address="bus1_navigation_out">
+                        <context context="navigation"/>
+                    </device>
+                </group>
+            </volumeGroups>
+        </zone>
+        <zone name="rear seat zone">
+            <volumeGroups>
+                <group>
+                    <device address="bus100_rear_seat">
+                        <context context="music"/>
+                        <context context="navigation"/>
+                        <context context="voice_command"/>
+                        <context context="call_ring"/>
+                        <context context="call"/>
+                        <context context="alarm"/>
+                        <context context="notification"/>
+                        <context context="system_sound"/>
+                    </device>
+                </group>
+            </volumeGroups>
+        </zone>
+    </zones>
+</carAudioConfiguration>
diff --git a/tests/carservice_test/res/raw/car_audio_configuration_V1_with_occupant_zone_id.xml b/tests/carservice_test/res/raw/car_audio_configuration_V1_with_occupant_zone_id.xml
new file mode 100644
index 0000000..564a13c
--- /dev/null
+++ b/tests/carservice_test/res/raw/car_audio_configuration_V1_with_occupant_zone_id.xml
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2020 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<carAudioConfiguration version="1">
+    <zones>
+        <zone name="primary zone" isPrimary="true" occupantZoneId="1">
+            <volumeGroups>
+                <group>
+                    <device address="bus0_media_out">
+                        <context context="music"/>
+                    </device>
+                    <device address="bus3_call_ring_out">
+                        <context context="call_ring"/>
+                    </device>
+                </group>
+                <group>
+                    <device address="bus1_navigation_out">
+                        <context context="navigation"/>
+                    </device>
+                </group>
+            </volumeGroups>
+        </zone>
+        <zone name="rear seat zone">
+            <volumeGroups>
+                <group>
+                    <device address="bus100_rear_seat">
+                        <context context="music"/>
+                        <context context="navigation"/>
+                        <context context="voice_command"/>
+                        <context context="call_ring"/>
+                        <context context="call"/>
+                        <context context="alarm"/>
+                        <context context="notification"/>
+                        <context context="system_sound"/>
+                    </device>
+                </group>
+            </volumeGroups>
+        </zone>
+    </zones>
+</carAudioConfiguration>
diff --git a/tests/carservice_test/res/raw/car_audio_configuration_duplicate_audio_zone_id.xml b/tests/carservice_test/res/raw/car_audio_configuration_duplicate_audio_zone_id.xml
new file mode 100644
index 0000000..2743c41
--- /dev/null
+++ b/tests/carservice_test/res/raw/car_audio_configuration_duplicate_audio_zone_id.xml
@@ -0,0 +1,74 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2020 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<carAudioConfiguration version="2">
+    <zones>
+        <zone name="primary zone" isPrimary="true" audioZoneId="0" occupantZoneId="1">
+            <volumeGroups>
+                <group>
+                    <device address="bus0_media_out">
+                        <context context="music"/>
+                    </device>
+                    <device address="bus3_call_ring_out">
+                        <context context="call_ring"/>
+                    </device>
+                </group>
+                <group>
+                    <device address="bus1_navigation_out">
+                        <context context="navigation"/>
+                    </device>
+                </group>
+            </volumeGroups>
+            <displays>
+                <display port="1"/>
+                <display port="2"/>
+            </displays>
+        </zone>
+        <zone name="rear seat zone" audioZoneId="1" occupantZoneId="2">
+            <volumeGroups>
+                <group>
+                    <device address="bus100_rear_seat">
+                        <context context="music"/>
+                        <context context="navigation"/>
+                        <context context="voice_command"/>
+                        <context context="call_ring"/>
+                        <context context="call"/>
+                        <context context="alarm"/>
+                        <context context="notification"/>
+                        <context context="system_sound"/>
+                    </device>
+                </group>
+            </volumeGroups>
+        </zone>
+        <zone name="rear seat zone 2" audioZoneId="1" occupantZoneId="3">
+            <volumeGroups>
+                <group>
+                    <device address="bus200_rear_seat">
+                        <context context="music"/>
+                        <context context="navigation"/>
+                        <context context="voice_command"/>
+                        <context context="call_ring"/>
+                        <context context="call"/>
+                        <context context="alarm"/>
+                        <context context="notification"/>
+                        <context context="system_sound"/>
+                    </device>
+                </group>
+            </volumeGroups>
+        </zone>
+    </zones>
+</carAudioConfiguration>
diff --git a/tests/carservice_test/res/raw/car_audio_configuration_duplicate_occupant_zone_id.xml b/tests/carservice_test/res/raw/car_audio_configuration_duplicate_occupant_zone_id.xml
new file mode 100644
index 0000000..613f6cd
--- /dev/null
+++ b/tests/carservice_test/res/raw/car_audio_configuration_duplicate_occupant_zone_id.xml
@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2020 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<carAudioConfiguration version="2">
+    <zones>
+        <zone name="primary zone" isPrimary="true" audioZoneId="0" occupantZoneId="1">
+            <volumeGroups>
+                <group>
+                    <device address="bus0_media_out">
+                        <context context="music"/>
+                    </device>
+                    <device address="bus3_call_ring_out">
+                        <context context="call_ring"/>
+                    </device>
+                </group>
+                <group>
+                    <device address="bus1_navigation_out">
+                        <context context="navigation"/>
+                    </device>
+                </group>
+            </volumeGroups>
+            <displays>
+                <display port="1"/>
+                <display port="2"/>
+            </displays>
+        </zone>
+        <zone name="rear seat zone" audioZoneId="2" occupantZoneId="1">
+            <volumeGroups>
+                <group>
+                    <device address="bus100_rear_seat">
+                        <context context="music"/>
+                        <context context="navigation"/>
+                        <context context="voice_command"/>
+                        <context context="call_ring"/>
+                        <context context="call"/>
+                        <context context="alarm"/>
+                        <context context="notification"/>
+                        <context context="system_sound"/>
+                    </device>
+                </group>
+            </volumeGroups>
+        </zone>
+    </zones>
+</carAudioConfiguration>
diff --git a/tests/carservice_test/res/raw/car_audio_configuration_empty_input_device.xml b/tests/carservice_test/res/raw/car_audio_configuration_empty_input_device.xml
new file mode 100644
index 0000000..d2ff43d
--- /dev/null
+++ b/tests/carservice_test/res/raw/car_audio_configuration_empty_input_device.xml
@@ -0,0 +1,62 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2020 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<carAudioConfiguration version="2">
+    <zones>
+        <zone name="primary zone" isPrimary="true" audioZoneId="0">
+            <volumeGroups>
+                <group>
+                    <device address="bus0_media_out">
+                        <context context="music"/>
+                    </device>
+                    <device address="bus3_call_ring_out">
+                        <context context="call_ring"/>
+                    </device>
+                </group>
+                <group>
+                    <device address="bus1_navigation_out">
+                        <context context="navigation"/>
+                    </device>
+                </group>
+            </volumeGroups>
+            <inputDevices>
+                <inputDevice address=""/>
+                <inputDevice address="Built-In Mic"/>
+            </inputDevices>
+        </zone>
+        <zone name="rear seat zone" audioZoneId="1">
+            <volumeGroups>
+                <group>
+                    <device address="bus100_rear_seat">
+                        <context context="music"/>
+                        <context context="navigation"/>
+                        <context context="voice_command"/>
+                        <context context="call_ring"/>
+                        <context context="call"/>
+                        <context context="alarm"/>
+                        <context context="notification"/>
+                        <context context="system_sound"/>
+                    </device>
+                </group>
+            </volumeGroups>
+            <inputDevices>
+                <inputDevice address="bus_1000_input"/>
+                <inputDevice address="Built-In Back Mic"/>
+            </inputDevices>
+        </zone>
+    </zones>
+</carAudioConfiguration>
diff --git a/tests/carservice_test/res/raw/car_audio_configuration_empty_occupant_zone_id.xml b/tests/carservice_test/res/raw/car_audio_configuration_empty_occupant_zone_id.xml
new file mode 100644
index 0000000..c4ef494
--- /dev/null
+++ b/tests/carservice_test/res/raw/car_audio_configuration_empty_occupant_zone_id.xml
@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2020 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<carAudioConfiguration version="2">
+    <zones>
+        <zone name="primary zone" isPrimary="true" audioZoneId="0" occupantZoneId="">
+            <volumeGroups>
+                <group>
+                    <device address="bus0_media_out">
+                        <context context="music"/>
+                    </device>
+                    <device address="bus3_call_ring_out">
+                        <context context="call_ring"/>
+                    </device>
+                </group>
+                <group>
+                    <device address="bus1_navigation_out">
+                        <context context="navigation"/>
+                    </device>
+                </group>
+            </volumeGroups>
+            <displays>
+                <display port="1"/>
+                <display port="2"/>
+            </displays>
+        </zone>
+        <zone name="rear seat zone" audioZoneId="2">
+            <volumeGroups>
+                <group>
+                    <device address="bus100_rear_seat">
+                        <context context="music"/>
+                        <context context="navigation"/>
+                        <context context="voice_command"/>
+                        <context context="call_ring"/>
+                        <context context="call"/>
+                        <context context="alarm"/>
+                        <context context="notification"/>
+                        <context context="system_sound"/>
+                    </device>
+                </group>
+            </volumeGroups>
+        </zone>
+    </zones>
+</carAudioConfiguration>
diff --git a/tests/carservice_test/res/raw/car_audio_configuration_missing_address.xml b/tests/carservice_test/res/raw/car_audio_configuration_missing_address.xml
new file mode 100644
index 0000000..7af8d68
--- /dev/null
+++ b/tests/carservice_test/res/raw/car_audio_configuration_missing_address.xml
@@ -0,0 +1,62 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2020 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<carAudioConfiguration version="2">
+    <zones>
+        <zone name="primary zone" isPrimary="true" audioZoneId="0">
+            <volumeGroups>
+                <group>
+                    <device address="bus0_media_out">
+                        <context context="music"/>
+                    </device>
+                    <device address="bus3_call_ring_out">
+                        <context context="call_ring"/>
+                    </device>
+                </group>
+                <group>
+                    <device address="bus1_navigation_out">
+                        <context context="navigation"/>
+                    </device>
+                </group>
+            </volumeGroups>
+            <inputDevices>
+                <inputDevice address="fm_tuner"/>
+                <inputDevice/>
+            </inputDevices>
+        </zone>
+        <zone name="rear seat zone" audioZoneId="1">
+            <volumeGroups>
+                <group>
+                    <device address="bus100_rear_seat">
+                        <context context="music"/>
+                        <context context="navigation"/>
+                        <context context="voice_command"/>
+                        <context context="call_ring"/>
+                        <context context="call"/>
+                        <context context="alarm"/>
+                        <context context="notification"/>
+                        <context context="system_sound"/>
+                    </device>
+                </group>
+            </volumeGroups>
+            <inputDevices>
+                <inputDevice address="bus_1000_input"/>
+                <inputDevice address="Built-In Back Mic"/>
+            </inputDevices>
+        </zone>
+    </zones>
+</carAudioConfiguration>
diff --git a/tests/carservice_test/res/raw/car_audio_configuration_negative_audio_zone_id.xml b/tests/carservice_test/res/raw/car_audio_configuration_negative_audio_zone_id.xml
new file mode 100644
index 0000000..3c202f3
--- /dev/null
+++ b/tests/carservice_test/res/raw/car_audio_configuration_negative_audio_zone_id.xml
@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2020 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<carAudioConfiguration version="2">
+    <zones>
+        <zone name="primary zone" isPrimary="true" audioZoneId="-1" occupantZoneId="1">
+            <volumeGroups>
+                <group>
+                    <device address="bus0_media_out">
+                        <context context="music"/>
+                    </device>
+                    <device address="bus3_call_ring_out">
+                        <context context="call_ring"/>
+                    </device>
+                </group>
+                <group>
+                    <device address="bus1_navigation_out">
+                        <context context="navigation"/>
+                    </device>
+                </group>
+            </volumeGroups>
+            <displays>
+                <display port="1"/>
+                <display port="2"/>
+            </displays>
+        </zone>
+        <zone name="rear seat zone" audioZoneId="2">
+            <volumeGroups>
+                <group>
+                    <device address="bus100_rear_seat">
+                        <context context="music"/>
+                        <context context="navigation"/>
+                        <context context="voice_command"/>
+                        <context context="call_ring"/>
+                        <context context="call"/>
+                        <context context="alarm"/>
+                        <context context="notification"/>
+                        <context context="system_sound"/>
+                    </device>
+                </group>
+            </volumeGroups>
+        </zone>
+    </zones>
+</carAudioConfiguration>
diff --git a/tests/carservice_test/res/raw/car_audio_configuration_negative_occupant_zone_id.xml b/tests/carservice_test/res/raw/car_audio_configuration_negative_occupant_zone_id.xml
new file mode 100644
index 0000000..ac240d6
--- /dev/null
+++ b/tests/carservice_test/res/raw/car_audio_configuration_negative_occupant_zone_id.xml
@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2020 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<carAudioConfiguration version="2">
+    <zones>
+        <zone name="primary zone" isPrimary="true" audioZoneId="0" occupantZoneId="-1">
+            <volumeGroups>
+                <group>
+                    <device address="bus0_media_out">
+                        <context context="music"/>
+                    </device>
+                    <device address="bus3_call_ring_out">
+                        <context context="call_ring"/>
+                    </device>
+                </group>
+                <group>
+                    <device address="bus1_navigation_out">
+                        <context context="navigation"/>
+                    </device>
+                </group>
+            </volumeGroups>
+            <displays>
+                <display port="1"/>
+                <display port="2"/>
+            </displays>
+        </zone>
+        <zone name="rear seat zone" audioZoneId="2">
+            <volumeGroups>
+                <group>
+                    <device address="bus100_rear_seat">
+                        <context context="music"/>
+                        <context context="navigation"/>
+                        <context context="voice_command"/>
+                        <context context="call_ring"/>
+                        <context context="call"/>
+                        <context context="alarm"/>
+                        <context context="notification"/>
+                        <context context="system_sound"/>
+                    </device>
+                </group>
+            </volumeGroups>
+        </zone>
+    </zones>
+</carAudioConfiguration>
diff --git a/tests/carservice_test/res/raw/car_audio_configuration_no_audio_zone_id_for_primary_zone.xml b/tests/carservice_test/res/raw/car_audio_configuration_no_audio_zone_id_for_primary_zone.xml
new file mode 100644
index 0000000..720e875
--- /dev/null
+++ b/tests/carservice_test/res/raw/car_audio_configuration_no_audio_zone_id_for_primary_zone.xml
@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2020 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<carAudioConfiguration version="2">
+    <zones>
+        <zone name="primary zone" isPrimary="true">
+            <volumeGroups>
+                <group>
+                    <device address="bus0_media_out">
+                        <context context="music"/>
+                    </device>
+                    <device address="bus3_call_ring_out">
+                        <context context="call_ring"/>
+                    </device>
+                </group>
+                <group>
+                    <device address="bus1_navigation_out">
+                        <context context="navigation"/>
+                    </device>
+                </group>
+            </volumeGroups>
+        </zone>
+        <zone name="rear seat zone" audioZoneId="2"
+              occupantZoneId="1">
+            <volumeGroups>
+                <group>
+                    <device address="bus100_rear_seat">
+                        <context context="music"/>
+                        <context context="navigation"/>
+                        <context context="voice_command"/>
+                        <context context="call_ring"/>
+                        <context context="call"/>
+                        <context context="alarm"/>
+                        <context context="notification"/>
+                        <context context="system_sound"/>
+                    </device>
+                </group>
+            </volumeGroups>
+        </zone>
+    </zones>
+</carAudioConfiguration>
diff --git a/tests/carservice_test/res/raw/car_audio_configuration_non_existent_input_device.xml b/tests/carservice_test/res/raw/car_audio_configuration_non_existent_input_device.xml
new file mode 100644
index 0000000..2f317d0
--- /dev/null
+++ b/tests/carservice_test/res/raw/car_audio_configuration_non_existent_input_device.xml
@@ -0,0 +1,62 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2020 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<carAudioConfiguration version="2">
+    <zones>
+        <zone name="primary zone" isPrimary="true">
+            <volumeGroups>
+                <group>
+                    <device address="bus0_media_out">
+                        <context context="music"/>
+                    </device>
+                    <device address="bus3_call_ring_out">
+                        <context context="call_ring"/>
+                    </device>
+                </group>
+                <group>
+                    <device address="bus1_navigation_out">
+                        <context context="navigation"/>
+                    </device>
+                </group>
+            </volumeGroups>
+            <inputDevices>
+                <inputDevice address="fm_tuner"/>
+                <inputDevice address="Built-In Mic"/>
+            </inputDevices>
+        </zone>
+        <zone name="rear seat zone" audioZoneId="1">
+            <volumeGroups>
+                <group>
+                    <device address="bus100_rear_seat">
+                        <context context="music"/>
+                        <context context="navigation"/>
+                        <context context="voice_command"/>
+                        <context context="call_ring"/>
+                        <context context="call"/>
+                        <context context="alarm"/>
+                        <context context="notification"/>
+                        <context context="system_sound"/>
+                    </device>
+                </group>
+            </volumeGroups>
+            <inputDevices>
+                <inputDevice address="MISSING_AUDIO_INPUT"/>
+                <inputDevice address="Built-In Back Mic"/>
+            </inputDevices>
+        </zone>
+    </zones>
+</carAudioConfiguration>
diff --git a/tests/carservice_test/res/raw/car_audio_configuration_non_numerical_audio_zone_id.xml b/tests/carservice_test/res/raw/car_audio_configuration_non_numerical_audio_zone_id.xml
new file mode 100644
index 0000000..2721b50
--- /dev/null
+++ b/tests/carservice_test/res/raw/car_audio_configuration_non_numerical_audio_zone_id.xml
@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2020 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<carAudioConfiguration version="2">
+    <zones>
+        <zone name="primary zone" isPrimary="true" audioZoneId="primary" occupantZoneId="1">
+            <volumeGroups>
+                <group>
+                    <device address="bus0_media_out">
+                        <context context="music"/>
+                    </device>
+                    <device address="bus3_call_ring_out">
+                        <context context="call_ring"/>
+                    </device>
+                </group>
+                <group>
+                    <device address="bus1_navigation_out">
+                        <context context="navigation"/>
+                    </device>
+                </group>
+            </volumeGroups>
+            <displays>
+                <display port="1"/>
+                <display port="2"/>
+            </displays>
+        </zone>
+        <zone name="rear seat zone" audioZoneId="2">
+            <volumeGroups>
+                <group>
+                    <device address="bus100_rear_seat">
+                        <context context="music"/>
+                        <context context="navigation"/>
+                        <context context="voice_command"/>
+                        <context context="call_ring"/>
+                        <context context="call"/>
+                        <context context="alarm"/>
+                        <context context="notification"/>
+                        <context context="system_sound"/>
+                    </device>
+                </group>
+            </volumeGroups>
+        </zone>
+    </zones>
+</carAudioConfiguration>
diff --git a/tests/carservice_test/res/raw/car_audio_configuration_non_numerical_occupant_zone_id.xml b/tests/carservice_test/res/raw/car_audio_configuration_non_numerical_occupant_zone_id.xml
new file mode 100644
index 0000000..85dd768
--- /dev/null
+++ b/tests/carservice_test/res/raw/car_audio_configuration_non_numerical_occupant_zone_id.xml
@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2020 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<carAudioConfiguration version="2">
+    <zones>
+        <zone name="primary zone" isPrimary="true" audioZoneId="0" occupantZoneId="one">
+            <volumeGroups>
+                <group>
+                    <device address="bus0_media_out">
+                        <context context="music"/>
+                    </device>
+                    <device address="bus3_call_ring_out">
+                        <context context="call_ring"/>
+                    </device>
+                </group>
+                <group>
+                    <device address="bus1_navigation_out">
+                        <context context="navigation"/>
+                    </device>
+                </group>
+            </volumeGroups>
+            <displays>
+                <display port="1"/>
+                <display port="2"/>
+            </displays>
+        </zone>
+        <zone name="rear seat zone" audioZoneId="2">
+            <volumeGroups>
+                <group>
+                    <device address="bus100_rear_seat">
+                        <context context="music"/>
+                        <context context="navigation"/>
+                        <context context="voice_command"/>
+                        <context context="call_ring"/>
+                        <context context="call"/>
+                        <context context="alarm"/>
+                        <context context="notification"/>
+                        <context context="system_sound"/>
+                    </device>
+                </group>
+            </volumeGroups>
+        </zone>
+    </zones>
+</carAudioConfiguration>
diff --git a/tests/carservice_test/res/raw/car_audio_configuration_non_primary_zone_with_primary_audio_zone_id.xml b/tests/carservice_test/res/raw/car_audio_configuration_non_primary_zone_with_primary_audio_zone_id.xml
new file mode 100644
index 0000000..d6889f6
--- /dev/null
+++ b/tests/carservice_test/res/raw/car_audio_configuration_non_primary_zone_with_primary_audio_zone_id.xml
@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2020 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<carAudioConfiguration version="2">
+    <zones>
+        <zone name="primary zone" isPrimary="true" occupantZoneId="1">
+            <volumeGroups>
+                <group>
+                    <device address="bus0_media_out">
+                        <context context="music"/>
+                    </device>
+                    <device address="bus3_call_ring_out">
+                        <context context="call_ring"/>
+                    </device>
+                </group>
+                <group>
+                    <device address="bus1_navigation_out">
+                        <context context="navigation"/>
+                    </device>
+                </group>
+            </volumeGroups>
+            <displays>
+                <display port="1"/>
+                <display port="2"/>
+            </displays>
+        </zone>
+        <zone name="rear seat zone" audioZoneId="0">
+            <volumeGroups>
+                <group>
+                    <device address="bus100_rear_seat">
+                        <context context="music"/>
+                        <context context="navigation"/>
+                        <context context="voice_command"/>
+                        <context context="call_ring"/>
+                        <context context="call"/>
+                        <context context="alarm"/>
+                        <context context="notification"/>
+                        <context context="system_sound"/>
+                    </device>
+                </group>
+            </volumeGroups>
+        </zone>
+    </zones>
+</carAudioConfiguration>
diff --git a/tests/carservice_test/res/raw/car_audio_configuration_output_address_does_not_exist.xml b/tests/carservice_test/res/raw/car_audio_configuration_output_address_does_not_exist.xml
new file mode 100644
index 0000000..ff6fe8a
--- /dev/null
+++ b/tests/carservice_test/res/raw/car_audio_configuration_output_address_does_not_exist.xml
@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2020 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<carAudioConfiguration version="2">
+    <zones>
+        <zone name="primary zone" isPrimary="true" audioZoneId="0" occupantZoneId="1">
+            <volumeGroups>
+                <group>
+                    <device address="bus0_media_out">
+                        <context context="music"/>
+                    </device>
+                    <device address="bus3_call_ring_out">
+                        <context context="call_ring"/>
+                    </device>
+                </group>
+                <group>
+                    <device address="bus1_navigation_out">
+                        <context context="navigation"/>
+                    </device>
+                </group>
+            </volumeGroups>
+            <displays>
+                <display port="1"/>
+                <display port="2"/>
+            </displays>
+        </zone>
+        <zone name="rear seat zone" audioZoneId="2">
+            <volumeGroups>
+                <group>
+                    <device address="bus1000_does_not_exist">
+                        <context context="music"/>
+                        <context context="navigation"/>
+                        <context context="voice_command"/>
+                        <context context="call_ring"/>
+                        <context context="call"/>
+                        <context context="alarm"/>
+                        <context context="notification"/>
+                        <context context="system_sound"/>
+                    </device>
+                </group>
+            </volumeGroups>
+        </zone>
+    </zones>
+</carAudioConfiguration>
diff --git a/tests/carservice_test/res/raw/car_audio_configuration_primary_zone_with_non_zero_audio_zone_id.xml b/tests/carservice_test/res/raw/car_audio_configuration_primary_zone_with_non_zero_audio_zone_id.xml
new file mode 100644
index 0000000..ee0b268
--- /dev/null
+++ b/tests/carservice_test/res/raw/car_audio_configuration_primary_zone_with_non_zero_audio_zone_id.xml
@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2020 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<carAudioConfiguration version="2">
+    <zones>
+        <zone name="primary zone" isPrimary="true" audioZoneId="1" occupantZoneId="1">
+            <volumeGroups>
+                <group>
+                    <device address="bus0_media_out">
+                        <context context="music"/>
+                    </device>
+                    <device address="bus3_call_ring_out">
+                        <context context="call_ring"/>
+                    </device>
+                </group>
+                <group>
+                    <device address="bus1_navigation_out">
+                        <context context="navigation"/>
+                    </device>
+                </group>
+            </volumeGroups>
+            <displays>
+                <display port="1"/>
+                <display port="2"/>
+            </displays>
+        </zone>
+        <zone name="rear seat zone" audioZoneId="2">
+            <volumeGroups>
+                <group>
+                    <device address="bus100_rear_seat">
+                        <context context="music"/>
+                        <context context="navigation"/>
+                        <context context="voice_command"/>
+                        <context context="call_ring"/>
+                        <context context="call"/>
+                        <context context="alarm"/>
+                        <context context="notification"/>
+                        <context context="system_sound"/>
+                    </device>
+                </group>
+            </volumeGroups>
+        </zone>
+    </zones>
+</carAudioConfiguration>
diff --git a/tests/carservice_test/res/raw/car_audio_configuration_repeat_input_device.xml b/tests/carservice_test/res/raw/car_audio_configuration_repeat_input_device.xml
new file mode 100644
index 0000000..1934fe7
--- /dev/null
+++ b/tests/carservice_test/res/raw/car_audio_configuration_repeat_input_device.xml
@@ -0,0 +1,62 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2020 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<carAudioConfiguration version="2">
+    <zones>
+        <zone name="primary zone" isPrimary="true" audioZoneId="0">
+            <volumeGroups>
+                <group>
+                    <device address="bus0_media_out">
+                        <context context="music"/>
+                    </device>
+                    <device address="bus3_call_ring_out">
+                        <context context="call_ring"/>
+                    </device>
+                </group>
+                <group>
+                    <device address="bus1_navigation_out">
+                        <context context="navigation"/>
+                    </device>
+                </group>
+            </volumeGroups>
+            <inputDevices>
+                <inputDevice address="fm_tuner"/>
+                <inputDevice address="Built-In Mic"/>
+            </inputDevices>
+        </zone>
+        <zone name="rear seat zone" audioZoneId="1">
+            <volumeGroups>
+                <group>
+                    <device address="bus100_rear_seat">
+                        <context context="music"/>
+                        <context context="navigation"/>
+                        <context context="voice_command"/>
+                        <context context="call_ring"/>
+                        <context context="call"/>
+                        <context context="alarm"/>
+                        <context context="notification"/>
+                        <context context="system_sound"/>
+                    </device>
+                </group>
+            </volumeGroups>
+            <inputDevices>
+                <inputDevice address="fm_tuner"/>
+                <inputDevice address="Built-In Back Mic"/>
+            </inputDevices>
+        </zone>
+    </zones>
+</carAudioConfiguration>
diff --git a/tests/carservice_test/res/raw/car_audio_configuration_with_input_devices.xml b/tests/carservice_test/res/raw/car_audio_configuration_with_input_devices.xml
new file mode 100644
index 0000000..3b6a013
--- /dev/null
+++ b/tests/carservice_test/res/raw/car_audio_configuration_with_input_devices.xml
@@ -0,0 +1,62 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2020 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<carAudioConfiguration version="2">
+    <zones>
+        <zone name="primary zone" isPrimary="true" audioZoneId="0">
+            <volumeGroups>
+                <group>
+                    <device address="bus0_media_out">
+                        <context context="music"/>
+                    </device>
+                    <device address="bus3_call_ring_out">
+                        <context context="call_ring"/>
+                    </device>
+                </group>
+                <group>
+                    <device address="bus1_navigation_out">
+                        <context context="navigation"/>
+                    </device>
+                </group>
+            </volumeGroups>
+            <inputDevices>
+                <inputDevice address="fm_tuner"/>
+                <inputDevice address="Built-In Mic"/>
+            </inputDevices>
+        </zone>
+        <zone name="rear seat zone" audioZoneId="1">
+            <volumeGroups>
+                <group>
+                    <device address="bus100_rear_seat">
+                        <context context="music"/>
+                        <context context="navigation"/>
+                        <context context="voice_command"/>
+                        <context context="call_ring"/>
+                        <context context="call"/>
+                        <context context="alarm"/>
+                        <context context="notification"/>
+                        <context context="system_sound"/>
+                    </device>
+                </group>
+            </volumeGroups>
+            <inputDevices>
+                <inputDevice address="bus_1000_input"/>
+                <inputDevice address="Built-In Back Mic"/>
+            </inputDevices>
+        </zone>
+    </zones>
+</carAudioConfiguration>
diff --git a/tests/carservice_test/src/android/media/tests/AudioPolicyTest.java b/tests/carservice_test/src/android/media/tests/AudioPolicyTest.java
index 9e0f311..4601432 100644
--- a/tests/carservice_test/src/android/media/tests/AudioPolicyTest.java
+++ b/tests/carservice_test/src/android/media/tests/AudioPolicyTest.java
@@ -32,9 +32,9 @@
 import android.os.Looper;
 import android.util.Log;
 
-import androidx.test.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.MediumTest;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -51,7 +51,7 @@
     private static final long WAIT_TIMEOUT_MS = 1000;
     private AudioManager mAudioManager;
     private Handler mHandler;
-    private Context mContext = InstrumentationRegistry.getContext();
+    private Context mContext = InstrumentationRegistry.getInstrumentation().getContext();
 
     @Before
     public void setUp() throws Exception {
@@ -68,7 +68,8 @@
     public void testAudioPorts() throws Exception {
         AudioPortUpdateListener listener = new AudioPortUpdateListener();
         mAudioManager.registerAudioPortUpdateListener(listener);
-        ArrayList<AudioPort> initialPorts = dumpAudioPorts("initial state");
+        // TODO(b/142554800): assert ports
+        dumpAudioPorts("initial state");
         AudioMix mediaMix = createAudioMix(AudioAttributes.CONTENT_TYPE_UNKNOWN,
                 AudioAttributes.CONTENT_TYPE_MUSIC);
         AudioPolicy audioPolicy = new AudioPolicy.Builder(mContext)
@@ -78,7 +79,7 @@
         mAudioManager.registerAudioPolicy(audioPolicy);
         dumpAudioPorts("policy set");
         mAudioManager.unregisterAudioPolicyAsync(audioPolicy);
-        ArrayList<AudioPort> afterUnregisterPorts = dumpAudioPorts("policy unset");
+        dumpAudioPorts("policy unset");
         mAudioManager.unregisterAudioPortUpdateListener(listener);
     }
 
@@ -87,7 +88,7 @@
         ArrayList<AudioPort> ports = new ArrayList<>();
         assertEquals(AudioManager.SUCCESS, AudioManager.listAudioPorts(ports));
         for (AudioPort port : ports) {
-            Log.i(TAG, "port:" + port.toString() + " name:" + port.name());
+            Log.i(TAG, "port:" + port + " name:" + port.name());
         }
         return ports;
     }
diff --git a/tests/carservice_test/src/com/android/car/AppFocusTest.java b/tests/carservice_test/src/com/android/car/AppFocusTest.java
index 3474d88..c7ee982 100644
--- a/tests/carservice_test/src/com/android/car/AppFocusTest.java
+++ b/tests/carservice_test/src/com/android/car/AppFocusTest.java
@@ -21,8 +21,8 @@
 import android.car.CarAppFocusManager;
 import android.util.Log;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.MediumTest;
-import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
diff --git a/tests/carservice_test/src/com/android/car/CarAudioManagerTest.java b/tests/carservice_test/src/com/android/car/CarAudioManagerTest.java
index d11159e..2e2cee9 100644
--- a/tests/carservice_test/src/com/android/car/CarAudioManagerTest.java
+++ b/tests/carservice_test/src/com/android/car/CarAudioManagerTest.java
@@ -19,8 +19,8 @@
 import android.car.media.CarAudioManager;
 import android.util.Log;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.MediumTest;
-import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
diff --git a/tests/carservice_test/src/com/android/car/CarCabinManagerTest.java b/tests/carservice_test/src/com/android/car/CarCabinManagerTest.java
index 46d54b7..378d587 100644
--- a/tests/carservice_test/src/com/android/car/CarCabinManagerTest.java
+++ b/tests/carservice_test/src/com/android/car/CarCabinManagerTest.java
@@ -33,8 +33,9 @@
 import android.util.Log;
 import android.util.MutableInt;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.FlakyTest;
 import androidx.test.filters.MediumTest;
-import androidx.test.runner.AndroidJUnit4;
 
 import com.android.car.vehiclehal.VehiclePropValueBuilder;
 import com.android.car.vehiclehal.test.MockedVehicleHal.VehicleHalPropertyHandler;
@@ -131,7 +132,7 @@
                 errorLatch.countDown();
             }
         });
-
+        mCarCabinManager.setBooleanProperty(PROP, AREA, true);
         getMockedVehicleHal().injectError(ERR_CODE, PROP, AREA);
         assertTrue(errorLatch.await(DEFAULT_WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS));
         assertEquals(PROP, propertyIdReceived.value);
@@ -141,6 +142,7 @@
 
     // Test an event
     @Test
+    @FlakyTest
     public void testEvent() throws Exception {
         mCarCabinManager.registerCallback(new EventListener());
         // Wait for two events generated on registration
diff --git a/tests/carservice_test/src/com/android/car/CarDiagnosticConstantsTest.java b/tests/carservice_test/src/com/android/car/CarDiagnosticConstantsTest.java
index 2955345..7ce3b5f 100644
--- a/tests/carservice_test/src/com/android/car/CarDiagnosticConstantsTest.java
+++ b/tests/carservice_test/src/com/android/car/CarDiagnosticConstantsTest.java
@@ -18,8 +18,8 @@
 
 import android.util.Log;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.MediumTest;
-import androidx.test.runner.AndroidJUnit4;
 
 import junit.framework.TestCase;
 
diff --git a/tests/carservice_test/src/com/android/car/CarDiagnosticManagerTest.java b/tests/carservice_test/src/com/android/car/CarDiagnosticManagerTest.java
index f73c401..fb7eee6 100644
--- a/tests/carservice_test/src/com/android/car/CarDiagnosticManagerTest.java
+++ b/tests/carservice_test/src/com/android/car/CarDiagnosticManagerTest.java
@@ -40,8 +40,9 @@
 import android.util.JsonWriter;
 import android.util.Log;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.FlakyTest;
 import androidx.test.filters.MediumTest;
-import androidx.test.runner.AndroidJUnit4;
 
 import com.android.car.vehiclehal.DiagnosticEventBuilder;
 import com.android.car.vehiclehal.DiagnosticJson;
@@ -203,6 +204,13 @@
     }
 
     @Override
+    protected synchronized void configureResourceOverrides(MockResources resources) {
+        super.configureResourceOverrides(resources);
+        resources.overrideResource(com.android.car.R.array.config_allowed_optional_car_features,
+                new String[]{Car.DIAGNOSTIC_SERVICE});
+    }
+
+    @Override
     protected synchronized void configureMockedHal() {
         java.util.Collection<Integer> numVendorSensors = Arrays.asList(0, 0);
         java.util.Collection<Integer> selectiveClear = Collections.singletonList(1);
@@ -487,7 +495,9 @@
         assertFalse(compressionIgnitionMonitors.NMHCCatalyst.incomplete);
     }
 
-    @Test public void testFuelType() throws Exception {
+    @Test
+    @FlakyTest
+    public void testFuelType() throws Exception {
         Listener listener = new Listener();
         mCarDiagnosticManager.registerListener(
                 listener,
diff --git a/tests/carservice_test/src/com/android/car/CarDrivingRestrictionsTest.java b/tests/carservice_test/src/com/android/car/CarDrivingRestrictionsTest.java
index 162cd3e..520e910 100644
--- a/tests/carservice_test/src/com/android/car/CarDrivingRestrictionsTest.java
+++ b/tests/carservice_test/src/com/android/car/CarDrivingRestrictionsTest.java
@@ -32,8 +32,8 @@
 import android.os.SystemClock;
 import android.util.Log;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
 
 import com.android.car.vehiclehal.VehiclePropValueBuilder;
 import com.android.internal.annotations.GuardedBy;
diff --git a/tests/carservice_test/src/com/android/car/CarHvacManagerTest.java b/tests/carservice_test/src/com/android/car/CarHvacManagerTest.java
index a32cca5..5b1199c 100644
--- a/tests/carservice_test/src/com/android/car/CarHvacManagerTest.java
+++ b/tests/carservice_test/src/com/android/car/CarHvacManagerTest.java
@@ -35,8 +35,8 @@
 import android.util.Log;
 import android.util.MutableInt;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.MediumTest;
-import androidx.test.runner.AndroidJUnit4;
 
 import com.android.car.vehiclehal.VehiclePropValueBuilder;
 import com.android.car.vehiclehal.test.MockedVehicleHal.VehicleHalPropertyHandler;
@@ -156,7 +156,7 @@
                 errorLatch.countDown();
             }
         });
-
+        mCarHvacManager.setBooleanProperty(PROP, AREA, true);
         getMockedVehicleHal().injectError(ERR_CODE, PROP, AREA);
         assertTrue(errorLatch.await(DEFAULT_WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS));
         assertEquals(PROP, propertyIdReceived.value);
diff --git a/tests/carservice_test/src/com/android/car/CarInfoManagerTest.java b/tests/carservice_test/src/com/android/car/CarInfoManagerTest.java
index 454295a..a6ae2fb 100644
--- a/tests/carservice_test/src/com/android/car/CarInfoManagerTest.java
+++ b/tests/carservice_test/src/com/android/car/CarInfoManagerTest.java
@@ -22,8 +22,8 @@
 import android.car.CarInfoManager;
 import android.hardware.automotive.vehicle.V2_0.VehicleProperty;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.MediumTest;
-import androidx.test.runner.AndroidJUnit4;
 
 import com.android.car.vehiclehal.VehiclePropValueBuilder;
 
diff --git a/tests/carservice_test/src/com/android/car/CarPackageManagerTest.java b/tests/carservice_test/src/com/android/car/CarPackageManagerTest.java
index 302e827..93a7103 100644
--- a/tests/carservice_test/src/com/android/car/CarPackageManagerTest.java
+++ b/tests/carservice_test/src/com/android/car/CarPackageManagerTest.java
@@ -25,9 +25,10 @@
 import android.car.content.pm.CarPackageManager;
 import android.util.Log;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.FlakyTest;
 import androidx.test.filters.SmallTest;
 import androidx.test.filters.Suppress;
-import androidx.test.runner.AndroidJUnit4;
 
 import com.android.car.pm.CarPackageManagerService;
 
@@ -81,6 +82,7 @@
     // if the test is necessary.
     @Suppress
     @Test
+    @FlakyTest
     public void testSettingWhitelist() throws Exception {
         init(false);
         final String carServicePackageName = "com.android.car";
diff --git a/tests/carservice_test/src/com/android/car/CarPowerManagementTest.java b/tests/carservice_test/src/com/android/car/CarPowerManagementTest.java
index 0ae1e1f..4d1c9df 100644
--- a/tests/carservice_test/src/com/android/car/CarPowerManagementTest.java
+++ b/tests/carservice_test/src/com/android/car/CarPowerManagementTest.java
@@ -30,8 +30,8 @@
 import android.os.SystemClock;
 
 import androidx.test.annotation.UiThreadTest;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.MediumTest;
-import androidx.test.runner.AndroidJUnit4;
 
 import com.android.car.systeminterface.DisplayInterface;
 import com.android.car.systeminterface.SystemInterface;
@@ -52,6 +52,9 @@
 @MediumTest
 public class CarPowerManagementTest extends MockedCarTestBase {
 
+    private static final int STATE_POLLING_INTERVAL_MS = 1; // Milliseconds
+    private static final int TEST_SHUTDOWN_TIMEOUT_MS = 10 * STATE_POLLING_INTERVAL_MS;
+
     private final PowerStatePropertyHandler mPowerStateHandler = new PowerStatePropertyHandler();
     private final MockDisplayInterface mMockDisplayInterface = new MockDisplayInterface();
 
@@ -139,7 +142,7 @@
      **********************************************************************************************/
     @Test
     @UiThreadTest
-    public void testInivalidTransitionsFromWaitForVhal() throws Exception {
+    public void testInvalidTransitionsFromWaitForVhal() throws Exception {
         assertWaitForVhal();
         mPowerStateHandler.sendStateAndExpectNoResponse(VehicleApPowerStateReq.CANCEL_SHUTDOWN, 0);
         mPowerStateHandler.sendStateAndExpectNoResponse(VehicleApPowerStateReq.FINISHED, 0);
@@ -162,8 +165,6 @@
     @UiThreadTest
     public void testInvalidTransitionsFromPrepareShutdown() throws Exception {
         assertWaitForVhal();
-        // Increase the timeout to handle all the test cases here
-        CarPowerManagementService.setShutdownPrepareTimeout(15 * 1000);
         // Transition to SHUTDOWN_PREPARE first
         mPowerStateHandler.sendStateAndCheckResponse(
                 VehicleApPowerStateReq.SHUTDOWN_PREPARE,
@@ -259,6 +260,22 @@
         mMockDisplayInterface.waitForDisplayState(false);
     }
 
+    @Test
+    @UiThreadTest
+    public void testSleepImmediateEntry() throws Exception {
+        assertWaitForVhal();
+        mMockDisplayInterface.waitForDisplayState(false);
+        mPowerStateHandler.sendStateAndCheckResponse(
+                VehicleApPowerStateReq.ON,
+                0,
+                VehicleApPowerStateReport.ON);
+        mMockDisplayInterface.waitForDisplayState(true);
+        mPowerStateHandler.sendPowerState(
+                VehicleApPowerStateReq.SHUTDOWN_PREPARE,
+                VehicleApPowerStateShutdownParam.SLEEP_IMMEDIATELY);
+        assertResponse(VehicleApPowerStateReport.SHUTDOWN_START, 0, true);
+    }
+
     // Check that 'expectedState' was reached and is the current state.
     private void assertResponse(int expectedState, int expectedParam, boolean checkParam)
             throws Exception {
@@ -293,7 +310,6 @@
         int[] first = setEvents.getFirst();
         assertEquals(VehicleApPowerStateReport.WAIT_FOR_VHAL, first[0]);
         assertEquals(0, first[1]);
-        CarPowerManagementService.setShutdownPrepareTimeout(0);
     }
 
     private final class MockDisplayInterface implements DisplayInterface {
@@ -320,16 +336,18 @@
         }
 
         @Override
-        public void startDisplayStateMonitoring(CarPowerManagementService service) {}
+        public void startDisplayStateMonitoring(CarPowerManagementService service) {
+            // To reduce test duration, decrease the polling interval and the
+            // time to wait for a shutdown
+            service.setShutdownTimersForTest(STATE_POLLING_INTERVAL_MS,
+                    TEST_SHUTDOWN_TIMEOUT_MS);
+        }
 
         @Override
         public void stopDisplayStateMonitoring() {}
 
         @Override
         public void refreshDisplayBrightness() {}
-
-        @Override
-        public void reconfigureSecondaryDisplays() {}
     }
 
     private class PowerStatePropertyHandler implements VehicleHalPropertyHandler {
@@ -399,6 +417,7 @@
                     if (found) {
                         LinkedList<int[]> res = mSetStates;
                         mSetStates = new LinkedList<>();
+                        mSetWaitSemaphore.drainPermits();
                         return res;
                     }
                 }
@@ -412,24 +431,24 @@
         }
 
         /**
-         * Checks that a power state transition does NOT occur.  If any state does occur during
-         * the timeout period, then the test fails.
+         * Checks that a power state transition does NOT occur. If any state does occur during
+         * the timeout period (other than a POSTPONE), then the test fails.
          */
         private void sendStateAndExpectNoResponse(int state, int param) throws Exception {
             sendPowerState(state, param);
             // Wait to see if a state transition occurs
-            if (!mSetWaitSemaphore.tryAcquire(DEFAULT_WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
+            if (!mSetWaitSemaphore.tryAcquire(TEST_SHUTDOWN_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
                 // No state transition, this is a success!
                 return;
-            } else {
-                synchronized (this) {
-                    int[] newState = mSetStates.pop();
-                    if (newState[0] != VehicleApPowerStateReport.SHUTDOWN_POSTPONE) {
-                        fail("Unexpected state change occured, state=" + newState[0]);
-                    }
-                    // Reset the collected states
-                    mSetStates = new LinkedList<>();
+            }
+            synchronized (this) {
+                int[] newState = mSetStates.pop();
+                if (newState[0] != VehicleApPowerStateReport.SHUTDOWN_POSTPONE) {
+                    fail("Unexpected state change occurred, state=" + newState[0]);
                 }
+                // Reset the collected states
+                mSetStates = new LinkedList<>();
+                mSetWaitSemaphore.drainPermits();
             }
         }
 
diff --git a/tests/carservice_test/src/com/android/car/CarProjectionManagerTest.java b/tests/carservice_test/src/com/android/car/CarProjectionManagerTest.java
index 9fe3a84..ebc493f 100644
--- a/tests/carservice_test/src/com/android/car/CarProjectionManagerTest.java
+++ b/tests/carservice_test/src/com/android/car/CarProjectionManagerTest.java
@@ -30,8 +30,8 @@
 import android.util.Log;
 import android.view.KeyEvent;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.MediumTest;
-import androidx.test.runner.AndroidJUnit4;
 
 import com.android.car.vehiclehal.VehiclePropValueBuilder;
 import com.android.car.vehiclehal.test.MockedVehicleHal.VehicleHalPropertyHandler;
diff --git a/tests/carservice_test/src/com/android/car/CarPropertyManagerTest.java b/tests/carservice_test/src/com/android/car/CarPropertyManagerTest.java
new file mode 100644
index 0000000..945d554
--- /dev/null
+++ b/tests/carservice_test/src/com/android/car/CarPropertyManagerTest.java
@@ -0,0 +1,285 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car;
+
+import android.car.Car;
+import android.car.VehicleAreaType;
+import android.car.VehiclePropertyIds;
+import android.car.hardware.CarPropertyConfig;
+import android.car.hardware.CarPropertyValue;
+import android.car.hardware.property.CarPropertyManager;
+import android.hardware.automotive.vehicle.V2_0.VehicleArea;
+import android.hardware.automotive.vehicle.V2_0.VehicleAreaSeat;
+import android.hardware.automotive.vehicle.V2_0.VehiclePropValue;
+import android.hardware.automotive.vehicle.V2_0.VehiclePropertyGroup;
+import android.hardware.automotive.vehicle.V2_0.VehiclePropertyType;
+import android.os.SystemClock;
+import android.util.Log;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.MediumTest;
+
+import com.android.car.vehiclehal.test.MockedVehicleHal.VehicleHalPropertyHandler;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+
+/**
+ * Test for {@link android.car.hardware.property.CarPropertyManager}
+ */
+@RunWith(AndroidJUnit4.class)
+@MediumTest
+public class CarPropertyManagerTest extends MockedCarTestBase {
+
+    private static final String TAG = CarPropertyManagerTest.class.getSimpleName();
+
+    /**
+     * configArray[0], 1 indicates the property has a String value
+     * configArray[1], 1 indicates the property has a Boolean value .
+     * configArray[2], 1 indicates the property has a Integer value
+     * configArray[3], the number indicates the size of Integer[]  in the property.
+     * configArray[4], 1 indicates the property has a Long value .
+     * configArray[5], the number indicates the size of Long[]  in the property.
+     * configArray[6], 1 indicates the property has a Float value .
+     * configArray[7], the number indicates the size of Float[] in the property.
+     * configArray[8], the number indicates the size of byte[] in the property.
+     */
+    private static final java.util.Collection<Integer> CONFIG_ARRAY_1 =
+            Arrays.asList(1, 0, 1, 0, 1, 0, 0, 0, 0);
+    private static final java.util.Collection<Integer> CONFIG_ARRAY_2 =
+            Arrays.asList(1, 1, 1, 0, 0, 0, 0, 2, 0);
+    private static final Object[] EXPECTED_VALUE_1 = {"android", 1, 1L};
+    private static final Object[] EXPECTED_VALUE_2 = {"android", true, 3, 1.1f, 2f};
+
+    private static final int CUSTOM_GLOBAL_MIXED_PROP_ID_1 =
+            0x1101 | VehiclePropertyGroup.VENDOR | VehiclePropertyType.MIXED | VehicleArea.SEAT;
+    private static final int CUSTOM_GLOBAL_MIXED_PROP_ID_2 =
+            0x1102 | VehiclePropertyGroup.VENDOR | VehiclePropertyType.MIXED | VehicleArea.GLOBAL;
+    // Use FAKE_PROPERTY_ID to test api return null or throw exception.
+    private static final int FAKE_PROPERTY_ID = 0x111;
+
+    private static final int DRIVER_SIDE_AREA_ID = VehicleAreaSeat.ROW_1_LEFT
+                                                    | VehicleAreaSeat.ROW_2_LEFT;
+    private static final int PASSENGER_SIDE_AREA_ID = VehicleAreaSeat.ROW_1_RIGHT
+                                                    | VehicleAreaSeat.ROW_2_CENTER
+                                                    | VehicleAreaSeat.ROW_2_RIGHT;
+    private static final float INIT_TEMP_VALUE = 16f;
+    private static final float CHANGED_TEMP_VALUE = 20f;
+
+    private CarPropertyManager mManager;
+
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        mManager = (CarPropertyManager) getCar().getCarManager(Car.PROPERTY_SERVICE);
+        Assert.assertNotNull(mManager);
+    }
+
+    @Test
+    public void testMixedPropertyConfigs() {
+        List<CarPropertyConfig> configs = mManager.getPropertyList();
+        Assert.assertEquals(3, configs.size());
+
+        for (CarPropertyConfig cfg : configs) {
+            switch (cfg.getPropertyId()) {
+                case CUSTOM_GLOBAL_MIXED_PROP_ID_1:
+                    Assert.assertArrayEquals(CONFIG_ARRAY_1.toArray(),
+                            cfg.getConfigArray().toArray());
+                    break;
+                case CUSTOM_GLOBAL_MIXED_PROP_ID_2:
+                    Assert.assertArrayEquals(CONFIG_ARRAY_2.toArray(),
+                            cfg.getConfigArray().toArray());
+                    break;
+                case VehiclePropertyIds.HVAC_TEMPERATURE_SET:
+                    break;
+                default:
+                    Assert.fail("Unexpected CarPropertyConfig: " + cfg.toString());
+            }
+        }
+    }
+
+    @Test
+    public void testGetMixTypeProperty() {
+        mManager.setProperty(Object[].class, CUSTOM_GLOBAL_MIXED_PROP_ID_1,
+                0, EXPECTED_VALUE_1);
+        CarPropertyValue<Object[]> result = mManager.getProperty(
+                CUSTOM_GLOBAL_MIXED_PROP_ID_1, 0);
+        Assert.assertArrayEquals(EXPECTED_VALUE_1, result.getValue());
+
+        mManager.setProperty(Object[].class, CUSTOM_GLOBAL_MIXED_PROP_ID_2,
+                0, EXPECTED_VALUE_2);
+        result = mManager.getProperty(
+                CUSTOM_GLOBAL_MIXED_PROP_ID_2, 0);
+        Assert.assertArrayEquals(EXPECTED_VALUE_2, result.getValue());
+    }
+
+    @Test
+    public void testGetPropertyConfig() {
+        CarPropertyConfig config = mManager.getCarPropertyConfig(CUSTOM_GLOBAL_MIXED_PROP_ID_1);
+        Assert.assertEquals(CUSTOM_GLOBAL_MIXED_PROP_ID_1, config.getPropertyId());
+        // return null if can not find the propertyConfig for the property.
+        Assert.assertNull(mManager.getCarPropertyConfig(FAKE_PROPERTY_ID));
+    }
+
+    @Test
+    public void testGetAreaId() {
+        int result = mManager.getAreaId(CUSTOM_GLOBAL_MIXED_PROP_ID_1, VehicleAreaSeat.ROW_1_LEFT);
+        Assert.assertEquals(DRIVER_SIDE_AREA_ID, result);
+
+        //test for the GLOBAL property
+        int globalAreaId =
+                mManager.getAreaId(CUSTOM_GLOBAL_MIXED_PROP_ID_2, VehicleAreaSeat.ROW_1_LEFT);
+        Assert.assertEquals(VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL, globalAreaId);
+
+        //test exception
+        try {
+            int areaId = mManager.getAreaId(CUSTOM_GLOBAL_MIXED_PROP_ID_1,
+                    VehicleAreaSeat.ROW_3_CENTER);
+            Assert.fail("Unexpected areaId: " + areaId);
+        } catch (IllegalArgumentException e) {
+            Log.v(TAG, e.getMessage());
+        }
+
+        try {
+            // test exception
+            int areaIdForFakeProp = mManager.getAreaId(FAKE_PROPERTY_ID,
+                    VehicleAreaSeat.ROW_1_LEFT);
+            Assert.fail("Unexpected areaId for fake property: " + areaIdForFakeProp);
+        } catch (IllegalArgumentException e) {
+            Log.v(TAG, e.getMessage());
+        }
+    }
+
+    @Test
+    public void testNotReceiveOnErrorEvent() {
+        TestCallback callback = new TestCallback();
+        mManager.registerCallback(callback, VehiclePropertyIds.HVAC_TEMPERATURE_SET,
+                CarPropertyManager.SENSOR_RATE_ONCHANGE);
+        injectErrorEvent(VehiclePropertyIds.HVAC_TEMPERATURE_SET, PASSENGER_SIDE_AREA_ID,
+                CarPropertyManager.CAR_SET_PROPERTY_ERROR_CODE_UNKNOWN);
+        // app never change the value of HVAC_TEMPERATURE_SET, it won't get an error code.
+        SystemClock.sleep(SHORT_WAIT_TIMEOUT_MS);
+        Assert.assertFalse(callback.mReceivedErrorEventWithErrorCode);
+        Assert.assertFalse(callback.mReceivedErrorEventWithOutErrorCode);
+    }
+
+    @Test
+    public void testReceiveOnErrorEvent() {
+        TestCallback callback = new TestCallback();
+        mManager.registerCallback(callback, VehiclePropertyIds.HVAC_TEMPERATURE_SET,
+                CarPropertyManager.SENSOR_RATE_ONCHANGE);
+        mManager.setFloatProperty(
+                VehiclePropertyIds.HVAC_TEMPERATURE_SET, PASSENGER_SIDE_AREA_ID,
+                CHANGED_TEMP_VALUE);
+        injectErrorEvent(VehiclePropertyIds.HVAC_TEMPERATURE_SET, PASSENGER_SIDE_AREA_ID,
+                CarPropertyManager.CAR_SET_PROPERTY_ERROR_CODE_UNKNOWN);
+        SystemClock.sleep(SHORT_WAIT_TIMEOUT_MS);
+        Assert.assertTrue(callback.mReceivedErrorEventWithErrorCode);
+        Assert.assertEquals(CarPropertyManager.CAR_SET_PROPERTY_ERROR_CODE_UNKNOWN,
+                callback.mErrorCode);
+        Assert.assertFalse(callback.mReceivedErrorEventWithOutErrorCode);
+    }
+
+    @Test
+    public void testNotReceiveOnErrorEventAfterUnregister() {
+        TestCallback callback1 = new TestCallback();
+        TestCallback callback2 = new TestCallback();
+        mManager.registerCallback(callback1, VehiclePropertyIds.HVAC_TEMPERATURE_SET,
+                CarPropertyManager.SENSOR_RATE_ONCHANGE);
+        mManager.registerCallback(callback2, VehiclePropertyIds.HVAC_TEMPERATURE_SET,
+                CarPropertyManager.SENSOR_RATE_ONCHANGE);
+        mManager.setFloatProperty(
+                VehiclePropertyIds.HVAC_TEMPERATURE_SET, PASSENGER_SIDE_AREA_ID,
+                CHANGED_TEMP_VALUE);
+        mManager.unregisterCallback(callback1, VehiclePropertyIds.HVAC_TEMPERATURE_SET);
+        injectErrorEvent(VehiclePropertyIds.HVAC_TEMPERATURE_SET, PASSENGER_SIDE_AREA_ID,
+                CarPropertyManager.CAR_SET_PROPERTY_ERROR_CODE_UNKNOWN);
+        SystemClock.sleep(SHORT_WAIT_TIMEOUT_MS);
+        Assert.assertFalse(callback1.mReceivedErrorEventWithErrorCode);
+        Assert.assertFalse(callback1.mReceivedErrorEventWithOutErrorCode);
+    }
+
+    @Override
+    protected synchronized void configureMockedHal() {
+        PropertyHandler handler = new PropertyHandler();
+        addProperty(CUSTOM_GLOBAL_MIXED_PROP_ID_1, handler).setConfigArray(CONFIG_ARRAY_1)
+                .addAreaConfig(DRIVER_SIDE_AREA_ID).addAreaConfig(PASSENGER_SIDE_AREA_ID);
+        addProperty(CUSTOM_GLOBAL_MIXED_PROP_ID_2, handler).setConfigArray(CONFIG_ARRAY_2);
+
+        VehiclePropValue tempValue = new VehiclePropValue();
+        tempValue.value.floatValues.add(INIT_TEMP_VALUE);
+        tempValue.prop = VehiclePropertyIds.HVAC_TEMPERATURE_SET;
+        addProperty(VehiclePropertyIds.HVAC_TEMPERATURE_SET, tempValue)
+                .addAreaConfig(DRIVER_SIDE_AREA_ID).addAreaConfig(PASSENGER_SIDE_AREA_ID);
+    }
+
+    private class PropertyHandler implements VehicleHalPropertyHandler {
+        HashMap<Integer, VehiclePropValue> mMap = new HashMap<>();
+        @Override
+        public synchronized void onPropertySet(VehiclePropValue value) {
+            mMap.put(value.prop, value);
+        }
+
+        @Override
+        public synchronized VehiclePropValue onPropertyGet(VehiclePropValue value) {
+            VehiclePropValue currentValue = mMap.get(value.prop);
+            return currentValue != null ? currentValue : value;
+        }
+
+        @Override
+        public synchronized void onPropertySubscribe(int property, float sampleRate) {
+            Log.d(TAG, "onPropertySubscribe property "
+                    + property + " sampleRate " + sampleRate);
+        }
+
+        @Override
+        public synchronized void onPropertyUnsubscribe(int property) {
+            Log.d(TAG, "onPropertyUnSubscribe property " + property);
+        }
+    }
+
+    private static class TestCallback implements CarPropertyManager.CarPropertyEventCallback {
+
+        private static final String CALLBACK_TAG = "ErrorEventTest";
+        private boolean mReceivedErrorEventWithErrorCode = false;
+        private boolean mReceivedErrorEventWithOutErrorCode = false;
+        private int mErrorCode;
+        @Override
+        public void onChangeEvent(CarPropertyValue value) {
+            Log.d(CALLBACK_TAG, "onChangeEvent: " + value);
+        }
+
+        @Override
+        public void onErrorEvent(int propId, int zone) {
+            mReceivedErrorEventWithOutErrorCode = true;
+            Log.d(CALLBACK_TAG, "onErrorEvent, propId: " + propId + " zone: " + zone);
+        }
+
+        @Override
+        public void onErrorEvent(int propId, int areaId, int errorCode) {
+            mReceivedErrorEventWithErrorCode = true;
+            mErrorCode = errorCode;
+            Log.d(CALLBACK_TAG, "onErrorEvent, propId: " + propId + " areaId: " + areaId
+                    + "errorCode: " + errorCode);
+        }
+    }
+}
diff --git a/tests/carservice_test/src/com/android/car/CarPropertyServiceTest.java b/tests/carservice_test/src/com/android/car/CarPropertyServiceTest.java
new file mode 100644
index 0000000..2d5c1c2
--- /dev/null
+++ b/tests/carservice_test/src/com/android/car/CarPropertyServiceTest.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyFloat;
+import static org.mockito.Mockito.atLeast;
+import static org.mockito.Mockito.verify;
+
+import static java.lang.Integer.toHexString;
+
+import android.hardware.automotive.vehicle.V2_0.VehicleGear;
+import android.hardware.automotive.vehicle.V2_0.VehiclePropValue;
+import android.hardware.automotive.vehicle.V2_0.VehicleProperty;
+import android.os.SystemClock;
+import android.util.Log;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.MediumTest;
+
+import com.android.car.vehiclehal.VehiclePropValueBuilder;
+import com.android.car.vehiclehal.test.MockedVehicleHal.VehicleHalPropertyHandler;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Test for {@link com.android.car.CarPropertyService}
+ */
+@RunWith(AndroidJUnit4.class)
+@MediumTest
+public class CarPropertyServiceTest extends MockedCarTestBase {
+    private static final String TAG = CarPropertyServiceTest.class.getSimpleName();
+
+    private final Map<Integer, VehiclePropValue> mDefaultPropValues = new HashMap<>();
+
+    private CarPropertyService mService;
+
+    public CarPropertyServiceTest() {
+        // Unusual default values for the vehicle properties registered to listen via
+        // CarPropertyService.registerListener. Unusual default values like the car is in motion,
+        // night mode is on, or the car is low on fuel.
+        mDefaultPropValues.put(VehicleProperty.GEAR_SELECTION,
+                VehiclePropValueBuilder.newBuilder(VehicleProperty.GEAR_SELECTION)
+                .addIntValue(VehicleGear.GEAR_DRIVE)
+                .setTimestamp(SystemClock.elapsedRealtimeNanos()).build());
+        mDefaultPropValues.put(VehicleProperty.PARKING_BRAKE_ON,
+                VehiclePropValueBuilder.newBuilder(VehicleProperty.PARKING_BRAKE_ON)
+                .setBooleanValue(false)
+                .setTimestamp(SystemClock.elapsedRealtimeNanos()).build());
+        mDefaultPropValues.put(VehicleProperty.PERF_VEHICLE_SPEED,
+                VehiclePropValueBuilder.newBuilder(VehicleProperty.PERF_VEHICLE_SPEED)
+                .addFloatValue(30.0f)
+                .setTimestamp(SystemClock.elapsedRealtimeNanos()).build());
+        mDefaultPropValues.put(VehicleProperty.NIGHT_MODE,
+                VehiclePropValueBuilder.newBuilder(VehicleProperty.NIGHT_MODE)
+                .setBooleanValue(true)
+                .setTimestamp(SystemClock.elapsedRealtimeNanos()).build());
+    }
+
+    @Override
+    protected synchronized void configureMockedHal() {
+        PropertyHandler handler = new PropertyHandler();
+        for (VehiclePropValue value : mDefaultPropValues.values()) {
+            handler.onPropertySet(value);
+            addProperty(value.prop, handler);
+        }
+    }
+
+    @Override
+    protected synchronized void spyOnInitMockedHal() {
+        mService = getCarPropertyService();
+        assertThat(mService).isNotNull();
+        spyOn(mService);
+    }
+
+    @Test
+    public void testMatchesDefaultPropertyValues() {
+        Set<Integer> expectedPropIds = mDefaultPropValues.keySet();
+        ArgumentCaptor<Integer> propIdCaptor = ArgumentCaptor.forClass(Integer.class);
+        verify(mService, atLeast(expectedPropIds.size())).registerListener(
+                propIdCaptor.capture(), anyFloat(), any());
+
+        Set<Integer> actualPropIds = new HashSet<Integer>(propIdCaptor.getAllValues());
+        assertWithMessage("Should assign default values for missing property IDs")
+                .that(expectedPropIds).containsAllIn(actualPropIds.toArray());
+        assertWithMessage("Missing registerListener for property IDs")
+                .that(actualPropIds).containsAllIn(expectedPropIds.toArray());
+    }
+
+    private static final class PropertyHandler implements VehicleHalPropertyHandler {
+        private final Map<Integer, VehiclePropValue> mMap = new HashMap<>();
+
+        @Override
+        public synchronized void onPropertySet(VehiclePropValue value) {
+            mMap.put(value.prop, value);
+        }
+
+        @Override
+        public synchronized VehiclePropValue onPropertyGet(VehiclePropValue value) {
+            assertWithMessage("onPropertyGet missing property: %s", toHexString(value.prop))
+                    .that(mMap).containsKey(value.prop);
+            VehiclePropValue currentValue = mMap.get(value.prop);
+            return currentValue != null ? currentValue : value;
+        }
+
+        @Override
+        public synchronized void onPropertySubscribe(int property, float sampleRate) {
+            assertWithMessage("onPropertySubscribe missing property: %s", toHexString(property))
+                    .that(mMap).containsKey(property);
+            Log.d(TAG, "onPropertySubscribe property "
+                    + property + " sampleRate " + sampleRate);
+        }
+
+        @Override
+        public synchronized void onPropertyUnsubscribe(int property) {
+            assertWithMessage("onPropertyUnsubscribe missing property: %s", toHexString(property))
+                    .that(mMap).containsKey(property);
+            Log.d(TAG, "onPropertyUnSubscribe property " + property);
+        }
+    }
+}
diff --git a/tests/carservice_test/src/com/android/car/CarSensorManagerTest.java b/tests/carservice_test/src/com/android/car/CarSensorManagerTest.java
index 5bc2c28..b6aa497 100644
--- a/tests/carservice_test/src/com/android/car/CarSensorManagerTest.java
+++ b/tests/carservice_test/src/com/android/car/CarSensorManagerTest.java
@@ -29,16 +29,20 @@
 import android.hardware.automotive.vehicle.V2_0.VehiclePropValue;
 import android.hardware.automotive.vehicle.V2_0.VehicleProperty;
 import android.os.SystemClock;
-import android.test.suitebuilder.annotation.MediumTest;
 import android.util.Log;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.MediumTest;
+
 import com.android.car.vehiclehal.VehiclePropValueBuilder;
 
 import org.junit.Test;
+import org.junit.runner.RunWith;
 
 /**
  * Test the public entry points for the CarSensorManager
  */
+@RunWith(AndroidJUnit4.class)
 @MediumTest
 public class CarSensorManagerTest extends MockedCarTestBase {
     private static final String TAG = CarSensorManagerTest.class.getSimpleName();
@@ -146,12 +150,12 @@
         // Set up our listener callback
         SensorListener listener = new SensorListener();
         mCarSensorManager.registerListener(listener,
-                CarSensorManager.SENSOR_TYPE_NIGHT,
+                CarSensorManager.SENSOR_TYPE_PARKING_BRAKE,
                 CarSensorManager.SENSOR_RATE_FASTEST);
 
         VehiclePropValue value;
         CarSensorEvent event;
-        CarSensorEvent.NightData data = null;
+        CarSensorEvent.ParkingBrakeData data = null;
 
         // Clear event generated by registerCallback()
         listener.waitForSensorChange();
@@ -159,56 +163,58 @@
 
         // Set the value TRUE and wait for the event to arrive
         getMockedVehicleHal().injectEvent(
-                VehiclePropValueBuilder.newBuilder(VehicleProperty.NIGHT_MODE)
+                VehiclePropValueBuilder.newBuilder(VehicleProperty.PARKING_BRAKE_ON)
                         .setBooleanValue(true)
                         .setTimestamp(51L)
                         .build(), true);
         assertTrue(listener.waitForSensorChange(51L));
 
         // Ensure we got the expected event
-        assertEquals(listener.getLastEvent().sensorType, CarSensorManager.SENSOR_TYPE_NIGHT);
+        assertEquals(listener.getLastEvent().sensorType,
+                CarSensorManager.SENSOR_TYPE_PARKING_BRAKE);
 
         // Ensure we got the expected value in our callback
-        data = listener.getLastEvent().getNightData(data);
-        Log.d(TAG, "NightMode " + data.isNightMode + " at " + data.timestamp);
-        assertTrue(data.isNightMode);
+        data = listener.getLastEvent().getParkingBrakeData(data);
+        Log.d(TAG, "Parking: " + data.isEngaged + " at " + data.timestamp);
+        assertTrue(data.isEngaged);
 
         // Ensure we have the expected value in the sensor manager's cache
-        event = mCarSensorManager.getLatestSensorEvent(CarSensorManager.SENSOR_TYPE_NIGHT);
+        event = mCarSensorManager.getLatestSensorEvent(CarSensorManager.SENSOR_TYPE_PARKING_BRAKE);
         assertNotNull(event);
-        data = event.getNightData(data);
+        data = event.getParkingBrakeData(data);
         assertEquals("Unexpected event timestamp", data.timestamp, 51);
-        assertTrue("Unexpected value", data.isNightMode);
+        assertTrue("Unexpected value", data.isEngaged);
 
         listener.reset();
         // Set the value FALSE
         getMockedVehicleHal().injectEvent(
-                VehiclePropValueBuilder.newBuilder(VehicleProperty.NIGHT_MODE)
+                VehiclePropValueBuilder.newBuilder(VehicleProperty.PARKING_BRAKE_ON)
                         .setTimestamp(1001)
                         .setBooleanValue(false)
                         .build(), true);
         assertTrue(listener.waitForSensorChange(1001));
 
         // Ensure we got the expected event
-        assertEquals(listener.getLastEvent().sensorType, CarSensorManager.SENSOR_TYPE_NIGHT);
+        assertEquals(listener.getLastEvent().sensorType,
+                CarSensorManager.SENSOR_TYPE_PARKING_BRAKE);
 
         // Ensure we got the expected value in our callback
-        data = listener.getLastEvent().getNightData(data);
+        data = listener.getLastEvent().getParkingBrakeData(data);
         assertEquals("Unexpected event timestamp", 1001, data.timestamp);
-        assertFalse("Unexpected value", data.isNightMode);
+        assertFalse("Unexpected value", data.isEngaged);
 
         // Ensure we have the expected value in the sensor manager's cache
-        event = mCarSensorManager.getLatestSensorEvent(CarSensorManager.SENSOR_TYPE_NIGHT);
+        event = mCarSensorManager.getLatestSensorEvent(CarSensorManager.SENSOR_TYPE_PARKING_BRAKE);
         assertNotNull(event);
-        data = event.getNightData(data);
-        assertFalse(data.isNightMode);
+        data = event.getParkingBrakeData(data);
+        assertFalse(data.isEngaged);
 
         // Unregister our handler (from all sensor types)
         mCarSensorManager.unregisterListener(listener);
 
         listener.reset();
         // Set the value TRUE again
-        value = VehiclePropValueBuilder.newBuilder(VehicleProperty.NIGHT_MODE)
+        value = VehiclePropValueBuilder.newBuilder(VehicleProperty.PARKING_BRAKE_ON)
                 .setTimestamp(2001)
                 .setBooleanValue(true)
                 .build();
@@ -219,11 +225,11 @@
         assertFalse(listener.waitForSensorChange(2001));
 
         // Despite us not having a callback registered, the Sensor Manager should see the update
-        event = mCarSensorManager.getLatestSensorEvent(CarSensorManager.SENSOR_TYPE_NIGHT);
+        event = mCarSensorManager.getLatestSensorEvent(CarSensorManager.SENSOR_TYPE_PARKING_BRAKE);
         assertNotNull(event);
-        data = event.getNightData(data);
+        data = event.getParkingBrakeData(data);
         assertEquals("Unexpected event timestamp", data.timestamp, 2001);
-        assertTrue("Unexpected value", data.isNightMode);
+        assertTrue("Unexpected value", data.isEngaged);
     }
 
     @Test
diff --git a/tests/carservice_test/src/com/android/car/CarStorageMonitoringTest.java b/tests/carservice_test/src/com/android/car/CarStorageMonitoringTest.java
index 61c23a0..7fb9066 100644
--- a/tests/carservice_test/src/com/android/car/CarStorageMonitoringTest.java
+++ b/tests/carservice_test/src/com/android/car/CarStorageMonitoringTest.java
@@ -21,10 +21,10 @@
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
-import static org.mockito.Mockito.doNothing;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
 import android.car.Car;
 import android.car.storagemonitoring.CarStorageMonitoringManager;
 import android.car.storagemonitoring.IoStats;
@@ -33,6 +33,7 @@
 import android.car.storagemonitoring.UidIoRecord;
 import android.car.storagemonitoring.WearEstimate;
 import android.car.storagemonitoring.WearEstimateChange;
+import android.content.Context;
 import android.content.Intent;
 import android.os.SystemClock;
 import android.util.JsonWriter;
@@ -40,8 +41,8 @@
 import android.util.Pair;
 import android.util.SparseArray;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.MediumTest;
-import androidx.test.runner.AndroidJUnit4;
 
 import com.android.car.storagemonitoring.LifetimeWriteInfoProvider;
 import com.android.car.storagemonitoring.UidIoStatsProvider;
@@ -58,7 +59,6 @@
 import org.junit.Test;
 import org.junit.rules.TestName;
 import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
 
 import java.io.File;
 import java.io.FileWriter;
@@ -308,8 +308,10 @@
 
     private CarStorageMonitoringManager mCarStorageMonitoringManager;
 
-    private ArgumentCaptor<Intent> mBroadcastIntentArg = ArgumentCaptor.forClass(Intent.class);
-    private ArgumentCaptor<String> mBroadcastStringArg = ArgumentCaptor.forClass(String.class);
+    @Override
+    protected MockedCarTestContext createMockedCarTestContext(Context context) {
+        return new CarStorageMonitoringTestContext(context);
+    }
 
     @Override
     protected synchronized SystemInterface.Builder getSystemInterfaceBuilder() {
@@ -371,6 +373,8 @@
     @Override
     protected synchronized void configureResourceOverrides(MockResources resources) {
         super.configureResourceOverrides(resources);
+        resources.overrideResource(com.android.car.R.array.config_allowed_optional_car_features,
+                new String[] {Car.STORAGE_MONITORING_SERVICE});
         final ResourceOverrides overrides = PER_TEST_RESOURCES.getOrDefault(getName(), null);
         if (overrides != null) {
             overrides.overrideResources(resources);
@@ -380,10 +384,7 @@
     @Override
     public void setUp() throws Exception {
         super.setUp();
-        doNothing().when(getCarServiceContext()).sendBroadcast(mBroadcastIntentArg.capture(),
-                mBroadcastStringArg.capture());
         mMockSystemStateInterface.executeBootCompletedActions();
-
         mCarStorageMonitoringManager =
             (CarStorageMonitoringManager) getCar().getCarManager(Car.STORAGE_MONITORING_SERVICE);
     }
@@ -654,7 +655,9 @@
         mMockStorageMonitoringInterface.addIoStatsRecord(record);
         mMockTimeInterface.setUptime(500).tick();
 
-        assertBroadcastArgs(mBroadcastIntentArg.getValue(), mBroadcastStringArg.getValue());
+        CarStorageMonitoringTestContext context = (CarStorageMonitoringTestContext) getContext();
+        assertBroadcastArgs(context.getLastBroadcastedIntent(),
+                context.getLastBroadcastedString());
     }
 
     @Test
@@ -676,7 +679,9 @@
         mMockStorageMonitoringInterface.addIoStatsRecord(record);
         mMockTimeInterface.setUptime(500).tick();
 
-        assertBroadcastArgs(mBroadcastIntentArg.getValue(), mBroadcastStringArg.getValue());
+        CarStorageMonitoringTestContext context = (CarStorageMonitoringTestContext) getContext();
+        assertBroadcastArgs(context.getLastBroadcastedIntent(),
+                context.getLastBroadcastedString());
     }
 
     @Test
@@ -771,6 +776,35 @@
 
     }
 
+    /**
+     * Special version of {@link MockedCarTestContext} that stores the last arguments used when
+     * invoking {@method sendBroadcast(Intent, String)} to be retrieved later by the test.
+     */
+    private class CarStorageMonitoringTestContext extends MockedCarTestContext {
+        private Intent mLastBroadcastedIntent;
+        private String mLastBroadcastedString;
+
+        CarStorageMonitoringTestContext(Context base) {
+            super(base);
+        }
+
+        @Override
+        public void sendBroadcast(@RequiresPermission Intent intent,
+                @Nullable String receiverPermission) {
+            mLastBroadcastedIntent = intent;
+            mLastBroadcastedString = receiverPermission;
+            super.sendBroadcast(intent, receiverPermission);
+        }
+
+        Intent getLastBroadcastedIntent() {
+            return mLastBroadcastedIntent;
+        }
+
+        String getLastBroadcastedString() {
+            return mLastBroadcastedString;
+        }
+    }
+
     static final class MockStorageMonitoringInterface implements StorageMonitoringInterface,
         WearInformationProvider {
         private WearInformation mWearInformation = null;
diff --git a/tests/carservice_test/src/com/android/car/CarUxRestrictionsConfigurationXmlParserTest.java b/tests/carservice_test/src/com/android/car/CarUxRestrictionsConfigurationXmlParserTest.java
index bb3c7bf..a6b3fe1 100644
--- a/tests/carservice_test/src/com/android/car/CarUxRestrictionsConfigurationXmlParserTest.java
+++ b/tests/carservice_test/src/com/android/car/CarUxRestrictionsConfigurationXmlParserTest.java
@@ -29,9 +29,9 @@
 import android.content.Context;
 import android.util.ArraySet;
 
-import androidx.test.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.MediumTest;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -48,7 +48,7 @@
     private static final String UX_RESTRICTION_MODE_PASSENGER = "passenger";
 
     private Context getContext() {
-        return InstrumentationRegistry.getTargetContext();
+        return InstrumentationRegistry.getInstrumentation().getTargetContext();
     }
 
     @Test
diff --git a/tests/carservice_test/src/com/android/car/CarUxRestrictionsManagerServiceTest.java b/tests/carservice_test/src/com/android/car/CarUxRestrictionsManagerServiceTest.java
index 0b77d8e..454a9eb 100644
--- a/tests/carservice_test/src/com/android/car/CarUxRestrictionsManagerServiceTest.java
+++ b/tests/carservice_test/src/com/android/car/CarUxRestrictionsManagerServiceTest.java
@@ -22,8 +22,12 @@
 import static android.car.drivingstate.CarUxRestrictions.UX_RESTRICTIONS_BASELINE;
 import static android.car.drivingstate.CarUxRestrictionsManager.UX_RESTRICTION_MODE_BASELINE;
 
+import static androidx.test.InstrumentationRegistry.getContext;
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
 import static com.android.car.CarUxRestrictionsManagerService.CONFIG_FILENAME_PRODUCTION;
 import static com.android.car.CarUxRestrictionsManagerService.CONFIG_FILENAME_STAGED;
+import static com.android.car.CarUxRestrictionsManagerService.DEFAULT_PORT;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -46,11 +50,16 @@
 import android.content.Context;
 import android.content.res.Resources;
 import android.hardware.automotive.vehicle.V2_0.VehicleProperty;
+import android.hardware.display.DisplayManager;
+import android.hardware.display.VirtualDisplay;
+import android.os.IBinder;
+import android.os.IRemoteCallback;
 import android.os.RemoteException;
 import android.os.SystemClock;
 import android.util.JsonWriter;
+import android.view.Display;
+import android.view.DisplayAddress;
 
-import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.MediumTest;
 import androidx.test.runner.AndroidJUnit4;
 
@@ -59,10 +68,12 @@
 import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
 import org.xmlpull.v1.XmlPullParserException;
 
 import java.io.File;
@@ -75,21 +86,36 @@
 import java.util.Arrays;
 import java.util.List;
 import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
 
 @RunWith(AndroidJUnit4.class)
 @MediumTest
 public class CarUxRestrictionsManagerServiceTest {
 
     private static final String UX_RESTRICTION_MODE_PASSENGER = "passenger";
+    private static final long TEST_TIMEOUT_MS = TimeUnit.SECONDS.toMillis(5);
 
     private CarUxRestrictionsManagerService mService;
 
+    @Rule
+    public final MockitoRule rule = MockitoJUnit.rule();
+
     @Mock
     private CarDrivingStateService mMockDrivingStateService;
     @Mock
     private CarPropertyService mMockCarPropertyService;
     @Mock
     private SystemInterface mMockSystemInterface;
+    @Mock
+    private IBinder mIBinder;
+    @Mock
+    private IRemoteCallback mRemoteCallback;
+    @Mock
+    private DisplayManager mDisplayManager;
+    @Mock
+    private Display mDisplay0;
+    @Mock
+    private Display mDisplay1;
 
     private Context mSpyContext;
 
@@ -97,10 +123,9 @@
 
     @Before
     public void setUp() throws Exception {
-        MockitoAnnotations.initMocks(this);
+        when(mRemoteCallback.asBinder()).thenReturn(mIBinder);
         // Spy context because service needs to access xml resource during init.
-        mSpyContext = spy(InstrumentationRegistry.getTargetContext());
-
+        mSpyContext = spy(getInstrumentation().getTargetContext());
         CarLocalServices.removeServiceForTest(SystemInterface.class);
         CarLocalServices.addService(SystemInterface.class, mMockSystemInterface);
 
@@ -172,7 +197,7 @@
     @Test
     public void testLoadConfig_UseProdConfig() throws IOException {
         CarUxRestrictionsConfiguration expected = createEmptyConfig();
-        setupMockFile(CONFIG_FILENAME_PRODUCTION, expected);
+        setupMockFile(CONFIG_FILENAME_PRODUCTION, List.of(expected));
 
         CarUxRestrictionsConfiguration actual = mService.loadConfig().get(0);
 
@@ -248,7 +273,7 @@
     public void testLoadConfig_PromoteStagedFileWhenParked() throws Exception {
         CarUxRestrictionsConfiguration expected = createEmptyConfig();
         // Staged file contains actual config. Ignore prod since it should be overwritten by staged.
-        File staged = setupMockFile(CONFIG_FILENAME_STAGED, expected);
+        File staged = setupMockFile(CONFIG_FILENAME_STAGED, List.of(expected));
         // Set up temp file for prod to avoid polluting other tests.
         setupMockFile(CONFIG_FILENAME_PRODUCTION, null);
 
@@ -264,7 +289,7 @@
         CarUxRestrictionsConfiguration expected = createEmptyConfig();
         File staged = setupMockFile(CONFIG_FILENAME_STAGED, null);
         // Prod file contains actual config. Ignore staged since it should not be promoted.
-        setupMockFile(CONFIG_FILENAME_PRODUCTION, expected);
+        setupMockFile(CONFIG_FILENAME_PRODUCTION, List.of(expected));
 
         setUpMockDrivingState();
         CarUxRestrictionsConfiguration actual = mService.loadConfig().get(0);
@@ -309,6 +334,103 @@
         assertTrue(restrictions.toString(), expected.isSameRestrictions(restrictions));
     }
 
+    @Test
+    public void testGetCurrentUxRestrictions_UnreportedVirtualDisplay_UseDefaultDisplayRestriction()
+            throws Exception {
+        // Create a virtual display that will get registered with the DisplayManager used by UXRE
+        String virtualDisplayName = "virtual_display";
+        DisplayManager displayManager = getContext().getSystemService(DisplayManager.class);
+        VirtualDisplay virtualDisplay = displayManager.createVirtualDisplay(
+                virtualDisplayName, 10, 10, 10, null, 0);
+        int virtualDisplayId = virtualDisplay.getDisplay().getDisplayId();
+
+        // Setup restrictions on the default display only
+        int defaultPortRestrictions = CarUxRestrictions.UX_RESTRICTIONS_NO_KEYBOARD;
+        CarUxRestrictionsConfiguration defaultPortConfig = createMovingConfig(DEFAULT_PORT,
+                defaultPortRestrictions);
+        setupMockFile(CONFIG_FILENAME_PRODUCTION, List.of(defaultPortConfig));
+
+        // Trigger restrictions by entering driving state
+        mService.init();
+        mService.handleDrivingStateEvent(
+                new CarDrivingStateEvent(CarDrivingStateEvent.DRIVING_STATE_MOVING,
+                        SystemClock.elapsedRealtime()));
+
+        // Virtual display should have restrictions for default display
+        CarUxRestrictions restrictions = mService.getCurrentUxRestrictions(virtualDisplayId);
+        CarUxRestrictions expected = new CarUxRestrictions.Builder(
+                /*reqOpt= */ true,
+                defaultPortRestrictions,
+                SystemClock.elapsedRealtimeNanos()).build();
+        assertTrue(restrictions.toString(), expected.isSameRestrictions(restrictions));
+
+        virtualDisplay.release();
+    }
+
+    @Test
+    public void testGetCurrentUxRestrictions_ReportedVirtualDisplay_ReturnsRestrictionsForPort()
+            throws Exception {
+        // Create a virtual display that we own in this process
+        String virtualDisplayName = "virtual_display";
+        DisplayManager displayManager = getContext().getSystemService(DisplayManager.class);
+        VirtualDisplay virtualDisplay = displayManager.createVirtualDisplay(
+                virtualDisplayName, 10, 10, 10, null, 0);
+
+        // Mock displays on two different physical ports, where the virtual display is on the
+        // second port. Don't use the virtual displayId since we are mocking out all the id logic.
+        int displayIdForPhysicalPort1 = 0;
+        int displayIdForPhysicalPort2 = 1;
+        int virtualDisplayId = 2;
+        int physicalPortForSecondDisplay = 11;
+        when(mSpyContext.getSystemService(DisplayManager.class)).thenReturn(mDisplayManager);
+        mockDisplay(mDisplayManager, mDisplay0, displayIdForPhysicalPort1, DEFAULT_PORT);
+        mockDisplay(mDisplayManager, mDisplay1, displayIdForPhysicalPort2,
+                physicalPortForSecondDisplay);
+        when(mDisplayManager.getDisplay(virtualDisplayId)).thenReturn(virtualDisplay.getDisplay());
+        when(mDisplayManager.getDisplays()).thenReturn(new Display[]{
+                mDisplay0,
+                mDisplay1,
+                virtualDisplay.getDisplay(),
+        });
+
+        // Setup different restrictions for each physical port
+        int port2Restrictions = CarUxRestrictions.UX_RESTRICTIONS_NO_KEYBOARD;
+        CarUxRestrictionsConfiguration defaultPortConfig = createMovingConfig(DEFAULT_PORT,
+                CarUxRestrictions.UX_RESTRICTIONS_NO_DIALPAD);
+        CarUxRestrictionsConfiguration port2Config = createMovingConfig(
+                (byte) physicalPortForSecondDisplay,
+                port2Restrictions);
+        setupMockFile(CONFIG_FILENAME_PRODUCTION, List.of(defaultPortConfig, port2Config));
+
+        // Enter driving state to trigger restrictions
+        mService = new CarUxRestrictionsManagerService(mSpyContext,
+                mMockDrivingStateService, mMockCarPropertyService);
+        mService.init();
+        // A CarActivityView would report this itself, but we fake the report here
+        mService.reportVirtualDisplayToPhysicalDisplay(mRemoteCallback, virtualDisplayId,
+                physicalPortForSecondDisplay);
+        mService.handleDrivingStateEvent(
+                new CarDrivingStateEvent(CarDrivingStateEvent.DRIVING_STATE_MOVING,
+                        SystemClock.elapsedRealtime()));
+
+        // Virtual display should have restrictions for port2
+        CarUxRestrictions restrictions = mService.getCurrentUxRestrictions(virtualDisplayId);
+        CarUxRestrictions expected = new CarUxRestrictions.Builder(
+                /*reqOpt= */ true,
+                port2Restrictions,
+                SystemClock.elapsedRealtimeNanos()).build();
+        assertTrue(restrictions.toString(), expected.isSameRestrictions(restrictions));
+
+        virtualDisplay.release();
+    }
+
+    private void mockDisplay(DisplayManager displayManager, Display display, int displayId,
+            int portAddress) {
+        when(displayManager.getDisplay(displayId)).thenReturn(display);
+        when(display.getDisplayId()).thenReturn(displayId);
+        when(display.getAddress()).thenReturn(DisplayAddress.fromPhysicalDisplayId(portAddress));
+    }
+
     // This test only involves calling a few methods and should finish very quickly. If it doesn't
     // finish in 20s, we probably encountered a deadlock.
     @Test(timeout = 20000)
@@ -513,6 +635,20 @@
         return builder.build();
     }
 
+    private CarUxRestrictionsConfiguration createMovingConfig(Byte port, int restrictions) {
+        Builder builder = new Builder();
+        if (port != null) {
+            builder.setPhysicalPort(port);
+        }
+        CarUxRestrictionsConfiguration.DrivingStateRestrictions drivingStateRestrictions =
+                new CarUxRestrictionsConfiguration.DrivingStateRestrictions();
+        drivingStateRestrictions.setDistractionOptimizationRequired(restrictions != 0);
+        drivingStateRestrictions.setRestrictions(restrictions);
+        builder.setUxRestrictions(CarDrivingStateEvent.DRIVING_STATE_MOVING,
+                drivingStateRestrictions);
+        return builder.build();
+    }
+
     private void setUpMockParkedState() {
         when(mMockDrivingStateService.getCurrentDrivingState()).thenReturn(
                 new CarDrivingStateEvent(CarDrivingStateEvent.DRIVING_STATE_PARKED, 0));
@@ -533,17 +669,15 @@
                 .thenReturn(speed);
     }
 
-    private File setupMockFile(String filename, CarUxRestrictionsConfiguration config)
+    private File setupMockFile(String filename, List<CarUxRestrictionsConfiguration> configs)
             throws IOException {
         File f = new File(mTempSystemCarDir, filename);
         assertTrue(f.createNewFile());
 
-        if (config != null) {
+        if (configs != null) {
             try (JsonWriter writer = new JsonWriter(
                     new OutputStreamWriter(new FileOutputStream(f), "UTF-8"))) {
-                writer.beginArray();
-                config.writeJson(writer);
-                writer.endArray();
+                mService.writeJson(writer, configs);
             }
         }
         return f;
diff --git a/tests/carservice_test/src/com/android/car/CarVendorExtensionManagerTest.java b/tests/carservice_test/src/com/android/car/CarVendorExtensionManagerTest.java
index 4848fa2..47ccb28 100644
--- a/tests/carservice_test/src/com/android/car/CarVendorExtensionManagerTest.java
+++ b/tests/carservice_test/src/com/android/car/CarVendorExtensionManagerTest.java
@@ -35,8 +35,8 @@
 import android.util.Log;
 import android.util.SparseArray;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.MediumTest;
-import androidx.test.runner.AndroidJUnit4;
 
 import com.android.car.vehiclehal.test.MockedVehicleHal;
 import com.android.car.vehiclehal.test.VehiclePropConfigBuilder;
diff --git a/tests/carservice_test/src/com/android/car/FastPairProviderTest.java b/tests/carservice_test/src/com/android/car/FastPairProviderTest.java
index 6aee11d..546f352 100644
--- a/tests/carservice_test/src/com/android/car/FastPairProviderTest.java
+++ b/tests/carservice_test/src/com/android/car/FastPairProviderTest.java
@@ -29,22 +29,27 @@
 import android.content.res.Resources;
 import android.os.ParcelUuid;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Assert;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
 
 import java.util.Arrays;
 
 @SmallTest
 @RunWith(AndroidJUnit4.class)
 public class FastPairProviderTest {
+    @Rule
+    public final MockitoRule rule = MockitoJUnit.rule();
+
     // Service ID assigned for FastPair.
     private static final ParcelUuid FastPairServiceUuid = ParcelUuid
             .fromString("0000FE2C-0000-1000-8000-00805f9b34fb");
@@ -62,7 +67,6 @@
 
     @Before
     public void setUp() {
-        MockitoAnnotations.initMocks(this);
         when(mMockContext.getSystemService(Context.BLUETOOTH_SERVICE)).thenReturn(
                 mMockBluetoothManager);
         when(mMockBluetoothManager.getAdapter()).thenReturn(mMockBluetoothAdapter);
diff --git a/tests/carservice_test/src/com/android/car/ICarImplTest.java b/tests/carservice_test/src/com/android/car/ICarImplTest.java
new file mode 100644
index 0000000..9502602
--- /dev/null
+++ b/tests/carservice_test/src/com/android/car/ICarImplTest.java
@@ -0,0 +1,218 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doThrow;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+
+import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+
+import android.car.Car;
+import android.content.Context;
+import android.content.res.Resources;
+import android.hardware.automotive.vehicle.V2_0.IVehicle;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.util.Log;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.car.systeminterface.ActivityManagerInterface;
+import com.android.car.systeminterface.DisplayInterface;
+import com.android.car.systeminterface.IOInterface;
+import com.android.car.systeminterface.StorageMonitoringInterface;
+import com.android.car.systeminterface.SystemInterface;
+import com.android.car.systeminterface.SystemInterface.Builder;
+import com.android.car.systeminterface.SystemStateInterface;
+import com.android.car.systeminterface.TimeInterface;
+import com.android.car.systeminterface.WakeLockInterface;
+import com.android.car.test.utils.TemporaryDirectory;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoSession;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.mockito.quality.Strictness;
+
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * This class contains unit tests for the {@link ICarImpl}.
+ * It tests that services started with {@link ICarImpl} are initialized properly.
+ *
+ * The following mocks are used:
+ * 1. {@link ActivityManagerInterface} broadcasts intent for a user.
+ * 2. {@link DisplayInterface} provides access to display operations.
+ * 3. {@link IVehicle} provides access to vehicle properties.
+ * 4. {@link StorageMonitoringInterface} provides access to storage monitoring operations.
+ * 5. {@link SystemStateInterface} provides system statuses (booting, sleeping, ...).
+ * 6. {@link TimeInterface} provides access to time operations.
+ * 7. {@link TimeInterface} provides access to wake lock operations.
+ */
+@RunWith(MockitoJUnitRunner.class)
+public class ICarImplTest {
+    private static final String TAG = "ICarImplTest";
+
+    @Mock private ActivityManagerInterface mMockActivityManagerInterface;
+    @Mock private DisplayInterface mMockDisplayInterface;
+    @Mock private IVehicle mMockVehicle;
+    @Mock private StorageMonitoringInterface mMockStorageMonitoringInterface;
+    @Mock private SystemStateInterface mMockSystemStateInterface;
+    @Mock private TimeInterface mMockTimeInterface;
+    @Mock private WakeLockInterface mMockWakeLockInterface;
+
+    private Context mContext;
+    private MockitoSession mSession;
+    private SystemInterface mFakeSystemInterface;
+    private UserManager mUserManager;
+
+    private final MockIOInterface mMockIOInterface = new MockIOInterface();
+
+    /**
+     * Initialize all of the objects with the @Mock annotation.
+     */
+    @Before
+    public void setUp() throws Exception {
+        mSession = mockitoSession()
+                .initMocks(this)
+                .strictness(Strictness.LENIENT)
+                .startMocking();
+
+        // InstrumentationTestRunner prepares a looper, but AndroidJUnitRunner does not.
+        // http://b/25897652.
+        if (Looper.myLooper() == null) {
+            Looper.prepare();
+        }
+
+        mContext = spy(InstrumentationRegistry.getInstrumentation().getTargetContext());
+
+        mUserManager = spy(mContext.getSystemService(UserManager.class));
+        doReturn(mUserManager).when(mContext).getSystemService(eq(UserManager.class));
+        doReturn(mUserManager).when(mContext).getSystemService(eq(Context.USER_SERVICE));
+
+        Resources resources = spy(mContext.getResources());
+        doReturn("").when(resources).getString(
+                eq(com.android.car.R.string.instrumentClusterRendererService));
+        doReturn(false).when(resources).getBoolean(
+                eq(com.android.car.R.bool.audioUseDynamicRouting));
+        doReturn(new String[0]).when(resources).getStringArray(
+                eq(com.android.car.R.array.config_earlyStartupServices));
+        doReturn(resources).when(mContext).getResources();
+
+        mFakeSystemInterface = Builder.newSystemInterface()
+                .withSystemStateInterface(mMockSystemStateInterface)
+                .withActivityManagerInterface(mMockActivityManagerInterface)
+                .withDisplayInterface(mMockDisplayInterface)
+                .withIOInterface(mMockIOInterface)
+                .withStorageMonitoringInterface(mMockStorageMonitoringInterface)
+                .withTimeInterface(mMockTimeInterface)
+                .withWakeLockInterface(mMockWakeLockInterface).build();
+        // ICarImpl will register new CarLocalServices services.
+        // This prevents one test failure in tearDown from triggering assertion failure for single
+        // CarLocalServices service.
+        CarLocalServices.removeAllServices();
+    }
+
+    /**
+     *  Clean up before running the next test.
+     */
+    @After
+    public void tearDown() {
+        if (mMockIOInterface != null) {
+            mMockIOInterface.tearDown();
+        }
+        mSession.finishMocking();
+        CarLocalServices.removeAllServices();
+    }
+
+    @Test
+    public void testNoShardedPreferencesAccessedBeforeUserZeroUnlock() {
+        doReturn(true).when(mContext).isCredentialProtectedStorage();
+        doReturn(false).when(mUserManager).isUserUnlockingOrUnlocked(anyInt());
+        doReturn(false).when(mUserManager).isUserUnlocked();
+        doReturn(false).when(mUserManager).isUserUnlocked(anyInt());
+        doReturn(false).when(mUserManager).isUserUnlocked(any(UserHandle.class));
+        doReturn(false).when(mUserManager).isUserUnlockingOrUnlocked(any(UserHandle.class));
+
+        doThrow(new NullPointerException()).when(mContext).getSharedPrefsFile(anyString());
+        doThrow(new NullPointerException()).when(mContext).getSharedPreferencesPath(any());
+        doThrow(new NullPointerException()).when(mContext).getSharedPreferences(
+                anyString(), anyInt());
+        doThrow(new NullPointerException()).when(mContext).getSharedPreferences(
+                any(File.class), anyInt());
+        doThrow(new NullPointerException()).when(mContext).getDataDir();
+
+        ICarImpl carImpl = new ICarImpl(mContext, mMockVehicle, mFakeSystemInterface,
+                /* errorNotifier= */ null, "MockedCar");
+        carImpl.init();
+        Car mCar = new Car(mContext, carImpl, /* handler= */ null);
+
+        // Post tasks for Handler Threads to ensure all the tasks that will be queued inside init
+        // will be done.
+        for (Thread t : Thread.getAllStackTraces().keySet()) {
+            if (!HandlerThread.class.isInstance(t)) {
+                continue;
+            }
+            HandlerThread ht = (HandlerThread) t;
+            CarServiceUtils.runOnLooperSync(ht.getLooper(), () -> {
+                // Do nothing, just need to make sure looper finishes current task.
+            });
+        }
+
+        mCar.disconnect();
+        carImpl.release();
+    }
+
+    static final class MockIOInterface implements IOInterface {
+        private TemporaryDirectory mFilesDir = null;
+
+        @Override
+        public File getSystemCarDir() {
+            if (mFilesDir == null) {
+                try {
+                    mFilesDir = new TemporaryDirectory(TAG);
+                } catch (IOException e) {
+                    Log.e(TAG, "failed to create temporary directory", e);
+                    fail("failed to create temporary directory. exception was: " + e);
+                }
+            }
+            return mFilesDir.getDirectory();
+        }
+
+        public void tearDown() {
+            if (mFilesDir != null) {
+                try {
+                    mFilesDir.close();
+                } catch (Exception e) {
+                    Log.w(TAG, "could not remove temporary directory", e);
+                }
+            }
+        }
+    }
+}
diff --git a/tests/carservice_test/src/com/android/car/MockedCarTestBase.java b/tests/carservice_test/src/com/android/car/MockedCarTestBase.java
index 69eba19..2395b47 100644
--- a/tests/carservice_test/src/com/android/car/MockedCarTestBase.java
+++ b/tests/carservice_test/src/com/android/car/MockedCarTestBase.java
@@ -15,17 +15,20 @@
  */
 package com.android.car;
 
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
-
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.fail;
-import static org.mockito.Mockito.doReturn;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
 
+import android.annotation.NonNull;
 import android.car.Car;
 import android.car.test.CarTestManager;
 import android.car.test.CarTestManagerBinderWrapper;
 import android.content.ComponentName;
 import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.Intent;
 import android.content.ServiceConnection;
 import android.content.res.Resources;
 import android.hardware.automotive.vehicle.V2_0.VehiclePropValue;
@@ -35,13 +38,15 @@
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Looper;
+import android.os.UserHandle;
 import android.util.Log;
 import android.util.SparseArray;
 
-import androidx.test.InstrumentationRegistry;
 import androidx.test.annotation.UiThreadTest;
+import androidx.test.platform.app.InstrumentationRegistry;
 
 import com.android.car.pm.CarPackageManagerService;
+import com.android.car.systeminterface.ActivityManagerInterface;
 import com.android.car.systeminterface.DisplayInterface;
 import com.android.car.systeminterface.IOInterface;
 import com.android.car.systeminterface.StorageMonitoringInterface;
@@ -51,12 +56,13 @@
 import com.android.car.systeminterface.TimeInterface;
 import com.android.car.systeminterface.WakeLockInterface;
 import com.android.car.test.utils.TemporaryDirectory;
+import com.android.car.user.CarUserService;
+import com.android.car.user.CarUserService.UserCallback;
 import com.android.car.vehiclehal.test.MockedVehicleHal;
 import com.android.car.vehiclehal.test.MockedVehicleHal.DefaultPropertyHandler;
 import com.android.car.vehiclehal.test.MockedVehicleHal.StaticPropertyHandler;
 import com.android.car.vehiclehal.test.MockedVehicleHal.VehicleHalPropertyHandler;
 import com.android.car.vehiclehal.test.VehiclePropConfigBuilder;
-import com.android.car.vms.VmsClientManager;
 
 import org.junit.After;
 import org.junit.Before;
@@ -64,7 +70,9 @@
 import java.io.File;
 import java.io.IOException;
 import java.time.Duration;
+import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 
 /**
@@ -82,6 +90,10 @@
     private MockedVehicleHal mMockedVehicleHal;
     private SystemInterface mFakeSystemInterface;
     private MockResources mResources;
+
+    private final List<CarUserService.UserCallback> mUserCallbacks = new ArrayList<>();
+    private final CarUserService mCarUserService = mock(CarUserService.class);
+
     private final MockIOInterface mMockIOInterface = new MockIOInterface();
 
     private final Handler mMainHandler = new Handler(Looper.getMainLooper());
@@ -93,6 +105,8 @@
     private static final IBinder mCarServiceToken = new Binder();
     private static boolean mRealCarServiceReleased = false;
 
+    private MockedCarTestContext mMockedCarTestContext;
+
     protected synchronized MockedVehicleHal createMockedVehicleHal() {
         return new MockedVehicleHal();
     }
@@ -108,9 +122,13 @@
     protected synchronized void configureMockedHal() {
     }
 
+    protected synchronized void spyOnInitMockedHal() {
+    }
+
     protected synchronized SystemInterface.Builder getSystemInterfaceBuilder() {
         return Builder.newSystemInterface()
                 .withSystemStateInterface(new MockSystemStateInterface())
+                .withActivityManagerInterface(new MockActivityManagerInterface())
                 .withDisplayInterface(new MockDisplayInterface())
                 .withIOInterface(mMockIOInterface)
                 .withStorageMonitoringInterface(new MockStorageMonitoringInterface())
@@ -123,14 +141,24 @@
     protected synchronized void configureResourceOverrides(MockResources resources) {
         resources.overrideResource(com.android.car.R.string.instrumentClusterRendererService, "");
         resources.overrideResource(com.android.car.R.bool.audioUseDynamicRouting, false);
+        resources.overrideResource(com.android.car.R.array.config_earlyStartupServices,
+                new String[0]);
     }
 
-    protected Context getContext() {
-        return InstrumentationRegistry.getTargetContext();
+    protected synchronized Context getContext() {
+        if (mMockedCarTestContext == null) {
+            mMockedCarTestContext = createMockedCarTestContext(
+                    InstrumentationRegistry.getInstrumentation().getTargetContext());
+        }
+        return mMockedCarTestContext;
+    }
+
+    protected MockedCarTestContext createMockedCarTestContext(Context context) {
+        return new MockedCarTestContext(context);
     }
 
     protected Context getTestContext() {
-        return InstrumentationRegistry.getContext();
+        return InstrumentationRegistry.getInstrumentation().getContext();
     }
 
     protected String getFlattenComponent(Class cls) {
@@ -138,6 +166,21 @@
         return cn.flattenToString();
     }
 
+    /**
+     * Emulates a call to {@link CarUserService#onSwitchUser(int)} that dispatches
+     * {@link UserCallback#onSwitchUser(int)} to the callbacks whose {@code toString()} method
+     * contains the given {@code filter}.
+     */
+    protected void switchUser(int userId, @NonNull String filter) {
+        Log.d(TAG, "switchUser(" + userId  + ", " + filter + "): callbacks=" + mUserCallbacks);
+        for (UserCallback callback : mUserCallbacks) {
+            if (callback.toString().contains(filter)) {
+                Log.i(TAG, "Notifying " + callback);
+                callback.onSwitchUser(userId);
+            }
+        }
+    }
+
     @Before
     @UiThreadTest
     public void setUp() throws Exception {
@@ -150,23 +193,33 @@
         mFakeSystemInterface = getSystemInterfaceBuilder().build();
         configureFakeSystemInterface();
 
-        Context context = getCarServiceContext();
-        spyOn(context);
-        mResources = new MockResources(context.getResources());
-        configureResourceOverrides(mResources);
-        doReturn(mResources).when(context).getResources();
+        mMockedCarTestContext = (MockedCarTestContext) getContext();
+        configureResourceOverrides((MockResources) mMockedCarTestContext.getResources());
+
+        doAnswer((invocation) -> {
+            CarUserService.UserCallback callback = invocation.getArgument(0);
+            Log.d(TAG, "Adding callback: " + callback);
+            mUserCallbacks.add(callback);
+            return null;
+        }).when(mCarUserService).addUserCallback(any());
+
+        doAnswer((invocation) -> {
+            CarUserService.UserCallback callback = invocation.getArgument(0);
+            Log.d(TAG, "Removing callback: " + callback);
+            mUserCallbacks.remove(callback);
+            return null;
+        }).when(mCarUserService).removeUserCallback(any());
 
         // ICarImpl will register new CarLocalServices services.
         // This prevents one test failure in tearDown from triggering assertion failure for single
         // CarLocalServices service.
         CarLocalServices.removeAllServices();
-        ICarImpl carImpl = new ICarImpl(context, mMockedVehicleHal, mFakeSystemInterface,
-                null /* error notifier */, "MockedCar");
+        mCarImpl = new ICarImpl(mMockedCarTestContext, mMockedVehicleHal, mFakeSystemInterface,
+                /* errorNotifier= */ null , "MockedCar", mCarUserService);
 
-        initMockedHal(carImpl, false /* no need to release */);
-        mCarImpl = carImpl;
-
-        mCar = new Car(context, mCarImpl, null /* handler */);
+        spyOnInitMockedHal();
+        initMockedHal(mCarImpl, false /* no need to release */);
+        mCar = new Car(mMockedCarTestContext, mCarImpl, null /* handler */);
     }
 
     @After
@@ -182,18 +235,18 @@
         }
     }
 
+    public CarPropertyService getCarPropertyService() {
+        return (CarPropertyService) mCarImpl.getCarService(Car.PROPERTY_SERVICE);
+    }
+
+    public void injectErrorEvent(int propId, int areaId, int errorCode) {
+        mMockedVehicleHal.injectError(errorCode, propId, areaId);
+    }
+
     public CarPackageManagerService getPackageManagerService() {
         return (CarPackageManagerService) mCarImpl.getCarService(Car.PACKAGE_SERVICE);
     }
 
-    public VmsClientManager getVmsClientManager() {
-        return (VmsClientManager) mCarImpl.getCarInternalService(ICarImpl.INTERNAL_VMS_MANAGER);
-    }
-
-    protected Context getCarServiceContext() {
-        return getContext();
-    }
-
     protected synchronized void reinitializeMockedHal() throws Exception {
         initMockedHal(mCarImpl, true /* release */);
     }
@@ -299,6 +352,13 @@
         }
     }
 
+    static final class MockActivityManagerInterface implements ActivityManagerInterface {
+        @Override
+        public void sendBroadcastAsUser(Intent intent, UserHandle user) {
+            Log.d(TAG, "Broadcast intent: " + intent.getAction() + " as user: " + user);
+        }
+    }
+
     static final class MockDisplayInterface implements DisplayInterface {
 
         @Override
@@ -315,9 +375,6 @@
 
         @Override
         public void refreshDisplayBrightness() {}
-
-        @Override
-        public void reconfigureSecondaryDisplays() {}
     }
 
     static final class MockIOInterface implements IOInterface {
@@ -347,6 +404,29 @@
         }
     }
 
+    /**
+     * Special version of {@link ContextWrapper} that overrides {@method getResources} by returning
+     * a {@link MockResources}, so tests are free to set resources. This class represents an
+     * alternative of using Mockito spy (see b/148240178).
+     *
+     * Tests may specialize this class. If they decide so, then they are required to override
+     * {@method newMockedCarContext} to provide their own context.
+     */
+    protected static class MockedCarTestContext extends ContextWrapper {
+
+        private final Resources mMockedResources;
+
+        MockedCarTestContext(Context base) {
+            super(base);
+            mMockedResources = new MockResources(base.getResources());
+        }
+
+        @Override
+        public Resources getResources() {
+            return mMockedResources;
+        }
+    }
+
     static final class MockResources extends Resources {
         private final HashMap<Integer, Boolean> mBooleanOverrides = new HashMap<>();
         private final HashMap<Integer, Integer> mIntegerOverrides = new HashMap<>();
diff --git a/tests/carservice_test/src/com/android/car/MockedVmsTestBase.java b/tests/carservice_test/src/com/android/car/MockedVmsTestBase.java
index ccc2e6c..ff649de 100644
--- a/tests/carservice_test/src/com/android/car/MockedVmsTestBase.java
+++ b/tests/carservice_test/src/com/android/car/MockedVmsTestBase.java
@@ -18,7 +18,6 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 
-import android.app.ActivityManager;
 import android.car.Car;
 import android.car.VehicleAreaType;
 import android.car.vms.VmsAvailableLayers;
@@ -50,27 +49,17 @@
 import java.util.concurrent.TimeUnit;
 
 public class MockedVmsTestBase extends MockedCarTestBase {
-    public static final long PUBLISHER_BIND_TIMEOUT_SECS = 2L;
-
+    public static final long PUBLISHER_CLIENT_TIMEOUT = 500L;
+    public static final long MESSAGE_RECEIVE_TIMEOUT = 500L;
     private static final String TAG = "MockedVmsTestBase";
-    private static CountDownLatch sPublisherIsReady = new CountDownLatch(1);
-    private static MockPublisherClient sPublisherClient;
+
+    private MockPublisherClient mPublisherClient;
+    private CountDownLatch mPublisherIsReady = new CountDownLatch(1);
     private VmsSubscriberManager mVmsSubscriberManager;
     private MockSubscriberClient mSubscriberClient;
     private MockHalClient mHalClient;
 
     @Override
-    protected synchronized void configureResourceOverrides(MockResources resources) {
-        super.configureResourceOverrides(resources);
-        // Override publisher client endpoint configurations
-        // Both lists must be set, but only one will be used (see setUp)
-        resources.overrideResource(com.android.car.R.array.vmsPublisherSystemClients,
-                new String[]{getFlattenComponent(MockPublisherClient.class)});
-        resources.overrideResource(com.android.car.R.array.vmsPublisherUserClients,
-                new String[]{getFlattenComponent(MockPublisherClient.class)});
-    }
-
-    @Override
     protected synchronized void configureMockedHal() {
         mHalClient = new MockHalClient();
         addProperty(VehicleProperty.VEHICLE_MAP_SERVICE, mHalClient)
@@ -81,14 +70,18 @@
 
     @Before
     public void setUpVms() throws Exception {
-        // Trigger VmsClientManager to bind to the MockPublisherClient
-        getVmsClientManager().mUserCallback.onSwitchUser(ActivityManager.getCurrentUser());
+        mPublisherClient = new MockPublisherClient();
+        mPublisherClient.setMockCar(getCar());
         mVmsSubscriberManager = (VmsSubscriberManager) getCar().getCarManager(
                 Car.VMS_SUBSCRIBER_SERVICE);
         mSubscriberClient = new MockSubscriberClient();
         mVmsSubscriberManager.setVmsSubscriberClientCallback(Executors.newSingleThreadExecutor(),
                 mSubscriberClient);
 
+        assertTrue(
+                "Timeout while waiting for publisher client to be ready",
+                mPublisherIsReady.await(PUBLISHER_CLIENT_TIMEOUT, TimeUnit.MILLISECONDS));
+
         // Validate session handshake
         List<Integer> v = mHalClient.receiveMessage().value.int32Values;
         assertEquals(VmsMessageType.START_SESSION,
@@ -114,26 +107,12 @@
                 (int) v.get(VmsAvailabilityStateIntegerValuesIndex.NUMBER_OF_ASSOCIATED_LAYERS));
     }
 
-    @Override
-    public void tearDown() throws Exception {
-        super.tearDown();
-        sPublisherIsReady = new CountDownLatch(1);
-        sPublisherClient = null;
-    }
-
     VmsSubscriberManager getSubscriberManager() {
         return mVmsSubscriberManager;
     }
 
     MockPublisherClient getMockPublisherClient() {
-        try {
-            assertTrue(
-                    "Timeout while waiting for publisher client to be ready",
-                    sPublisherIsReady.await(PUBLISHER_BIND_TIMEOUT_SECS, TimeUnit.SECONDS));
-        } catch (InterruptedException e) {
-            throw new RuntimeException(e);
-        }
-        return sPublisherClient;
+        return mPublisherClient;
     }
 
     MockSubscriberClient getMockSubscriberClient() {
@@ -144,20 +123,24 @@
         return mHalClient;
     }
 
-    public static class MockPublisherClient extends VmsPublisherClientService {
+    class MockPublisherClient extends VmsPublisherClientService {
         private BlockingQueue<VmsSubscriptionState> mSubscriptionState =
                 new LinkedBlockingQueue<>();
 
+        void setMockCar(Car car) {
+            onCarLifecycleChanged(car, true);
+        }
+
         @Override
         protected void onVmsPublisherServiceReady() {
             Log.d(TAG, "MockPublisherClient.onVmsPublisherServiceReady");
-            sPublisherClient = this;
-            sPublisherIsReady.countDown();
+            mPublisherIsReady.countDown();
         }
 
         @Override
         public void onVmsSubscriptionChange(VmsSubscriptionState subscriptionState) {
-            Log.d(TAG, "MockPublisherClient.onVmsSubscriptionChange");
+            Log.d(TAG, "MockPublisherClient.onVmsSubscriptionChange: "
+                    + subscriptionState.getSequenceNumber());
             mSubscriptionState.add(subscriptionState);
         }
 
@@ -224,7 +207,7 @@
 
     private static <T> T receiveWithTimeout(BlockingQueue<T> queue) {
         try {
-            return queue.poll(2L, TimeUnit.SECONDS);
+            return queue.poll(MESSAGE_RECEIVE_TIMEOUT, TimeUnit.MILLISECONDS);
         } catch (InterruptedException e) {
             throw new RuntimeException(e);
         }
diff --git a/tests/carservice_test/src/com/android/car/SystemActivityMonitoringServiceTest.java b/tests/carservice_test/src/com/android/car/SystemActivityMonitoringServiceTest.java
index 4595f9e..778b635 100644
--- a/tests/carservice_test/src/com/android/car/SystemActivityMonitoringServiceTest.java
+++ b/tests/carservice_test/src/com/android/car/SystemActivityMonitoringServiceTest.java
@@ -29,9 +29,10 @@
 import android.util.Log;
 import android.view.Display;
 
-import androidx.test.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.FlakyTest;
 import androidx.test.filters.MediumTest;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
 
 import com.android.car.SystemActivityMonitoringService.TopTaskInfoContainer;
 
@@ -71,6 +72,7 @@
     }
 
     @Test
+    @FlakyTest
     public void testActivityLaunch() throws Exception {
         ComponentName activityA = toComponentName(getTestContext(), ActivityA.class);
         mService.registerActivityLaunchListener(new FilteredLaunchListener(activityA));
@@ -84,6 +86,7 @@
     }
 
     @Test
+    @FlakyTest
     public void testActivityBlocking() throws Exception {
         ComponentName blackListedActivity = toComponentName(getTestContext(), ActivityC.class);
         ComponentName blockingActivity = toComponentName(getTestContext(), BlockingActivity.class);
@@ -102,6 +105,7 @@
     }
 
     @Test
+    @FlakyTest
     public void testRemovesFromTopTasks() throws Exception {
         ComponentName activityThatFinishesImmediately =
                 toComponentName(getTestContext(), ActivityThatFinishesImmediately.class);
@@ -111,6 +115,7 @@
     }
 
     @Test
+    @FlakyTest
     public void testGetTopTasksOnMultiDisplay() throws Exception {
         String virtualDisplayName = "virtual_display";
         DisplayManager displayManager = getContext().getSystemService(DisplayManager.class);
@@ -181,11 +186,11 @@
     }
 
     private Context getContext() {
-        return InstrumentationRegistry.getTargetContext();
+        return InstrumentationRegistry.getInstrumentation().getTargetContext();
     }
 
     private Context getTestContext() {
-        return InstrumentationRegistry.getContext();
+        return InstrumentationRegistry.getInstrumentation().getContext();
     }
 
     private static ComponentName toComponentName(Context ctx, Class<?> cls) {
diff --git a/tests/carservice_test/src/com/android/car/VmsHalServiceSubscriptionEventTest.java b/tests/carservice_test/src/com/android/car/VmsHalServiceSubscriptionEventTest.java
index cc32231..4260ee0 100644
--- a/tests/carservice_test/src/com/android/car/VmsHalServiceSubscriptionEventTest.java
+++ b/tests/carservice_test/src/com/android/car/VmsHalServiceSubscriptionEventTest.java
@@ -23,9 +23,11 @@
 import android.hardware.automotive.vehicle.V2_0.VmsMessageType;
 import android.hardware.automotive.vehicle.V2_0.VmsSubscriptionsStateIntegerValuesIndex;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.MediumTest;
 
 import org.junit.Test;
+import org.junit.runner.RunWith;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -33,6 +35,7 @@
 import java.util.HashSet;
 import java.util.List;
 
+@RunWith(AndroidJUnit4.class)
 @MediumTest
 public class VmsHalServiceSubscriptionEventTest extends MockedVmsTestBase {
     @Test
diff --git a/tests/carservice_test/src/com/android/car/VmsOperationRecorderTest.java b/tests/carservice_test/src/com/android/car/VmsOperationRecorderTest.java
index 2c41f32..cd44e43 100644
--- a/tests/carservice_test/src/com/android/car/VmsOperationRecorderTest.java
+++ b/tests/carservice_test/src/com/android/car/VmsOperationRecorderTest.java
@@ -24,8 +24,8 @@
 import android.car.vms.VmsOperationRecorder;
 import android.util.Log;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.MediumTest;
-import androidx.test.runner.AndroidJUnit4;
 
 import org.json.JSONArray;
 import org.json.JSONException;
diff --git a/tests/carservice_test/src/com/android/car/VmsPublisherClientPermissionTest.java b/tests/carservice_test/src/com/android/car/VmsPublisherClientPermissionTest.java
deleted file mode 100644
index 0bb9d4f..0000000
--- a/tests/carservice_test/src/com/android/car/VmsPublisherClientPermissionTest.java
+++ /dev/null
@@ -1,123 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.car;
-
-import static com.android.car.MockedVmsTestBase.PUBLISHER_BIND_TIMEOUT_SECS;
-
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-
-import android.app.ActivityManager;
-import android.car.vms.VmsPublisherClientService;
-import android.car.vms.VmsSubscriptionState;
-
-import org.junit.Before;
-import org.junit.Test;
-
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-public class VmsPublisherClientPermissionTest extends MockedCarTestBase {
-    private static CountDownLatch sPublisherExpectedPermission = new CountDownLatch(1);
-    private static CountDownLatch sPublisherWrongPermission = new CountDownLatch(1);
-    private static CountDownLatch sPublisherMissingPermission = new CountDownLatch(1);
-
-    private static class MockPublisherClient extends VmsPublisherClientService {
-        private CountDownLatch mReadyLatch;
-        MockPublisherClient(CountDownLatch readyLatch) {
-            mReadyLatch = readyLatch;
-        }
-        @Override
-        protected void onVmsPublisherServiceReady() {
-            mReadyLatch.countDown();
-        }
-
-        @Override
-        public void onVmsSubscriptionChange(VmsSubscriptionState subscriptionState) {}
-    }
-
-
-    // AndroidManifest.xml:
-    // <service android:name="com.android.car.VmsPublisherClientPermissionTest$PublisherClientExpectedPermission"
-    //         android:exported="true"
-    //         android:permission="android.car.permission.BIND_VMS_CLIENT"/>
-    public static class PublisherClientExpectedPermission extends MockPublisherClient {
-        public PublisherClientExpectedPermission() {
-            super(sPublisherExpectedPermission);
-        }
-    }
-
-    // AndroidManifest.xml:
-    // <service android:name="com.android.car.VmsPublisherClientPermissionTest$PublisherClientWrongPermission"
-    //         android:exported="true"
-    //         android:permission="android.car.permission.VMS_PUBLISHER"/>
-    public static class PublisherClientWrongPermission extends MockPublisherClient {
-        public PublisherClientWrongPermission() {
-            super(sPublisherWrongPermission);
-        }
-    }
-
-    // AndroidManifest.xml:
-    // <service android:name="com.android.car.VmsPublisherClientPermissionTest$PublisherClientMissingPermission"
-    //         android:exported="true"/>
-    public static class PublisherClientMissingPermission extends MockPublisherClient {
-        public PublisherClientMissingPermission() {
-            super(sPublisherMissingPermission);
-        }
-    }
-
-    @Override
-    protected synchronized void configureResourceOverrides(MockResources resources) {
-        super.configureResourceOverrides(resources);
-        resources.overrideResource(com.android.car.R.array.vmsPublisherSystemClients, new String[]{
-                getFlattenComponent(PublisherClientExpectedPermission.class),
-                getFlattenComponent(PublisherClientWrongPermission.class),
-                getFlattenComponent(PublisherClientMissingPermission.class)
-        });
-        resources.overrideResource(com.android.car.R.array.vmsPublisherUserClients, new String[]{
-                getFlattenComponent(PublisherClientExpectedPermission.class),
-                getFlattenComponent(PublisherClientWrongPermission.class),
-                getFlattenComponent(PublisherClientMissingPermission.class)
-        });
-    }
-
-    @Before
-    public void triggerClientBinding() {
-        getVmsClientManager().mUserCallback.onSwitchUser(ActivityManager.getCurrentUser());
-    }
-
-    @Test
-    public void testExpectedPermission() throws Exception {
-        assertTrue(
-                "Timeout while waiting for publisher client to be ready",
-                sPublisherExpectedPermission.await(PUBLISHER_BIND_TIMEOUT_SECS, TimeUnit.SECONDS));
-    }
-
-    @Test
-    public void testWrongPermission() throws Exception {
-        assertFalse(
-                "Publisher with wrong android:permission was bound unexpectedly",
-                sPublisherWrongPermission.await(PUBLISHER_BIND_TIMEOUT_SECS, TimeUnit.SECONDS));
-    }
-
-    @Test
-    public void testMissingPermission() throws Exception {
-        assertFalse(
-                "Publisher with missing android:permission was bound unexpectedly",
-                sPublisherMissingPermission.await(PUBLISHER_BIND_TIMEOUT_SECS, TimeUnit.SECONDS));
-    }
-}
diff --git a/tests/carservice_test/src/com/android/car/VmsPublisherClientServiceTest.java b/tests/carservice_test/src/com/android/car/VmsPublisherClientServiceTest.java
deleted file mode 100644
index 2f79019..0000000
--- a/tests/carservice_test/src/com/android/car/VmsPublisherClientServiceTest.java
+++ /dev/null
@@ -1,79 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.car;
-
-import static org.junit.Assert.assertArrayEquals;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-
-import android.car.vms.VmsLayer;
-import android.hardware.automotive.vehicle.V2_0.VehiclePropValue;
-import android.hardware.automotive.vehicle.V2_0.VmsBaseMessageIntegerValuesIndex;
-import android.hardware.automotive.vehicle.V2_0.VmsMessageType;
-import android.hardware.automotive.vehicle.V2_0.VmsMessageWithLayerIntegerValuesIndex;
-
-import androidx.test.filters.MediumTest;
-
-import org.junit.Test;
-
-@MediumTest
-public class VmsPublisherClientServiceTest extends MockedVmsTestBase {
-    private static final int MOCK_PUBLISHER_LAYER_ID = 12;
-    private static final int MOCK_PUBLISHER_LAYER_VERSION = 34;
-    private static final int MOCK_PUBLISHER_LAYER_SUBTYPE = 56;
-    public static final int MOCK_PUBLISHER_ID = 1234;
-    public static final VmsLayer MOCK_PUBLISHER_LAYER =
-            new VmsLayer(MOCK_PUBLISHER_LAYER_ID,
-                    MOCK_PUBLISHER_LAYER_SUBTYPE,
-                    MOCK_PUBLISHER_LAYER_VERSION);
-    public static final byte[] PAYLOAD = new byte[]{1, 1, 2, 3, 5, 8, 13};
-
-    @Test
-    public void testPublish() throws Exception {
-        MockHalClient client = getMockHalClient();
-        client.sendMessage(
-                VmsMessageType.SUBSCRIBE,
-                MOCK_PUBLISHER_LAYER_ID,
-                MOCK_PUBLISHER_LAYER_SUBTYPE,
-                MOCK_PUBLISHER_LAYER_VERSION);
-
-        getMockPublisherClient().publish(MOCK_PUBLISHER_LAYER, MOCK_PUBLISHER_ID, PAYLOAD);
-
-        VehiclePropValue message;
-        do {
-            message = client.receiveMessage();
-        } while (message != null && message.value.int32Values.get(
-                VmsBaseMessageIntegerValuesIndex.MESSAGE_TYPE) != VmsMessageType.DATA);
-        assertNotNull("No data message received", message);
-
-        VehiclePropValue.RawValue rawValue = message.value;
-        int messageType = rawValue.int32Values.get(
-                VmsMessageWithLayerIntegerValuesIndex.MESSAGE_TYPE);
-        int layerId = rawValue.int32Values.get(
-                VmsMessageWithLayerIntegerValuesIndex.LAYER_TYPE);
-        int layerVersion = rawValue.int32Values.get(
-                VmsMessageWithLayerIntegerValuesIndex.LAYER_VERSION);
-        byte[] payload = new byte[rawValue.bytes.size()];
-        for (int i = 0; i < rawValue.bytes.size(); ++i) {
-            payload[i] = rawValue.bytes.get(i);
-        }
-        assertEquals(VmsMessageType.DATA, messageType);
-        assertEquals(MOCK_PUBLISHER_LAYER_ID, layerId);
-        assertEquals(MOCK_PUBLISHER_LAYER_VERSION, layerVersion);
-        assertArrayEquals(PAYLOAD, payload);
-    }
-}
diff --git a/tests/carservice_test/src/com/android/car/VmsPublisherSubscriberTest.java b/tests/carservice_test/src/com/android/car/VmsPublisherSubscriberTest.java
index 87cf1b8..a95a45a 100644
--- a/tests/carservice_test/src/com/android/car/VmsPublisherSubscriberTest.java
+++ b/tests/carservice_test/src/com/android/car/VmsPublisherSubscriberTest.java
@@ -31,10 +31,14 @@
 import android.car.vms.VmsSubscriptionState;
 import android.util.Pair;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.FlakyTest;
 import androidx.test.filters.MediumTest;
+import androidx.test.filters.RequiresDevice;
 
 import org.junit.Before;
 import org.junit.Test;
+import org.junit.runner.RunWith;
 
 import java.util.Arrays;
 import java.util.Collections;
@@ -43,6 +47,7 @@
 import java.util.function.Supplier;
 import java.util.function.ToIntFunction;
 
+@RunWith(AndroidJUnit4.class)
 @MediumTest
 public class VmsPublisherSubscriberTest extends MockedVmsTestBase {
     private static final VmsLayer SUBSCRIPTION_LAYER = new VmsLayer(1, 1, 1);
@@ -245,6 +250,7 @@
     }
 
     @Test
+    @RequiresDevice
     public void testSubscribe() {
         mSubscriber.subscribe(SUBSCRIPTION_LAYER);
         assertSubscriptionState(1, SUBSCRIPTION_LAYER);
@@ -514,6 +520,7 @@
     }
 
     @Test
+    @FlakyTest
     public void testUnsubscribeToPublisher_MultiplePublishers() {
         int publisherId = mPublisher.getPublisherId(PUBLISHER_INFO);
         int publisherId2 = mPublisher.getPublisherId(PUBLISHER_INFO_OTHER);
diff --git a/tests/carservice_test/src/com/android/car/VmsSubscriberManagerTest.java b/tests/carservice_test/src/com/android/car/VmsSubscriberManagerTest.java
index 19f9ec0..423718c 100644
--- a/tests/carservice_test/src/com/android/car/VmsSubscriberManagerTest.java
+++ b/tests/carservice_test/src/com/android/car/VmsSubscriberManagerTest.java
@@ -27,9 +27,11 @@
 import android.hardware.automotive.vehicle.V2_0.VmsMessageType;
 import android.util.Pair;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.MediumTest;
 
 import org.junit.Test;
+import org.junit.runner.RunWith;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -37,6 +39,7 @@
 import java.util.HashSet;
 import java.util.Set;
 
+@RunWith(AndroidJUnit4.class)
 @MediumTest
 public class VmsSubscriberManagerTest extends MockedVmsTestBase {
     private static final int PUBLISHER_ID = 17;
@@ -420,13 +423,36 @@
         assertEquals(1, sequenceNumber);
         assertEquals(1, numberLayers);
 
+        int[] offeringMessage2 = {
+                VmsMessageType.OFFERING, // MessageType
+                PUBLISHER_ID,
+                2, // Number of offered layers
+
+                SUBSCRIPTION_LAYER_ID,
+                MOCK_PUBLISHER_LAYER_SUBTYPE,
+                SUBSCRIPTION_LAYER_VERSION,
+                0, // number of dependencies for layer
+
+                SUBSCRIPTION_DEPENDANT_LAYER_ID_1,
+                MOCK_PUBLISHER_LAYER_SUBTYPE,
+                SUBSCRIPTION_DEPENDANT_LAYER_VERSION_1,
+                1, // number of dependencies for layer
+                SUBSCRIPTION_LAYER_ID,
+                MOCK_PUBLISHER_LAYER_SUBTYPE,
+                SUBSCRIPTION_LAYER_VERSION,
+        };
+
+
         // Inject second offer.
-        getMockHalClient().sendMessage(offeringMessage);
+        getMockHalClient().sendMessage(offeringMessage2);
 
         // Verify applications API.
         availableLayers = getMockSubscriberClient().receiveLayerAvailability();
         assertEquals(
-                Collections.singleton(SUBSCRIPTION_ASSOCIATED_LAYER),
+                new HashSet<>(Arrays.asList(
+                        SUBSCRIPTION_ASSOCIATED_LAYER,
+                        SUBSCRIPTION_DEPENDANT_ASSOCIATED_LAYER_1
+                )),
                 availableLayers.getAssociatedLayers());
         assertEquals(2, availableLayers.getSequence());
 
@@ -439,7 +465,7 @@
 
         assertEquals(messageType, VmsMessageType.AVAILABILITY_CHANGE);
         assertEquals(2, sequenceNumber);
-        assertEquals(1, numberLayers);
+        assertEquals(2, numberLayers);
 
     }
 
diff --git a/tests/carservice_test/src/com/android/car/audio/AudioFocusTest.java b/tests/carservice_test/src/com/android/car/audio/AudioFocusTest.java
deleted file mode 100644
index ff4c517..0000000
--- a/tests/carservice_test/src/com/android/car/audio/AudioFocusTest.java
+++ /dev/null
@@ -1,488 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.car.audio;
-
-import static org.junit.Assert.assertEquals;
-
-import android.media.AudioAttributes;
-import android.media.AudioFocusRequest;
-import android.media.AudioManager;
-
-import androidx.test.core.app.ApplicationProvider;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@RunWith(AndroidJUnit4.class)
-public class AudioFocusTest {
-
-    private static final String TAG = "AudioFocusTest";
-
-    private static final int TEST_TIMING_TOLERANCE_MS = 100;
-
-    // ContextNumber.INVALID
-    private static final AudioAttributes ATTR_VIRTUAL_SOURCE = new AudioAttributes.Builder()
-            .setUsage(AudioAttributes.USAGE_VIRTUAL_SOURCE)
-            .setContentType(AudioAttributes.USAGE_VIRTUAL_SOURCE)
-            .build();
-    // ContextNumber.MUSIC
-    private static final AudioAttributes ATTR_MEDIA = new AudioAttributes.Builder()
-            .setUsage(AudioAttributes.USAGE_MEDIA)
-            .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
-            .build();
-    // ContextNumber.NAVIGATION
-    private static final AudioAttributes ATTR_DRIVE_DIR = new AudioAttributes.Builder()
-            .setUsage(AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE)
-            .setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
-            .build();
-    // ContextNumber.VOICE_COMMAND
-    private static final AudioAttributes ATTR_A11Y = new AudioAttributes.Builder()
-            .setUsage(AudioAttributes.USAGE_ASSISTANCE_ACCESSIBILITY)
-            .setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
-            .build();
-    // ContextNumber.CALL_RING
-    private static final AudioAttributes ATTR_RINGTONE = new AudioAttributes.Builder()
-            .setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE)
-            .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
-            .build();
-    // ContextNumber.CALL
-    private static final AudioAttributes ATTR_VOICE_COM = new AudioAttributes.Builder()
-            .setUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION)
-            .setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
-            .build();
-    // ContextNumber.ALARM
-    private static final AudioAttributes ATTR_ALARM = new AudioAttributes.Builder()
-            .setUsage(AudioAttributes.USAGE_ALARM)
-            .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
-            .build();
-    // ContextNumber.NOTIFICATION
-    private static final AudioAttributes ATTR_NOTIFICATION = new AudioAttributes.Builder()
-            .setUsage(AudioAttributes.USAGE_NOTIFICATION)
-            .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
-            .build();
-    // ContextNumber.SYSTEM_SOUND
-    private static final AudioAttributes ATTR_A11Y_NOTIFICATION = new AudioAttributes.Builder()
-            .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION)
-            .setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
-            .build();
-
-    private AudioManager mAudioManager;
-
-    @Before
-    public void setUp() {
-        mAudioManager = new AudioManager(ApplicationProvider.getApplicationContext());
-    }
-
-    @Test
-    public void individualAttributeFocusRequest_focusRequestGranted() throws Exception {
-        // Make sure each usage is able to request and release audio focus individually
-        requestAndLoseFocusForAttribute(ATTR_VIRTUAL_SOURCE);
-        requestAndLoseFocusForAttribute(ATTR_MEDIA);
-        requestAndLoseFocusForAttribute(ATTR_DRIVE_DIR);
-        requestAndLoseFocusForAttribute(ATTR_A11Y);
-        requestAndLoseFocusForAttribute(ATTR_RINGTONE);
-        requestAndLoseFocusForAttribute(ATTR_VOICE_COM);
-        requestAndLoseFocusForAttribute(ATTR_ALARM);
-        requestAndLoseFocusForAttribute(ATTR_NOTIFICATION);
-        requestAndLoseFocusForAttribute(ATTR_A11Y_NOTIFICATION);
-    }
-
-    @Test
-    public void exclusiveInteractionsForFocusGain_requestGrantedAndFocusLossSent()
-            throws Exception {
-        // For each interaction the focus request is granted and on the second request
-        // focus lost is dispatched to the first focus listener
-
-        // Test Exclusive interactions with audio focus gain request without pause
-        // instead of ducking
-        testExclusiveInteractions(AudioManager.AUDIOFOCUS_GAIN, false);
-        // Test Exclusive interactions with audio focus gain request with pause instead of ducking
-        testExclusiveInteractions(AudioManager.AUDIOFOCUS_GAIN, true);
-    }
-
-    @Test
-    public void exclusiveInteractionsTransient_requestGrantedAndFocusLossSent()
-            throws Exception {
-        // For each interaction the focus request is granted and on the second request
-        // focus lost transient is dispatched to the first focus listener
-
-        // Test Exclusive interactions with audio focus gain transient request
-        // without pause instead of ducking
-        testExclusiveInteractions(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT, false);
-        // Test Exclusive interactions with audio focus gain transient request
-        // with pause instead of ducking
-        testExclusiveInteractions(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT, true);
-    }
-
-    @Test
-    public void exclusiveInteractionsTransientMayDuck_requestGrantedAndFocusLossSent()
-            throws Exception {
-        // For each interaction the focus request is granted and on the second request
-        // focus lost transient is dispatched to the first focus listener
-
-        // Test exclusive interactions with audio focus transient may duck focus request
-        // without pause instead of ducking
-        testExclusiveInteractions(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK,
-                false);
-        // Test exclusive interactions with audio focus transient may duck focus request
-        // with pause instead of ducking
-        testExclusiveInteractions(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK,
-                true);
-    }
-
-    @Test
-    public void rejectedInteractions_focusRequestRejected() throws Exception {
-        // Test different paired interaction between different usages
-        // for each interaction pair the first focus request will be granted but the second
-        // will be rejected
-        int interaction = CarAudioFocus.INTERACTION_REJECT;
-        int gain = AudioManager.AUDIOFOCUS_GAIN;
-        testInteraction(ATTR_VIRTUAL_SOURCE, ATTR_VIRTUAL_SOURCE, interaction, gain, false);
-        testInteraction(ATTR_VIRTUAL_SOURCE, ATTR_MEDIA, interaction, gain, false);
-        testInteraction(ATTR_VIRTUAL_SOURCE, ATTR_DRIVE_DIR, interaction, gain, false);
-        testInteraction(ATTR_VIRTUAL_SOURCE, ATTR_A11Y, interaction, gain, false);
-        testInteraction(ATTR_VIRTUAL_SOURCE, ATTR_RINGTONE, interaction, gain, false);
-        testInteraction(ATTR_VIRTUAL_SOURCE, ATTR_VOICE_COM, interaction, gain, false);
-        testInteraction(ATTR_VIRTUAL_SOURCE, ATTR_ALARM, interaction, gain, false);
-        testInteraction(ATTR_VIRTUAL_SOURCE, ATTR_NOTIFICATION, interaction, gain, false);
-        testInteraction(ATTR_VIRTUAL_SOURCE, ATTR_A11Y_NOTIFICATION, interaction, gain, false);
-
-        testInteraction(ATTR_MEDIA, ATTR_VIRTUAL_SOURCE, interaction, gain, false);
-
-        testInteraction(ATTR_DRIVE_DIR, ATTR_VIRTUAL_SOURCE, interaction, gain, false);
-
-        testInteraction(ATTR_A11Y, ATTR_VIRTUAL_SOURCE, interaction, gain, false);
-        testInteraction(ATTR_A11Y, ATTR_DRIVE_DIR, interaction, gain, false);
-        testInteraction(ATTR_A11Y, ATTR_NOTIFICATION, interaction, gain, false);
-        testInteraction(ATTR_A11Y, ATTR_A11Y_NOTIFICATION, interaction, gain, false);
-
-        testInteraction(ATTR_RINGTONE, ATTR_VIRTUAL_SOURCE, interaction, gain, false);
-        testInteraction(ATTR_RINGTONE, ATTR_MEDIA, interaction, gain, false);
-        testInteraction(ATTR_RINGTONE, ATTR_ALARM, interaction, gain, false);
-        testInteraction(ATTR_RINGTONE, ATTR_NOTIFICATION, interaction, gain, false);
-
-        testInteraction(ATTR_VOICE_COM, ATTR_VIRTUAL_SOURCE, interaction, gain, false);
-        testInteraction(ATTR_VOICE_COM, ATTR_MEDIA, interaction, gain, false);
-        testInteraction(ATTR_VOICE_COM, ATTR_A11Y, interaction, gain, false);
-
-        testInteraction(ATTR_ALARM, ATTR_VIRTUAL_SOURCE, interaction, gain, false);
-
-        testInteraction(ATTR_NOTIFICATION, ATTR_VIRTUAL_SOURCE, interaction, gain, false);
-
-        testInteraction(ATTR_A11Y_NOTIFICATION, ATTR_VIRTUAL_SOURCE, interaction, gain, false);
-    }
-
-    @Test
-    public void concurrentInteractionsFocusGain_requestGrantedAndFocusLossSent() throws Exception {
-        // Test concurrent interactions i.e. interactions that can
-        // potentially gain focus at the same time.
-        // For this test permanent focus gain is requested by two usages.
-        // The focus request will be granted for both and on the second focus request focus
-        // lost will dispatched to the first focus listener listener.
-        testConcurrentInteractions(AudioManager.AUDIOFOCUS_GAIN, false);
-    }
-
-    @Test
-    public void concurrentInteractionsTransientGain_requestGrantedAndFocusLossTransientSent()
-            throws Exception {
-        // Test concurrent interactions i.e. interactions that can
-        // potentially gain focus at the same time.
-        // For this test permanent focus gain is requested by first usage and focus gain transient
-        // is requested by second usage.
-        // The focus request will be granted for both and on the second focus request focus
-        // lost transient will dispatched to the first focus listener listener.
-        testConcurrentInteractions(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT, false);
-        // Repeat the test this time with pause for ducking on first listener
-        testConcurrentInteractions(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT, true);
-    }
-
-    @Test
-    public void concurrentInteractionsTransientGainMayDuck_requestGrantedAndNoFocusLossSent()
-            throws Exception {
-        // Test concurrent interactions i.e. interactions that can
-        // potentially gain focus at the same time.
-        // For this test permanent focus gain is requested by first usage and focus gain transient
-        // may duck is requested by second usage.
-        // The focus request will be granted for both but no focus lost is sent to the first focus
-        // listener, as each usage actually has shared focus and  should play at the same time.
-        testConcurrentInteractions(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK, false);
-        // Test the same behaviour but this time with pause for ducking on the first focus listener
-        testConcurrentInteractions(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK, true);
-    }
-
-    private void testConcurrentInteractions(int gain, boolean pauseForDucking)
-            throws Exception {
-        // Test paired concurrent interactions i.e. interactions that can
-        // potentially gain focus at the same time.
-        int interaction = CarAudioFocus.INTERACTION_CONCURRENT;
-        testInteraction(ATTR_MEDIA, ATTR_DRIVE_DIR, interaction, gain, pauseForDucking);
-        testInteraction(ATTR_MEDIA, ATTR_NOTIFICATION, interaction, gain, pauseForDucking);
-        testInteraction(ATTR_MEDIA, ATTR_A11Y_NOTIFICATION, interaction, gain, pauseForDucking);
-
-        testInteraction(ATTR_DRIVE_DIR, ATTR_MEDIA, interaction, gain, pauseForDucking);
-        testInteraction(ATTR_DRIVE_DIR, ATTR_DRIVE_DIR, interaction, gain, pauseForDucking);
-        testInteraction(ATTR_DRIVE_DIR, ATTR_RINGTONE, interaction, gain, pauseForDucking);
-        testInteraction(ATTR_DRIVE_DIR, ATTR_ALARM, interaction, gain, pauseForDucking);
-        testInteraction(ATTR_DRIVE_DIR, ATTR_NOTIFICATION, interaction, gain, pauseForDucking);
-        testInteraction(ATTR_DRIVE_DIR, ATTR_A11Y_NOTIFICATION, interaction, gain, pauseForDucking);
-
-        testInteraction(ATTR_A11Y, ATTR_MEDIA, interaction, gain, pauseForDucking);
-        testInteraction(ATTR_A11Y, ATTR_A11Y, interaction, gain, pauseForDucking);
-
-        testInteraction(ATTR_RINGTONE, ATTR_DRIVE_DIR, interaction, gain, pauseForDucking);
-        testInteraction(ATTR_RINGTONE, ATTR_A11Y, interaction, gain, pauseForDucking);
-        testInteraction(ATTR_RINGTONE, ATTR_RINGTONE, interaction, gain, pauseForDucking);
-        testInteraction(ATTR_RINGTONE, ATTR_VOICE_COM, interaction, gain, pauseForDucking);
-
-        testInteraction(ATTR_VOICE_COM, ATTR_DRIVE_DIR, interaction, gain, pauseForDucking);
-        testInteraction(ATTR_VOICE_COM, ATTR_RINGTONE, interaction, gain, pauseForDucking);
-        testInteraction(ATTR_VOICE_COM, ATTR_VOICE_COM, interaction, gain, pauseForDucking);
-        testInteraction(ATTR_VOICE_COM, ATTR_ALARM, interaction, gain, pauseForDucking);
-        testInteraction(ATTR_VOICE_COM, ATTR_NOTIFICATION, interaction, gain, pauseForDucking);
-
-        testInteraction(ATTR_ALARM, ATTR_MEDIA, interaction, gain, pauseForDucking);
-        testInteraction(ATTR_ALARM, ATTR_DRIVE_DIR, interaction, gain, pauseForDucking);
-        testInteraction(ATTR_ALARM, ATTR_ALARM, interaction, gain, pauseForDucking);
-        testInteraction(ATTR_ALARM, ATTR_NOTIFICATION, interaction, gain, pauseForDucking);
-        testInteraction(ATTR_ALARM, ATTR_A11Y_NOTIFICATION, interaction, gain, pauseForDucking);
-
-        testInteraction(ATTR_NOTIFICATION, ATTR_MEDIA, interaction, gain, pauseForDucking);
-        testInteraction(ATTR_NOTIFICATION, ATTR_DRIVE_DIR, interaction, gain, pauseForDucking);
-        testInteraction(ATTR_NOTIFICATION, ATTR_ALARM, interaction, gain, pauseForDucking);
-        testInteraction(ATTR_NOTIFICATION, ATTR_NOTIFICATION, interaction, gain, pauseForDucking);
-        testInteraction(ATTR_NOTIFICATION, ATTR_A11Y_NOTIFICATION, interaction, gain,
-                pauseForDucking);
-
-
-        testInteraction(ATTR_A11Y_NOTIFICATION, ATTR_MEDIA, interaction, gain, pauseForDucking);
-        testInteraction(ATTR_A11Y_NOTIFICATION, ATTR_DRIVE_DIR, interaction, gain, pauseForDucking);
-        testInteraction(ATTR_A11Y_NOTIFICATION, ATTR_ALARM, interaction, gain, pauseForDucking);
-        testInteraction(ATTR_A11Y_NOTIFICATION, ATTR_NOTIFICATION, interaction, gain,
-                pauseForDucking);
-        testInteraction(ATTR_A11Y_NOTIFICATION, ATTR_A11Y_NOTIFICATION, interaction, gain,
-                pauseForDucking);
-    }
-
-    private void testExclusiveInteractions(int gain, boolean pauseForDucking)
-            throws Exception {
-
-        // Test exclusive interaction, interaction where each usage will not share focus with other
-        // another usage. As a result once focus is gained any current focus listener
-        // in this interaction will lose focus.
-        int interaction = CarAudioFocus.INTERACTION_EXCLUSIVE;
-        testInteraction(ATTR_MEDIA, ATTR_MEDIA, interaction, gain, pauseForDucking);
-        testInteraction(ATTR_MEDIA, ATTR_A11Y, interaction, gain, pauseForDucking);
-        testInteraction(ATTR_MEDIA, ATTR_RINGTONE, interaction, gain, pauseForDucking);
-        testInteraction(ATTR_MEDIA, ATTR_VOICE_COM, interaction, gain, pauseForDucking);
-        testInteraction(ATTR_MEDIA, ATTR_ALARM, interaction, gain, pauseForDucking);
-
-        testInteraction(ATTR_DRIVE_DIR, ATTR_A11Y, interaction, gain, pauseForDucking);
-        testInteraction(ATTR_DRIVE_DIR, ATTR_VOICE_COM, interaction, gain, pauseForDucking);
-
-        testInteraction(ATTR_A11Y, ATTR_RINGTONE, interaction, gain, pauseForDucking);
-        testInteraction(ATTR_A11Y, ATTR_VOICE_COM, interaction, gain, pauseForDucking);
-
-        testInteraction(ATTR_ALARM, ATTR_A11Y, interaction, gain, pauseForDucking);
-        testInteraction(ATTR_ALARM, ATTR_RINGTONE, interaction, gain, pauseForDucking);
-        testInteraction(ATTR_ALARM, ATTR_VOICE_COM, interaction, gain, pauseForDucking);
-
-        testInteraction(ATTR_NOTIFICATION, ATTR_A11Y, interaction, gain, pauseForDucking);
-        testInteraction(ATTR_NOTIFICATION, ATTR_RINGTONE, interaction, gain, pauseForDucking);
-        testInteraction(ATTR_NOTIFICATION, ATTR_VOICE_COM, interaction, gain, pauseForDucking);
-
-        testInteraction(ATTR_A11Y_NOTIFICATION, ATTR_A11Y, interaction, gain, pauseForDucking);
-        testInteraction(ATTR_A11Y_NOTIFICATION, ATTR_RINGTONE, interaction, gain, pauseForDucking);
-        testInteraction(ATTR_A11Y_NOTIFICATION, ATTR_VOICE_COM, interaction, gain, pauseForDucking);
-    }
-
-
-    /**
-     * Test paired usage interactions with gainType and pause instead ducking
-     * @param attributes1 Attributes of the first usage (first focus requester) in the interaction
-     * @param attributes2 Attributes of the second usage (second focus requester) in the interaction
-     * @param interaction type of interaction {@link CarAudioFocus.INTERACTION_REJECT},
-     * {@link CarAudioFocus.INTERACTION_EXCLUSIVE}, {@link CarAudioFocus.INTERACTION_CONCURRENT}
-     * @param gainType Type of gain {@link AudioManager.AUDIOFOCUS_GAIN} ,
-     * {@link CarAudioFocus.AUDIOFOCUS_GAIN_TRANSIENT},
-     * {@link CarAudioFocus.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK}
-     * @param pauseForDucking flag to indicate if the first focus listener should pause
-     *                        instead of ducking
-     * @throws Exception
-     */
-    private void testInteraction(AudioAttributes attributes1,
-            AudioAttributes attributes2,
-            int interaction,
-            int gainType,
-            boolean pauseForDucking) throws Exception {
-
-        final FocusChangeListener focusChangeListener1 = new FocusChangeListener();
-        final AudioFocusRequest audioFocusRequest1 = new AudioFocusRequest
-                .Builder(AudioManager.AUDIOFOCUS_GAIN)
-                .setAudioAttributes(attributes1)
-                .setOnAudioFocusChangeListener(focusChangeListener1)
-                .setForceDucking(false)
-                .setWillPauseWhenDucked(pauseForDucking)
-                .build();
-
-        final FocusChangeListener focusChangeListener2 = new FocusChangeListener();
-        final AudioFocusRequest audioFocusRequest2 = new AudioFocusRequest
-                .Builder(gainType)
-                .setAudioAttributes(attributes2)
-                .setOnAudioFocusChangeListener(focusChangeListener2)
-                .setForceDucking(false)
-                .build();
-
-        int expectedLoss = 0;
-
-        // Each focus gain type will return a different focus lost type
-        switch (gainType) {
-            case AudioManager.AUDIOFOCUS_GAIN:
-                expectedLoss = AudioManager.AUDIOFOCUS_LOSS;
-                break;
-            case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT:
-                expectedLoss = AudioManager.AUDIOFOCUS_LOSS_TRANSIENT;
-                break;
-            case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK:
-                expectedLoss = AudioManager.AUDIOFOCUS_LOSS_TRANSIENT;
-                // Note loss or gain will not be sent as both can live concurrently
-                if (interaction == CarAudioFocus.INTERACTION_CONCURRENT && !pauseForDucking) {
-                    expectedLoss = AudioManager.AUDIOFOCUS_NONE;
-                }
-                break;
-        }
-
-        int secondRequestResultsExpected = AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
-
-        if (interaction == CarAudioFocus.INTERACTION_REJECT) {
-            secondRequestResultsExpected = AudioManager.AUDIOFOCUS_REQUEST_FAILED;
-        }
-
-        int requestResult = mAudioManager.requestAudioFocus(audioFocusRequest1);
-        String message = "Focus gain request failed  for 1st "
-                + AudioAttributes.usageToString(attributes1.getUsage());
-        assertEquals(message, AudioManager.AUDIOFOCUS_REQUEST_GRANTED, requestResult);
-
-
-        requestResult = mAudioManager.requestAudioFocus(audioFocusRequest2);
-        message = "Focus gain request failed for 2nd "
-                + AudioAttributes.usageToString(attributes2.getUsage());
-        assertEquals(message, secondRequestResultsExpected, requestResult);
-
-        // If the results is rejected for second one we only have to clean up first
-        // as the second focus request is rejected
-        if (interaction == CarAudioFocus.INTERACTION_REJECT) {
-            requestResult = mAudioManager.abandonAudioFocusRequest(audioFocusRequest1);
-            message = "Focus loss request failed for 1st "
-                    + AudioAttributes.usageToString(attributes1.getUsage());
-            assertEquals(message, AudioManager.AUDIOFOCUS_REQUEST_GRANTED, requestResult);
-        }
-
-        // If exclusive we expect to lose focus on 1st one
-        // unless we have a concurrent interaction
-        if (interaction == CarAudioFocus.INTERACTION_EXCLUSIVE
-                || interaction == CarAudioFocus.INTERACTION_CONCURRENT) {
-            Thread.sleep(TEST_TIMING_TOLERANCE_MS);
-            message = "Focus change was not dispatched for 1st "
-                    + AudioAttributes.usageToString(ATTR_MEDIA.getUsage());
-            assertEquals(message, expectedLoss,
-                    focusChangeListener1.getFocusChangeAndReset());
-
-            requestResult = mAudioManager.abandonAudioFocusRequest(audioFocusRequest2);
-            message = "Focus loss request failed  for 2nd "
-                    + AudioAttributes.usageToString(ATTR_MEDIA.getUsage());
-            assertEquals(message, AudioManager.AUDIOFOCUS_REQUEST_GRANTED, requestResult);
-
-            // If the loss was transient then we should have received back on 1st
-            if ((gainType == AudioManager.AUDIOFOCUS_GAIN_TRANSIENT
-                    || gainType == AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK)) {
-
-                // Since ducking and concurrent can exist together
-                // this needs to be skipped as the focus lost is not sent
-                if (!(interaction == CarAudioFocus.INTERACTION_CONCURRENT
-                        && gainType == AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK)) {
-                    Thread.sleep(TEST_TIMING_TOLERANCE_MS);
-                    message = "Focus change was not dispatched for 1st "
-                            + AudioAttributes.usageToString(ATTR_MEDIA.getUsage());
-                    assertEquals(message, AudioManager.AUDIOFOCUS_GAIN,
-                            focusChangeListener1.getFocusChangeAndReset());
-                }
-                // For concurrent focus interactions still needs to be released
-                message = "Focus loss request failed  for 1st  "
-                        + AudioAttributes.usageToString(attributes1.getUsage());
-                requestResult = mAudioManager.abandonAudioFocusRequest(audioFocusRequest1);
-                assertEquals(message, AudioManager.AUDIOFOCUS_REQUEST_GRANTED,
-                        requestResult);
-            }
-        }
-    }
-
-    /**
-     * Verifies usage can request audio focus and release it
-     * @param attribute usage attribute to request focus
-     * @throws Exception
-     */
-    private void requestAndLoseFocusForAttribute(AudioAttributes attribute)  throws Exception {
-        final FocusChangeListener focusChangeListener = new FocusChangeListener();
-        final AudioFocusRequest audioFocusRequest = new AudioFocusRequest
-                .Builder(AudioManager.AUDIOFOCUS_GAIN)
-                .setAudioAttributes(attribute)
-                .setOnAudioFocusChangeListener(focusChangeListener)
-                .setForceDucking(false)
-                .build();
-
-
-        int requestResult = mAudioManager.requestAudioFocus(audioFocusRequest);
-        String message = "Focus gain request failed  for "
-                + AudioAttributes.usageToString(attribute.getUsage());
-        assertEquals(message, AudioManager.AUDIOFOCUS_REQUEST_GRANTED, requestResult);
-
-        Thread.sleep(TEST_TIMING_TOLERANCE_MS);
-        // Verify no focus changed dispatched
-        message = "Focus change was dispatched for "
-                + AudioAttributes.usageToString(attribute.getUsage());
-        assertEquals(message, AudioManager.AUDIOFOCUS_NONE,
-                focusChangeListener.getFocusChangeAndReset());
-
-        requestResult = mAudioManager.abandonAudioFocusRequest(audioFocusRequest);
-        message = "Focus loss request failed  for "
-                + AudioAttributes.usageToString(attribute.getUsage());
-        assertEquals(message, AudioManager.AUDIOFOCUS_REQUEST_GRANTED, requestResult);
-    }
-
-    private static class FocusChangeListener implements AudioManager.OnAudioFocusChangeListener {
-        private final Object mLock = new Object();
-        private int mFocusChange = AudioManager.AUDIOFOCUS_NONE;
-
-        int getFocusChangeAndReset() {
-            final int change;
-            synchronized (mLock) {
-                change = mFocusChange;
-                mFocusChange = AudioManager.AUDIOFOCUS_NONE;
-            }
-            return change;
-        }
-
-        @Override
-        public void onAudioFocusChange(int focusChange) {
-            synchronized (mLock) {
-                mFocusChange = focusChange;
-            }
-        }
-    }
-}
diff --git a/tests/carservice_test/src/com/android/car/audio/CarAudioFocusTest.java b/tests/carservice_test/src/com/android/car/audio/CarAudioFocusTest.java
new file mode 100644
index 0000000..e481de1
--- /dev/null
+++ b/tests/carservice_test/src/com/android/car/audio/CarAudioFocusTest.java
@@ -0,0 +1,504 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.audio;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assume.assumeTrue;
+
+import android.content.Context;
+import android.media.AudioAttributes;
+import android.media.AudioFocusRequest;
+import android.media.AudioManager;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.FlakyTest;
+import androidx.test.filters.RequiresDevice;
+
+import com.android.car.R;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class CarAudioFocusTest {
+    private static final int TEST_TIMING_TOLERANCE_MS = 100;
+    private static final int INTERACTION_REJECT     = 0;    // Focus not granted
+    private static final int INTERACTION_EXCLUSIVE  = 1;    // Focus granted, others loose focus
+    private static final int INTERACTION_CONCURRENT = 2;    // Focus granted, others keep focus
+
+    // CarAudioContext.INVALID
+    private static final AudioAttributes ATTR_VIRTUAL_SOURCE = new AudioAttributes.Builder()
+            .setUsage(AudioAttributes.USAGE_VIRTUAL_SOURCE)
+            .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
+            .build();
+    // CarAudioContext.MUSIC
+    private static final AudioAttributes ATTR_MEDIA = new AudioAttributes.Builder()
+            .setUsage(AudioAttributes.USAGE_MEDIA)
+            .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
+            .build();
+    // CarAudioContext.NAVIGATION
+    private static final AudioAttributes ATTR_DRIVE_DIR = new AudioAttributes.Builder()
+            .setUsage(AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE)
+            .setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
+            .build();
+    // CarAudioContext.VOICE_COMMAND
+    private static final AudioAttributes ATTR_A11Y = new AudioAttributes.Builder()
+            .setUsage(AudioAttributes.USAGE_ASSISTANCE_ACCESSIBILITY)
+            .setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
+            .build();
+    // CarAudioContext.CALL_RING
+    private static final AudioAttributes ATTR_RINGTONE = new AudioAttributes.Builder()
+            .setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE)
+            .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
+            .build();
+    // CarAudioContext.CALL
+    private static final AudioAttributes ATTR_VOICE_COM = new AudioAttributes.Builder()
+            .setUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION)
+            .setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
+            .build();
+    // CarAudioContext.ALARM
+    private static final AudioAttributes ATTR_ALARM = new AudioAttributes.Builder()
+            .setUsage(AudioAttributes.USAGE_ALARM)
+            .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
+            .build();
+    // CarAudioContext.NOTIFICATION
+    private static final AudioAttributes ATTR_NOTIFICATION = new AudioAttributes.Builder()
+            .setUsage(AudioAttributes.USAGE_NOTIFICATION)
+            .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
+            .build();
+    // CarAudioContext.SYSTEM_SOUND
+    private static final AudioAttributes ATTR_A11Y_NOTIFICATION = new AudioAttributes.Builder()
+            .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION)
+            .setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
+            .build();
+
+    private AudioManager mAudioManager;
+
+    @Before
+    public void setUp() {
+        Context context = ApplicationProvider.getApplicationContext();
+        mAudioManager = new AudioManager(context);
+
+        boolean isDynamicRoutingEnabled = context.getResources().getBoolean(
+                R.bool.audioUseDynamicRouting);
+        assumeTrue("Dynamic routing must be enabled to run CarAudioFocusTests",
+                isDynamicRoutingEnabled);
+    }
+
+    @Test
+    public void individualAttributeFocusRequest_focusRequestGranted() throws Exception {
+        // Make sure each usage is able to request and release audio focus individually
+        requestAndLoseFocusForAttribute(ATTR_VIRTUAL_SOURCE);
+        requestAndLoseFocusForAttribute(ATTR_MEDIA);
+        requestAndLoseFocusForAttribute(ATTR_DRIVE_DIR);
+        requestAndLoseFocusForAttribute(ATTR_A11Y);
+        requestAndLoseFocusForAttribute(ATTR_RINGTONE);
+        requestAndLoseFocusForAttribute(ATTR_VOICE_COM);
+        requestAndLoseFocusForAttribute(ATTR_ALARM);
+        requestAndLoseFocusForAttribute(ATTR_NOTIFICATION);
+        requestAndLoseFocusForAttribute(ATTR_A11Y_NOTIFICATION);
+    }
+
+    @Test
+    @FlakyTest
+    public void exclusiveInteractionsForFocusGain_requestGrantedAndFocusLossSent()
+            throws Exception {
+        // For each interaction the focus request is granted and on the second request
+        // focus lost is dispatched to the first focus listener
+
+        // Test Exclusive interactions with audio focus gain request without pause
+        // instead of ducking
+        testExclusiveInteractions(AudioManager.AUDIOFOCUS_GAIN, false);
+        // Test Exclusive interactions with audio focus gain request with pause instead of ducking
+        testExclusiveInteractions(AudioManager.AUDIOFOCUS_GAIN, true);
+    }
+
+    @Test
+    public void exclusiveInteractionsTransient_requestGrantedAndFocusLossSent()
+            throws Exception {
+        // For each interaction the focus request is granted and on the second request
+        // focus lost transient is dispatched to the first focus listener
+
+        // Test Exclusive interactions with audio focus gain transient request
+        // without pause instead of ducking
+        testExclusiveInteractions(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT, false);
+        // Test Exclusive interactions with audio focus gain transient request
+        // with pause instead of ducking
+        testExclusiveInteractions(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT, true);
+    }
+
+    @RequiresDevice
+    @Test
+    public void exclusiveInteractionsTransientMayDuck_requestGrantedAndFocusLossSent()
+            throws Exception {
+        // For each interaction the focus request is granted and on the second request
+        // focus lost transient is dispatched to the first focus listener
+
+        // Test exclusive interactions with audio focus transient may duck focus request
+        // without pause instead of ducking
+        testExclusiveInteractions(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK,
+                false);
+        // Test exclusive interactions with audio focus transient may duck focus request
+        // with pause instead of ducking
+        testExclusiveInteractions(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK,
+                true);
+    }
+
+    @RequiresDevice
+    @Test
+    public void rejectedInteractions_focusRequestRejected() throws Exception {
+        // Test different paired interaction between different usages
+        // for each interaction pair the first focus request will be granted but the second
+        // will be rejected
+        int interaction = INTERACTION_REJECT;
+        int gain = AudioManager.AUDIOFOCUS_GAIN;
+        testInteraction(ATTR_VIRTUAL_SOURCE, ATTR_VIRTUAL_SOURCE, interaction, gain, false);
+        testInteraction(ATTR_VIRTUAL_SOURCE, ATTR_MEDIA, interaction, gain, false);
+        testInteraction(ATTR_VIRTUAL_SOURCE, ATTR_DRIVE_DIR, interaction, gain, false);
+        testInteraction(ATTR_VIRTUAL_SOURCE, ATTR_A11Y, interaction, gain, false);
+        testInteraction(ATTR_VIRTUAL_SOURCE, ATTR_RINGTONE, interaction, gain, false);
+        testInteraction(ATTR_VIRTUAL_SOURCE, ATTR_VOICE_COM, interaction, gain, false);
+        testInteraction(ATTR_VIRTUAL_SOURCE, ATTR_ALARM, interaction, gain, false);
+        testInteraction(ATTR_VIRTUAL_SOURCE, ATTR_NOTIFICATION, interaction, gain, false);
+        testInteraction(ATTR_VIRTUAL_SOURCE, ATTR_A11Y_NOTIFICATION, interaction, gain, false);
+
+        testInteraction(ATTR_MEDIA, ATTR_VIRTUAL_SOURCE, interaction, gain, false);
+
+        testInteraction(ATTR_DRIVE_DIR, ATTR_VIRTUAL_SOURCE, interaction, gain, false);
+
+        testInteraction(ATTR_A11Y, ATTR_VIRTUAL_SOURCE, interaction, gain, false);
+        testInteraction(ATTR_A11Y, ATTR_DRIVE_DIR, interaction, gain, false);
+        testInteraction(ATTR_A11Y, ATTR_NOTIFICATION, interaction, gain, false);
+        testInteraction(ATTR_A11Y, ATTR_A11Y_NOTIFICATION, interaction, gain, false);
+
+        testInteraction(ATTR_RINGTONE, ATTR_VIRTUAL_SOURCE, interaction, gain, false);
+        testInteraction(ATTR_RINGTONE, ATTR_MEDIA, interaction, gain, false);
+        testInteraction(ATTR_RINGTONE, ATTR_ALARM, interaction, gain, false);
+        testInteraction(ATTR_RINGTONE, ATTR_NOTIFICATION, interaction, gain, false);
+
+        testInteraction(ATTR_VOICE_COM, ATTR_VIRTUAL_SOURCE, interaction, gain, false);
+        testInteraction(ATTR_VOICE_COM, ATTR_MEDIA, interaction, gain, false);
+        testInteraction(ATTR_VOICE_COM, ATTR_A11Y, interaction, gain, false);
+
+        testInteraction(ATTR_ALARM, ATTR_VIRTUAL_SOURCE, interaction, gain, false);
+
+        testInteraction(ATTR_NOTIFICATION, ATTR_VIRTUAL_SOURCE, interaction, gain, false);
+
+        testInteraction(ATTR_A11Y_NOTIFICATION, ATTR_VIRTUAL_SOURCE, interaction, gain, false);
+    }
+
+    @Test
+    public void concurrentInteractionsFocusGain_requestGrantedAndFocusLossSent() throws Exception {
+        // Test concurrent interactions i.e. interactions that can
+        // potentially gain focus at the same time.
+        // For this test permanent focus gain is requested by two usages.
+        // The focus request will be granted for both and on the second focus request focus
+        // lost will dispatched to the first focus listener listener.
+        testConcurrentInteractions(AudioManager.AUDIOFOCUS_GAIN, false);
+    }
+
+    @Test
+    @FlakyTest
+    public void concurrentInteractionsTransientGain_requestGrantedAndFocusLossTransientSent()
+            throws Exception {
+        // Test concurrent interactions i.e. interactions that can
+        // potentially gain focus at the same time.
+        // For this test permanent focus gain is requested by first usage and focus gain transient
+        // is requested by second usage.
+        // The focus request will be granted for both and on the second focus request focus
+        // lost transient will dispatched to the first focus listener listener.
+        testConcurrentInteractions(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT, false);
+        // Repeat the test this time with pause for ducking on first listener
+        testConcurrentInteractions(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT, true);
+    }
+
+    @RequiresDevice
+    @Test
+    public void concurrentInteractionsTransientGainMayDuck_requestGrantedAndNoFocusLossSent()
+            throws Exception {
+        // Test concurrent interactions i.e. interactions that can
+        // potentially gain focus at the same time.
+        // For this test permanent focus gain is requested by first usage and focus gain transient
+        // may duck is requested by second usage.
+        // The focus request will be granted for both but no focus lost is sent to the first focus
+        // listener, as each usage actually has shared focus and  should play at the same time.
+        testConcurrentInteractions(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK, false);
+        // Test the same behaviour but this time with pause for ducking on the first focus listener
+        testConcurrentInteractions(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK, true);
+    }
+
+    private void testConcurrentInteractions(int gain, boolean pauseForDucking)
+            throws Exception {
+        // Test paired concurrent interactions i.e. interactions that can
+        // potentially gain focus at the same time.
+        int interaction = INTERACTION_CONCURRENT;
+        testInteraction(ATTR_MEDIA, ATTR_DRIVE_DIR, interaction, gain, pauseForDucking);
+        testInteraction(ATTR_MEDIA, ATTR_NOTIFICATION, interaction, gain, pauseForDucking);
+        testInteraction(ATTR_MEDIA, ATTR_A11Y_NOTIFICATION, interaction, gain, pauseForDucking);
+
+        testInteraction(ATTR_DRIVE_DIR, ATTR_MEDIA, interaction, gain, pauseForDucking);
+        testInteraction(ATTR_DRIVE_DIR, ATTR_DRIVE_DIR, interaction, gain, pauseForDucking);
+        testInteraction(ATTR_DRIVE_DIR, ATTR_RINGTONE, interaction, gain, pauseForDucking);
+        testInteraction(ATTR_DRIVE_DIR, ATTR_ALARM, interaction, gain, pauseForDucking);
+        testInteraction(ATTR_DRIVE_DIR, ATTR_NOTIFICATION, interaction, gain, pauseForDucking);
+        testInteraction(ATTR_DRIVE_DIR, ATTR_A11Y_NOTIFICATION, interaction, gain, pauseForDucking);
+
+        testInteraction(ATTR_A11Y, ATTR_MEDIA, interaction, gain, pauseForDucking);
+        testInteraction(ATTR_A11Y, ATTR_A11Y, interaction, gain, pauseForDucking);
+
+        testInteraction(ATTR_RINGTONE, ATTR_DRIVE_DIR, interaction, gain, pauseForDucking);
+        testInteraction(ATTR_RINGTONE, ATTR_A11Y, interaction, gain, pauseForDucking);
+        testInteraction(ATTR_RINGTONE, ATTR_RINGTONE, interaction, gain, pauseForDucking);
+        testInteraction(ATTR_RINGTONE, ATTR_VOICE_COM, interaction, gain, pauseForDucking);
+
+        testInteraction(ATTR_VOICE_COM, ATTR_DRIVE_DIR, interaction, gain, pauseForDucking);
+        testInteraction(ATTR_VOICE_COM, ATTR_RINGTONE, interaction, gain, pauseForDucking);
+        testInteraction(ATTR_VOICE_COM, ATTR_VOICE_COM, interaction, gain, pauseForDucking);
+        testInteraction(ATTR_VOICE_COM, ATTR_ALARM, interaction, gain, pauseForDucking);
+        testInteraction(ATTR_VOICE_COM, ATTR_NOTIFICATION, interaction, gain, pauseForDucking);
+
+        testInteraction(ATTR_ALARM, ATTR_MEDIA, interaction, gain, pauseForDucking);
+        testInteraction(ATTR_ALARM, ATTR_DRIVE_DIR, interaction, gain, pauseForDucking);
+        testInteraction(ATTR_ALARM, ATTR_ALARM, interaction, gain, pauseForDucking);
+        testInteraction(ATTR_ALARM, ATTR_NOTIFICATION, interaction, gain, pauseForDucking);
+        testInteraction(ATTR_ALARM, ATTR_A11Y_NOTIFICATION, interaction, gain, pauseForDucking);
+
+        testInteraction(ATTR_NOTIFICATION, ATTR_MEDIA, interaction, gain, pauseForDucking);
+        testInteraction(ATTR_NOTIFICATION, ATTR_DRIVE_DIR, interaction, gain, pauseForDucking);
+        testInteraction(ATTR_NOTIFICATION, ATTR_ALARM, interaction, gain, pauseForDucking);
+        testInteraction(ATTR_NOTIFICATION, ATTR_NOTIFICATION, interaction, gain, pauseForDucking);
+        testInteraction(ATTR_NOTIFICATION, ATTR_A11Y_NOTIFICATION, interaction, gain,
+                pauseForDucking);
+
+
+        testInteraction(ATTR_A11Y_NOTIFICATION, ATTR_MEDIA, interaction, gain, pauseForDucking);
+        testInteraction(ATTR_A11Y_NOTIFICATION, ATTR_DRIVE_DIR, interaction, gain, pauseForDucking);
+        testInteraction(ATTR_A11Y_NOTIFICATION, ATTR_ALARM, interaction, gain, pauseForDucking);
+        testInteraction(ATTR_A11Y_NOTIFICATION, ATTR_NOTIFICATION, interaction, gain,
+                pauseForDucking);
+        testInteraction(ATTR_A11Y_NOTIFICATION, ATTR_A11Y_NOTIFICATION, interaction, gain,
+                pauseForDucking);
+    }
+
+    private void testExclusiveInteractions(int gain, boolean pauseForDucking)
+            throws Exception {
+
+        // Test exclusive interaction, interaction where each usage will not share focus with other
+        // another usage. As a result once focus is gained any current focus listener
+        // in this interaction will lose focus.
+        int interaction = INTERACTION_EXCLUSIVE;
+        testInteraction(ATTR_MEDIA, ATTR_MEDIA, interaction, gain, pauseForDucking);
+        testInteraction(ATTR_MEDIA, ATTR_A11Y, interaction, gain, pauseForDucking);
+        testInteraction(ATTR_MEDIA, ATTR_RINGTONE, interaction, gain, pauseForDucking);
+        testInteraction(ATTR_MEDIA, ATTR_VOICE_COM, interaction, gain, pauseForDucking);
+        testInteraction(ATTR_MEDIA, ATTR_ALARM, interaction, gain, pauseForDucking);
+
+        testInteraction(ATTR_DRIVE_DIR, ATTR_A11Y, interaction, gain, pauseForDucking);
+        testInteraction(ATTR_DRIVE_DIR, ATTR_VOICE_COM, interaction, gain, pauseForDucking);
+
+        testInteraction(ATTR_A11Y, ATTR_RINGTONE, interaction, gain, pauseForDucking);
+        testInteraction(ATTR_A11Y, ATTR_VOICE_COM, interaction, gain, pauseForDucking);
+
+        testInteraction(ATTR_ALARM, ATTR_A11Y, interaction, gain, pauseForDucking);
+        testInteraction(ATTR_ALARM, ATTR_RINGTONE, interaction, gain, pauseForDucking);
+        testInteraction(ATTR_ALARM, ATTR_VOICE_COM, interaction, gain, pauseForDucking);
+
+        testInteraction(ATTR_NOTIFICATION, ATTR_A11Y, interaction, gain, pauseForDucking);
+        testInteraction(ATTR_NOTIFICATION, ATTR_RINGTONE, interaction, gain, pauseForDucking);
+        testInteraction(ATTR_NOTIFICATION, ATTR_VOICE_COM, interaction, gain, pauseForDucking);
+
+        testInteraction(ATTR_A11Y_NOTIFICATION, ATTR_A11Y, interaction, gain, pauseForDucking);
+        testInteraction(ATTR_A11Y_NOTIFICATION, ATTR_RINGTONE, interaction, gain, pauseForDucking);
+        testInteraction(ATTR_A11Y_NOTIFICATION, ATTR_VOICE_COM, interaction, gain, pauseForDucking);
+    }
+
+
+    /**
+     * Test paired usage interactions with gainType and pause instead ducking
+     * @param attributes1 Attributes of the first usage (first focus requester) in the interaction
+     * @param attributes2 Attributes of the second usage (second focus requester) in the interaction
+     * @param interaction type of interaction {@link INTERACTION_REJECT},
+     * {@link INTERACTION_EXCLUSIVE}, {@link INTERACTION_CONCURRENT}
+     * @param gainType Type of gain {@link AudioManager.AUDIOFOCUS_GAIN} ,
+     * {@link CarAudioFocus.AUDIOFOCUS_GAIN_TRANSIENT},
+     * {@link CarAudioFocus.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK}
+     * @param pauseForDucking flag to indicate if the first focus listener should pause
+     *                        instead of ducking
+     * @throws Exception
+     */
+    private void testInteraction(AudioAttributes attributes1,
+            AudioAttributes attributes2,
+            int interaction,
+            int gainType,
+            boolean pauseForDucking) throws Exception {
+
+        final FocusChangeListener focusChangeListener1 = new FocusChangeListener();
+        final AudioFocusRequest audioFocusRequest1 = new AudioFocusRequest
+                .Builder(AudioManager.AUDIOFOCUS_GAIN)
+                .setAudioAttributes(attributes1)
+                .setOnAudioFocusChangeListener(focusChangeListener1)
+                .setForceDucking(false)
+                .setWillPauseWhenDucked(pauseForDucking)
+                .build();
+
+        final FocusChangeListener focusChangeListener2 = new FocusChangeListener();
+        final AudioFocusRequest audioFocusRequest2 = new AudioFocusRequest
+                .Builder(gainType)
+                .setAudioAttributes(attributes2)
+                .setOnAudioFocusChangeListener(focusChangeListener2)
+                .setForceDucking(false)
+                .build();
+
+        int expectedLoss = 0;
+
+        // Each focus gain type will return a different focus lost type
+        switch (gainType) {
+            case AudioManager.AUDIOFOCUS_GAIN:
+                expectedLoss = AudioManager.AUDIOFOCUS_LOSS;
+                break;
+            case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT:
+                expectedLoss = AudioManager.AUDIOFOCUS_LOSS_TRANSIENT;
+                break;
+            case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK:
+                expectedLoss = AudioManager.AUDIOFOCUS_LOSS_TRANSIENT;
+                // Note loss or gain will not be sent as both can live concurrently
+                if (interaction == INTERACTION_CONCURRENT && !pauseForDucking) {
+                    expectedLoss = AudioManager.AUDIOFOCUS_NONE;
+                }
+                break;
+        }
+
+        int secondRequestResultsExpected = AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
+
+        if (interaction == INTERACTION_REJECT) {
+            secondRequestResultsExpected = AudioManager.AUDIOFOCUS_REQUEST_FAILED;
+        }
+
+        int requestResult = mAudioManager.requestAudioFocus(audioFocusRequest1);
+        String message = "Focus gain request failed  for 1st "
+                + AudioAttributes.usageToString(attributes1.getUsage());
+        assertEquals(message, AudioManager.AUDIOFOCUS_REQUEST_GRANTED, requestResult);
+
+
+        requestResult = mAudioManager.requestAudioFocus(audioFocusRequest2);
+        message = "Focus gain request failed for 2nd "
+                + AudioAttributes.usageToString(attributes2.getUsage());
+        assertEquals(message, secondRequestResultsExpected, requestResult);
+
+        // If the results is rejected for second one we only have to clean up first
+        // as the second focus request is rejected
+        if (interaction == INTERACTION_REJECT) {
+            requestResult = mAudioManager.abandonAudioFocusRequest(audioFocusRequest1);
+            message = "Focus loss request failed for 1st "
+                    + AudioAttributes.usageToString(attributes1.getUsage());
+            assertEquals(message, AudioManager.AUDIOFOCUS_REQUEST_GRANTED, requestResult);
+        }
+
+        // If exclusive we expect to lose focus on 1st one
+        // unless we have a concurrent interaction
+        if (interaction == INTERACTION_EXCLUSIVE || interaction == INTERACTION_CONCURRENT) {
+            Thread.sleep(TEST_TIMING_TOLERANCE_MS);
+            message = "Focus change was not dispatched for 1st "
+                    + AudioAttributes.usageToString(ATTR_MEDIA.getUsage());
+            assertEquals(message, expectedLoss,
+                    focusChangeListener1.getFocusChangeAndReset());
+
+            requestResult = mAudioManager.abandonAudioFocusRequest(audioFocusRequest2);
+            message = "Focus loss request failed  for 2nd "
+                    + AudioAttributes.usageToString(ATTR_MEDIA.getUsage());
+            assertEquals(message, AudioManager.AUDIOFOCUS_REQUEST_GRANTED, requestResult);
+
+            // If the loss was transient then we should have received back on 1st
+            if ((gainType == AudioManager.AUDIOFOCUS_GAIN_TRANSIENT
+                    || gainType == AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK)) {
+
+                // Since ducking and concurrent can exist together
+                // this needs to be skipped as the focus lost is not sent
+                if (!(interaction == INTERACTION_CONCURRENT
+                        && gainType == AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK)) {
+                    Thread.sleep(TEST_TIMING_TOLERANCE_MS);
+                    message = "Focus change was not dispatched for 1st "
+                            + AudioAttributes.usageToString(ATTR_MEDIA.getUsage());
+                    assertEquals(message, AudioManager.AUDIOFOCUS_GAIN,
+                            focusChangeListener1.getFocusChangeAndReset());
+                }
+                // For concurrent focus interactions still needs to be released
+                message = "Focus loss request failed  for 1st  "
+                        + AudioAttributes.usageToString(attributes1.getUsage());
+                requestResult = mAudioManager.abandonAudioFocusRequest(audioFocusRequest1);
+                assertEquals(message, AudioManager.AUDIOFOCUS_REQUEST_GRANTED,
+                        requestResult);
+            }
+        }
+    }
+
+    /**
+     * Verifies usage can request audio focus and release it
+     * @param attribute usage attribute to request focus
+     * @throws Exception
+     */
+    private void requestAndLoseFocusForAttribute(AudioAttributes attribute)  throws Exception {
+        final FocusChangeListener focusChangeListener = new FocusChangeListener();
+        final AudioFocusRequest audioFocusRequest = new AudioFocusRequest
+                .Builder(AudioManager.AUDIOFOCUS_GAIN)
+                .setAudioAttributes(attribute)
+                .setOnAudioFocusChangeListener(focusChangeListener)
+                .setForceDucking(false)
+                .build();
+
+
+        int requestResult = mAudioManager.requestAudioFocus(audioFocusRequest);
+        String message = "Focus gain request failed  for "
+                + AudioAttributes.usageToString(attribute.getUsage());
+        assertEquals(message, AudioManager.AUDIOFOCUS_REQUEST_GRANTED, requestResult);
+
+        Thread.sleep(TEST_TIMING_TOLERANCE_MS);
+        // Verify no focus changed dispatched
+        message = "Focus change was dispatched for "
+                + AudioAttributes.usageToString(attribute.getUsage());
+        assertEquals(message, AudioManager.AUDIOFOCUS_NONE,
+                focusChangeListener.getFocusChangeAndReset());
+
+        requestResult = mAudioManager.abandonAudioFocusRequest(audioFocusRequest);
+        message = "Focus loss request failed  for "
+                + AudioAttributes.usageToString(attribute.getUsage());
+        assertEquals(message, AudioManager.AUDIOFOCUS_REQUEST_GRANTED, requestResult);
+    }
+
+    private static class FocusChangeListener implements AudioManager.OnAudioFocusChangeListener {
+        private final Object mLock = new Object();
+        private int mFocusChange = AudioManager.AUDIOFOCUS_NONE;
+
+        int getFocusChangeAndReset() {
+            final int change;
+            synchronized (mLock) {
+                change = mFocusChange;
+                mFocusChange = AudioManager.AUDIOFOCUS_NONE;
+            }
+            return change;
+        }
+
+        @Override
+        public void onAudioFocusChange(int focusChange) {
+            synchronized (mLock) {
+                mFocusChange = focusChange;
+            }
+        }
+    }
+}
diff --git a/tests/carservice_test/src/com/android/car/audio/CarAudioZoneTest.java b/tests/carservice_test/src/com/android/car/audio/CarAudioZoneTest.java
new file mode 100644
index 0000000..74d9781
--- /dev/null
+++ b/tests/carservice_test/src/com/android/car/audio/CarAudioZoneTest.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.car.audio;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.when;
+import static org.testng.Assert.expectThrows;
+
+import android.car.media.CarAudioManager;
+import android.hardware.automotive.audiocontrol.V1_0.ContextNumber;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+
+@RunWith(MockitoJUnitRunner.class)
+public class CarAudioZoneTest {
+    private static final String MUSIC_ADDRESS = "bus0_music";
+    private static final String NAV_ADDRESS = "bus1_nav";
+
+    @Mock
+    private CarVolumeGroup mMockMusicGroup;
+    @Mock
+    private CarVolumeGroup mMockNavGroup;
+    private CarAudioZone mTestAudioZone =
+            new CarAudioZone(CarAudioManager.PRIMARY_AUDIO_ZONE, "Primary zone");
+    @Before
+    public void setUp() {
+        when(mMockMusicGroup.getAddressForContext(ContextNumber.MUSIC)).thenReturn(MUSIC_ADDRESS);
+        when(mMockMusicGroup.getContexts()).thenReturn(new int[]{ContextNumber.MUSIC});
+
+        when(mMockNavGroup.getAddressForContext(ContextNumber.NAVIGATION)).thenReturn(NAV_ADDRESS);
+        when(mMockNavGroup.getContexts()).thenReturn(new int[]{ContextNumber.NAVIGATION});
+    }
+
+    @Test
+    public void getAddressForContext_returnsExpectedDeviceAddress() {
+        mTestAudioZone.addVolumeGroup(mMockMusicGroup);
+        mTestAudioZone.addVolumeGroup(mMockNavGroup);
+
+        String musicAddress = mTestAudioZone.getAddressForContext(ContextNumber.MUSIC);
+        assertThat(musicAddress).isEqualTo(MUSIC_ADDRESS);
+
+        String navAddress = mTestAudioZone.getAddressForContext(ContextNumber.NAVIGATION);
+        assertThat(navAddress).matches(NAV_ADDRESS);
+    }
+
+    @Test
+    public void getAddressForContext_throwsOnInvalidContext() {
+        IllegalArgumentException thrown =
+                expectThrows(IllegalArgumentException.class,
+                        () -> mTestAudioZone.getAddressForContext(ContextNumber.INVALID));
+
+        assertThat(thrown).hasMessageThat().contains("audioContext 0 is invalid");
+    }
+
+    @Test
+    public void getAddressForContext_throwsOnNonExistentContext() {
+        IllegalStateException thrown =
+                expectThrows(IllegalStateException.class,
+                        () -> mTestAudioZone.getAddressForContext(ContextNumber.MUSIC));
+
+        assertThat(thrown).hasMessageThat().contains("Could not find output device in zone");
+    }
+}
diff --git a/tests/carservice_test/src/com/android/car/audio/CarAudioZonesHelperLegacyTest.java b/tests/carservice_test/src/com/android/car/audio/CarAudioZonesHelperLegacyTest.java
new file mode 100644
index 0000000..1fda2ed
--- /dev/null
+++ b/tests/carservice_test/src/com/android/car/audio/CarAudioZonesHelperLegacyTest.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.car.audio;
+
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.hardware.automotive.audiocontrol.V1_0.IAudioControl;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.google.common.collect.Lists;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+import org.mockito.Mockito;
+
+import java.util.List;
+
+@RunWith(AndroidJUnit4.class)
+public class CarAudioZonesHelperLegacyTest {
+
+    @Rule
+    public final ExpectedException thrown = ExpectedException.none();
+
+    @Test
+    public void constructor_checksForNoDuplicateBusNumbers() {
+        Context context = ApplicationProvider.getApplicationContext();
+
+        IAudioControl mockAudioControl = Mockito.mock(IAudioControl.class);
+
+        CarAudioDeviceInfo deviceInfo1 = Mockito.mock(CarAudioDeviceInfo.class);
+        when(deviceInfo1.getAddress()).thenReturn("bus001_media");
+        CarAudioDeviceInfo deviceInfo2 = Mockito.mock(CarAudioDeviceInfo.class);
+        when(deviceInfo2.getAddress()).thenReturn("bus001_notifications");
+        List<CarAudioDeviceInfo> carAudioDeviceInfos = Lists.newArrayList(deviceInfo1, deviceInfo2);
+
+        thrown.expect(RuntimeException.class);
+        thrown.expectMessage(
+                "Two addresses map to same bus number: bus001_notifications and bus001_media");
+
+        new CarAudioZonesHelperLegacy(context, 1,
+                carAudioDeviceInfos, mockAudioControl);
+    }
+}
diff --git a/tests/carservice_test/src/com/android/car/audio/CarAudioZonesHelperTest.java b/tests/carservice_test/src/com/android/car/audio/CarAudioZonesHelperTest.java
index bd8862d..631e85c 100644
--- a/tests/carservice_test/src/com/android/car/audio/CarAudioZonesHelperTest.java
+++ b/tests/carservice_test/src/com/android/car/audio/CarAudioZonesHelperTest.java
@@ -15,18 +15,23 @@
  */
 package com.android.car.audio;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import static junit.framework.TestCase.fail;
 
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
+import static org.testng.Assert.expectThrows;
 
+import android.car.media.CarAudioManager;
 import android.content.Context;
-import android.hardware.automotive.audiocontrol.V1_0.ContextNumber;
-import android.media.AudioGain;
-import android.util.SparseArray;
+import android.media.AudioDeviceAttributes;
+import android.media.AudioDeviceInfo;
+import android.util.SparseIntArray;
 import android.view.DisplayAddress;
 
 import androidx.test.core.app.ApplicationProvider;
@@ -34,26 +39,44 @@
 
 import com.android.car.R;
 
+import com.google.common.collect.ImmutableList;
+
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.mockito.Mockito;
 import org.xmlpull.v1.XmlPullParserException;
 
 import java.io.IOException;
 import java.io.InputStream;
+import java.util.Arrays;
 import java.util.List;
+import java.util.stream.Collectors;
 
 @RunWith(AndroidJUnit4.class)
 public class CarAudioZonesHelperTest {
-    private SparseArray<CarAudioDeviceInfo> mBusToMockCarAudioDeviceInfo;
+    private List<CarAudioDeviceInfo> mCarAudioOutputDeviceInfos;
+    private AudioDeviceInfo[] mInputAudioDeviceInfos;
     private Context mContext;
     private InputStream mInputStream;
+    private static final String BUS_0_ADDRESS = "bus0_media_out";
+    private static final String BUS_1_ADDRESS = "bus1_navigation_out";
+    private static final String BUS_3_ADDRESS = "bus3_call_ring_out";
+    private static final String BUS_100_ADDRESS = "bus100_rear_seat";
+    private static final String BUS_1000_ADDRESS_DOES_NOT_EXIST = "bus1000_does_not_exist";
+
+    private static final String PRIMARY_ZONE_MICROPHONE_ADDRESS = "Built-In Mic";
+    private static final String PRIMARY_ZONE_FM_TUNER_ADDRESS = "fm_tuner";
+    private static final String SECONDARY_ZONE_BACK_MICROPHONE_ADDRESS = "Built-In Back Mic";
+    private static final String SECONDARY_ZONE_BUS_1000_INPUT_ADDRESS = "bus_1000_input";
+
+    private static final int PRIMARY_OCCUPANT_ID = 1;
+    private static final int SECONDARY_ZONE_ID = 2;
 
     @Before
     public void setUp() {
-        mBusToMockCarAudioDeviceInfo = generateBusToCarDeviceInfo();
+        mCarAudioOutputDeviceInfos = generateCarDeviceInfos();
+        mInputAudioDeviceInfos = generateInputDeviceInfos();
         mContext = ApplicationProvider.getApplicationContext();
         mInputStream = mContext.getResources().openRawResource(R.raw.car_audio_configuration);
     }
@@ -65,41 +88,108 @@
         }
     }
 
-    private SparseArray<CarAudioDeviceInfo> generateBusToCarDeviceInfo() {
-        SparseArray<CarAudioDeviceInfo> busToCarAudioDeviceInfo = new SparseArray<>();
-        busToCarAudioDeviceInfo.put(0, generateCarAudioDeviceInfo());
-        busToCarAudioDeviceInfo.put(1, generateCarAudioDeviceInfo());
-        busToCarAudioDeviceInfo.put(3, generateCarAudioDeviceInfo());
-        busToCarAudioDeviceInfo.put(100, generateCarAudioDeviceInfo());
-
-        return busToCarAudioDeviceInfo;
+    private List<CarAudioDeviceInfo> generateCarDeviceInfos() {
+        return ImmutableList.of(
+                generateCarAudioDeviceInfo(BUS_0_ADDRESS),
+                generateCarAudioDeviceInfo(BUS_1_ADDRESS),
+                generateCarAudioDeviceInfo(BUS_3_ADDRESS),
+                generateCarAudioDeviceInfo(BUS_100_ADDRESS),
+                generateCarAudioDeviceInfo(""),
+                generateCarAudioDeviceInfo(""),
+                generateCarAudioDeviceInfo(null),
+                generateCarAudioDeviceInfo(null)
+        );
     }
 
-    private CarAudioDeviceInfo generateCarAudioDeviceInfo() {
-        CarAudioDeviceInfo cadiMock = Mockito.mock(CarAudioDeviceInfo.class);
-        AudioGain audioGainMock = Mockito.mock(AudioGain.class);
-        when(audioGainMock.stepValue()).thenReturn(1);
-        when(cadiMock.getAudioGain()).thenReturn(audioGainMock);
+    private AudioDeviceInfo[] generateInputDeviceInfos() {
+        return new AudioDeviceInfo[] {
+                generateInputAudioDeviceInfo(PRIMARY_ZONE_MICROPHONE_ADDRESS,
+                        AudioDeviceInfo.TYPE_BUILTIN_MIC),
+                generateInputAudioDeviceInfo(PRIMARY_ZONE_FM_TUNER_ADDRESS,
+                        AudioDeviceInfo.TYPE_FM_TUNER),
+                generateInputAudioDeviceInfo(SECONDARY_ZONE_BACK_MICROPHONE_ADDRESS,
+                        AudioDeviceInfo.TYPE_BUS),
+                generateInputAudioDeviceInfo(SECONDARY_ZONE_BUS_1000_INPUT_ADDRESS,
+                        AudioDeviceInfo.TYPE_BUILTIN_MIC)
+        };
+    }
+
+    private CarAudioDeviceInfo generateCarAudioDeviceInfo(String address) {
+        CarAudioDeviceInfo cadiMock = mock(CarAudioDeviceInfo.class);
+        when(cadiMock.getStepValue()).thenReturn(1);
         when(cadiMock.getDefaultGain()).thenReturn(2);
         when(cadiMock.getMaxGain()).thenReturn(5);
         when(cadiMock.getMinGain()).thenReturn(0);
+        when(cadiMock.getAddress()).thenReturn(address);
         return cadiMock;
     }
 
+    private AudioDeviceInfo generateInputAudioDeviceInfo(String address, int type) {
+        AudioDeviceInfo inputMock = mock(AudioDeviceInfo.class);
+        when(inputMock.getAddress()).thenReturn(address);
+        when(inputMock.getType()).thenReturn(type);
+        when(inputMock.isSource()).thenReturn(true);
+        when(inputMock.isSink()).thenReturn(false);
+        return inputMock;
+    }
+
     @Test
-    public void loadAudioZones_parsesAllZones() throws IOException, XmlPullParserException {
+    public void loadAudioZones_parsesAllZones() throws Exception {
         CarAudioZonesHelper cazh = new CarAudioZonesHelper(mContext, mInputStream,
-                mBusToMockCarAudioDeviceInfo);
+                mCarAudioOutputDeviceInfos, mInputAudioDeviceInfos);
+
+        CarAudioZone[] zones = cazh.loadAudioZones();
+
+        assertThat(zones.length).isEqualTo(2);
+    }
+
+    @Test
+    public void loadAudioZones_versionOneParsesAllZones() throws Exception {
+        try (InputStream versionOneStream = mContext.getResources().openRawResource(
+                R.raw.car_audio_configuration_V1)) {
+            CarAudioZonesHelper cazh = new CarAudioZonesHelper(mContext, versionOneStream,
+                    mCarAudioOutputDeviceInfos, mInputAudioDeviceInfos);
+
+            CarAudioZone[] zones = cazh.loadAudioZones();
+
+            assertThat(zones.length).isEqualTo(2);
+        }
+    }
+
+    @Test
+    public void loadAudioZones_parsesAudioZoneId() throws Exception {
+        CarAudioZonesHelper cazh = new CarAudioZonesHelper(mContext, mInputStream,
+                mCarAudioOutputDeviceInfos, mInputAudioDeviceInfos);
+
+        CarAudioZone[] zones = cazh.loadAudioZones();
+
+        List<Integer> zoneIds = getListOfZoneIds(zones);
+        assertThat(zoneIds.size()).isEqualTo(2);
+        assertThat(zoneIds)
+                .containsAllOf(CarAudioManager.PRIMARY_AUDIO_ZONE, SECONDARY_ZONE_ID).inOrder();
+    }
+
+    @Test
+    public void loadAudioZones_parsesOccupantZoneId() throws Exception {
+        CarAudioZonesHelper cazh = new CarAudioZonesHelper(mContext, mInputStream,
+                mCarAudioOutputDeviceInfos, mInputAudioDeviceInfos);
 
         CarAudioZone[] zones = cazh.loadAudioZones();
 
         assertEquals(2, zones.length);
+
+        SparseIntArray audioZoneIdToOccupantZoneIdMapping =
+                cazh.getCarAudioZoneIdToOccupantZoneIdMapping();
+        assertThat(audioZoneIdToOccupantZoneIdMapping.get(CarAudioManager.PRIMARY_AUDIO_ZONE))
+                .isEqualTo(PRIMARY_OCCUPANT_ID);
+        assertThat(audioZoneIdToOccupantZoneIdMapping.get(SECONDARY_ZONE_ID, -1))
+                .isEqualTo(-1);
     }
 
     @Test
     public void loadAudioZones_parsesZoneName() throws IOException, XmlPullParserException {
         CarAudioZonesHelper cazh = new CarAudioZonesHelper(mContext, mInputStream,
-                mBusToMockCarAudioDeviceInfo);
+                mCarAudioOutputDeviceInfos, mInputAudioDeviceInfos);
 
         CarAudioZone[] zones = cazh.loadAudioZones();
 
@@ -110,7 +200,7 @@
     @Test
     public void loadAudioZones_parsesIsPrimary() throws IOException, XmlPullParserException {
         CarAudioZonesHelper cazh = new CarAudioZonesHelper(mContext, mInputStream,
-                mBusToMockCarAudioDeviceInfo);
+                mCarAudioOutputDeviceInfos, mInputAudioDeviceInfos);
 
         CarAudioZone[] zones = cazh.loadAudioZones();
 
@@ -124,7 +214,7 @@
     @Test
     public void loadAudioZones_parsesVolumeGroups() throws IOException, XmlPullParserException {
         CarAudioZonesHelper cazh = new CarAudioZonesHelper(mContext, mInputStream,
-                mBusToMockCarAudioDeviceInfo);
+                mCarAudioOutputDeviceInfos, mInputAudioDeviceInfos);
 
         CarAudioZone[] zones = cazh.loadAudioZones();
 
@@ -133,38 +223,36 @@
     }
 
     @Test
-    public void loadAudioZones_parsesBuses() throws IOException, XmlPullParserException {
+    public void loadAudioZones_parsesAddresses() throws IOException, XmlPullParserException {
         CarAudioZonesHelper cazh = new CarAudioZonesHelper(mContext, mInputStream,
-                mBusToMockCarAudioDeviceInfo);
+                mCarAudioOutputDeviceInfos, mInputAudioDeviceInfos);
 
         CarAudioZone[] zones = cazh.loadAudioZones();
 
         CarAudioZone primaryZone = zones[0];
         CarVolumeGroup volumeGroup = primaryZone.getVolumeGroups()[0];
-        int[] busNumbers = volumeGroup.getBusNumbers();
-        assertEquals(2, busNumbers.length);
-        assertEquals(0, busNumbers[0]);
-        assertEquals(3, busNumbers[1]);
+        List<String> addresses = volumeGroup.getAddresses();
+        assertEquals(2, addresses.size());
+        assertEquals(BUS_0_ADDRESS, addresses.get(0));
+        assertEquals(BUS_3_ADDRESS, addresses.get(1));
     }
 
     @Test
     public void loadAudioZones_parsesContexts() throws IOException, XmlPullParserException {
         CarAudioZonesHelper cazh = new CarAudioZonesHelper(mContext, mInputStream,
-                mBusToMockCarAudioDeviceInfo);
+                mCarAudioOutputDeviceInfos, mInputAudioDeviceInfos);
 
         CarAudioZone[] zones = cazh.loadAudioZones();
 
         CarAudioZone primaryZone = zones[0];
         CarVolumeGroup volumeGroup = primaryZone.getVolumeGroups()[0];
-        int[] expectedContextForBus0 = {ContextNumber.MUSIC};
-        assertArrayEquals(expectedContextForBus0, volumeGroup.getContextsForBus(0));
+        int[] expectedContextForBus0 = {CarAudioContext.MUSIC};
+        assertArrayEquals(expectedContextForBus0, volumeGroup.getContextsForAddress(BUS_0_ADDRESS));
 
-        int[] expectedContextForBus100 = new int[]{ContextNumber.MUSIC, ContextNumber.NAVIGATION,
-                ContextNumber.VOICE_COMMAND, ContextNumber.CALL_RING, ContextNumber.CALL,
-                ContextNumber.ALARM, ContextNumber.NOTIFICATION, ContextNumber.SYSTEM_SOUND};
+        int[] expectedContextForBus100 = CarAudioContext.CONTEXTS;
         CarAudioZone rearSeatEntertainmentZone = zones[1];
         CarVolumeGroup rseVolumeGroup = rearSeatEntertainmentZone.getVolumeGroups()[0];
-        int[] contextForBus100 = rseVolumeGroup.getContextsForBus(100);
+        int[] contextForBus100 = rseVolumeGroup.getContextsForAddress(BUS_100_ADDRESS);
         assertArrayEquals(expectedContextForBus100, contextForBus100);
     }
 
@@ -172,7 +260,7 @@
     public void loadAudioZones_parsesPhysicalDisplayAddresses()
             throws IOException, XmlPullParserException {
         CarAudioZonesHelper cazh = new CarAudioZonesHelper(mContext, mInputStream,
-                mBusToMockCarAudioDeviceInfo);
+                mCarAudioOutputDeviceInfos, mInputAudioDeviceInfos);
 
         CarAudioZone[] zones = cazh.loadAudioZones();
 
@@ -187,7 +275,7 @@
     public void loadAudioZones_defaultsDisplayAddressesToEmptyList()
             throws IOException, XmlPullParserException {
         CarAudioZonesHelper cazh = new CarAudioZonesHelper(mContext, mInputStream,
-                mBusToMockCarAudioDeviceInfo);
+                mCarAudioOutputDeviceInfos, mInputAudioDeviceInfos);
 
         CarAudioZone[] zones = cazh.loadAudioZones();
 
@@ -201,7 +289,7 @@
         try (InputStream duplicatePortStream = mContext.getResources().openRawResource(
                 R.raw.car_audio_configuration_duplicate_ports)) {
             CarAudioZonesHelper cazh = new CarAudioZonesHelper(mContext, duplicatePortStream,
-                    mBusToMockCarAudioDeviceInfo);
+                    mCarAudioOutputDeviceInfos, mInputAudioDeviceInfos);
 
             cazh.loadAudioZones();
         }
@@ -213,7 +301,7 @@
         try (InputStream duplicatePortStream = mContext.getResources().openRawResource(
                 R.raw.car_audio_configuration_non_numerical_port)) {
             CarAudioZonesHelper cazh = new CarAudioZonesHelper(mContext, duplicatePortStream,
-                    mBusToMockCarAudioDeviceInfo);
+                    mCarAudioOutputDeviceInfos, mInputAudioDeviceInfos);
 
             try {
                 cazh.loadAudioZones();
@@ -223,4 +311,272 @@
             }
         }
     }
+
+    @Test
+    public void loadAudioZones_passesOnMissingAudioZoneIdForPrimary() throws Exception {
+        try (InputStream missingAudioZoneIdStream = mContext.getResources().openRawResource(
+                R.raw.car_audio_configuration_no_audio_zone_id_for_primary_zone)) {
+            CarAudioZonesHelper cazh = new CarAudioZonesHelper(mContext, missingAudioZoneIdStream,
+                    mCarAudioOutputDeviceInfos, mInputAudioDeviceInfos);
+
+            CarAudioZone[] zones = cazh.loadAudioZones();
+
+            List<Integer> zoneIds = getListOfZoneIds(zones);
+            assertThat(zoneIds.size()).isEqualTo(2);
+            assertThat(zoneIds).contains(CarAudioManager.PRIMARY_AUDIO_ZONE);
+            assertThat(zoneIds).contains(SECONDARY_ZONE_ID);
+        }
+    }
+
+    @Test
+    public void loadAudioZones_versionOneFailsOnAudioZoneId() throws Exception {
+        try (InputStream versionOneAudioZoneIdStream = mContext.getResources().openRawResource(
+                R.raw.car_audio_configuration_V1_with_audio_zone_id)) {
+            CarAudioZonesHelper cazh = new CarAudioZonesHelper(mContext,
+                    versionOneAudioZoneIdStream, mCarAudioOutputDeviceInfos,
+                    mInputAudioDeviceInfos);
+            IllegalArgumentException thrown =
+                    expectThrows(IllegalArgumentException.class,
+                            () -> cazh.loadAudioZones());
+            assertThat(thrown).hasMessageThat().contains("Invalid audio attribute audioZoneId");
+        }
+    }
+
+    @Test
+    public void loadAudioZones_versionOneFailsOnOccupantZoneId() throws Exception {
+        try (InputStream versionOneOccupantIdStream = mContext.getResources().openRawResource(
+                R.raw.car_audio_configuration_V1_with_occupant_zone_id)) {
+            CarAudioZonesHelper cazh = new CarAudioZonesHelper(mContext, versionOneOccupantIdStream,
+                    mCarAudioOutputDeviceInfos, mInputAudioDeviceInfos);
+            IllegalArgumentException thrown =
+                    expectThrows(IllegalArgumentException.class,
+                            () -> cazh.loadAudioZones());
+            assertThat(thrown).hasMessageThat().contains("Invalid audio attribute occupantZoneId");
+        }
+    }
+
+    @Test
+    public void loadAudioZones_parsesInputDevices() throws Exception {
+        try (InputStream inputDevicesStream = mContext.getResources().openRawResource(
+                R.raw.car_audio_configuration_with_input_devices)) {
+            CarAudioZonesHelper cazh = new CarAudioZonesHelper(mContext, inputDevicesStream,
+                    mCarAudioOutputDeviceInfos, mInputAudioDeviceInfos);
+
+            CarAudioZone[] zones = cazh.loadAudioZones();
+
+            CarAudioZone primaryZone = zones[0];
+            List<AudioDeviceAttributes> primaryZoneInputDevices =
+                    primaryZone.getInputAudioDevices();
+            assertThat(primaryZoneInputDevices).hasSize(2);
+
+            List<String> primaryZoneInputAddresses =
+                    primaryZoneInputDevices.stream().map(a ->a.getAddress()).collect(
+                            Collectors.toList());
+            assertThat(primaryZoneInputAddresses).containsAllOf(PRIMARY_ZONE_FM_TUNER_ADDRESS,
+                    PRIMARY_ZONE_MICROPHONE_ADDRESS).inOrder();
+
+            CarAudioZone secondaryZone = zones[1];
+            List<AudioDeviceAttributes> secondaryZoneInputDevices =
+                    secondaryZone.getInputAudioDevices();
+            List<String> secondaryZoneInputAddresses =
+                    secondaryZoneInputDevices.stream().map(a ->a.getAddress()).collect(
+                            Collectors.toList());
+            assertThat(secondaryZoneInputAddresses).containsAllOf(
+                    SECONDARY_ZONE_BUS_1000_INPUT_ADDRESS,
+                    SECONDARY_ZONE_BACK_MICROPHONE_ADDRESS).inOrder();
+        }
+    }
+
+    @Test
+    public void loadAudioZones_failsOnDuplicateOccupantZoneId() throws Exception {
+        try (InputStream duplicateOccupantZoneIdStream = mContext.getResources().openRawResource(
+                R.raw.car_audio_configuration_duplicate_occupant_zone_id)) {
+            CarAudioZonesHelper cazh = new CarAudioZonesHelper(mContext,
+                    duplicateOccupantZoneIdStream, mCarAudioOutputDeviceInfos,
+                    mInputAudioDeviceInfos);
+            IllegalArgumentException thrown =
+                    expectThrows(IllegalArgumentException.class,
+                            () -> cazh.loadAudioZones());
+            assertThat(thrown).hasMessageThat().contains("already associated with a zone");
+        }
+    }
+
+    @Test
+    public void loadAudioZones_failsOnDuplicateAudioZoneId() throws Exception {
+        try (InputStream duplicateAudioZoneIdStream = mContext.getResources().openRawResource(
+                R.raw.car_audio_configuration_duplicate_audio_zone_id)) {
+            CarAudioZonesHelper cazh = new CarAudioZonesHelper(mContext, duplicateAudioZoneIdStream,
+                    mCarAudioOutputDeviceInfos, mInputAudioDeviceInfos);
+            IllegalArgumentException thrown =
+                    expectThrows(IllegalArgumentException.class,
+                            () -> cazh.loadAudioZones());
+            assertThat(thrown).hasMessageThat().contains("already associated with a zone");
+        }
+    }
+    @Test
+    public void loadAudioZones_failsOnEmptyInputDeviceAddress() throws Exception {
+        try (InputStream inputDevicesStream = mContext.getResources().openRawResource(
+                R.raw.car_audio_configuration_empty_input_device)) {
+            CarAudioZonesHelper cazh = new CarAudioZonesHelper(mContext, inputDevicesStream,
+                    mCarAudioOutputDeviceInfos, mInputAudioDeviceInfos);
+
+            IllegalArgumentException thrown =
+                    expectThrows(IllegalArgumentException.class,
+                            () -> cazh.loadAudioZones());
+            assertThat(thrown).hasMessageThat().contains("empty.");
+        }
+    }
+
+    @Test
+    public void loadAudioZones_failsOnNonNumericalAudioZoneId() throws Exception {
+        try (InputStream nonNumericalStream = mContext.getResources().openRawResource(
+                R.raw.car_audio_configuration_non_numerical_audio_zone_id)) {
+            CarAudioZonesHelper cazh = new CarAudioZonesHelper(mContext, nonNumericalStream,
+                    mCarAudioOutputDeviceInfos, mInputAudioDeviceInfos);
+            IllegalArgumentException thrown =
+                    expectThrows(IllegalArgumentException.class,
+                            () -> cazh.loadAudioZones());
+            assertThat(thrown).hasMessageThat().contains("was \"primary\" instead.");
+        }
+    }
+
+    @Test
+    public void loadAudioZones_failsOnNegativeAudioZoneId() throws Exception {
+        try (InputStream negativeAudioZoneIdStream = mContext.getResources().openRawResource(
+                R.raw.car_audio_configuration_negative_audio_zone_id)) {
+            CarAudioZonesHelper cazh = new CarAudioZonesHelper(mContext, negativeAudioZoneIdStream,
+                    mCarAudioOutputDeviceInfos, mInputAudioDeviceInfos);
+            IllegalArgumentException thrown =
+                    expectThrows(IllegalArgumentException.class,
+                            () -> cazh.loadAudioZones());
+            assertThat(thrown).hasMessageThat().contains("but was \"-1\" instead.");
+        }
+    }
+
+    @Test
+    public void loadAudioZones_failsOnMissingInputDevice() throws Exception {
+        try (InputStream inputDevicesStream = mContext.getResources().openRawResource(
+                R.raw.car_audio_configuration_missing_address)) {
+            CarAudioZonesHelper cazh = new CarAudioZonesHelper(mContext, inputDevicesStream,
+                    mCarAudioOutputDeviceInfos, mInputAudioDeviceInfos);
+
+            NullPointerException thrown =
+                    expectThrows(NullPointerException.class,
+                            () -> cazh.loadAudioZones());
+            assertThat(thrown).hasMessageThat().contains("present.");
+        }
+    }
+
+    @Test
+    public void loadAudioZones_failsOnNonNumericalOccupantZoneId() throws Exception {
+        try (InputStream nonNumericalStream = mContext.getResources().openRawResource(
+                R.raw.car_audio_configuration_non_numerical_occupant_zone_id)) {
+            CarAudioZonesHelper cazh = new CarAudioZonesHelper(mContext, nonNumericalStream,
+                    mCarAudioOutputDeviceInfos, mInputAudioDeviceInfos);
+            IllegalArgumentException thrown =
+                    expectThrows(IllegalArgumentException.class,
+                            () -> cazh.loadAudioZones());
+            assertThat(thrown).hasMessageThat().contains("was \"one\" instead.");
+        }
+    }
+
+    @Test
+    public void loadAudioZones_failsOnNegativeOccupantZoneId() throws Exception {
+        try (InputStream negativeOccupantZoneIdStream = mContext.getResources().openRawResource(
+                R.raw.car_audio_configuration_negative_occupant_zone_id)) {
+            CarAudioZonesHelper cazh = new CarAudioZonesHelper(mContext,
+                    negativeOccupantZoneIdStream, mCarAudioOutputDeviceInfos,
+                    mInputAudioDeviceInfos);
+            IllegalArgumentException thrown =
+                    expectThrows(IllegalArgumentException.class,
+                            () -> cazh.loadAudioZones());
+            assertThat(thrown).hasMessageThat().contains("was \"-1\" instead.");
+        }
+    }
+
+    @Test
+    public void loadAudioZones_failsOnNonExistentInputDevice() throws Exception {
+        try (InputStream inputDevicesStream = mContext.getResources().openRawResource(
+                R.raw.car_audio_configuration_non_existent_input_device)) {
+            CarAudioZonesHelper cazh = new CarAudioZonesHelper(mContext, inputDevicesStream,
+                    mCarAudioOutputDeviceInfos, mInputAudioDeviceInfos);
+
+            IllegalArgumentException thrown =
+                    expectThrows(IllegalArgumentException.class,
+                            () -> cazh.loadAudioZones());
+            assertThat(thrown).hasMessageThat().contains("does not exist");
+        }
+    }
+
+    @Test
+    public void loadAudioZones_failsOnEmptyOccupantZoneId() throws Exception {
+        try (InputStream emptyOccupantZoneIdStream = mContext.getResources().openRawResource(
+                R.raw.car_audio_configuration_empty_occupant_zone_id)) {
+            CarAudioZonesHelper cazh = new CarAudioZonesHelper(mContext, emptyOccupantZoneIdStream,
+                    mCarAudioOutputDeviceInfos, mInputAudioDeviceInfos);
+            IllegalArgumentException thrown =
+                    expectThrows(IllegalArgumentException.class,
+                            () -> cazh.loadAudioZones());
+            assertThat(thrown).hasMessageThat().contains("but was \"\" instead.");
+        }
+    }
+
+    @Test
+    public void loadAudioZones_failsOnNonZeroAudioZoneIdForPrimary() throws Exception {
+        try (InputStream nonZeroForPrimaryStream = mContext.getResources().openRawResource(
+                R.raw.car_audio_configuration_primary_zone_with_non_zero_audio_zone_id)) {
+            CarAudioZonesHelper cazh = new CarAudioZonesHelper(mContext, nonZeroForPrimaryStream,
+                    mCarAudioOutputDeviceInfos, mInputAudioDeviceInfos);
+            IllegalArgumentException thrown =
+                    expectThrows(IllegalArgumentException.class,
+                            () -> cazh.loadAudioZones());
+            assertThat(thrown).hasMessageThat().contains("it can be left empty.");
+        }
+    }
+
+    @Test
+    public void loadAudioZones_failsOnZeroAudioZoneIdForSecondary() throws Exception {
+        try (InputStream zeroZoneIdForSecondaryStream = mContext.getResources().openRawResource(
+                R.raw.car_audio_configuration_non_primary_zone_with_primary_audio_zone_id)) {
+            CarAudioZonesHelper cazh = new CarAudioZonesHelper(mContext,
+                    zeroZoneIdForSecondaryStream, mCarAudioOutputDeviceInfos,
+                    mInputAudioDeviceInfos);
+            IllegalArgumentException thrown =
+                    expectThrows(IllegalArgumentException.class,
+                            () -> cazh.loadAudioZones());
+            assertThat(thrown).hasMessageThat().contains("for primary zone");
+        }
+    }
+
+    @Test
+    public void loadAudioZones_failsOnRepeatedInputDevice() throws Exception {
+        try (InputStream inputDevicesStream = mContext.getResources().openRawResource(
+                R.raw.car_audio_configuration_repeat_input_device)) {
+            CarAudioZonesHelper cazh = new CarAudioZonesHelper(mContext, inputDevicesStream,
+                    mCarAudioOutputDeviceInfos, mInputAudioDeviceInfos);
+
+            IllegalArgumentException thrown =
+                    expectThrows(IllegalArgumentException.class,
+                            () -> cazh.loadAudioZones());
+            assertThat(thrown).hasMessageThat().contains("can not repeat.");
+        }
+    }
+
+    @Test
+    public void loadAudioZones_failsOnMissingOutputDevice() throws Exception {
+        try (InputStream outputDevicesStream = mContext.getResources().openRawResource(
+                R.raw.car_audio_configuration_output_address_does_not_exist)) {
+            CarAudioZonesHelper cazh = new CarAudioZonesHelper(mContext, outputDevicesStream,
+                    mCarAudioOutputDeviceInfos, mInputAudioDeviceInfos);
+
+            IllegalStateException thrown =
+                    expectThrows(IllegalStateException.class,
+                            () -> cazh.loadAudioZones());
+            assertThat(thrown).hasMessageThat().contains(BUS_1000_ADDRESS_DOES_NOT_EXIST);
+        }
+    }
+
+    private List<Integer> getListOfZoneIds(CarAudioZone[] zones) {
+        return Arrays.stream(zones).map(CarAudioZone::getId).collect(Collectors.toList());
+    }
 }
diff --git a/tests/carservice_test/src/com/android/car/audio/CarAudioZonesValidatorTest.java b/tests/carservice_test/src/com/android/car/audio/CarAudioZonesValidatorTest.java
new file mode 100644
index 0000000..c171321
--- /dev/null
+++ b/tests/carservice_test/src/com/android/car/audio/CarAudioZonesValidatorTest.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.car.audio;
+
+import static org.mockito.Mockito.when;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.google.common.collect.Lists;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+import org.mockito.Mockito;
+
+@RunWith(AndroidJUnit4.class)
+public class CarAudioZonesValidatorTest {
+    @Rule
+    public final ExpectedException thrown = ExpectedException.none();
+
+    @Test
+    public void validate_thereIsAtLeastOneZone() {
+        thrown.expect(RuntimeException.class);
+        thrown.expectMessage("At least one zone should be defined");
+
+        CarAudioZonesValidator.validate(new CarAudioZone[0]);
+    }
+
+    @Test
+    public void validate_volumeGroupsForEachZone() {
+        CarAudioZone primaryZone = Mockito.mock(CarAudioZone.class);
+        when(primaryZone.validateVolumeGroups()).thenReturn(true);
+        CarAudioZone zoneOne = Mockito.mock(CarAudioZone.class);
+        when(zoneOne.validateVolumeGroups()).thenReturn(false);
+        when(zoneOne.getId()).thenReturn(1);
+
+        thrown.expect(RuntimeException.class);
+        thrown.expectMessage("Invalid volume groups configuration for zone " + 1);
+
+        CarAudioZonesValidator.validate(new CarAudioZone[]{primaryZone, zoneOne});
+    }
+
+    @Test
+    public void validate_eachAddressAppearsInOnlyOneZone() {
+        CarAudioZone primaryZone = Mockito.mock(CarAudioZone.class);
+        CarVolumeGroup mockVolumeGroup = Mockito.mock(CarVolumeGroup.class);
+        when(mockVolumeGroup.getAddresses()).thenReturn(Lists.newArrayList("one", "two", "three"));
+        when(primaryZone.getVolumeGroups()).thenReturn(new CarVolumeGroup[]{mockVolumeGroup});
+        when(primaryZone.validateVolumeGroups()).thenReturn(true);
+
+        CarAudioZone secondaryZone = Mockito.mock(CarAudioZone.class);
+        CarVolumeGroup mockSecondaryVolmeGroup = Mockito.mock(CarVolumeGroup.class);
+        when(mockSecondaryVolmeGroup.getAddresses()).thenReturn(
+                Lists.newArrayList("three", "four", "five"));
+        when(secondaryZone.getVolumeGroups()).thenReturn(
+                new CarVolumeGroup[]{mockSecondaryVolmeGroup});
+        when(secondaryZone.validateVolumeGroups()).thenReturn(true);
+
+        thrown.expect(RuntimeException.class);
+        thrown.expectMessage(
+                "Device with address three appears in multiple volume groups or audio zones");
+
+        CarAudioZonesValidator.validate(new CarAudioZone[]{primaryZone, secondaryZone});
+    }
+
+    @Test
+    public void validate_passesWithoutExceptionForValidZoneConfiguration() {
+        CarAudioZone primaryZone = Mockito.mock(CarAudioZone.class);
+        when(primaryZone.validateVolumeGroups()).thenReturn(true);
+        when(primaryZone.getVolumeGroups()).thenReturn(new CarVolumeGroup[0]);
+
+        CarAudioZonesValidator.validate(new CarAudioZone[]{primaryZone});
+    }
+}
diff --git a/tests/carservice_test/src/com/android/car/audio/CarVolumeGroupTest.java b/tests/carservice_test/src/com/android/car/audio/CarVolumeGroupTest.java
new file mode 100644
index 0000000..ee79d5b
--- /dev/null
+++ b/tests/carservice_test/src/com/android/car/audio/CarVolumeGroupTest.java
@@ -0,0 +1,425 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.car.audio;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.google.common.primitives.Ints;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+import org.mockito.Mockito;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+@RunWith(AndroidJUnit4.class)
+public class CarVolumeGroupTest {
+    private static final int STEP_VALUE = 2;
+    private static final int MIN_GAIN = 0;
+    private static final int MAX_GAIN = 5;
+    private static final int DEFAULT_GAIN = 0;
+    private static final String OTHER_ADDRESS = "other_address";
+    private static final String MEDIA_DEVICE_ADDRESS = "music";
+    private static final String NAVIGATION_DEVICE_ADDRESS = "navigation";
+
+    @Rule
+    public final ExpectedException thrown = ExpectedException.none();
+
+
+    private CarAudioDeviceInfo mMediaDevice;
+    private CarAudioDeviceInfo mNavigationDevice;
+
+    @Before
+    public void setUp() {
+        mMediaDevice = generateCarAudioDeviceInfo(MEDIA_DEVICE_ADDRESS);
+        mNavigationDevice = generateCarAudioDeviceInfo(NAVIGATION_DEVICE_ADDRESS);
+    }
+
+    @Test
+    public void bind_associatesDeviceAddresses() {
+        CarVolumeSettings settings =
+                generateCarVolumeGroupSettings(0 , 0, 2);
+        CarVolumeGroup carVolumeGroup = new CarVolumeGroup(settings, 0, 0);
+
+        carVolumeGroup.bind(CarAudioContext.MUSIC, mMediaDevice);
+        assertEquals(1, carVolumeGroup.getAddresses().size());
+
+        carVolumeGroup.bind(CarAudioContext.NAVIGATION, mNavigationDevice);
+
+        List<String> addresses = carVolumeGroup.getAddresses();
+        assertEquals(2, addresses.size());
+        assertTrue(addresses.contains(MEDIA_DEVICE_ADDRESS));
+        assertTrue(addresses.contains(NAVIGATION_DEVICE_ADDRESS));
+    }
+
+    @Test
+    public void bind_checksForSameStepSize() {
+        CarVolumeSettings settings =
+                generateCarVolumeGroupSettings(0 , 0, 2);
+        CarVolumeGroup carVolumeGroup = new CarVolumeGroup(settings, 0, 0);
+
+        carVolumeGroup.bind(CarAudioContext.MUSIC, mMediaDevice);
+        CarAudioDeviceInfo differentStepValueDevice = generateCarAudioDeviceInfo(
+                NAVIGATION_DEVICE_ADDRESS, STEP_VALUE + 1,
+                MIN_GAIN, MAX_GAIN);
+
+        thrown.expect(IllegalArgumentException.class);
+        thrown.expectMessage("Gain controls within one group must have same step value");
+        carVolumeGroup.bind(CarAudioContext.NAVIGATION, differentStepValueDevice);
+    }
+
+    @Test
+    public void bind_updatesMinGainToSmallestValue() {
+        CarVolumeSettings settings =
+                generateCarVolumeGroupSettings(0 , 0, 2);
+        CarVolumeGroup carVolumeGroup = new CarVolumeGroup(settings, 0, 0);
+
+        CarAudioDeviceInfo largestMinGain = generateCarAudioDeviceInfo(
+                NAVIGATION_DEVICE_ADDRESS, 1, 10, 10);
+        carVolumeGroup.bind(CarAudioContext.NAVIGATION, largestMinGain);
+
+        assertEquals(0, carVolumeGroup.getMaxGainIndex());
+
+        CarAudioDeviceInfo smallestMinGain = generateCarAudioDeviceInfo(
+                NAVIGATION_DEVICE_ADDRESS, 1, 2, 10);
+        carVolumeGroup.bind(CarAudioContext.NOTIFICATION, smallestMinGain);
+
+        assertEquals(8, carVolumeGroup.getMaxGainIndex());
+
+        CarAudioDeviceInfo middleMinGain = generateCarAudioDeviceInfo(
+                NAVIGATION_DEVICE_ADDRESS, 1, 7, 10);
+        carVolumeGroup.bind(CarAudioContext.VOICE_COMMAND, middleMinGain);
+
+        assertEquals(8, carVolumeGroup.getMaxGainIndex());
+    }
+
+    @Test
+    public void bind_updatesMaxGainToLargestValue() {
+        CarVolumeSettings settings =
+                generateCarVolumeGroupSettings(0 , 0, 2);
+        CarVolumeGroup carVolumeGroup = new CarVolumeGroup(settings, 0, 0);
+
+        CarAudioDeviceInfo smallestMaxGain = generateCarAudioDeviceInfo(
+                NAVIGATION_DEVICE_ADDRESS, 1, 1, 5);
+        carVolumeGroup.bind(CarAudioContext.NAVIGATION, smallestMaxGain);
+
+        assertEquals(4, carVolumeGroup.getMaxGainIndex());
+
+        CarAudioDeviceInfo largestMaxGain = generateCarAudioDeviceInfo(
+                NAVIGATION_DEVICE_ADDRESS, 1, 1, 10);
+        carVolumeGroup.bind(CarAudioContext.NOTIFICATION, largestMaxGain);
+
+        assertEquals(9, carVolumeGroup.getMaxGainIndex());
+
+        CarAudioDeviceInfo middleMaxGain = generateCarAudioDeviceInfo(
+                NAVIGATION_DEVICE_ADDRESS, 1, 1, 7);
+        carVolumeGroup.bind(CarAudioContext.VOICE_COMMAND, middleMaxGain);
+
+        assertEquals(9, carVolumeGroup.getMaxGainIndex());
+    }
+
+    @Test
+    public void bind_checksThatTheSameContextIsNotBoundTwice() {
+        CarVolumeSettings settings =
+                generateCarVolumeGroupSettings(0 , 0, 2);
+        CarVolumeGroup carVolumeGroup = new CarVolumeGroup(settings, 0, 0);
+
+        carVolumeGroup.bind(CarAudioContext.NAVIGATION, mMediaDevice);
+
+        thrown.expect(IllegalArgumentException.class);
+        thrown.expectMessage(
+                "Context NAVIGATION has already been bound to " + MEDIA_DEVICE_ADDRESS);
+
+        carVolumeGroup.bind(CarAudioContext.NAVIGATION, mMediaDevice);
+    }
+
+    @Test
+    public void getContexts_returnsAllContextsBoundToVolumeGroup() {
+        CarVolumeGroup carVolumeGroup = testVolumeGroupSetup();
+
+        int[] contexts = carVolumeGroup.getContexts();
+
+        assertEquals(6, contexts.length);
+
+        List<Integer> contextsList = Ints.asList(contexts);
+        assertTrue(contextsList.contains(CarAudioContext.MUSIC));
+        assertTrue(contextsList.contains(CarAudioContext.CALL));
+        assertTrue(contextsList.contains(CarAudioContext.CALL_RING));
+        assertTrue(contextsList.contains(CarAudioContext.NAVIGATION));
+        assertTrue(contextsList.contains(CarAudioContext.ALARM));
+        assertTrue(contextsList.contains(CarAudioContext.NOTIFICATION));
+    }
+
+    @Test
+    public void getContextsForAddress_returnsContextsBoundToThatAddress() {
+        CarVolumeGroup carVolumeGroup = testVolumeGroupSetup();
+
+        int[] contexts = carVolumeGroup.getContextsForAddress(MEDIA_DEVICE_ADDRESS);
+
+        assertEquals(3, contexts.length);
+        List<Integer> contextsList = Ints.asList(contexts);
+        assertTrue(contextsList.contains(CarAudioContext.MUSIC));
+        assertTrue(contextsList.contains(CarAudioContext.CALL));
+        assertTrue(contextsList.contains(CarAudioContext.CALL_RING));
+    }
+
+    @Test
+    public void getContextsForAddress_returnsEmptyArrayIfAddressNotBound() {
+        CarVolumeGroup carVolumeGroup = testVolumeGroupSetup();
+
+        int[] contexts = carVolumeGroup.getContextsForAddress(OTHER_ADDRESS);
+
+        assertEquals(0, contexts.length);
+    }
+
+    @Test
+    public void getCarAudioDeviceInfoForAddress_returnsExpectedDevice() {
+        CarVolumeGroup carVolumeGroup = testVolumeGroupSetup();
+
+        CarAudioDeviceInfo actualDevice = carVolumeGroup.getCarAudioDeviceInfoForAddress(
+                MEDIA_DEVICE_ADDRESS);
+
+        assertEquals(mMediaDevice, actualDevice);
+    }
+
+    @Test
+    public void getCarAudioDeviceInfoForAddress_returnsNullIfAddressNotBound() {
+        CarVolumeGroup carVolumeGroup = testVolumeGroupSetup();
+
+        CarAudioDeviceInfo actualDevice = carVolumeGroup.getCarAudioDeviceInfoForAddress(
+                OTHER_ADDRESS);
+
+        assertNull(actualDevice);
+    }
+
+    @Test
+    public void setCurrentGainIndex_setsGainOnAllBoundDevices() {
+        CarVolumeGroup carVolumeGroup = testVolumeGroupSetup();
+
+        carVolumeGroup.setCurrentGainIndex(2);
+        verify(mMediaDevice).setCurrentGain(4);
+        verify(mNavigationDevice).setCurrentGain(4);
+    }
+
+    @Test
+    public void setCurrentGainIndex_updatesCurrentGainIndex() {
+        CarVolumeGroup carVolumeGroup = testVolumeGroupSetup();
+
+        carVolumeGroup.setCurrentGainIndex(2);
+
+        assertEquals(2, carVolumeGroup.getCurrentGainIndex());
+    }
+
+    @Test
+    public void setCurrentGainIndex_checksNewGainIsAboveMin() {
+        CarVolumeGroup carVolumeGroup = testVolumeGroupSetup();
+
+        thrown.expect(IllegalArgumentException.class);
+        thrown.expectMessage("Gain out of range (0:5) -2index -1");
+
+        carVolumeGroup.setCurrentGainIndex(-1);
+    }
+
+    @Test
+    public void setCurrentGainIndex_checksNewGainIsBelowMax() {
+        CarVolumeGroup carVolumeGroup = testVolumeGroupSetup();
+
+        thrown.expect(IllegalArgumentException.class);
+        thrown.expectMessage("Gain out of range (0:5) 6index 3");
+
+        carVolumeGroup.setCurrentGainIndex(3);
+    }
+
+    @Test
+    public void getMinGainIndex_alwaysReturnsZero() {
+
+        CarVolumeSettings settings =
+                generateCarVolumeGroupSettings(0 , 0, 2);
+        CarVolumeGroup carVolumeGroup = new CarVolumeGroup(settings, 0, 0);
+        CarAudioDeviceInfo minGainPlusOneDevice = generateCarAudioDeviceInfo(
+                NAVIGATION_DEVICE_ADDRESS, STEP_VALUE, 10, MAX_GAIN);
+        carVolumeGroup.bind(CarAudioContext.NAVIGATION, minGainPlusOneDevice);
+
+        assertEquals(0, carVolumeGroup.getMinGainIndex());
+
+        CarAudioDeviceInfo minGainDevice = generateCarAudioDeviceInfo(
+                NAVIGATION_DEVICE_ADDRESS, STEP_VALUE, 1, MAX_GAIN);
+        carVolumeGroup.bind(CarAudioContext.NOTIFICATION, minGainDevice);
+
+        assertEquals(0, carVolumeGroup.getMinGainIndex());
+    }
+
+    @Test
+    public void loadVolumesForUser_setsCurrentGainIndexForUser() {
+
+        List<Integer> users = new ArrayList<>();
+        users.add(10);
+        users.add(11);
+
+        Map<Integer, Integer> storedGainIndex = new HashMap<>();
+        storedGainIndex.put(10, 2);
+        storedGainIndex.put(11, 0);
+
+        CarVolumeSettings settings =
+                generateCarVolumeGroupSettings(users, 0 , 0, storedGainIndex);
+        CarVolumeGroup carVolumeGroup = new CarVolumeGroup(settings, 0, 0);
+
+        CarAudioDeviceInfo deviceInfo = generateCarAudioDeviceInfo(
+                NAVIGATION_DEVICE_ADDRESS, STEP_VALUE, MIN_GAIN, MAX_GAIN);
+        carVolumeGroup.bind(CarAudioContext.NAVIGATION, deviceInfo);
+        carVolumeGroup.loadVolumesForUser(10);
+
+        assertEquals(2, carVolumeGroup.getCurrentGainIndex());
+
+        carVolumeGroup.loadVolumesForUser(11);
+
+        assertEquals(0, carVolumeGroup.getCurrentGainIndex());
+    }
+
+    @Test
+    public void loadUserStoredGainIndex_setsCurrentGainIndexToDefault() {
+        CarVolumeSettings settings =
+                generateCarVolumeGroupSettings(0, 0 , 0, 10);
+        CarVolumeGroup carVolumeGroup = new CarVolumeGroup(settings, 0, 0);
+
+        CarAudioDeviceInfo deviceInfo = generateCarAudioDeviceInfo(
+                NAVIGATION_DEVICE_ADDRESS, STEP_VALUE, MIN_GAIN, MAX_GAIN);
+        carVolumeGroup.bind(CarAudioContext.NAVIGATION, deviceInfo);
+
+        carVolumeGroup.setCurrentGainIndex(2);
+
+        assertEquals(2, carVolumeGroup.getCurrentGainIndex());
+
+        carVolumeGroup.loadVolumesForUser(0);
+
+        assertEquals(0, carVolumeGroup.getCurrentGainIndex());
+    }
+
+    @Test
+    public void bind_setsCurrentGainIndexToStoredGainIndex() {
+        CarVolumeSettings settings =
+                generateCarVolumeGroupSettings(0 , 0, 2);
+        CarVolumeGroup carVolumeGroup = new CarVolumeGroup(settings, 0, 0);
+
+        CarAudioDeviceInfo deviceInfo = generateCarAudioDeviceInfo(
+                NAVIGATION_DEVICE_ADDRESS, STEP_VALUE, MIN_GAIN, MAX_GAIN);
+        carVolumeGroup.bind(CarAudioContext.NAVIGATION, deviceInfo);
+
+
+        assertEquals(2, carVolumeGroup.getCurrentGainIndex());
+    }
+
+    @Test
+    public void getAddressForContext_returnsExpectedDeviceAddress() {
+        CarVolumeSettings settings =
+                generateCarVolumeGroupSettings(0 , 0, 2);
+        CarVolumeGroup carVolumeGroup = new CarVolumeGroup(settings, 0, 0);
+
+
+        carVolumeGroup.bind(CarAudioContext.MUSIC, mMediaDevice);
+
+        String mediaAddress = carVolumeGroup.getAddressForContext(CarAudioContext.MUSIC);
+
+        assertEquals(mMediaDevice.getAddress(), mediaAddress);
+    }
+
+    @Test
+    public void getAddressForContext_returnsNull() {
+        CarVolumeSettings settings =
+                generateCarVolumeGroupSettings(0 , 0, 2);
+        CarVolumeGroup carVolumeGroup = new CarVolumeGroup(settings, 0, 0);
+
+        String nullAddress = carVolumeGroup.getAddressForContext(CarAudioContext.MUSIC);
+
+        assertNull(nullAddress);
+    }
+
+    private CarVolumeGroup testVolumeGroupSetup() {
+        CarVolumeSettings settings =
+                generateCarVolumeGroupSettings(0 , 0, 2);
+        CarVolumeGroup carVolumeGroup = new CarVolumeGroup(settings, 0, 0);
+
+
+        carVolumeGroup.bind(CarAudioContext.MUSIC, mMediaDevice);
+        carVolumeGroup.bind(CarAudioContext.CALL, mMediaDevice);
+        carVolumeGroup.bind(CarAudioContext.CALL_RING, mMediaDevice);
+
+        carVolumeGroup.bind(CarAudioContext.NAVIGATION, mNavigationDevice);
+        carVolumeGroup.bind(CarAudioContext.ALARM, mNavigationDevice);
+        carVolumeGroup.bind(CarAudioContext.NOTIFICATION, mNavigationDevice);
+
+        return carVolumeGroup;
+    }
+
+    private CarAudioDeviceInfo generateCarAudioDeviceInfo(String address) {
+        return generateCarAudioDeviceInfo(address, STEP_VALUE, MIN_GAIN, MAX_GAIN);
+    }
+
+    private CarAudioDeviceInfo generateCarAudioDeviceInfo(String address, int stepValue,
+            int minGain, int maxGain) {
+        CarAudioDeviceInfo cadiMock = Mockito.mock(CarAudioDeviceInfo.class);
+        when(cadiMock.getStepValue()).thenReturn(stepValue);
+        when(cadiMock.getDefaultGain()).thenReturn(DEFAULT_GAIN);
+        when(cadiMock.getMaxGain()).thenReturn(maxGain);
+        when(cadiMock.getMinGain()).thenReturn(minGain);
+        when(cadiMock.getAddress()).thenReturn(address);
+        return cadiMock;
+    }
+
+    private CarVolumeSettings generateCarVolumeGroupSettings(int userId,
+            int zoneId, int id, int storedGainIndex) {
+        CarVolumeSettings settingsMock = Mockito.mock(CarVolumeSettings.class);
+        when(settingsMock.getStoredVolumeGainIndexForUser(userId, zoneId, id))
+                .thenReturn(storedGainIndex);
+
+        return settingsMock;
+    }
+
+    private CarVolumeSettings generateCarVolumeGroupSettings(
+            int zoneId, int id, int storedGainIndex) {
+        CarVolumeSettings settingsMock = Mockito.mock(CarVolumeSettings.class);
+
+        when(settingsMock.getStoredVolumeGainIndexForUser(anyInt(), eq(zoneId),
+                eq(id))).thenReturn(storedGainIndex);
+
+        return settingsMock;
+    }
+
+    private CarVolumeSettings generateCarVolumeGroupSettings(List<Integer> users,
+            int zoneId, int id, Map<Integer, Integer> storedGainIndex) {
+        CarVolumeSettings settingsMock = Mockito.mock(CarVolumeSettings.class);
+        for (Integer user : users) {
+            when(settingsMock.getStoredVolumeGainIndexForUser(user, zoneId,
+                    id)).thenReturn(storedGainIndex.get(user));
+        }
+        return settingsMock;
+    }
+
+}
diff --git a/tests/carservice_test/src/com/android/car/audio/OWNERS b/tests/carservice_test/src/com/android/car/audio/OWNERS
new file mode 100644
index 0000000..8d34cdd
--- /dev/null
+++ b/tests/carservice_test/src/com/android/car/audio/OWNERS
@@ -0,0 +1,3 @@
+# Audio owners
+haydengomes@google.com
+oscarazu@google.com
\ No newline at end of file
diff --git a/tests/carservice_test/src/com/android/car/garagemode/ControllerTest.java b/tests/carservice_test/src/com/android/car/garagemode/ControllerTest.java
index 88881d7..d013d28 100644
--- a/tests/carservice_test/src/com/android/car/garagemode/ControllerTest.java
+++ b/tests/carservice_test/src/com/android/car/garagemode/ControllerTest.java
@@ -24,6 +24,7 @@
 
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
@@ -35,22 +36,26 @@
 import android.content.Intent;
 import android.os.Handler;
 import android.os.Looper;
+import android.os.UserHandle;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
 
 import com.android.car.CarLocalServices;
+import com.android.car.systeminterface.SystemInterface;
 import com.android.car.user.CarUserService;
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
 import org.mockito.Captor;
 import org.mockito.Mock;
 import org.mockito.Mockito;
-import org.mockito.MockitoAnnotations;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -61,13 +66,17 @@
 public class ControllerTest {
     private static final Logger LOG = new Logger("ControllerTest");
 
+    @Rule public final MockitoRule rule = MockitoJUnit.rule();
+
     @Mock private Context mContextMock;
     @Mock private Looper mLooperMock;
     @Mock private Handler mHandlerMock;
     @Mock private Car mCarMock;
     @Mock private CarPowerManager mCarPowerManagerMock;
     @Mock private CarUserService mCarUserServiceMock;
+    @Mock private SystemInterface mSystemInterfaceMock;
     private CarUserService mCarUserServiceOriginal;
+    private SystemInterface mSystemInterfaceOriginal;
     @Captor private ArgumentCaptor<Intent> mIntentCaptor;
     @Captor private ArgumentCaptor<Integer> mIntegerCaptor;
 
@@ -88,7 +97,6 @@
 
     @Before
     public void setUp() {
-        MockitoAnnotations.initMocks(this);
         mWakeupPolicy = new WakeupPolicy(sTemplateWakeupSchedule);
         mController = new Controller(
                 mContextMock,
@@ -101,19 +109,26 @@
         mCarUserServiceOriginal = CarLocalServices.getService(CarUserService.class);
         CarLocalServices.removeServiceForTest(CarUserService.class);
         CarLocalServices.addService(CarUserService.class, mCarUserServiceMock);
+        CarLocalServices.removeServiceForTest(SystemInterface.class);
+        CarLocalServices.addService(SystemInterface.class, mSystemInterfaceMock);
         doReturn(new ArrayList<Integer>()).when(mCarUserServiceMock).startAllBackgroundUsers();
+        doNothing().when(mSystemInterfaceMock)
+                .sendBroadcastAsUser(any(Intent.class), any(UserHandle.class));
     }
 
     @After
     public void tearDown() {
         CarLocalServices.removeServiceForTest(CarUserService.class);
         CarLocalServices.addService(CarUserService.class, mCarUserServiceOriginal);
+        CarLocalServices.removeServiceForTest(SystemInterface.class);
+        CarLocalServices.addService(SystemInterface.class, mSystemInterfaceOriginal);
     }
 
     @Test
     public void testOnShutdownPrepare_shouldInitiateGarageMode() {
         startAndAssertGarageModeWithSignal(CarPowerStateListener.SHUTDOWN_PREPARE);
-        verify(mContextMock).sendBroadcast(mIntentCaptor.capture());
+        verify(mSystemInterfaceMock)
+                .sendBroadcastAsUser(mIntentCaptor.capture(), eq(UserHandle.ALL));
         verifyGarageModeBroadcast(mIntentCaptor.getAllValues(), 1, ACTION_GARAGE_MODE_ON);
     }
 
@@ -132,7 +147,8 @@
         verify(mHandlerMock, Mockito.atLeastOnce()).removeCallbacks(any(Runnable.class));
 
         // Verify that OFF signal broadcasted to JobScheduler
-        verify(mContextMock, times(2)).sendBroadcast(mIntentCaptor.capture());
+        verify(mSystemInterfaceMock, times(2))
+                .sendBroadcastAsUser(mIntentCaptor.capture(), eq(UserHandle.ALL));
         verifyGarageModeBroadcast(mIntentCaptor.getAllValues(), 1, ACTION_GARAGE_MODE_ON);
         verifyGarageModeBroadcast(mIntentCaptor.getAllValues(), 2, ACTION_GARAGE_MODE_OFF);
 
diff --git a/tests/carservice_test/src/com/android/car/garagemode/GarageModeServiceTest.java b/tests/carservice_test/src/com/android/car/garagemode/GarageModeServiceTest.java
index 83f2c87..89e41cf 100644
--- a/tests/carservice_test/src/com/android/car/garagemode/GarageModeServiceTest.java
+++ b/tests/carservice_test/src/com/android/car/garagemode/GarageModeServiceTest.java
@@ -25,16 +25,18 @@
 import android.content.ContentResolver;
 import android.content.Context;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
 import org.mockito.Captor;
 import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
 
 import java.io.PrintWriter;
 import java.util.List;
@@ -42,6 +44,7 @@
 @RunWith(AndroidJUnit4.class)
 @SmallTest
 public class GarageModeServiceTest {
+    @Rule public final MockitoRule rule = MockitoJUnit.rule();
     @Mock private Context mMockContext;
     @Mock private Controller mMockController;
     @Mock private ContentResolver mMockContentResolver;
@@ -52,7 +55,6 @@
 
     @Before
     public void setUp() {
-        MockitoAnnotations.initMocks(this);
         when(mMockContext.getContentResolver()).thenReturn(mMockContentResolver);
         mService = new GarageModeService(mMockContext, mMockController);
     }
diff --git a/tests/carservice_test/src/com/android/car/garagemode/WakeupPolicyTest.java b/tests/carservice_test/src/com/android/car/garagemode/WakeupPolicyTest.java
index 921434b..092724f 100644
--- a/tests/carservice_test/src/com/android/car/garagemode/WakeupPolicyTest.java
+++ b/tests/carservice_test/src/com/android/car/garagemode/WakeupPolicyTest.java
@@ -20,9 +20,9 @@
 
 import android.content.Context;
 
-import androidx.test.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
 
 import com.android.car.R;
 
@@ -82,6 +82,6 @@
     }
 
     private Context getContext() {
-        return InstrumentationRegistry.getTargetContext();
+        return InstrumentationRegistry.getInstrumentation().getTargetContext();
     }
 }
diff --git a/tests/carservice_test/src/com/android/car/pm/ActivityBlockingActivityTest.java b/tests/carservice_test/src/com/android/car/pm/ActivityBlockingActivityTest.java
new file mode 100644
index 0000000..c7781d5
--- /dev/null
+++ b/tests/carservice_test/src/com/android/car/pm/ActivityBlockingActivityTest.java
@@ -0,0 +1,248 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.pm;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertNotNull;
+
+import android.app.Activity;
+import android.app.ActivityOptions;
+import android.car.Car;
+import android.car.drivingstate.CarDrivingStateEvent;
+import android.car.drivingstate.CarDrivingStateManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.UiDevice;
+import android.support.test.uiautomator.Until;
+import android.view.Display;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.MediumTest;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@MediumTest
+public class ActivityBlockingActivityTest {
+    private static final String ACTIVITY_BLOCKING_ACTIVITY_TEXTVIEW_ID =
+            "com.android.car:id/blocking_text";
+
+    private static final int UI_TIMEOUT_MS = 2000;
+    private static final int NOT_FOUND_UI_TIMEOUT_MS = 1000;
+    private static final long ACTIVITY_TIMEOUT_MS = 5000;
+
+    private CarDrivingStateManager mCarDrivingStateManager;
+
+    private UiDevice mDevice;
+
+    @Before
+    public void setUp() throws Exception {
+        Car car = Car.createCar(getContext());
+        mCarDrivingStateManager = (CarDrivingStateManager)
+                car.getCarManager(Car.CAR_DRIVING_STATE_SERVICE);
+        assertNotNull(mCarDrivingStateManager);
+
+        mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+
+        setDrivingStateMoving();
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        setDrivingStateParked();
+    }
+
+    @Test
+    public void testBlockingActivity_doActivity_isNotBlocked() throws Exception {
+        startActivity(toComponentName(getTestContext(), DoActivity.class));
+
+        assertThat(mDevice.wait(Until.findObject(By.text(
+                DoActivity.class.getSimpleName())),
+                UI_TIMEOUT_MS)).isNotNull();
+        assertBlockingActivityNotFound();
+    }
+
+    @Test
+    public void testBlockingActivity_nonDoActivity_isBlocked() throws Exception {
+        startNonDoActivity(NonDoActivity.EXTRA_DO_NOTHING);
+
+        assertThat(mDevice.wait(Until.findObject(By.res(ACTIVITY_BLOCKING_ACTIVITY_TEXTVIEW_ID)),
+                UI_TIMEOUT_MS)).isNotNull();
+    }
+
+    @Test
+    public void testBlockingActivity_nonDoFinishesOnCreate_noBlockingActivity()
+            throws Exception {
+        startNonDoActivity(NonDoActivity.EXTRA_ONCREATE_FINISH_IMMEDIATELY);
+
+        assertBlockingActivityNotFound();
+    }
+
+    @Test
+    public void testBlockingActivity_nonDoLaunchesDoOnCreate_noBlockingActivity()
+            throws Exception {
+        startNonDoActivity(NonDoActivity.EXTRA_ONCREATE_LAUNCH_DO_IMMEDIATELY);
+
+        assertBlockingActivityNotFound();
+    }
+
+    @Test
+    public void testBlockingActivity_nonDoFinishesOnResume_noBlockingActivity()
+            throws Exception {
+        startNonDoActivity(NonDoActivity.EXTRA_ONRESUME_FINISH_IMMEDIATELY);
+
+        assertBlockingActivityNotFound();
+    }
+
+    @Test
+    public void testBlockingActivity_nonDoLaunchesDoOnResume_noBlockingActivity()
+            throws Exception {
+        startNonDoActivity(NonDoActivity.EXTRA_ONRESUME_LAUNCH_DO_IMMEDIATELY);
+
+        assertBlockingActivityNotFound();
+    }
+
+    @Test
+    public void testBlockingActivity_nonDoNoHistory_isBlocked() throws Exception {
+        startActivity(toComponentName(getTestContext(), NonDoNoHistoryActivity.class));
+
+        assertThat(mDevice.wait(Until.findObject(By.res(ACTIVITY_BLOCKING_ACTIVITY_TEXTVIEW_ID)),
+                UI_TIMEOUT_MS)).isNotNull();
+    }
+
+    private void assertBlockingActivityNotFound() {
+        assertThat(mDevice.wait(Until.findObject(By.res(ACTIVITY_BLOCKING_ACTIVITY_TEXTVIEW_ID)),
+                NOT_FOUND_UI_TIMEOUT_MS)).isNull();
+    }
+
+    private void startActivity(ComponentName name) {
+        Intent intent = new Intent();
+        intent.setComponent(name);
+        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+
+        ActivityOptions options = ActivityOptions.makeBasic();
+        options.setLaunchDisplayId(Display.DEFAULT_DISPLAY);
+
+        getContext().startActivity(intent, options.toBundle());
+    }
+
+    private void startNonDoActivity(int firstActionFlag) {
+        ComponentName activity = toComponentName(getTestContext(), NonDoActivity.class);
+        Intent intent = new Intent();
+        intent.setComponent(activity);
+        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        intent.putExtra(NonDoActivity.EXTRA_FIRST_ACTION, firstActionFlag);
+
+        ActivityOptions options = ActivityOptions.makeBasic();
+        options.setLaunchDisplayId(Display.DEFAULT_DISPLAY);
+
+        getContext().startActivity(intent, options.toBundle());
+    }
+
+
+    private void setDrivingStateMoving() {
+        mCarDrivingStateManager.injectDrivingState(CarDrivingStateEvent.DRIVING_STATE_MOVING);
+    }
+
+    private void setDrivingStateParked() {
+        mCarDrivingStateManager.injectDrivingState(CarDrivingStateEvent.DRIVING_STATE_PARKED);
+    }
+
+    private static ComponentName toComponentName(Context ctx, Class<?> cls) {
+        return ComponentName.createRelative(ctx, cls.getName());
+    }
+
+    public static class NonDoActivity extends TempActivity {
+
+        static final String EXTRA_FIRST_ACTION = "first_action";
+
+        static final int EXTRA_DO_NOTHING = 0;
+        static final int EXTRA_ONCREATE_FINISH_IMMEDIATELY = 1;
+        static final int EXTRA_ONCREATE_LAUNCH_DO_IMMEDIATELY = 2;
+        static final int EXTRA_ONRESUME_FINISH_IMMEDIATELY = 3;
+        static final int EXTRA_ONRESUME_LAUNCH_DO_IMMEDIATELY = 4;
+
+        @Override
+        protected void onCreate(Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+            Bundle extras = getIntent().getExtras();
+            if (extras != null) {
+                switch (extras.getInt(EXTRA_FIRST_ACTION, EXTRA_DO_NOTHING)) {
+                    case EXTRA_ONCREATE_LAUNCH_DO_IMMEDIATELY:
+                        startActivity(new Intent(this, DoActivity.class));
+                        finish();
+                        break;
+                    case EXTRA_ONCREATE_FINISH_IMMEDIATELY:
+                        finish();
+                        break;
+                    default:
+                        // do nothing
+                }
+            }
+        }
+
+        @Override
+        protected void onResume() {
+            super.onResume();
+            Bundle extras = getIntent().getExtras();
+            if (extras != null) {
+                switch (extras.getInt(EXTRA_FIRST_ACTION, EXTRA_DO_NOTHING)) {
+                    case EXTRA_ONRESUME_LAUNCH_DO_IMMEDIATELY:
+                        startActivity(new Intent(this, DoActivity.class));
+                        finish();
+                        break;
+                    case EXTRA_ONRESUME_FINISH_IMMEDIATELY:
+                        finish();
+                        break;
+                    default:
+                        // do nothing
+                }
+            }
+        }
+    }
+
+    public static class NonDoNoHistoryActivity extends TempActivity {
+    }
+
+    public static class DoActivity extends TempActivity {
+    }
+
+    /** Activity that closes itself after some timeout to clean up the screen. */
+    public static class TempActivity extends Activity {
+        @Override
+        protected void onResume() {
+            super.onResume();
+            getMainThreadHandler().postDelayed(this::finish, ACTIVITY_TIMEOUT_MS);
+        }
+    }
+
+    private Context getContext() {
+        return InstrumentationRegistry.getInstrumentation().getTargetContext();
+    }
+
+    private Context getTestContext() {
+        return InstrumentationRegistry.getInstrumentation().getContext();
+    }
+}
diff --git a/tests/carservice_test/src/com/android/car/pm/CarPackageManagerServiceTest.java b/tests/carservice_test/src/com/android/car/pm/CarPackageManagerServiceTest.java
index 9745a1e..8a29b1a 100644
--- a/tests/carservice_test/src/com/android/car/pm/CarPackageManagerServiceTest.java
+++ b/tests/carservice_test/src/com/android/car/pm/CarPackageManagerServiceTest.java
@@ -19,20 +19,22 @@
 import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.assertTrue;
 
-import android.car.userlib.CarUserManagerHelper;
 import android.content.Context;
 
-import androidx.test.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
 
 import com.android.car.CarUxRestrictionsManagerService;
 import com.android.car.SystemActivityMonitoringService;
+import com.android.car.user.CarUserService;
 
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
 
 import java.util.HashMap;
 import java.util.Map;
@@ -45,19 +47,23 @@
 public class CarPackageManagerServiceTest {
     CarPackageManagerService mService;
 
+    @Rule
+    public final MockitoRule rule = MockitoJUnit.rule();
+
     private Context mContext;
     @Mock
     private CarUxRestrictionsManagerService mMockUxrService;
     @Mock
     private SystemActivityMonitoringService mMockSamService;
+    @Mock
+    private CarUserService mMockUserService;
 
     @Before
     public void setUp() {
-        MockitoAnnotations.initMocks(this);
-        mContext = InstrumentationRegistry.getTargetContext();
+        mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
 
         mService = new CarPackageManagerService(mContext, mMockUxrService, mMockSamService,
-                new CarUserManagerHelper(mContext));
+                mMockUserService);
     }
 
     @Test
diff --git a/tests/carservice_test/src/com/android/car/vms/VmsClientTest.java b/tests/carservice_test/src/com/android/car/vms/VmsClientTest.java
new file mode 100644
index 0000000..235e438
--- /dev/null
+++ b/tests/carservice_test/src/com/android/car/vms/VmsClientTest.java
@@ -0,0 +1,2431 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.vms;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.testng.Assert.assertThrows;
+
+import static java.util.Collections.emptySet;
+
+import android.car.Car;
+import android.car.vms.VmsAssociatedLayer;
+import android.car.vms.VmsAvailableLayers;
+import android.car.vms.VmsClient;
+import android.car.vms.VmsClientManager;
+import android.car.vms.VmsClientManager.VmsClientCallback;
+import android.car.vms.VmsLayer;
+import android.car.vms.VmsLayerDependency;
+import android.car.vms.VmsSubscriptionState;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.MediumTest;
+
+import com.android.car.MockedCarTestBase;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+
+@RunWith(AndroidJUnit4.class)
+@MediumTest
+public class VmsClientTest extends MockedCarTestBase {
+    private static final long CONNECT_TIMEOUT = 1000;
+
+    private static final byte[] PROVIDER_DESC1 = {1, 2, 3, 4, 5};
+    private static final byte[] PROVIDER_DESC2 = {5, 4, 3, 2, 1};
+
+    private static final VmsAvailableLayers DEFAULT_AVAILABLE_LAYERS =
+            new VmsAvailableLayers(0, emptySet());
+    private static final VmsSubscriptionState DEFAULT_SUBSCRIPTION_STATE =
+            new VmsSubscriptionState(0, emptySet(), emptySet());
+
+    private static final VmsLayer LAYER1 = new VmsLayer(1, 1, 1);
+    private static final VmsLayer LAYER2 = new VmsLayer(2, 1, 1);
+    private static final VmsLayer LAYER3 = new VmsLayer(3, 1, 1);
+
+    private static final byte[] PAYLOAD = {1, 2, 3, 4, 5, 6, 7, 8};
+
+    @Rule
+    public MockitoRule mMockitoRule = MockitoJUnit.rule();
+    @Mock
+    private VmsClientCallback mClientCallback1;
+    @Captor
+    private ArgumentCaptor<VmsClient> mClientCaptor;
+    @Mock
+    private VmsClientCallback mClientCallback2;
+
+    private final ExecutorService mExecutor = Executors.newSingleThreadExecutor();
+
+    private VmsClientManager mClientManager;
+
+    @Before
+    public void setUpTest() {
+        mClientManager = (VmsClientManager) getCar().getCarManager(Car.VEHICLE_MAP_SERVICE);
+    }
+
+    @Test
+    public void testRegister() {
+        VmsClient client = connectVmsClient(mClientCallback1);
+
+        awaitTaskCompletion();
+        assertThat(client.getAvailableLayers()).isEqualTo(DEFAULT_AVAILABLE_LAYERS);
+        assertThat(client.getSubscriptionState()).isEqualTo(DEFAULT_SUBSCRIPTION_STATE);
+        verifyLayerAvailability(mClientCallback1, DEFAULT_AVAILABLE_LAYERS);
+        verifySubscriptionState(mClientCallback1, DEFAULT_SUBSCRIPTION_STATE);
+    }
+
+    @Test
+    public void testRegister_ReceivesCurrentLayerAvailabilityAndSubscriptions() {
+        VmsClient client = connectVmsClient(mClientCallback1);
+        int providerId = client.registerProvider(PROVIDER_DESC1);
+        client.setProviderOfferings(providerId, asSet(
+                new VmsLayerDependency(LAYER1)));
+        client.setSubscriptions(asSet(
+                new VmsAssociatedLayer(LAYER1, emptySet()),
+                new VmsAssociatedLayer(LAYER2, asSet(12345))
+        ));
+        VmsClient client2 = connectVmsClient(mClientCallback2);
+
+        awaitTaskCompletion();
+        VmsAvailableLayers expectedLayers = new VmsAvailableLayers(1, asSet(
+                new VmsAssociatedLayer(LAYER1,
+                        asSet(providerId)))
+        );
+        VmsSubscriptionState expectedSubscriptions = new VmsSubscriptionState(1,
+                asSet(LAYER1),
+                asSet(new VmsAssociatedLayer(LAYER2, asSet(12345)))
+        );
+        assertThat(client2.getAvailableLayers()).isEqualTo(expectedLayers);
+        assertThat(client2.getSubscriptionState()).isEqualTo(expectedSubscriptions);
+        verify(mClientCallback2).onLayerAvailabilityChanged(expectedLayers);
+        verify(mClientCallback2).onSubscriptionStateChanged(expectedSubscriptions);
+    }
+
+    @Test
+    public void testRegisterProvider_SameIdForSameInfo() {
+        VmsClient client = connectVmsClient(mClientCallback1);
+
+        int providerId = client.registerProvider(PROVIDER_DESC1);
+        int providerId2 = client.registerProvider(PROVIDER_DESC1);
+
+        assertThat(providerId).isEqualTo(providerId2);
+    }
+
+    @Test
+    public void testRegisterProvider_SameIdForSameInfo_MultipleClients() {
+        VmsClient client = connectVmsClient(mClientCallback1);
+        VmsClient client2 = connectVmsClient(mClientCallback2);
+
+        int providerId = client.registerProvider(PROVIDER_DESC1);
+        int providerId2 = client2.registerProvider(PROVIDER_DESC1);
+
+        assertThat(providerId).isEqualTo(providerId2);
+    }
+
+    @Test
+    public void testRegisterProvider_DifferentIdForDifferentInfo() {
+        VmsClient client = connectVmsClient(mClientCallback1);
+
+        int providerId = client.registerProvider(PROVIDER_DESC1);
+        int providerId2 = client.registerProvider(PROVIDER_DESC2);
+
+        assertThat(providerId).isNotEqualTo(providerId2);
+    }
+
+    @Test
+    public void testUnregisterProvider() {
+        VmsClient client = connectVmsClient(mClientCallback1);
+        int providerId = client.registerProvider(PROVIDER_DESC1);
+
+        client.setProviderOfferings(providerId, asSet(
+                new VmsLayerDependency(LAYER1)
+        ));
+
+        client.unregisterProvider(providerId);
+
+        awaitTaskCompletion();
+        VmsAvailableLayers expectedLayers = new VmsAvailableLayers(2, emptySet());
+        verifyLayerAvailability(mClientCallback1, expectedLayers);
+    }
+
+    @Test
+    public void testUnregisterProvider_UnknownId() {
+        VmsClient client = connectVmsClient(mClientCallback1);
+
+        client.unregisterProvider(12345);
+    }
+
+    @Test
+    public void testGetProviderDescription_UnknownId() {
+        VmsClient client = connectVmsClient(mClientCallback1);
+
+        assertThat(client.getProviderDescription(12345)).isNull();
+    }
+
+    @Test
+    public void testGetProviderDescription_RegisteredProvider() {
+        VmsClient client = connectVmsClient(mClientCallback1);
+
+        int providerId = client.registerProvider(PROVIDER_DESC1);
+
+        assertThat(client.getProviderDescription(providerId)).isEqualTo(PROVIDER_DESC1);
+    }
+
+    @Test
+    public void testSetSubscriptions() {
+        VmsClient client = connectVmsClient(mClientCallback1);
+
+        client.setSubscriptions(asSet(
+                new VmsAssociatedLayer(LAYER1, emptySet())
+        ));
+
+        awaitTaskCompletion();
+        VmsSubscriptionState expectedSubscriptions = new VmsSubscriptionState(1,
+                asSet(LAYER1),
+                emptySet());
+        verifySubscriptionState(mClientCallback1, expectedSubscriptions);
+    }
+
+    @Test
+    public void testSetSubscriptions_MultipleClients() {
+        VmsClient client = connectVmsClient(mClientCallback1);
+        VmsClient client2 = connectVmsClient(mClientCallback2);
+
+        client.setSubscriptions(asSet(
+                new VmsAssociatedLayer(LAYER1, emptySet())
+        ));
+        client2.setSubscriptions(asSet(
+                new VmsAssociatedLayer(LAYER1, emptySet())
+        ));
+
+        awaitTaskCompletion();
+        VmsSubscriptionState expectedSubscriptions = new VmsSubscriptionState(1,
+                asSet(LAYER1),
+                emptySet());
+        verifySubscriptionState(mClientCallback1, expectedSubscriptions);
+        verifySubscriptionState(mClientCallback2, expectedSubscriptions);
+    }
+
+    @Test
+    public void testSetSubscriptions_OverwriteSubscription() {
+        VmsClient client = connectVmsClient(mClientCallback1);
+
+        client.setSubscriptions(asSet(
+                new VmsAssociatedLayer(LAYER1, emptySet())
+        ));
+        client.setSubscriptions(asSet(
+                new VmsAssociatedLayer(LAYER2, emptySet())
+        ));
+
+        awaitTaskCompletion();
+        VmsSubscriptionState expectedSubscriptions = new VmsSubscriptionState(2,
+                asSet(LAYER2),
+                emptySet());
+        verifySubscriptionState(mClientCallback1, expectedSubscriptions);
+    }
+
+    @Test
+    public void testSetSubscriptions_OverwriteSubscription_MultipleClients() {
+        VmsClient client = connectVmsClient(mClientCallback1);
+        VmsClient client2 = connectVmsClient(mClientCallback2);
+
+        client.setSubscriptions(asSet(
+                new VmsAssociatedLayer(LAYER1, emptySet())
+        ));
+        client2.setSubscriptions(asSet(
+                new VmsAssociatedLayer(LAYER2, emptySet())
+        ));
+        client2.setSubscriptions(asSet(
+                new VmsAssociatedLayer(LAYER3, emptySet())
+        ));
+
+        awaitTaskCompletion();
+        VmsSubscriptionState expectedSubscriptions = new VmsSubscriptionState(3,
+                asSet(LAYER1, LAYER3),
+                emptySet());
+        verifySubscriptionState(mClientCallback1, expectedSubscriptions);
+        verifySubscriptionState(mClientCallback2, expectedSubscriptions);
+    }
+
+    @Test
+    public void testSetSubscriptions_RemoveSubscription() {
+        VmsClient client = connectVmsClient(mClientCallback1);
+
+        client.setSubscriptions(asSet(
+                new VmsAssociatedLayer(LAYER1, emptySet())
+        ));
+        client.setSubscriptions(emptySet());
+
+        awaitTaskCompletion();
+        VmsSubscriptionState expectedSubscriptions = new VmsSubscriptionState(2,
+                emptySet(),
+                emptySet());
+        verifySubscriptionState(mClientCallback1, expectedSubscriptions);
+    }
+
+    @Test
+    public void testSetSubscriptions_RemoveSubscription_MultipleClients() {
+        VmsClient client = connectVmsClient(mClientCallback1);
+        VmsClient client2 = connectVmsClient(mClientCallback2);
+
+        client.setSubscriptions(asSet(
+                new VmsAssociatedLayer(LAYER1, emptySet())
+        ));
+        client2.setSubscriptions(asSet(
+                new VmsAssociatedLayer(LAYER2, emptySet())
+        ));
+        client2.setSubscriptions(emptySet());
+
+        awaitTaskCompletion();
+        VmsSubscriptionState expectedSubscriptions = new VmsSubscriptionState(3,
+                asSet(LAYER1),
+                emptySet());
+        verifySubscriptionState(mClientCallback1, expectedSubscriptions);
+        verifySubscriptionState(mClientCallback2, expectedSubscriptions);
+    }
+
+    @Test
+    public void testSetSubscriptions_RemoveSubscription_MultipleClients_SameLayer() {
+        VmsClient client = connectVmsClient(mClientCallback1);
+        VmsClient client2 = connectVmsClient(mClientCallback2);
+
+        client.setSubscriptions(asSet(
+                new VmsAssociatedLayer(LAYER1, emptySet())
+        ));
+        client2.setSubscriptions(asSet(
+                new VmsAssociatedLayer(LAYER1, emptySet())
+        ));
+        client2.setSubscriptions(emptySet());
+
+        awaitTaskCompletion();
+        VmsSubscriptionState expectedSubscriptions = new VmsSubscriptionState(1,
+                asSet(LAYER1),
+                emptySet());
+        verifySubscriptionState(mClientCallback1, expectedSubscriptions);
+        verifySubscriptionState(mClientCallback2, expectedSubscriptions);
+    }
+
+    @Test
+    public void testSetSubscriptions_RemoveSubscription_OnUnregister_MultipleClients() {
+        VmsClient client = connectVmsClient(mClientCallback1);
+        VmsClient client2 = connectVmsClient(mClientCallback2);
+
+        client.setSubscriptions(asSet(
+                new VmsAssociatedLayer(LAYER1, emptySet())
+        ));
+        client2.setSubscriptions(asSet(
+                new VmsAssociatedLayer(LAYER2, emptySet())
+        ));
+        mClientManager.unregisterVmsClientCallback(mClientCallback2);
+
+        awaitTaskCompletion();
+        VmsSubscriptionState expectedSubscriptions = new VmsSubscriptionState(3,
+                asSet(LAYER1),
+                emptySet());
+        verifySubscriptionState(mClientCallback1, expectedSubscriptions);
+    }
+
+    @Test
+    public void testSetSubscriptions_RemoveSubscription_OnUnregister_MultipleClients_SameLayer() {
+        VmsClient client = connectVmsClient(mClientCallback1);
+        VmsClient client2 = connectVmsClient(mClientCallback2);
+
+        client.setSubscriptions(asSet(
+                new VmsAssociatedLayer(LAYER1, emptySet())
+        ));
+        client2.setSubscriptions(asSet(
+                new VmsAssociatedLayer(LAYER1, emptySet())
+        ));
+        mClientManager.unregisterVmsClientCallback(mClientCallback2);
+
+        awaitTaskCompletion();
+        VmsSubscriptionState expectedSubscriptions = new VmsSubscriptionState(1,
+                asSet(LAYER1),
+                emptySet());
+        verifySubscriptionState(mClientCallback1, expectedSubscriptions);
+    }
+
+    @Test
+    public void testSetSubscriptions_MultipleLayers() {
+        VmsClient client = connectVmsClient(mClientCallback1);
+
+        client.setSubscriptions(asSet(
+                new VmsAssociatedLayer(LAYER1, emptySet()),
+                new VmsAssociatedLayer(LAYER2, emptySet())
+        ));
+
+        awaitTaskCompletion();
+        VmsSubscriptionState expectedSubscriptions = new VmsSubscriptionState(1,
+                asSet(LAYER1, LAYER2),
+                emptySet());
+        verifySubscriptionState(mClientCallback1, expectedSubscriptions);
+    }
+
+    @Test
+    public void testSetSubscriptions_MultipleLayers_MultipleClients() {
+        VmsClient client = connectVmsClient(mClientCallback1);
+        VmsClient client2 = connectVmsClient(mClientCallback2);
+
+        client.setSubscriptions(asSet(
+                new VmsAssociatedLayer(LAYER1, emptySet())
+        ));
+        client2.setSubscriptions(asSet(
+                new VmsAssociatedLayer(LAYER2, emptySet()),
+                new VmsAssociatedLayer(LAYER3, emptySet())
+        ));
+
+        awaitTaskCompletion();
+        VmsSubscriptionState expectedSubscriptions = new VmsSubscriptionState(2,
+                asSet(LAYER1, LAYER2, LAYER3),
+                emptySet());
+        verifySubscriptionState(mClientCallback1, expectedSubscriptions);
+        verifySubscriptionState(mClientCallback2, expectedSubscriptions);
+    }
+
+    @Test
+    public void testSetSubscriptions_LayerAndProvider() {
+        VmsClient client = connectVmsClient(mClientCallback1);
+
+        client.setSubscriptions(asSet(
+                new VmsAssociatedLayer(LAYER1, asSet(12345))
+        ));
+
+        awaitTaskCompletion();
+        VmsSubscriptionState expectedSubscriptions = new VmsSubscriptionState(1,
+                emptySet(),
+                asSet(new VmsAssociatedLayer(LAYER1, asSet(12345))));
+        verifySubscriptionState(mClientCallback1, expectedSubscriptions);
+    }
+
+    @Test
+    public void testSetSubscriptions_LayerAndProvider_MultipleClients() {
+        VmsClient client = connectVmsClient(mClientCallback1);
+        VmsClient client2 = connectVmsClient(mClientCallback2);
+
+        client.setSubscriptions(asSet(
+                new VmsAssociatedLayer(LAYER1, asSet(12345))
+        ));
+        client2.setSubscriptions(asSet(
+                new VmsAssociatedLayer(LAYER1, asSet(54321))
+        ));
+
+        awaitTaskCompletion();
+        VmsSubscriptionState expectedSubscriptions = new VmsSubscriptionState(2,
+                emptySet(),
+                asSet(new VmsAssociatedLayer(LAYER1, asSet(12345, 54321))));
+        verifySubscriptionState(mClientCallback1, expectedSubscriptions);
+        verifySubscriptionState(mClientCallback2, expectedSubscriptions);
+    }
+
+    @Test
+    public void testSetSubscriptions_LayerAndProvider_OverwriteSubscription() {
+        VmsClient client = connectVmsClient(mClientCallback1);
+
+        client.setSubscriptions(asSet(
+                new VmsAssociatedLayer(LAYER1, asSet(12345))
+        ));
+        client.setSubscriptions(asSet(
+                new VmsAssociatedLayer(LAYER2, asSet(54321))
+        ));
+
+        awaitTaskCompletion();
+        VmsSubscriptionState expectedSubscriptions = new VmsSubscriptionState(2,
+                emptySet(),
+                asSet(new VmsAssociatedLayer(LAYER2, asSet(54321))));
+        verifySubscriptionState(mClientCallback1, expectedSubscriptions);
+    }
+
+    @Test
+    public void testSetSubscriptions_LayerAndProvider_OverwriteSubscription_MultipleClients() {
+        VmsClient client = connectVmsClient(mClientCallback1);
+        VmsClient client2 = connectVmsClient(mClientCallback2);
+
+        client.setSubscriptions(asSet(
+                new VmsAssociatedLayer(LAYER1, asSet(12345))
+        ));
+        client2.setSubscriptions(asSet(
+                new VmsAssociatedLayer(LAYER2, asSet(54321))
+        ));
+        client2.setSubscriptions(asSet(
+                new VmsAssociatedLayer(LAYER3, asSet(98765))
+        ));
+
+        awaitTaskCompletion();
+        VmsSubscriptionState expectedSubscriptions = new VmsSubscriptionState(3,
+                emptySet(),
+                asSet(
+                        new VmsAssociatedLayer(LAYER1, asSet(12345)),
+                        new VmsAssociatedLayer(LAYER3, asSet(98765))));
+        verifySubscriptionState(mClientCallback1, expectedSubscriptions);
+        verifySubscriptionState(mClientCallback2, expectedSubscriptions);
+    }
+
+    @Test
+    public void testSetSubscriptions_LayerAndProvider_OverwriteSubscription_MultipleClients_SameLayer() {
+        VmsClient client = connectVmsClient(mClientCallback1);
+        VmsClient client2 = connectVmsClient(mClientCallback2);
+
+        client.setSubscriptions(asSet(
+                new VmsAssociatedLayer(LAYER1, asSet(12345))
+        ));
+        client2.setSubscriptions(asSet(
+                new VmsAssociatedLayer(LAYER2, asSet(54321))
+        ));
+        client2.setSubscriptions(asSet(
+                new VmsAssociatedLayer(LAYER1, asSet(98765))
+        ));
+
+        awaitTaskCompletion();
+        VmsSubscriptionState expectedSubscriptions = new VmsSubscriptionState(3,
+                emptySet(),
+                asSet(new VmsAssociatedLayer(LAYER1, asSet(12345, 98765))));
+        verifySubscriptionState(mClientCallback1, expectedSubscriptions);
+        verifySubscriptionState(mClientCallback2, expectedSubscriptions);
+    }
+
+    @Test
+    public void testSetSubscriptions_LayerAndProvider_OverwriteSubscription_MultipleClients_SameLayerAndProvider() {
+        VmsClient client = connectVmsClient(mClientCallback1);
+        VmsClient client2 = connectVmsClient(mClientCallback2);
+
+        client.setSubscriptions(asSet(
+                new VmsAssociatedLayer(LAYER1, asSet(12345))
+        ));
+        client2.setSubscriptions(asSet(
+                new VmsAssociatedLayer(LAYER2, asSet(54321))
+        ));
+        client2.setSubscriptions(asSet(
+                new VmsAssociatedLayer(LAYER1, asSet(12345))
+        ));
+
+        awaitTaskCompletion();
+        VmsSubscriptionState expectedSubscriptions = new VmsSubscriptionState(3,
+                emptySet(),
+                asSet(new VmsAssociatedLayer(LAYER1, asSet(12345))));
+        verifySubscriptionState(mClientCallback1, expectedSubscriptions);
+        verifySubscriptionState(mClientCallback2, expectedSubscriptions);
+    }
+
+    @Test
+    public void testSetSubscriptions_LayerAndProvider_RemoveSubscription() {
+        VmsClient client = connectVmsClient(mClientCallback1);
+
+        client.setSubscriptions(asSet(
+                new VmsAssociatedLayer(LAYER1, asSet(12345))
+        ));
+        client.setSubscriptions(emptySet());
+
+        awaitTaskCompletion();
+        VmsSubscriptionState expectedSubscriptions = new VmsSubscriptionState(2,
+                emptySet(),
+                emptySet());
+        verifySubscriptionState(mClientCallback1, expectedSubscriptions);
+    }
+
+    @Test
+    public void testSetSubscriptions_LayerAndProvider_RemoveSubscription_MultipleClients() {
+        VmsClient client = connectVmsClient(mClientCallback1);
+        VmsClient client2 = connectVmsClient(mClientCallback2);
+
+        client.setSubscriptions(asSet(
+                new VmsAssociatedLayer(LAYER1, asSet(12345))
+        ));
+        client2.setSubscriptions(asSet(
+                new VmsAssociatedLayer(LAYER2, asSet(54321))
+        ));
+        client2.setSubscriptions(emptySet());
+
+        awaitTaskCompletion();
+        VmsSubscriptionState expectedSubscriptions = new VmsSubscriptionState(3,
+                emptySet(),
+                asSet(new VmsAssociatedLayer(LAYER1, asSet(12345))));
+        verifySubscriptionState(mClientCallback1, expectedSubscriptions);
+        verifySubscriptionState(mClientCallback2, expectedSubscriptions);
+    }
+
+    @Test
+    public void testSetSubscriptions_LayerAndProvider_RemoveSubscription_MultipleClients_SameLayer() {
+        VmsClient client = connectVmsClient(mClientCallback1);
+        VmsClient client2 = connectVmsClient(mClientCallback2);
+
+        client.setSubscriptions(asSet(
+                new VmsAssociatedLayer(LAYER1, asSet(12345))
+        ));
+        client2.setSubscriptions(asSet(
+                new VmsAssociatedLayer(LAYER1, asSet(54321))
+        ));
+        client2.setSubscriptions(emptySet());
+
+        awaitTaskCompletion();
+        VmsSubscriptionState expectedSubscriptions = new VmsSubscriptionState(3,
+                emptySet(),
+                asSet(new VmsAssociatedLayer(LAYER1, asSet(12345))));
+        verifySubscriptionState(mClientCallback1, expectedSubscriptions);
+        verifySubscriptionState(mClientCallback2, expectedSubscriptions);
+    }
+
+    @Test
+    public void testSetSubscriptions_LayerAndProvider_RemoveSubscription_MultipleClients_SameLayerAndProvider() {
+        VmsClient client = connectVmsClient(mClientCallback1);
+        VmsClient client2 = connectVmsClient(mClientCallback2);
+
+        client.setSubscriptions(asSet(
+                new VmsAssociatedLayer(LAYER1, asSet(12345))
+        ));
+        client2.setSubscriptions(asSet(
+                new VmsAssociatedLayer(LAYER1, asSet(12345))
+        ));
+        client2.setSubscriptions(emptySet());
+
+        awaitTaskCompletion();
+        VmsSubscriptionState expectedSubscriptions = new VmsSubscriptionState(1,
+                emptySet(),
+                asSet(new VmsAssociatedLayer(LAYER1, asSet(12345))));
+        verifySubscriptionState(mClientCallback1, expectedSubscriptions);
+        verifySubscriptionState(mClientCallback2, expectedSubscriptions);
+    }
+
+    @Test
+    public void testSetSubscriptions_LayerAndProvider_RemoveSubscription_OnUnregister_MultipleClients() {
+        VmsClient client = connectVmsClient(mClientCallback1);
+        VmsClient client2 = connectVmsClient(mClientCallback2);
+
+        client.setSubscriptions(asSet(
+                new VmsAssociatedLayer(LAYER1, asSet(12345))
+        ));
+        client2.setSubscriptions(asSet(
+                new VmsAssociatedLayer(LAYER2, asSet(54321))
+        ));
+        mClientManager.unregisterVmsClientCallback(mClientCallback2);
+
+        awaitTaskCompletion();
+        VmsSubscriptionState expectedSubscriptions = new VmsSubscriptionState(3,
+                emptySet(),
+                asSet(new VmsAssociatedLayer(LAYER1, asSet(12345))));
+        verifySubscriptionState(mClientCallback1, expectedSubscriptions);
+    }
+
+    @Test
+    public void testSetSubscriptions_LayerAndProvider_RemoveSubscription_OnUnregister_MultipleClients_SameLayer() {
+        VmsClient client = connectVmsClient(mClientCallback1);
+        VmsClient client2 = connectVmsClient(mClientCallback2);
+
+        client.setSubscriptions(asSet(
+                new VmsAssociatedLayer(LAYER1, asSet(12345))
+        ));
+        client2.setSubscriptions(asSet(
+                new VmsAssociatedLayer(LAYER1, asSet(54321))
+        ));
+        mClientManager.unregisterVmsClientCallback(mClientCallback2);
+
+        awaitTaskCompletion();
+        VmsSubscriptionState expectedSubscriptions = new VmsSubscriptionState(3,
+                emptySet(),
+                asSet(new VmsAssociatedLayer(LAYER1, asSet(12345))));
+        verifySubscriptionState(mClientCallback1, expectedSubscriptions);
+    }
+
+    @Test
+    public void testSetSubscriptions_LayerAndProvider_RemoveSubscription_OnUnregister_MultipleClients_SameLayerAndProvider() {
+        VmsClient client = connectVmsClient(mClientCallback1);
+        VmsClient client2 = connectVmsClient(mClientCallback2);
+
+        client.setSubscriptions(asSet(
+                new VmsAssociatedLayer(LAYER1, asSet(12345))
+        ));
+        client2.setSubscriptions(asSet(
+                new VmsAssociatedLayer(LAYER1, asSet(12345))
+        ));
+        mClientManager.unregisterVmsClientCallback(mClientCallback2);
+
+        awaitTaskCompletion();
+        VmsSubscriptionState expectedSubscriptions = new VmsSubscriptionState(1,
+                emptySet(),
+                asSet(new VmsAssociatedLayer(LAYER1, asSet(12345))));
+        verifySubscriptionState(mClientCallback1, expectedSubscriptions);
+    }
+
+    @Test
+    public void testSetSubscriptions_LayerAndMultipleProviders() {
+        VmsClient client = connectVmsClient(mClientCallback1);
+
+        client.setSubscriptions(asSet(
+                new VmsAssociatedLayer(LAYER1, asSet(12345, 54321))
+        ));
+
+        awaitTaskCompletion();
+        VmsSubscriptionState expectedSubscriptions = new VmsSubscriptionState(1,
+                emptySet(),
+                asSet(new VmsAssociatedLayer(LAYER1, asSet(12345, 54321))));
+        verifySubscriptionState(mClientCallback1, expectedSubscriptions);
+    }
+
+    @Test
+    public void testSetSubscriptions_LayerAndMultipleProviders_MultipleClients() {
+        VmsClient client = connectVmsClient(mClientCallback1);
+        VmsClient client2 = connectVmsClient(mClientCallback2);
+
+        client.setSubscriptions(asSet(
+                new VmsAssociatedLayer(LAYER1, asSet(12345))
+        ));
+        client2.setSubscriptions(asSet(
+                new VmsAssociatedLayer(LAYER1, asSet(54321))
+        ));
+
+        awaitTaskCompletion();
+        VmsSubscriptionState expectedSubscriptions = new VmsSubscriptionState(2,
+                emptySet(),
+                asSet(new VmsAssociatedLayer(LAYER1, asSet(12345, 54321))));
+        verifySubscriptionState(mClientCallback1, expectedSubscriptions);
+        verifySubscriptionState(mClientCallback2, expectedSubscriptions);
+    }
+
+    @Test
+    public void testSetSubscriptions_MultipleLayersAndProvider() {
+        VmsClient client = connectVmsClient(mClientCallback1);
+
+        client.setSubscriptions(asSet(
+                new VmsAssociatedLayer(LAYER1, emptySet()),
+                new VmsAssociatedLayer(LAYER2, asSet(12345))
+        ));
+
+        awaitTaskCompletion();
+        VmsSubscriptionState expectedSubscriptions = new VmsSubscriptionState(1,
+                asSet(LAYER1),
+                asSet(new VmsAssociatedLayer(LAYER2, asSet(12345))));
+        verifySubscriptionState(mClientCallback1, expectedSubscriptions);
+    }
+
+    @Test
+    public void testSetSubscriptions_MultipleLayersAndProvider_MultipleClients() {
+        VmsClient client = connectVmsClient(mClientCallback1);
+        VmsClient client2 = connectVmsClient(mClientCallback2);
+
+        client.setSubscriptions(asSet(
+                new VmsAssociatedLayer(LAYER1, emptySet()),
+                new VmsAssociatedLayer(LAYER2, asSet(12345))
+        ));
+        client2.setSubscriptions(asSet(
+                new VmsAssociatedLayer(LAYER3, emptySet()),
+                new VmsAssociatedLayer(LAYER2, asSet(54321))
+        ));
+
+        awaitTaskCompletion();
+        VmsSubscriptionState expectedSubscriptions = new VmsSubscriptionState(2,
+                asSet(LAYER1, LAYER3),
+                asSet(new VmsAssociatedLayer(LAYER2, asSet(12345, 54321))));
+        verifySubscriptionState(mClientCallback1, expectedSubscriptions);
+        verifySubscriptionState(mClientCallback2, expectedSubscriptions);
+    }
+
+    @Test
+    public void testSetSubscriptions_MultipleLayersAndProvider_OverwriteSubscription() {
+        VmsClient client = connectVmsClient(mClientCallback1);
+
+        client.setSubscriptions(asSet(
+                new VmsAssociatedLayer(LAYER1, emptySet()),
+                new VmsAssociatedLayer(LAYER2, asSet(12345))
+        ));
+        client.setSubscriptions(asSet(
+                new VmsAssociatedLayer(LAYER2, emptySet()),
+                new VmsAssociatedLayer(LAYER1, asSet(54321))
+        ));
+
+        awaitTaskCompletion();
+        VmsSubscriptionState expectedSubscriptions = new VmsSubscriptionState(2,
+                asSet(LAYER2),
+                asSet(new VmsAssociatedLayer(LAYER1, asSet(54321))));
+        verifySubscriptionState(mClientCallback1, expectedSubscriptions);
+    }
+
+    @Test
+    public void testSetSubscriptions_MultipleLayersAndProvider_OverwriteSubscription_MultipleClients() {
+        VmsClient client = connectVmsClient(mClientCallback1);
+        VmsClient client2 = connectVmsClient(mClientCallback2);
+
+        client.setSubscriptions(asSet(
+                new VmsAssociatedLayer(LAYER1, emptySet()),
+                new VmsAssociatedLayer(LAYER2, asSet(12345))
+        ));
+        client2.setSubscriptions(asSet(
+                new VmsAssociatedLayer(LAYER2, emptySet()),
+                new VmsAssociatedLayer(LAYER1, asSet(54321))
+        ));
+        client2.setSubscriptions(asSet(
+                new VmsAssociatedLayer(LAYER3, emptySet()),
+                new VmsAssociatedLayer(LAYER2, asSet(54321))
+        ));
+
+        awaitTaskCompletion();
+        VmsSubscriptionState expectedSubscriptions = new VmsSubscriptionState(3,
+                asSet(LAYER1, LAYER3),
+                asSet(new VmsAssociatedLayer(LAYER2, asSet(12345, 54321))));
+        verifySubscriptionState(mClientCallback1, expectedSubscriptions);
+        verifySubscriptionState(mClientCallback2, expectedSubscriptions);
+    }
+
+    @Test
+    public void testSetSubscriptions_MultipleLayersAndProvider_RemoveSubscription() {
+        VmsClient client = connectVmsClient(mClientCallback1);
+
+        client.setSubscriptions(asSet(
+                new VmsAssociatedLayer(LAYER1, emptySet()),
+                new VmsAssociatedLayer(LAYER2, asSet(12345))
+        ));
+        client.setSubscriptions(emptySet());
+
+        awaitTaskCompletion();
+        VmsSubscriptionState expectedSubscriptions = new VmsSubscriptionState(2,
+                emptySet(),
+                emptySet());
+        verifySubscriptionState(mClientCallback1, expectedSubscriptions);
+    }
+
+    @Test
+    public void testSetSubscriptions_MultipleLayersAndProvider_RemoveSubscription_MultipleClients() {
+        VmsClient client = connectVmsClient(mClientCallback1);
+        VmsClient client2 = connectVmsClient(mClientCallback2);
+
+        client.setSubscriptions(asSet(
+                new VmsAssociatedLayer(LAYER1, emptySet()),
+                new VmsAssociatedLayer(LAYER2, asSet(12345))
+        ));
+        client2.setSubscriptions(asSet(
+                new VmsAssociatedLayer(LAYER2, emptySet()),
+                new VmsAssociatedLayer(LAYER1, asSet(54321))
+        ));
+        client2.setSubscriptions(emptySet());
+
+        awaitTaskCompletion();
+        VmsSubscriptionState expectedSubscriptions = new VmsSubscriptionState(3,
+                asSet(LAYER1),
+                asSet(new VmsAssociatedLayer(LAYER2, asSet(12345))));
+        verifySubscriptionState(mClientCallback1, expectedSubscriptions);
+        verifySubscriptionState(mClientCallback2, expectedSubscriptions);
+    }
+
+    @Test
+    public void testSetSubscriptions_MultipleLayersAndProvider_RemoveSubscription_OnUnregister_MultipleClients() {
+        VmsClient client = connectVmsClient(mClientCallback1);
+        VmsClient client2 = connectVmsClient(mClientCallback2);
+
+        client.setSubscriptions(asSet(
+                new VmsAssociatedLayer(LAYER1, emptySet()),
+                new VmsAssociatedLayer(LAYER2, asSet(12345))
+        ));
+        client2.setSubscriptions(asSet(
+                new VmsAssociatedLayer(LAYER2, emptySet()),
+                new VmsAssociatedLayer(LAYER1, asSet(54321))
+        ));
+        mClientManager.unregisterVmsClientCallback(mClientCallback2);
+
+        awaitTaskCompletion();
+        VmsSubscriptionState expectedSubscriptions = new VmsSubscriptionState(3,
+                asSet(LAYER1),
+                asSet(new VmsAssociatedLayer(LAYER2, asSet(12345))));
+        verifySubscriptionState(mClientCallback1, expectedSubscriptions);
+    }
+
+    @Test
+    public void testSetSubscriptions_MultipleLayersAndMultipleProviders() {
+        VmsClient client = connectVmsClient(mClientCallback1);
+
+        client.setSubscriptions(asSet(
+                new VmsAssociatedLayer(LAYER1, asSet(54321)),
+                new VmsAssociatedLayer(LAYER2, asSet(12345))
+        ));
+
+        awaitTaskCompletion();
+        VmsSubscriptionState expectedSubscriptions = new VmsSubscriptionState(1,
+                emptySet(),
+                asSet(
+                        new VmsAssociatedLayer(LAYER1, asSet(54321)),
+                        new VmsAssociatedLayer(LAYER2, asSet(12345))));
+        verifySubscriptionState(mClientCallback1, expectedSubscriptions);
+    }
+
+    @Test
+    public void testSetSubscriptions_MultipleLayersAndMultipleProviders_MultipleClients() {
+        VmsClient client = connectVmsClient(mClientCallback1);
+        VmsClient client2 = connectVmsClient(mClientCallback2);
+
+        client.setSubscriptions(asSet(
+                new VmsAssociatedLayer(LAYER1, asSet(54321))
+        ));
+        client2.setSubscriptions(asSet(
+                new VmsAssociatedLayer(LAYER2, asSet(12345))
+        ));
+
+        awaitTaskCompletion();
+        VmsSubscriptionState expectedSubscriptions = new VmsSubscriptionState(2,
+                emptySet(),
+                asSet(
+                        new VmsAssociatedLayer(LAYER1, asSet(54321)),
+                        new VmsAssociatedLayer(LAYER2, asSet(12345))));
+        verifySubscriptionState(mClientCallback1, expectedSubscriptions);
+    }
+
+    @Test
+    public void testSetSubscriptions_LayerOnlySupersedesLayerAndProvider() {
+        VmsClient client = connectVmsClient(mClientCallback1);
+
+        client.setSubscriptions(asSet(
+                new VmsAssociatedLayer(LAYER1, emptySet()),
+                new VmsAssociatedLayer(LAYER1, asSet(12345))
+        ));
+
+        awaitTaskCompletion();
+        VmsSubscriptionState expectedSubscriptions = new VmsSubscriptionState(1,
+                asSet(LAYER1),
+                emptySet());
+        verifySubscriptionState(mClientCallback1, expectedSubscriptions);
+    }
+
+    @Test
+    public void testSetSubscriptions_LayerOnlySupersedesLayerAndProvider_MultipleClients() {
+        VmsClient client = connectVmsClient(mClientCallback1);
+        VmsClient client2 = connectVmsClient(mClientCallback2);
+
+        client.setSubscriptions(asSet(
+                new VmsAssociatedLayer(LAYER1, emptySet())
+        ));
+        client2.setSubscriptions(asSet(
+                new VmsAssociatedLayer(LAYER1, asSet(12345))
+        ));
+
+        awaitTaskCompletion();
+        VmsSubscriptionState expectedSubscriptions = new VmsSubscriptionState(1,
+                asSet(LAYER1),
+                emptySet());
+        verifySubscriptionState(mClientCallback1, expectedSubscriptions);
+        verifySubscriptionState(mClientCallback2, expectedSubscriptions);
+    }
+
+    @Test
+    public void testSetSubscriptions_LayerOnlySupersedesLayerAndProvider_RemoveLayerSubscription() {
+        VmsClient client = connectVmsClient(mClientCallback1);
+
+        client.setSubscriptions(asSet(
+                new VmsAssociatedLayer(LAYER1, emptySet()),
+                new VmsAssociatedLayer(LAYER1, asSet(12345))
+        ));
+        client.setSubscriptions(asSet(
+                new VmsAssociatedLayer(LAYER1, asSet(12345))
+        ));
+
+        awaitTaskCompletion();
+        VmsSubscriptionState expectedSubscriptions = new VmsSubscriptionState(2,
+                emptySet(),
+                asSet(new VmsAssociatedLayer(LAYER1, asSet(12345))));
+        verifySubscriptionState(mClientCallback1, expectedSubscriptions);
+    }
+
+    @Test
+    public void testSetSubscriptions_LayerOnlySupersedesLayerAndProvider_RemoveLayerSubscription_MultipleClients() {
+        VmsClient client = connectVmsClient(mClientCallback1);
+        VmsClient client2 = connectVmsClient(mClientCallback2);
+
+        client.setSubscriptions(asSet(
+                new VmsAssociatedLayer(LAYER1, asSet(12345))
+        ));
+        client2.setSubscriptions(asSet(
+                new VmsAssociatedLayer(LAYER1, emptySet())
+        ));
+        client2.setSubscriptions(emptySet());
+
+        awaitTaskCompletion();
+        VmsSubscriptionState expectedSubscriptions = new VmsSubscriptionState(3,
+                emptySet(),
+                asSet(new VmsAssociatedLayer(LAYER1, asSet(12345))));
+        verifySubscriptionState(mClientCallback1, expectedSubscriptions);
+        verifySubscriptionState(mClientCallback2, expectedSubscriptions);
+    }
+
+    @Test
+    public void testSetMonitoringEnabled_Enable_NoSubscriptionChange() {
+        VmsClient client = connectVmsClient(mClientCallback1);
+
+        client.setMonitoringEnabled(true);
+
+        awaitTaskCompletion();
+        verifySubscriptionState(mClientCallback1, DEFAULT_SUBSCRIPTION_STATE);
+    }
+
+    @Test
+    public void testSetMonitoringEnabled_Disable_NoSubscriptionChange() {
+        VmsClient client = connectVmsClient(mClientCallback1);
+
+        client.setMonitoringEnabled(false);
+
+        awaitTaskCompletion();
+        verifySubscriptionState(mClientCallback1, DEFAULT_SUBSCRIPTION_STATE);
+    }
+
+    @Test
+    public void testSetProviderOfferings_UnknownProviderId() {
+        VmsClient client = connectVmsClient(mClientCallback1);
+
+        assertThrows(
+                IllegalArgumentException.class,
+                () -> client.setProviderOfferings(12345, emptySet()));
+    }
+
+    @Test
+    public void testSetProviderOfferings_OtherClientsProviderId() {
+        VmsClient client = connectVmsClient(mClientCallback1);
+        int providerId = client.registerProvider(PROVIDER_DESC1);
+        VmsClient client2 = connectVmsClient(mClientCallback2);
+
+        assertThrows(
+                IllegalArgumentException.class,
+                () -> client2.setProviderOfferings(providerId, emptySet()));
+    }
+
+    @Test
+    public void testSetProviderOfferings_SingleProvider() {
+        VmsClient client = connectVmsClient(mClientCallback1);
+        int providerId = client.registerProvider(PROVIDER_DESC1);
+
+        client.setProviderOfferings(providerId, asSet(
+                new VmsLayerDependency(LAYER1)
+        ));
+
+        awaitTaskCompletion();
+        VmsAvailableLayers expectedLayers = new VmsAvailableLayers(1, asSet(
+                new VmsAssociatedLayer(LAYER1, asSet(providerId)))
+        );
+        verifyLayerAvailability(mClientCallback1, expectedLayers);
+    }
+
+    @Test
+    public void testSetProviderOfferings_MultipleProviders() {
+        VmsClient client = connectVmsClient(mClientCallback1);
+        int providerId = client.registerProvider(PROVIDER_DESC1);
+        int providerId2 = client.registerProvider(PROVIDER_DESC2);
+
+        client.setProviderOfferings(providerId, asSet(
+                new VmsLayerDependency(LAYER1)
+        ));
+        client.setProviderOfferings(providerId2, asSet(
+                new VmsLayerDependency(LAYER1)
+        ));
+
+        awaitTaskCompletion();
+        VmsAvailableLayers expectedLayers = new VmsAvailableLayers(2, asSet(
+                new VmsAssociatedLayer(LAYER1, asSet(providerId, providerId2)))
+        );
+        verifyLayerAvailability(mClientCallback1, expectedLayers);
+    }
+
+    @Test
+    public void testSetProviderOfferings_MultipleClients() {
+        VmsClient client = connectVmsClient(mClientCallback1);
+        int providerId = client.registerProvider(PROVIDER_DESC1);
+
+        VmsClient client2 = connectVmsClient(mClientCallback2);
+        int providerId2 = client2.registerProvider(PROVIDER_DESC2);
+
+        client.setProviderOfferings(providerId, asSet(
+                new VmsLayerDependency(LAYER1)
+        ));
+        client2.setProviderOfferings(providerId2, asSet(
+                new VmsLayerDependency(LAYER1)
+        ));
+
+        awaitTaskCompletion();
+        VmsAvailableLayers expectedLayers = new VmsAvailableLayers(2, asSet(
+                new VmsAssociatedLayer(LAYER1, asSet(providerId, providerId2)))
+        );
+        verifyLayerAvailability(mClientCallback1, expectedLayers);
+        verifyLayerAvailability(mClientCallback2, expectedLayers);
+    }
+
+    @Test
+    public void testSetProviderOfferings_MultipleClients_SingleProvider() {
+        VmsClient client = connectVmsClient(mClientCallback1);
+        int providerId = client.registerProvider(PROVIDER_DESC1);
+
+        VmsClient client2 = connectVmsClient(mClientCallback2);
+        client2.registerProvider(PROVIDER_DESC1);
+
+        client.setProviderOfferings(providerId, asSet(
+                new VmsLayerDependency(LAYER1)
+        ));
+        client2.setProviderOfferings(providerId, asSet(
+                new VmsLayerDependency(LAYER1)
+        ));
+
+        awaitTaskCompletion();
+        VmsAvailableLayers expectedLayers = new VmsAvailableLayers(1, asSet(
+                new VmsAssociatedLayer(LAYER1, asSet(providerId)))
+        );
+        verifyLayerAvailability(mClientCallback1, expectedLayers);
+        verifyLayerAvailability(mClientCallback2, expectedLayers);
+    }
+
+    @Test
+    public void testSetProviderOfferings_MultipleLayers_SingleProvider() {
+        VmsClient client = connectVmsClient(mClientCallback1);
+        int providerId = client.registerProvider(PROVIDER_DESC1);
+
+        client.setProviderOfferings(providerId, asSet(
+                new VmsLayerDependency(LAYER1),
+                new VmsLayerDependency(LAYER2)
+        ));
+
+        awaitTaskCompletion();
+        VmsAvailableLayers expectedLayers = new VmsAvailableLayers(1, asSet(
+                new VmsAssociatedLayer(LAYER1, asSet(providerId)),
+                new VmsAssociatedLayer(LAYER2, asSet(providerId)))
+        );
+        verifyLayerAvailability(mClientCallback1, expectedLayers);
+    }
+
+    @Test
+    public void testSetProviderOfferings_MultipleLayers_MultipleProviders() {
+        VmsClient client = connectVmsClient(mClientCallback1);
+        int providerId = client.registerProvider(PROVIDER_DESC1);
+        int providerId2 = client.registerProvider(PROVIDER_DESC2);
+
+        client.setProviderOfferings(providerId, asSet(
+                new VmsLayerDependency(LAYER1)
+        ));
+        client.setProviderOfferings(providerId2, asSet(
+                new VmsLayerDependency(LAYER2)
+        ));
+
+        awaitTaskCompletion();
+        VmsAvailableLayers expectedLayers = new VmsAvailableLayers(2, asSet(
+                new VmsAssociatedLayer(LAYER1, asSet(providerId)),
+                new VmsAssociatedLayer(LAYER2, asSet(providerId2)))
+        );
+        verifyLayerAvailability(mClientCallback1, expectedLayers);
+    }
+
+    @Test
+    public void testSetProviderOfferings_MultipleLayers_MultipleClients() {
+        VmsClient client = connectVmsClient(mClientCallback1);
+        int providerId = client.registerProvider(PROVIDER_DESC1);
+
+        VmsClient client2 = connectVmsClient(mClientCallback2);
+        int providerId2 = client2.registerProvider(PROVIDER_DESC2);
+
+        client.setProviderOfferings(providerId, asSet(
+                new VmsLayerDependency(LAYER1)
+        ));
+        client2.setProviderOfferings(providerId2, asSet(
+                new VmsLayerDependency(LAYER2)
+        ));
+
+        awaitTaskCompletion();
+        VmsAvailableLayers expectedLayers = new VmsAvailableLayers(2, asSet(
+                new VmsAssociatedLayer(LAYER1, asSet(providerId)),
+                new VmsAssociatedLayer(LAYER2, asSet(providerId2)))
+        );
+        verifyLayerAvailability(mClientCallback1, expectedLayers);
+        verifyLayerAvailability(mClientCallback2, expectedLayers);
+    }
+
+    @Test
+    public void testSetProviderOfferings_MultipleLayers_MultipleClients_SingleProvider() {
+        VmsClient client = connectVmsClient(mClientCallback1);
+        int providerId = client.registerProvider(PROVIDER_DESC1);
+
+        VmsClient client2 = connectVmsClient(mClientCallback2);
+        client2.registerProvider(PROVIDER_DESC1);
+
+        client.setProviderOfferings(providerId, asSet(
+                new VmsLayerDependency(LAYER1)
+        ));
+        client2.setProviderOfferings(providerId, asSet(
+                new VmsLayerDependency(LAYER2)
+        ));
+
+        awaitTaskCompletion();
+        VmsAvailableLayers expectedLayers = new VmsAvailableLayers(2, asSet(
+                new VmsAssociatedLayer(LAYER1, asSet(providerId)),
+                new VmsAssociatedLayer(LAYER2, asSet(providerId)))
+        );
+        verifyLayerAvailability(mClientCallback1, expectedLayers);
+        verifyLayerAvailability(mClientCallback2, expectedLayers);
+    }
+
+    @Test
+    public void testSetProviderOfferings_OverwriteOffering_SingleProvider() {
+        VmsClient client = connectVmsClient(mClientCallback1);
+        int providerId = client.registerProvider(PROVIDER_DESC1);
+
+        client.setProviderOfferings(providerId, asSet(
+                new VmsLayerDependency(LAYER1)
+        ));
+        client.setProviderOfferings(providerId, asSet(
+                new VmsLayerDependency(LAYER2)
+        ));
+
+        awaitTaskCompletion();
+        VmsAvailableLayers expectedLayers = new VmsAvailableLayers(2, asSet(
+                new VmsAssociatedLayer(LAYER2, asSet(providerId)))
+        );
+        verifyLayerAvailability(mClientCallback1, expectedLayers);
+    }
+
+    @Test
+    public void testSetProviderOfferings_OverwriteOffering_MultipleProviders() {
+        VmsClient client = connectVmsClient(mClientCallback1);
+        int providerId = client.registerProvider(PROVIDER_DESC1);
+        int providerId2 = client.registerProvider(PROVIDER_DESC2);
+
+        client.setProviderOfferings(providerId, asSet(
+                new VmsLayerDependency(LAYER1)
+        ));
+        client.setProviderOfferings(providerId2, asSet(
+                new VmsLayerDependency(LAYER1)
+        ));
+        client.setProviderOfferings(providerId2, asSet(
+                new VmsLayerDependency(LAYER2)
+        ));
+
+        awaitTaskCompletion();
+        VmsAvailableLayers expectedLayers = new VmsAvailableLayers(3, asSet(
+                new VmsAssociatedLayer(LAYER1, asSet(providerId)),
+                new VmsAssociatedLayer(LAYER2, asSet(providerId2)))
+        );
+        verifyLayerAvailability(mClientCallback1, expectedLayers);
+    }
+
+    @Test
+    public void testSetProviderOfferings_OverwriteOffering_MultipleClients() {
+        VmsClient client = connectVmsClient(mClientCallback1);
+        int providerId = client.registerProvider(PROVIDER_DESC1);
+
+        VmsClient client2 = connectVmsClient(mClientCallback2);
+        int providerId2 = client2.registerProvider(PROVIDER_DESC2);
+
+        client.setProviderOfferings(providerId, asSet(
+                new VmsLayerDependency(LAYER1)
+        ));
+        client2.setProviderOfferings(providerId2, asSet(
+                new VmsLayerDependency(LAYER1)
+        ));
+        client2.setProviderOfferings(providerId2, asSet(
+                new VmsLayerDependency(LAYER2)
+        ));
+
+        awaitTaskCompletion();
+        VmsAvailableLayers expectedLayers = new VmsAvailableLayers(3, asSet(
+                new VmsAssociatedLayer(LAYER1, asSet(providerId)),
+                new VmsAssociatedLayer(LAYER2, asSet(providerId2)))
+        );
+        verifyLayerAvailability(mClientCallback1, expectedLayers);
+        verifyLayerAvailability(mClientCallback2, expectedLayers);
+    }
+
+    @Test
+    public void testSetProviderOfferings_OverwriteOffering_MultipleClients_SingleProvider() {
+        VmsClient client = connectVmsClient(mClientCallback1);
+        int providerId = client.registerProvider(PROVIDER_DESC1);
+
+        VmsClient client2 = connectVmsClient(mClientCallback2);
+        client2.registerProvider(PROVIDER_DESC1);
+
+        client.setProviderOfferings(providerId, asSet(
+                new VmsLayerDependency(LAYER1)
+        ));
+        client2.setProviderOfferings(providerId, asSet(
+                new VmsLayerDependency(LAYER1)
+        ));
+        client2.setProviderOfferings(providerId, asSet(
+                new VmsLayerDependency(LAYER2)
+        ));
+
+        awaitTaskCompletion();
+        VmsAvailableLayers expectedLayers = new VmsAvailableLayers(2, asSet(
+                new VmsAssociatedLayer(LAYER1, asSet(providerId)),
+                new VmsAssociatedLayer(LAYER2, asSet(providerId)))
+        );
+        verifyLayerAvailability(mClientCallback1, expectedLayers);
+        verifyLayerAvailability(mClientCallback2, expectedLayers);
+    }
+
+    @Test
+    public void testSetProviderOfferings_RemoveOfferings_SingleProvider() {
+        VmsClient client = connectVmsClient(mClientCallback1);
+        int providerId = client.registerProvider(PROVIDER_DESC1);
+
+        client.setProviderOfferings(providerId, asSet(
+                new VmsLayerDependency(LAYER1)
+        ));
+        client.setProviderOfferings(providerId, emptySet());
+
+        awaitTaskCompletion();
+        VmsAvailableLayers expectedLayers = new VmsAvailableLayers(2, emptySet());
+        verifyLayerAvailability(mClientCallback1, expectedLayers);
+    }
+
+    @Test
+    public void testSetProviderOfferings_RemoveOfferings_MultipleProviders() {
+        VmsClient client = connectVmsClient(mClientCallback1);
+        int providerId = client.registerProvider(PROVIDER_DESC1);
+        int providerId2 = client.registerProvider(PROVIDER_DESC2);
+
+        client.setProviderOfferings(providerId, asSet(
+                new VmsLayerDependency(LAYER1)
+        ));
+        client.setProviderOfferings(providerId2, asSet(
+                new VmsLayerDependency(LAYER1)
+        ));
+        client.setProviderOfferings(providerId2, emptySet());
+
+        awaitTaskCompletion();
+        VmsAvailableLayers expectedLayers = new VmsAvailableLayers(3, asSet(
+                new VmsAssociatedLayer(LAYER1, asSet(providerId)))
+        );
+        verifyLayerAvailability(mClientCallback1, expectedLayers);
+    }
+
+    @Test
+    public void testSetProviderOfferings_RemoveOfferings_MultipleClients() {
+        VmsClient client = connectVmsClient(mClientCallback1);
+        int providerId = client.registerProvider(PROVIDER_DESC1);
+
+        VmsClient client2 = connectVmsClient(mClientCallback2);
+        int providerId2 = client2.registerProvider(PROVIDER_DESC2);
+
+        client.setProviderOfferings(providerId, asSet(
+                new VmsLayerDependency(LAYER1)
+        ));
+        client2.setProviderOfferings(providerId2, asSet(
+                new VmsLayerDependency(LAYER1)
+        ));
+        client2.setProviderOfferings(providerId2, emptySet());
+
+        awaitTaskCompletion();
+        VmsAvailableLayers expectedLayers = new VmsAvailableLayers(3, asSet(
+                new VmsAssociatedLayer(LAYER1, asSet(providerId)))
+        );
+        verifyLayerAvailability(mClientCallback1, expectedLayers);
+        verifyLayerAvailability(mClientCallback2, expectedLayers);
+    }
+
+    @Test
+    public void testSetProviderOfferings_RemoveOfferings_MultipleClients_SingleProvider() {
+        VmsClient client = connectVmsClient(mClientCallback1);
+        int providerId = client.registerProvider(PROVIDER_DESC1);
+
+        VmsClient client2 = connectVmsClient(mClientCallback2);
+        client2.registerProvider(PROVIDER_DESC1);
+
+        client.setProviderOfferings(providerId, asSet(
+                new VmsLayerDependency(LAYER1)
+        ));
+        client2.setProviderOfferings(providerId, asSet(
+                new VmsLayerDependency(LAYER1)
+        ));
+        client2.setProviderOfferings(providerId, emptySet());
+
+        awaitTaskCompletion();
+        VmsAvailableLayers expectedLayers = new VmsAvailableLayers(2, asSet(
+                new VmsAssociatedLayer(LAYER1, asSet(providerId)))
+        );
+        verifyLayerAvailability(mClientCallback1, expectedLayers);
+        verifyLayerAvailability(mClientCallback2, expectedLayers);
+    }
+
+    @Test
+    public void testSetProviderOfferings_RemoveOfferings_OnUnregister_SingleProvider() {
+        VmsClient client = connectVmsClient(mClientCallback1);
+        int providerId = client.registerProvider(PROVIDER_DESC1);
+
+        // Register second client to verify layer availability after first client disconnects
+        connectVmsClient(mClientCallback2);
+
+        client.setProviderOfferings(providerId, asSet(
+                new VmsLayerDependency(LAYER1)
+        ));
+        mClientManager.unregisterVmsClientCallback(mClientCallback1);
+
+        awaitTaskCompletion();
+        VmsAvailableLayers expectedLayers1 = new VmsAvailableLayers(1, asSet(
+                new VmsAssociatedLayer(LAYER1, asSet(providerId)))
+        );
+        VmsAvailableLayers expectedLayers2 = new VmsAvailableLayers(2, emptySet());
+        verifyLayerAvailability(mClientCallback1, expectedLayers1);
+        verifyLayerAvailability(mClientCallback2, expectedLayers2);
+    }
+
+    @Test
+    public void testSetProviderOfferings_RemoveOfferings_OnUnregister_MultipleProviders() {
+        VmsClient client = connectVmsClient(mClientCallback1);
+        int providerId = client.registerProvider(PROVIDER_DESC1);
+        int providerId2 = client.registerProvider(PROVIDER_DESC2);
+
+        // Register second client to verify layer availability after first client disconnects
+        connectVmsClient(mClientCallback2);
+
+        client.setProviderOfferings(providerId, asSet(
+                new VmsLayerDependency(LAYER1)
+        ));
+        client.setProviderOfferings(providerId2, asSet(
+                new VmsLayerDependency(LAYER1)
+        ));
+        mClientManager.unregisterVmsClientCallback(mClientCallback1);
+
+        awaitTaskCompletion();
+        VmsAvailableLayers expectedLayers1 = new VmsAvailableLayers(2, asSet(
+                new VmsAssociatedLayer(LAYER1, asSet(providerId, providerId2)))
+        );
+        VmsAvailableLayers expectedLayers2 = new VmsAvailableLayers(3, emptySet());
+        verifyLayerAvailability(mClientCallback1, expectedLayers1);
+        verifyLayerAvailability(mClientCallback2, expectedLayers2);
+    }
+
+    @Test
+    public void testSetProviderOfferings_RemoveOfferings_OnUnregister_MultipleClients() {
+        VmsClient client = connectVmsClient(mClientCallback1);
+        int providerId = client.registerProvider(PROVIDER_DESC1);
+
+        VmsClient client2 = connectVmsClient(mClientCallback2);
+        int providerId2 = client2.registerProvider(PROVIDER_DESC2);
+
+        client.setProviderOfferings(providerId, asSet(
+                new VmsLayerDependency(LAYER1)
+        ));
+        client2.setProviderOfferings(providerId2, asSet(
+                new VmsLayerDependency(LAYER1)
+        ));
+        mClientManager.unregisterVmsClientCallback(mClientCallback1);
+
+        awaitTaskCompletion();
+        VmsAvailableLayers expectedLayers1 = new VmsAvailableLayers(2, asSet(
+                new VmsAssociatedLayer(LAYER1, asSet(providerId, providerId2)))
+        );
+        VmsAvailableLayers expectedLayers2 = new VmsAvailableLayers(3, asSet(
+                new VmsAssociatedLayer(LAYER1, asSet(providerId2)))
+        );
+        verifyLayerAvailability(mClientCallback1, expectedLayers1);
+        verifyLayerAvailability(mClientCallback2, expectedLayers2);
+    }
+
+    @Test
+    public void testSetProviderOfferings_RemoveOfferings_OnUnregister_MultipleClients_SingleProvider() {
+        VmsClient client = connectVmsClient(mClientCallback1);
+        int providerId = client.registerProvider(PROVIDER_DESC1);
+
+        VmsClient client2 = connectVmsClient(mClientCallback2);
+        client2.registerProvider(PROVIDER_DESC1);
+
+        client.setProviderOfferings(providerId, asSet(
+                new VmsLayerDependency(LAYER1)
+        ));
+        client2.setProviderOfferings(providerId, asSet(
+                new VmsLayerDependency(LAYER1)
+        ));
+        mClientManager.unregisterVmsClientCallback(mClientCallback1);
+
+        awaitTaskCompletion();
+        VmsAvailableLayers expectedLayers1 = new VmsAvailableLayers(1, asSet(
+                new VmsAssociatedLayer(LAYER1, asSet(providerId)))
+        );
+        VmsAvailableLayers expectedLayers2 = new VmsAvailableLayers(1, asSet(
+                new VmsAssociatedLayer(LAYER1, asSet(providerId)))
+        );
+        verifyLayerAvailability(mClientCallback1, expectedLayers1);
+        verifyLayerAvailability(mClientCallback2, expectedLayers2);
+    }
+
+    @Test
+    public void testSetProviderOfferings_DependencyMet_SingleProvider() {
+        VmsClient client = connectVmsClient(mClientCallback1);
+        int providerId = client.registerProvider(PROVIDER_DESC1);
+
+        client.setProviderOfferings(providerId, asSet(
+                new VmsLayerDependency(LAYER1, asSet(LAYER2)),
+                new VmsLayerDependency(LAYER2)
+        ));
+
+        awaitTaskCompletion();
+        VmsAvailableLayers expectedLayers = new VmsAvailableLayers(1, asSet(
+                new VmsAssociatedLayer(LAYER1, asSet(providerId)),
+                new VmsAssociatedLayer(LAYER2, asSet(providerId)))
+        );
+        verifyLayerAvailability(mClientCallback1, expectedLayers);
+    }
+
+    @Test
+    public void testSetProviderOfferings_DependencyMet_MultipleProviders() {
+        VmsClient client = connectVmsClient(mClientCallback1);
+        int providerId = client.registerProvider(PROVIDER_DESC1);
+        int providerId2 = client.registerProvider(PROVIDER_DESC2);
+
+        client.setProviderOfferings(providerId, asSet(
+                new VmsLayerDependency(LAYER1, asSet(LAYER2))
+        ));
+        client.setProviderOfferings(providerId2, asSet(
+                new VmsLayerDependency(LAYER2)
+        ));
+
+        awaitTaskCompletion();
+        VmsAvailableLayers expectedLayers = new VmsAvailableLayers(2, asSet(
+                new VmsAssociatedLayer(LAYER1, asSet(providerId)),
+                new VmsAssociatedLayer(LAYER2, asSet(providerId2)))
+        );
+        verifyLayerAvailability(mClientCallback1, expectedLayers);
+    }
+
+    @Test
+    public void testSetProviderOfferings_DependencyMet_MultipleClients() {
+        VmsClient client = connectVmsClient(mClientCallback1);
+        int providerId = client.registerProvider(PROVIDER_DESC1);
+
+        VmsClient client2 = connectVmsClient(mClientCallback2);
+        int providerId2 = client2.registerProvider(PROVIDER_DESC2);
+
+        client.setProviderOfferings(providerId, asSet(
+                new VmsLayerDependency(LAYER1, asSet(LAYER2))
+        ));
+        client2.setProviderOfferings(providerId2, asSet(
+                new VmsLayerDependency(LAYER2)
+        ));
+
+        awaitTaskCompletion();
+        VmsAvailableLayers expectedLayers = new VmsAvailableLayers(2, asSet(
+                new VmsAssociatedLayer(LAYER1, asSet(providerId)),
+                new VmsAssociatedLayer(LAYER2, asSet(providerId2)))
+        );
+        verifyLayerAvailability(mClientCallback1, expectedLayers);
+        verifyLayerAvailability(mClientCallback2, expectedLayers);
+    }
+
+    @Test
+    public void testSetProviderOfferings_DependencyMet_MultipleClients_SingleProvider() {
+        VmsClient client = connectVmsClient(mClientCallback1);
+        int providerId = client.registerProvider(PROVIDER_DESC1);
+
+        VmsClient client2 = connectVmsClient(mClientCallback2);
+        client2.registerProvider(PROVIDER_DESC1);
+
+        client.setProviderOfferings(providerId, asSet(
+                new VmsLayerDependency(LAYER1, asSet(LAYER2))
+        ));
+        client2.setProviderOfferings(providerId, asSet(
+                new VmsLayerDependency(LAYER2)
+        ));
+
+        awaitTaskCompletion();
+        VmsAvailableLayers expectedLayers = new VmsAvailableLayers(2, asSet(
+                new VmsAssociatedLayer(LAYER1, asSet(providerId)),
+                new VmsAssociatedLayer(LAYER2, asSet(providerId)))
+        );
+        verifyLayerAvailability(mClientCallback1, expectedLayers);
+        verifyLayerAvailability(mClientCallback2, expectedLayers);
+    }
+
+    @Test
+    public void testSetProviderOfferings_DependencyMet_MultipleDependencies_SingleProvider() {
+        VmsClient client = connectVmsClient(mClientCallback1);
+        int providerId = client.registerProvider(PROVIDER_DESC1);
+
+        client.setProviderOfferings(providerId, asSet(
+                new VmsLayerDependency(LAYER1, asSet(LAYER2, LAYER3)),
+                new VmsLayerDependency(LAYER2),
+                new VmsLayerDependency(LAYER3)
+        ));
+
+        awaitTaskCompletion();
+        VmsAvailableLayers expectedLayers = new VmsAvailableLayers(1, asSet(
+                new VmsAssociatedLayer(LAYER1, asSet(providerId)),
+                new VmsAssociatedLayer(LAYER2, asSet(providerId)),
+                new VmsAssociatedLayer(LAYER3, asSet(providerId)))
+        );
+        verifyLayerAvailability(mClientCallback1, expectedLayers);
+    }
+
+    @Test
+    public void testSetProviderOfferings_DependencyMet_MultipleDependencies_MultipleProviders() {
+        VmsClient client = connectVmsClient(mClientCallback1);
+        int providerId = client.registerProvider(PROVIDER_DESC1);
+        int providerId2 = client.registerProvider(PROVIDER_DESC2);
+
+        client.setProviderOfferings(providerId, asSet(
+                new VmsLayerDependency(LAYER1, asSet(LAYER2, LAYER3)),
+                new VmsLayerDependency(LAYER2)
+        ));
+        client.setProviderOfferings(providerId2, asSet(
+                new VmsLayerDependency(LAYER3)
+        ));
+
+        awaitTaskCompletion();
+        VmsAvailableLayers expectedLayers = new VmsAvailableLayers(2, asSet(
+                new VmsAssociatedLayer(LAYER1, asSet(providerId)),
+                new VmsAssociatedLayer(LAYER2, asSet(providerId)),
+                new VmsAssociatedLayer(LAYER3, asSet(providerId2)))
+        );
+        verifyLayerAvailability(mClientCallback1, expectedLayers);
+    }
+
+    @Test
+    public void testSetProviderOfferings_DependencyMet_MultipleDependencies_MultipleClients() {
+        VmsClient client = connectVmsClient(mClientCallback1);
+        int providerId = client.registerProvider(PROVIDER_DESC1);
+
+        VmsClient client2 = connectVmsClient(mClientCallback2);
+        int providerId2 = client2.registerProvider(PROVIDER_DESC2);
+
+        client.setProviderOfferings(providerId, asSet(
+                new VmsLayerDependency(LAYER1, asSet(LAYER2, LAYER3)),
+                new VmsLayerDependency(LAYER2)
+        ));
+        client2.setProviderOfferings(providerId2, asSet(
+                new VmsLayerDependency(LAYER3)
+        ));
+
+        awaitTaskCompletion();
+        VmsAvailableLayers expectedLayers = new VmsAvailableLayers(2, asSet(
+                new VmsAssociatedLayer(LAYER1, asSet(providerId)),
+                new VmsAssociatedLayer(LAYER2, asSet(providerId)),
+                new VmsAssociatedLayer(LAYER3, asSet(providerId2)))
+        );
+        verifyLayerAvailability(mClientCallback1, expectedLayers);
+        verifyLayerAvailability(mClientCallback2, expectedLayers);
+    }
+
+    @Test
+    public void testSetProviderOfferings_DependencyMet_MultipleDependencies_MultipleClients_SingleProvider() {
+        VmsClient client = connectVmsClient(mClientCallback1);
+        int providerId = client.registerProvider(PROVIDER_DESC1);
+
+        VmsClient client2 = connectVmsClient(mClientCallback2);
+        client2.registerProvider(PROVIDER_DESC1);
+
+        client.setProviderOfferings(providerId, asSet(
+                new VmsLayerDependency(LAYER1, asSet(LAYER2, LAYER3)),
+                new VmsLayerDependency(LAYER2)
+        ));
+        client2.setProviderOfferings(providerId, asSet(
+                new VmsLayerDependency(LAYER3)
+        ));
+
+        awaitTaskCompletion();
+        VmsAvailableLayers expectedLayers = new VmsAvailableLayers(2, asSet(
+                new VmsAssociatedLayer(LAYER1, asSet(providerId)),
+                new VmsAssociatedLayer(LAYER2, asSet(providerId)),
+                new VmsAssociatedLayer(LAYER3, asSet(providerId)))
+        );
+        verifyLayerAvailability(mClientCallback1, expectedLayers);
+        verifyLayerAvailability(mClientCallback2, expectedLayers);
+    }
+
+    @Test
+    public void testSetProviderOfferings_DependencyMet_ChainedDependencies_SingleProvider() {
+        VmsClient client = connectVmsClient(mClientCallback1);
+        int providerId = client.registerProvider(PROVIDER_DESC1);
+
+        client.setProviderOfferings(providerId, asSet(
+                new VmsLayerDependency(LAYER1, asSet(LAYER2)),
+                new VmsLayerDependency(LAYER2, asSet(LAYER3)),
+                new VmsLayerDependency(LAYER3)
+        ));
+
+        awaitTaskCompletion();
+        VmsAvailableLayers expectedLayers = new VmsAvailableLayers(1, asSet(
+                new VmsAssociatedLayer(LAYER1, asSet(providerId)),
+                new VmsAssociatedLayer(LAYER2, asSet(providerId)),
+                new VmsAssociatedLayer(LAYER3, asSet(providerId)))
+        );
+        verifyLayerAvailability(mClientCallback1, expectedLayers);
+    }
+
+    @Test
+    public void testSetProviderOfferings_DependencyMet_ChainedDependencies_MultipleProviders() {
+        VmsClient client = connectVmsClient(mClientCallback1);
+        int providerId = client.registerProvider(PROVIDER_DESC1);
+        int providerId2 = client.registerProvider(PROVIDER_DESC2);
+
+        client.setProviderOfferings(providerId, asSet(
+                new VmsLayerDependency(LAYER1, asSet(LAYER2)),
+                new VmsLayerDependency(LAYER3)
+        ));
+        client.setProviderOfferings(providerId2, asSet(
+                new VmsLayerDependency(LAYER2, asSet(LAYER3))
+        ));
+
+        awaitTaskCompletion();
+        VmsAvailableLayers expectedLayers = new VmsAvailableLayers(2, asSet(
+                new VmsAssociatedLayer(LAYER1, asSet(providerId)),
+                new VmsAssociatedLayer(LAYER2, asSet(providerId2)),
+                new VmsAssociatedLayer(LAYER3, asSet(providerId)))
+        );
+        verifyLayerAvailability(mClientCallback1, expectedLayers);
+    }
+
+    @Test
+    public void testSetProviderOfferings_DependencyMet_ChainedDependencies_MultipleClients() {
+        VmsClient client = connectVmsClient(mClientCallback1);
+        int providerId = client.registerProvider(PROVIDER_DESC1);
+
+        VmsClient client2 = connectVmsClient(mClientCallback2);
+        int providerId2 = client2.registerProvider(PROVIDER_DESC2);
+
+        client.setProviderOfferings(providerId, asSet(
+                new VmsLayerDependency(LAYER1, asSet(LAYER2)),
+                new VmsLayerDependency(LAYER3)
+        ));
+        client2.setProviderOfferings(providerId2, asSet(
+                new VmsLayerDependency(LAYER2, asSet(LAYER3))
+        ));
+
+        awaitTaskCompletion();
+        VmsAvailableLayers expectedLayers = new VmsAvailableLayers(2, asSet(
+                new VmsAssociatedLayer(LAYER1, asSet(providerId)),
+                new VmsAssociatedLayer(LAYER2, asSet(providerId2)),
+                new VmsAssociatedLayer(LAYER3, asSet(providerId)))
+        );
+        verifyLayerAvailability(mClientCallback1, expectedLayers);
+        verifyLayerAvailability(mClientCallback2, expectedLayers);
+    }
+
+    @Test
+    public void testSetProviderOfferings_DependencyMet_ChainedDependencies_MultipleClients_SingleProvider() {
+        VmsClient client = connectVmsClient(mClientCallback1);
+        int providerId = client.registerProvider(PROVIDER_DESC1);
+
+        VmsClient client2 = connectVmsClient(mClientCallback2);
+        client2.registerProvider(PROVIDER_DESC1);
+
+        client.setProviderOfferings(providerId, asSet(
+                new VmsLayerDependency(LAYER1, asSet(LAYER2)),
+                new VmsLayerDependency(LAYER3)
+        ));
+        client2.setProviderOfferings(providerId, asSet(
+                new VmsLayerDependency(LAYER2, asSet(LAYER3))
+        ));
+
+        awaitTaskCompletion();
+        VmsAvailableLayers expectedLayers = new VmsAvailableLayers(2, asSet(
+                new VmsAssociatedLayer(LAYER1, asSet(providerId)),
+                new VmsAssociatedLayer(LAYER2, asSet(providerId)),
+                new VmsAssociatedLayer(LAYER3, asSet(providerId)))
+        );
+        verifyLayerAvailability(mClientCallback1, expectedLayers);
+    }
+
+    @Test
+    public void testSetProviderOfferings_DependencyCircular_SingleProvider() {
+        VmsClient client = connectVmsClient(mClientCallback1);
+        int providerId = client.registerProvider(PROVIDER_DESC1);
+
+        client.setProviderOfferings(providerId, asSet(
+                new VmsLayerDependency(LAYER1, asSet(LAYER2)),
+                new VmsLayerDependency(LAYER2, asSet(LAYER1))
+        ));
+
+        awaitTaskCompletion();
+        VmsAvailableLayers expectedLayers = new VmsAvailableLayers(1, emptySet());
+        verifyLayerAvailability(mClientCallback1, expectedLayers);
+    }
+
+    @Test
+    public void testSetProviderOfferings_DependencyCircular_MultipleProviders() {
+        VmsClient client = connectVmsClient(mClientCallback1);
+        int providerId = client.registerProvider(PROVIDER_DESC1);
+        int providerId2 = client.registerProvider(PROVIDER_DESC2);
+
+        client.setProviderOfferings(providerId, asSet(
+                new VmsLayerDependency(LAYER1, asSet(LAYER2))
+        ));
+        client.setProviderOfferings(providerId2, asSet(
+                new VmsLayerDependency(LAYER2, asSet(LAYER1))
+        ));
+
+        awaitTaskCompletion();
+        VmsAvailableLayers expectedLayers = new VmsAvailableLayers(2, emptySet());
+        verifyLayerAvailability(mClientCallback1, expectedLayers);
+    }
+
+    @Test
+    public void testSetProviderOfferings_DependencyCircular_MultipleClients() {
+        VmsClient client = connectVmsClient(mClientCallback1);
+        int providerId = client.registerProvider(PROVIDER_DESC1);
+
+        VmsClient client2 = connectVmsClient(mClientCallback2);
+        int providerId2 = client2.registerProvider(PROVIDER_DESC2);
+
+        client.setProviderOfferings(providerId, asSet(
+                new VmsLayerDependency(LAYER1, asSet(LAYER2))
+        ));
+        client2.setProviderOfferings(providerId2, asSet(
+                new VmsLayerDependency(LAYER2, asSet(LAYER1))
+        ));
+
+        awaitTaskCompletion();
+        VmsAvailableLayers expectedLayers = new VmsAvailableLayers(2, emptySet());
+        verifyLayerAvailability(mClientCallback1, expectedLayers);
+        verifyLayerAvailability(mClientCallback2, expectedLayers);
+    }
+
+    @Test
+    public void testSetProviderOfferings_DependencyCircular_MultipleClients_SingleProvider() {
+        VmsClient client = connectVmsClient(mClientCallback1);
+        int providerId = client.registerProvider(PROVIDER_DESC1);
+
+        VmsClient client2 = connectVmsClient(mClientCallback2);
+        client2.registerProvider(PROVIDER_DESC1);
+
+        client.setProviderOfferings(providerId, asSet(
+                new VmsLayerDependency(LAYER1, asSet(LAYER2))
+        ));
+        client2.setProviderOfferings(providerId, asSet(
+                new VmsLayerDependency(LAYER2, asSet(LAYER1))
+        ));
+
+        awaitTaskCompletion();
+        VmsAvailableLayers expectedLayers = new VmsAvailableLayers(2, emptySet());
+        verifyLayerAvailability(mClientCallback1, expectedLayers);
+        verifyLayerAvailability(mClientCallback2, expectedLayers);
+    }
+
+    @Test
+    public void testSetProviderOfferings_DependencyCircular_MultipleDependencies_SingleProvider() {
+        VmsClient client = connectVmsClient(mClientCallback1);
+        int providerId = client.registerProvider(PROVIDER_DESC1);
+
+        client.setProviderOfferings(providerId, asSet(
+                new VmsLayerDependency(LAYER1, asSet(LAYER2, LAYER3)),
+                new VmsLayerDependency(LAYER2),
+                new VmsLayerDependency(LAYER3, asSet(LAYER1))
+        ));
+
+        awaitTaskCompletion();
+        VmsAvailableLayers expectedLayers = new VmsAvailableLayers(1, asSet(
+                new VmsAssociatedLayer(LAYER2, asSet(providerId)))
+        );
+        verifyLayerAvailability(mClientCallback1, expectedLayers);
+    }
+
+    @Test
+    public void testSetProviderOfferings_DependencyCircular_MultipleDependencies_MultipleProviders() {
+        VmsClient client = connectVmsClient(mClientCallback1);
+        int providerId = client.registerProvider(PROVIDER_DESC1);
+        int providerId2 = client.registerProvider(PROVIDER_DESC2);
+
+        client.setProviderOfferings(providerId, asSet(
+                new VmsLayerDependency(LAYER1, asSet(LAYER2, LAYER3)),
+                new VmsLayerDependency(LAYER2)
+        ));
+        client.setProviderOfferings(providerId2, asSet(
+                new VmsLayerDependency(LAYER3, asSet(LAYER1))
+        ));
+
+        awaitTaskCompletion();
+        VmsAvailableLayers expectedLayers = new VmsAvailableLayers(2, asSet(
+                new VmsAssociatedLayer(LAYER2, asSet(providerId)))
+        );
+        verifyLayerAvailability(mClientCallback1, expectedLayers);
+    }
+
+    @Test
+    public void testSetProviderOfferings_DependencyCircular_MultipleDependencies_MultipleClients() {
+        VmsClient client = connectVmsClient(mClientCallback1);
+        int providerId = client.registerProvider(PROVIDER_DESC1);
+
+        VmsClient client2 = connectVmsClient(mClientCallback2);
+        int providerId2 = client2.registerProvider(PROVIDER_DESC2);
+
+        client.setProviderOfferings(providerId, asSet(
+                new VmsLayerDependency(LAYER1, asSet(LAYER2, LAYER3)),
+                new VmsLayerDependency(LAYER2)
+        ));
+        client2.setProviderOfferings(providerId2, asSet(
+                new VmsLayerDependency(LAYER3, asSet(LAYER1))
+        ));
+
+        awaitTaskCompletion();
+        VmsAvailableLayers expectedLayers = new VmsAvailableLayers(2, asSet(
+                new VmsAssociatedLayer(LAYER2, asSet(providerId)))
+        );
+        verifyLayerAvailability(mClientCallback1, expectedLayers);
+        verifyLayerAvailability(mClientCallback2, expectedLayers);
+    }
+
+    @Test
+    public void testSetProviderOfferings_DependencyCircular_MultipleDependencies_MultipleClients_SingleProvider() {
+        VmsClient client = connectVmsClient(mClientCallback1);
+        int providerId = client.registerProvider(PROVIDER_DESC1);
+
+        VmsClient client2 = connectVmsClient(mClientCallback2);
+        client2.registerProvider(PROVIDER_DESC1);
+
+        client.setProviderOfferings(providerId, asSet(
+                new VmsLayerDependency(LAYER1, asSet(LAYER2, LAYER3)),
+                new VmsLayerDependency(LAYER2)
+        ));
+        client2.setProviderOfferings(providerId, asSet(
+                new VmsLayerDependency(LAYER3, asSet(LAYER1))
+        ));
+
+        awaitTaskCompletion();
+        VmsAvailableLayers expectedLayers = new VmsAvailableLayers(2, asSet(
+                new VmsAssociatedLayer(LAYER2, asSet(providerId)))
+        );
+        verifyLayerAvailability(mClientCallback1, expectedLayers);
+        verifyLayerAvailability(mClientCallback2, expectedLayers);
+    }
+
+    @Test
+    public void testSetProviderOfferings_DependencyCircular_ChainedDependencies_SingleProvider() {
+        VmsClient client = connectVmsClient(mClientCallback1);
+        int providerId = client.registerProvider(PROVIDER_DESC1);
+
+        client.setProviderOfferings(providerId, asSet(
+                new VmsLayerDependency(LAYER1, asSet(LAYER2)),
+                new VmsLayerDependency(LAYER2, asSet(LAYER3)),
+                new VmsLayerDependency(LAYER3, asSet(LAYER1))
+        ));
+
+        awaitTaskCompletion();
+        VmsAvailableLayers expectedLayers = new VmsAvailableLayers(1, emptySet());
+        verifyLayerAvailability(mClientCallback1, expectedLayers);
+    }
+
+    @Test
+    public void testSetProviderOfferings_DependencyCircular_ChainedDependencies_MultipleProviders() {
+        VmsClient client = connectVmsClient(mClientCallback1);
+        int providerId = client.registerProvider(PROVIDER_DESC1);
+        int providerId2 = client.registerProvider(PROVIDER_DESC2);
+
+        client.setProviderOfferings(providerId, asSet(
+                new VmsLayerDependency(LAYER1, asSet(LAYER2)),
+                new VmsLayerDependency(LAYER3, asSet(LAYER1))
+        ));
+        client.setProviderOfferings(providerId2, asSet(
+                new VmsLayerDependency(LAYER2, asSet(LAYER3))
+        ));
+
+        awaitTaskCompletion();
+        VmsAvailableLayers expectedLayers = new VmsAvailableLayers(2, emptySet());
+        verifyLayerAvailability(mClientCallback1, expectedLayers);
+    }
+
+    @Test
+    public void testSetProviderOfferings_DependencyCircular_ChainedDependencies_MultipleClients() {
+        VmsClient client = connectVmsClient(mClientCallback1);
+        int providerId = client.registerProvider(PROVIDER_DESC1);
+
+        VmsClient client2 = connectVmsClient(mClientCallback2);
+        int providerId2 = client2.registerProvider(PROVIDER_DESC2);
+
+        client.setProviderOfferings(providerId, asSet(
+                new VmsLayerDependency(LAYER1, asSet(LAYER2)),
+                new VmsLayerDependency(LAYER3, asSet(LAYER1))
+        ));
+        client2.setProviderOfferings(providerId2, asSet(
+                new VmsLayerDependency(LAYER2, asSet(LAYER3))
+        ));
+
+        awaitTaskCompletion();
+        VmsAvailableLayers expectedLayers = new VmsAvailableLayers(2, emptySet());
+        verifyLayerAvailability(mClientCallback1, expectedLayers);
+        verifyLayerAvailability(mClientCallback2, expectedLayers);
+    }
+
+    @Test
+    public void testSetProviderOfferings_DependencyCircular_ChainedDependencies_MultipleClients_SingleProvider() {
+        VmsClient client = connectVmsClient(mClientCallback1);
+        int providerId = client.registerProvider(PROVIDER_DESC1);
+
+        VmsClient client2 = connectVmsClient(mClientCallback2);
+        client2.registerProvider(PROVIDER_DESC1);
+
+        client.setProviderOfferings(providerId, asSet(
+                new VmsLayerDependency(LAYER1, asSet(LAYER2)),
+                new VmsLayerDependency(LAYER3, asSet(LAYER1))
+        ));
+        client2.setProviderOfferings(providerId, asSet(
+                new VmsLayerDependency(LAYER2, asSet(LAYER3))
+        ));
+
+        awaitTaskCompletion();
+        VmsAvailableLayers expectedLayers = new VmsAvailableLayers(2, emptySet());
+        verifyLayerAvailability(mClientCallback1, expectedLayers);
+        verifyLayerAvailability(mClientCallback2, expectedLayers);
+    }
+
+    @Test
+    public void testSetProviderOfferings_DependencyUnmet_SingleProvider() {
+        VmsClient client = connectVmsClient(mClientCallback1);
+        int providerId = client.registerProvider(PROVIDER_DESC1);
+
+        client.setProviderOfferings(providerId, asSet(
+                new VmsLayerDependency(LAYER1, asSet(LAYER2))
+        ));
+
+        awaitTaskCompletion();
+        VmsAvailableLayers expectedLayers = new VmsAvailableLayers(1, emptySet());
+        verifyLayerAvailability(mClientCallback1, expectedLayers);
+    }
+
+    @Test
+    public void testSetProviderOfferings_DependencyUnmet_MultipleDependencies_SingleProvider() {
+        VmsClient client = connectVmsClient(mClientCallback1);
+        int providerId = client.registerProvider(PROVIDER_DESC1);
+
+        client.setProviderOfferings(providerId, asSet(
+                new VmsLayerDependency(LAYER1, asSet(LAYER2, LAYER3)),
+                new VmsLayerDependency(LAYER2)
+        ));
+
+        awaitTaskCompletion();
+        VmsAvailableLayers expectedLayers = new VmsAvailableLayers(1, asSet(
+                new VmsAssociatedLayer(LAYER2, asSet(providerId)))
+        );
+        verifyLayerAvailability(mClientCallback1, expectedLayers);
+    }
+
+    @Test
+    public void testSetProviderOfferings_DependencyUnmet_MultipleDependencies_MultipleProviders() {
+        VmsClient client = connectVmsClient(mClientCallback1);
+        int providerId = client.registerProvider(PROVIDER_DESC1);
+        int providerId2 = client.registerProvider(PROVIDER_DESC2);
+
+        client.setProviderOfferings(providerId, asSet(
+                new VmsLayerDependency(LAYER1, asSet(LAYER2, LAYER3))
+        ));
+        client.setProviderOfferings(providerId2, asSet(
+                new VmsLayerDependency(LAYER3)
+        ));
+
+        awaitTaskCompletion();
+        VmsAvailableLayers expectedLayers = new VmsAvailableLayers(2, asSet(
+                new VmsAssociatedLayer(LAYER3, asSet(providerId2)))
+        );
+        verifyLayerAvailability(mClientCallback1, expectedLayers);
+    }
+
+    @Test
+    public void testSetProviderOfferings_DependencyUnmet_MultipleDependencies_MultipleClients() {
+        VmsClient client = connectVmsClient(mClientCallback1);
+        int providerId = client.registerProvider(PROVIDER_DESC1);
+
+        VmsClient client2 = connectVmsClient(mClientCallback2);
+        int providerId2 = client2.registerProvider(PROVIDER_DESC2);
+
+        client.setProviderOfferings(providerId, asSet(
+                new VmsLayerDependency(LAYER1, asSet(LAYER2, LAYER3))
+        ));
+        client2.setProviderOfferings(providerId2, asSet(
+                new VmsLayerDependency(LAYER3)
+        ));
+
+        awaitTaskCompletion();
+        VmsAvailableLayers expectedLayers = new VmsAvailableLayers(2, asSet(
+                new VmsAssociatedLayer(LAYER3, asSet(providerId2)))
+        );
+        verifyLayerAvailability(mClientCallback1, expectedLayers);
+        verifyLayerAvailability(mClientCallback2, expectedLayers);
+    }
+
+    @Test
+    public void testSetProviderOfferings_DependencyUnmet_MultipleDependencies_MultipleClients_SingleProvider() {
+        VmsClient client = connectVmsClient(mClientCallback1);
+        int providerId = client.registerProvider(PROVIDER_DESC1);
+
+        VmsClient client2 = connectVmsClient(mClientCallback2);
+        client2.registerProvider(PROVIDER_DESC1);
+
+        client.setProviderOfferings(providerId, asSet(
+                new VmsLayerDependency(LAYER1, asSet(LAYER2, LAYER3))
+        ));
+        client2.setProviderOfferings(providerId, asSet(
+                new VmsLayerDependency(LAYER3)
+        ));
+
+        awaitTaskCompletion();
+        VmsAvailableLayers expectedLayers = new VmsAvailableLayers(2, asSet(
+                new VmsAssociatedLayer(LAYER3, asSet(providerId)))
+        );
+        verifyLayerAvailability(mClientCallback1, expectedLayers);
+        verifyLayerAvailability(mClientCallback2, expectedLayers);
+    }
+
+    @Test
+    public void testSetProviderOfferings_DependencyUnmet_ChainedDependencies_SingleProvider() {
+        VmsClient client = connectVmsClient(mClientCallback1);
+        int providerId = client.registerProvider(PROVIDER_DESC1);
+
+        client.setProviderOfferings(providerId, asSet(
+                new VmsLayerDependency(LAYER1, asSet(LAYER2)),
+                new VmsLayerDependency(LAYER2, asSet(LAYER3))
+        ));
+
+        awaitTaskCompletion();
+        VmsAvailableLayers expectedLayers = new VmsAvailableLayers(1, emptySet());
+        verifyLayerAvailability(mClientCallback1, expectedLayers);
+    }
+
+    @Test
+    public void testSetProviderOfferings_DependencyUnmet_ChainedDependencies_MultipleProviders() {
+        VmsClient client = connectVmsClient(mClientCallback1);
+        int providerId = client.registerProvider(PROVIDER_DESC1);
+        int providerId2 = client.registerProvider(PROVIDER_DESC2);
+
+        client.setProviderOfferings(providerId, asSet(
+                new VmsLayerDependency(LAYER1, asSet(LAYER2))
+        ));
+        client.setProviderOfferings(providerId2, asSet(
+                new VmsLayerDependency(LAYER2, asSet(LAYER3))
+        ));
+
+        awaitTaskCompletion();
+        VmsAvailableLayers expectedLayers = new VmsAvailableLayers(2, emptySet());
+        verifyLayerAvailability(mClientCallback1, expectedLayers);
+    }
+
+    @Test
+    public void testSetProviderOfferings_DependencyUnmet_ChainedDependencies_MultipleClients() {
+        VmsClient client = connectVmsClient(mClientCallback1);
+        int providerId = client.registerProvider(PROVIDER_DESC1);
+
+        VmsClient client2 = connectVmsClient(mClientCallback2);
+        int providerId2 = client2.registerProvider(PROVIDER_DESC2);
+
+        client.setProviderOfferings(providerId, asSet(
+                new VmsLayerDependency(LAYER1, asSet(LAYER2))
+        ));
+        client2.setProviderOfferings(providerId2, asSet(
+                new VmsLayerDependency(LAYER2, asSet(LAYER3))
+        ));
+
+        awaitTaskCompletion();
+        VmsAvailableLayers expectedLayers = new VmsAvailableLayers(2, emptySet());
+        verifyLayerAvailability(mClientCallback1, expectedLayers);
+        verifyLayerAvailability(mClientCallback2, expectedLayers);
+    }
+
+    @Test
+    public void testSetProviderOfferings_DependencyUnmet_ChainedDependencies_MultipleClients_SingleProvider() {
+        VmsClient client = connectVmsClient(mClientCallback1);
+        int providerId = client.registerProvider(PROVIDER_DESC1);
+
+        VmsClient client2 = connectVmsClient(mClientCallback2);
+        client2.registerProvider(PROVIDER_DESC1);
+
+        client.setProviderOfferings(providerId, asSet(
+                new VmsLayerDependency(LAYER1, asSet(LAYER2))
+        ));
+        client2.setProviderOfferings(providerId, asSet(
+                new VmsLayerDependency(LAYER2, asSet(LAYER3))
+        ));
+
+        awaitTaskCompletion();
+        VmsAvailableLayers expectedLayers = new VmsAvailableLayers(2, emptySet());
+        verifyLayerAvailability(mClientCallback1, expectedLayers);
+        verifyLayerAvailability(mClientCallback2, expectedLayers);
+    }
+
+    @Test
+    public void testPublishPacket_UnknownOffering() {
+        VmsClient client = connectVmsClient(mClientCallback1);
+        int providerId = client.registerProvider(PROVIDER_DESC1);
+
+        assertThrows(
+                IllegalArgumentException.class,
+                () -> client.publishPacket(providerId, LAYER1, PAYLOAD));
+    }
+
+    @Test
+    public void testPublishPacket_NoSubscribers() {
+        VmsClient client = connectVmsClient(mClientCallback1);
+        int providerId = client.registerProvider(PROVIDER_DESC1);
+        client.setProviderOfferings(providerId, asSet(
+                new VmsLayerDependency(LAYER1)
+        ));
+        connectVmsClient(mClientCallback2);
+
+        client.publishPacket(providerId, LAYER1, PAYLOAD);
+
+        awaitTaskCompletion();
+        verifyNoPacketsReceived(mClientCallback1, providerId, LAYER1);
+        verifyNoPacketsReceived(mClientCallback2, providerId, LAYER1);
+    }
+
+    @Test
+    public void testPublishPacket_MonitorSubscriber_Enabled() {
+        VmsClient client = connectVmsClient(mClientCallback1);
+        int providerId = client.registerProvider(PROVIDER_DESC1);
+        client.setProviderOfferings(providerId, asSet(
+                new VmsLayerDependency(LAYER1)
+        ));
+        connectVmsClient(mClientCallback2);
+
+        client.setMonitoringEnabled(true);
+        client.publishPacket(providerId, LAYER1, PAYLOAD);
+
+        awaitTaskCompletion();
+        verifyPacketReceived(mClientCallback1, providerId, LAYER1, PAYLOAD);
+        verifyNoPacketsReceived(mClientCallback2, providerId, LAYER1);
+    }
+
+    @Test
+    public void testPublishPacket_MonitorSubscriber_EnabledAndDisabled() {
+        VmsClient client = connectVmsClient(mClientCallback1);
+        int providerId = client.registerProvider(PROVIDER_DESC1);
+        client.setProviderOfferings(providerId, asSet(
+                new VmsLayerDependency(LAYER1)
+        ));
+        connectVmsClient(mClientCallback2);
+
+        client.setMonitoringEnabled(true);
+        client.setMonitoringEnabled(false);
+        client.publishPacket(providerId, LAYER1, PAYLOAD);
+
+        awaitTaskCompletion();
+        verifyNoPacketsReceived(mClientCallback1, providerId, LAYER1);
+        verifyNoPacketsReceived(mClientCallback2, providerId, LAYER1);
+    }
+
+    @Test
+    public void testPublishPacket_LayerSubscriber() {
+        VmsClient client = connectVmsClient(mClientCallback1);
+        int providerId = client.registerProvider(PROVIDER_DESC1);
+        client.setProviderOfferings(providerId, asSet(
+                new VmsLayerDependency(LAYER1)
+        ));
+        connectVmsClient(mClientCallback2);
+
+        client.setSubscriptions(asSet(
+                new VmsAssociatedLayer(LAYER1, emptySet())
+        ));
+        client.publishPacket(providerId, LAYER1, PAYLOAD);
+
+        awaitTaskCompletion();
+        verifyPacketReceived(mClientCallback1, providerId, LAYER1, PAYLOAD);
+        verifyNoPacketsReceived(mClientCallback2, providerId, LAYER1);
+    }
+
+    @Test
+    public void testPublishPacket_LayerSubscriber_Unsubscribe() {
+        VmsClient client = connectVmsClient(mClientCallback1);
+        int providerId = client.registerProvider(PROVIDER_DESC1);
+        client.setProviderOfferings(providerId, asSet(
+                new VmsLayerDependency(LAYER1)
+        ));
+        connectVmsClient(mClientCallback2);
+
+        client.setSubscriptions(asSet(
+                new VmsAssociatedLayer(LAYER1, emptySet())
+        ));
+        client.setSubscriptions(emptySet());
+        client.publishPacket(providerId, LAYER1, PAYLOAD);
+
+        awaitTaskCompletion();
+        verifyNoPacketsReceived(mClientCallback1, providerId, LAYER1);
+        verifyNoPacketsReceived(mClientCallback2, providerId, LAYER1);
+    }
+
+    @Test
+    public void testPublishPacket_LayerSubscriber_DifferentLayer() {
+        VmsClient client = connectVmsClient(mClientCallback1);
+        int providerId = client.registerProvider(PROVIDER_DESC1);
+        client.setProviderOfferings(providerId, asSet(
+                new VmsLayerDependency(LAYER1)
+        ));
+        connectVmsClient(mClientCallback2);
+
+        client.setSubscriptions(asSet(
+                new VmsAssociatedLayer(LAYER2, emptySet())
+        ));
+        client.publishPacket(providerId, LAYER1, PAYLOAD);
+
+        awaitTaskCompletion();
+        verifyNoPacketsReceived(mClientCallback1, providerId, LAYER1);
+        verifyNoPacketsReceived(mClientCallback2, providerId, LAYER1);
+    }
+
+    @Test
+    public void testPublishPacket_MultipleLayerSubscribers() {
+        VmsClient client = connectVmsClient(mClientCallback1);
+        int providerId = client.registerProvider(PROVIDER_DESC1);
+        client.setProviderOfferings(providerId, asSet(
+                new VmsLayerDependency(LAYER1)
+        ));
+        VmsClient client2 = connectVmsClient(mClientCallback2);
+
+        client.setSubscriptions(asSet(
+                new VmsAssociatedLayer(LAYER1, emptySet())
+        ));
+        client2.setSubscriptions(asSet(
+                new VmsAssociatedLayer(LAYER1, emptySet())
+        ));
+        client.publishPacket(providerId, LAYER1, PAYLOAD);
+
+        awaitTaskCompletion();
+        verifyPacketReceived(mClientCallback1, providerId, LAYER1, PAYLOAD);
+        verifyPacketReceived(mClientCallback2, providerId, LAYER1, PAYLOAD);
+    }
+
+    @Test
+    public void testPublishPacket_LayerAndProviderSubscriber() {
+        VmsClient client = connectVmsClient(mClientCallback1);
+        int providerId = client.registerProvider(PROVIDER_DESC1);
+        client.setProviderOfferings(providerId, asSet(
+                new VmsLayerDependency(LAYER1)
+        ));
+        connectVmsClient(mClientCallback2);
+
+        client.setSubscriptions(asSet(
+                new VmsAssociatedLayer(LAYER1, asSet(providerId))
+        ));
+        client.publishPacket(providerId, LAYER1, PAYLOAD);
+
+        awaitTaskCompletion();
+        verifyPacketReceived(mClientCallback1, providerId, LAYER1, PAYLOAD);
+        verifyNoPacketsReceived(mClientCallback2, providerId, LAYER1);
+    }
+
+    @Test
+    public void testPublishPacket_LayerAndProviderSubscriber_Unsubscribe() {
+        VmsClient client = connectVmsClient(mClientCallback1);
+        int providerId = client.registerProvider(PROVIDER_DESC1);
+        client.setProviderOfferings(providerId, asSet(
+                new VmsLayerDependency(LAYER1)
+        ));
+        connectVmsClient(mClientCallback2);
+
+        client.setSubscriptions(asSet(
+                new VmsAssociatedLayer(LAYER1, asSet(providerId))
+        ));
+        client.setSubscriptions(emptySet());
+        client.publishPacket(providerId, LAYER1, PAYLOAD);
+
+        verifyNoPacketsReceived(mClientCallback1, providerId, LAYER1);
+        verifyNoPacketsReceived(mClientCallback2, providerId, LAYER1);
+    }
+
+    @Test
+    public void testPublishPacket_LayerAndProviderSubscriber_DifferentProvider() {
+        VmsClient client = connectVmsClient(mClientCallback1);
+        int providerId = client.registerProvider(PROVIDER_DESC1);
+        int providerId2 = client.registerProvider(PROVIDER_DESC2);
+        client.setProviderOfferings(providerId, asSet(
+                new VmsLayerDependency(LAYER1)
+        ));
+        connectVmsClient(mClientCallback2);
+
+        client.setSubscriptions(asSet(
+                new VmsAssociatedLayer(LAYER1, asSet(providerId2))
+        ));
+        client.publishPacket(providerId, LAYER1, PAYLOAD);
+
+        awaitTaskCompletion();
+        verifyNoPacketsReceived(mClientCallback1, providerId, LAYER1);
+        verifyNoPacketsReceived(mClientCallback2, providerId, LAYER1);
+    }
+
+    @Test
+    public void testPublishPacket_MultipleLayerAndProviderSubscribers() {
+        VmsClient client = connectVmsClient(mClientCallback1);
+        int providerId = client.registerProvider(PROVIDER_DESC1);
+        client.setProviderOfferings(providerId, asSet(
+                new VmsLayerDependency(LAYER1)
+        ));
+        VmsClient client2 = connectVmsClient(mClientCallback2);
+
+        client.setSubscriptions(asSet(
+                new VmsAssociatedLayer(LAYER1, asSet(providerId))
+        ));
+        client2.setSubscriptions(asSet(
+                new VmsAssociatedLayer(LAYER1, asSet(providerId))
+        ));
+        client.publishPacket(providerId, LAYER1, PAYLOAD);
+
+        awaitTaskCompletion();
+        verifyPacketReceived(mClientCallback1, providerId, LAYER1, PAYLOAD);
+        verifyPacketReceived(mClientCallback2, providerId, LAYER1, PAYLOAD);
+    }
+
+    private VmsClient connectVmsClient(VmsClientCallback callback) {
+        mClientManager.registerVmsClientCallback(mExecutor, callback);
+        verify(callback, timeout(CONNECT_TIMEOUT)).onClientConnected(mClientCaptor.capture());
+        return mClientCaptor.getValue();
+    }
+
+    private void awaitTaskCompletion() {
+        mExecutor.shutdown();
+        try {
+            mExecutor.awaitTermination(2L, TimeUnit.SECONDS);
+        } catch (InterruptedException e) {
+            fail("Executor termination interrupted");
+        }
+    }
+
+    private static void verifyLayerAvailability(
+            VmsClientCallback callback,
+            VmsAvailableLayers availableLayers) {
+        ArgumentCaptor<VmsAvailableLayers> availableLayersCaptor =
+                ArgumentCaptor.forClass(VmsAvailableLayers.class);
+        verify(callback, times(availableLayers.getSequenceNumber() + 1))
+                .onLayerAvailabilityChanged(availableLayersCaptor.capture());
+        assertThat(availableLayersCaptor.getValue()).isEqualTo(availableLayers);
+    }
+
+    private static void verifySubscriptionState(
+            VmsClientCallback callback,
+            VmsSubscriptionState subscriptionState) {
+        ArgumentCaptor<VmsSubscriptionState> subscriptionStateCaptor =
+                ArgumentCaptor.forClass(VmsSubscriptionState.class);
+        verify(callback, times(subscriptionState.getSequenceNumber() + 1))
+                .onSubscriptionStateChanged(subscriptionStateCaptor.capture());
+        assertThat(subscriptionStateCaptor.getValue()).isEqualTo(subscriptionState);
+    }
+
+    private static void verifyNoPacketsReceived(
+            VmsClientCallback callback,
+            int providerId, VmsLayer layer) {
+        verify(callback, never()).onPacketReceived(eq(providerId), eq(layer), any());
+    }
+
+    private static void verifyPacketReceived(
+            VmsClientCallback callback,
+            int providerId, VmsLayer layer, byte[] payload) {
+        verify(callback).onPacketReceived(providerId, layer, payload);
+    }
+
+    private static <T> Set<T> asSet(T... values) {
+        return new HashSet<T>(Arrays.asList(values));
+    }
+}
diff --git a/tests/carservice_unit_test/Android.mk b/tests/carservice_unit_test/Android.mk
index f172a61..06367f3 100644
--- a/tests/carservice_unit_test/Android.mk
+++ b/tests/carservice_unit_test/Android.mk
@@ -46,20 +46,23 @@
     android.car.userlib \
     android.test.runner \
     android.test.base \
-    android.test.mock
+    android.test.mock \
+    EncryptionRunner
 
 LOCAL_STATIC_JAVA_LIBRARIES := \
     androidx.test.core \
     androidx.test.ext.junit \
     androidx.test.rules \
     car-frameworks-service \
-    car-service-lib-for-test \
+    car-service-test-static-lib \
     com.android.car.test.utils \
     frameworks-base-testutils \
     mockito-target-extended \
     testng \
     truth-prebuilt
 
+LOCAL_COMPATIBILITY_SUITE := general-tests
+
 # mockito-target-inline dependency
 LOCAL_JNI_SHARED_LIBRARIES := \
     libdexmakerjvmtiagent \
diff --git a/tests/carservice_unit_test/AndroidManifest.xml b/tests/carservice_unit_test/AndroidManifest.xml
index 5ed59ef..e4c0687 100644
--- a/tests/carservice_unit_test/AndroidManifest.xml
+++ b/tests/carservice_unit_test/AndroidManifest.xml
@@ -16,8 +16,8 @@
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
         xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
-        package="com.android.car.carservice_unittest"
-        android:sharedUserId="android.uid.system" >
+        android:sharedUserId="com.google.android.car.uid.kitchensink"
+        package="com.android.car.carservice_unittest">
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
             android:targetPackage="com.android.car.carservice_unittest"
             android:label="Unit Tests for Car APIs"/>
diff --git a/tests/carservice_unit_test/src/android/car/CarTest.java b/tests/carservice_unit_test/src/android/car/CarTest.java
index d1f5ac6..ddb18c7 100644
--- a/tests/carservice_unit_test/src/android/car/CarTest.java
+++ b/tests/carservice_unit_test/src/android/car/CarTest.java
@@ -36,25 +36,26 @@
 import android.os.ServiceManager;
 import android.util.Pair;
 
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-
 import com.android.car.CarServiceUtils;
 
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
 import org.mockito.Mock;
 import org.mockito.MockitoSession;
 import org.mockito.invocation.InvocationOnMock;
 import org.mockito.quality.Strictness;
 
+import java.util.Collections;
+import java.util.List;
 import java.util.concurrent.CopyOnWriteArrayList;
 
 /**
  * Unit test for Car API.
  */
-@RunWith(AndroidJUnit4.class)
+@RunWith(JUnit4.class)
 public class CarTest {
     private static final String TAG = CarTest.class.getSimpleName();
 
@@ -72,6 +73,15 @@
         }
 
         @Override
+        public void onUserLifecycleEvent(int eventType, long timestampMs, int fromUserId,
+                int toUserId) {
+        }
+
+        @Override
+        public void onFirstUserUnlocked(int userId, long timestampMs, long duration) {
+        }
+
+        @Override
         public void setUserLockStatus(int userHandle, int unlocked) {
         }
 
@@ -80,6 +90,41 @@
         }
 
         @Override
+        public boolean isFeatureEnabled(String featureName) {
+            return false;
+        }
+
+        @Override
+        public int enableFeature(String featureName) {
+            return Car.FEATURE_REQUEST_SUCCESS;
+        }
+
+        @Override
+        public int disableFeature(String featureName) {
+            return Car.FEATURE_REQUEST_SUCCESS;
+        }
+
+        @Override
+        public List<String> getAllEnabledFeatures() {
+            return Collections.EMPTY_LIST;
+        }
+
+        @Override
+        public List<String> getAllPendingDisabledFeatures() {
+            return Collections.EMPTY_LIST;
+        }
+
+        @Override
+        public List<String> getAllPendingEnabledFeatures() {
+            return Collections.EMPTY_LIST;
+        }
+
+        @Override
+        public String getCarManagerClassForFeature(String featureName) {
+            return null;
+        }
+
+        @Override
         public android.os.IBinder getCarService(java.lang.String serviceName) {
             return null;
         }
diff --git a/tests/carservice_unit_test/src/android/car/encryptionrunner/EncryptionRunnerTest.java b/tests/carservice_unit_test/src/android/car/encryptionrunner/EncryptionRunnerTest.java
new file mode 100644
index 0000000..d4e6e2d
--- /dev/null
+++ b/tests/carservice_unit_test/src/android/car/encryptionrunner/EncryptionRunnerTest.java
@@ -0,0 +1,282 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.car.encryptionrunner;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.util.Log;
+
+import junit.framework.Assert;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class EncryptionRunnerTest {
+
+    private Key mClientKey;
+    private Key mServerKey;
+    private byte[] mData = "testData".getBytes();
+
+    private interface RunnerFactory {
+        EncryptionRunner newRunner();
+    }
+
+    @Test
+    public void happyFlow_dummyRunner() throws Exception {
+        verifyRunners(EncryptionRunnerFactory::newDummyRunner);
+    }
+
+    @Test
+    public void happyFlow_ukey2Runner() throws Exception {
+        verifyRunners(EncryptionRunnerFactory::newRunner);
+    }
+
+    @Test
+    public void happyFlow_dummyRunner_reconnect() throws Exception {
+        setUpFirstConnection(EncryptionRunnerFactory::newDummyRunner);
+        verifyRunnersReconnect(EncryptionRunnerFactory::newDummyRunner);
+    }
+
+    @Test
+    public void happyFlow_uKey2Runner_reconnect() throws Exception {
+        setUpFirstConnection(EncryptionRunnerFactory::newRunner);
+        verifyRunnersReconnect(EncryptionRunnerFactory::newRunner);
+    }
+
+    @Test
+    public void uKey2Runner_reconnect_encrypt_and_decrypt() throws Exception {
+        setUpFirstConnection(EncryptionRunnerFactory::newRunner);
+        setUpReconnection(EncryptionRunnerFactory::newRunner);
+        assertThat(mClientKey.decryptData(mServerKey.encryptData(mData))).isEqualTo(mData);
+    }
+
+    @Test
+    public void dummyRunner_reconnect_encrypt_and_decrypt() throws Exception {
+        setUpFirstConnection(EncryptionRunnerFactory::newDummyRunner);
+        setUpReconnection(EncryptionRunnerFactory::newDummyRunner);
+        assertThat(mClientKey.decryptData(mServerKey.encryptData(mData))).isEqualTo(mData);
+    }
+
+    private void setUpFirstConnection(RunnerFactory runnerFactory) throws Exception {
+        EncryptionRunner clientRunner = runnerFactory.newRunner();
+        EncryptionRunner serverRunner = runnerFactory.newRunner();
+        verifyHandshake(clientRunner, serverRunner);
+        HandshakeMessage finalServerMessage = serverRunner.verifyPin();
+        HandshakeMessage finalClientMessage = clientRunner.verifyPin();
+        mServerKey = finalServerMessage.getKey();
+        mClientKey = finalClientMessage.getKey();
+    }
+
+    private void setUpReconnection(RunnerFactory runnerFactory) throws Exception {
+        setUpFirstConnection(runnerFactory);
+        EncryptionRunner clientRunner = runnerFactory.newRunner();
+        EncryptionRunner serverRunner = runnerFactory.newRunner();
+        verifyHandshakeReconnect(clientRunner, serverRunner);
+        HandshakeMessage nextClientMessage =
+                clientRunner.initReconnectAuthentication(mClientKey.asBytes());
+        HandshakeMessage finalServerMessage = serverRunner.authenticateReconnection(
+                nextClientMessage.getNextMessage(), mServerKey.asBytes());
+        HandshakeMessage finalClientMessage = clientRunner.authenticateReconnection(
+                finalServerMessage.getNextMessage(), mServerKey.asBytes());
+        mServerKey = finalServerMessage.getKey();
+        mClientKey = finalClientMessage.getKey();
+    }
+
+    /**
+     * Runs through a happy flow of encryption runners and verifies that they behave as expected.
+     * Some * of the test is implementation specific because the interface doesn't specify how many
+     * round * trips may be needed but this test makes assumptions( i.e. white box testing).
+     */
+    private void verifyRunners(RunnerFactory runnerFactory) throws Exception {
+        EncryptionRunner clientRunner = runnerFactory.newRunner();
+        EncryptionRunner serverRunner = runnerFactory.newRunner();
+
+        verifyHandshake(clientRunner, serverRunner);
+
+        HandshakeMessage finalServerMessage = serverRunner.verifyPin();
+        assertThat(finalServerMessage.getHandshakeState())
+                .isEqualTo(HandshakeMessage.HandshakeState.FINISHED);
+        assertThat(finalServerMessage.getKey()).isNotNull();
+        assertThat(finalServerMessage.getNextMessage()).isNull();
+
+        HandshakeMessage finalClientMessage = clientRunner.verifyPin();
+        assertThat(finalClientMessage.getHandshakeState())
+                .isEqualTo(HandshakeMessage.HandshakeState.FINISHED);
+        assertThat(finalClientMessage.getKey()).isNotNull();
+        assertThat(finalClientMessage.getNextMessage()).isNull();
+
+        assertThat(finalServerMessage.getKey()
+                .decryptData(finalClientMessage.getKey().encryptData(mData)))
+                .isEqualTo(mData);
+        assertThat(finalClientMessage.getKey()
+                .decryptData(finalServerMessage.getKey().encryptData(mData)))
+                .isEqualTo(mData);
+    }
+
+    private void verifyRunnersReconnect(RunnerFactory runnerFactory) throws Exception {
+        EncryptionRunner clientRunner = runnerFactory.newRunner();
+        EncryptionRunner serverRunner = runnerFactory.newRunner();
+        verifyHandshakeReconnect(clientRunner, serverRunner);
+
+        HandshakeMessage nextClientMessage =
+                clientRunner.initReconnectAuthentication(mClientKey.asBytes());
+        assertThat(nextClientMessage.getHandshakeState())
+                .isEqualTo(HandshakeMessage.HandshakeState.RESUMING_SESSION);
+        assertThat(nextClientMessage.getKey()).isNull();
+        assertThat(nextClientMessage.getNextMessage()).isNotNull();
+
+        HandshakeMessage finalServerMessage = serverRunner.authenticateReconnection(
+                nextClientMessage.getNextMessage(), mServerKey.asBytes());
+        assertThat(finalServerMessage.getHandshakeState())
+                .isEqualTo(HandshakeMessage.HandshakeState.FINISHED);
+        assertThat(finalServerMessage.getKey()).isNotNull();
+        assertThat(finalServerMessage.getNextMessage()).isNotNull();
+
+        HandshakeMessage finalClientMessage = clientRunner.authenticateReconnection(
+                finalServerMessage.getNextMessage(), mServerKey.asBytes());
+        assertThat(finalClientMessage.getHandshakeState())
+                .isEqualTo(HandshakeMessage.HandshakeState.FINISHED);
+        assertThat(finalClientMessage.getKey()).isNotNull();
+        assertThat(finalClientMessage.getNextMessage()).isNull();
+    }
+
+    private void verifyHandshake(EncryptionRunner clientRunner, EncryptionRunner serverRunner)
+            throws Exception {
+        HandshakeMessage initialClientMessage = clientRunner.initHandshake();
+
+        assertThat(initialClientMessage.getHandshakeState())
+                .isEqualTo(HandshakeMessage.HandshakeState.IN_PROGRESS);
+        assertThat(initialClientMessage.getKey()).isNull();
+        assertThat(initialClientMessage.getNextMessage()).isNotNull();
+
+        // This and the following similar log statements are useful when running this test to
+        // find the payload sizes.
+        Log.i(EncryptionRunner.TAG,
+                "initial client size:" + initialClientMessage.getNextMessage().length);
+
+        HandshakeMessage initialServerMessage =
+                serverRunner.respondToInitRequest(initialClientMessage.getNextMessage());
+
+        assertThat(initialServerMessage.getHandshakeState())
+                .isEqualTo(HandshakeMessage.HandshakeState.IN_PROGRESS);
+        assertThat(initialServerMessage.getKey()).isNull();
+        assertThat(initialServerMessage.getNextMessage()).isNotNull();
+
+        Log.i(EncryptionRunner.TAG,
+                "initial server message size:" + initialServerMessage.getNextMessage().length);
+
+        HandshakeMessage clientMessage =
+                clientRunner.continueHandshake(initialServerMessage.getNextMessage());
+
+        assertThat(clientMessage.getHandshakeState())
+                .isEqualTo(HandshakeMessage.HandshakeState.VERIFICATION_NEEDED);
+        assertThat(clientMessage.getKey()).isNull();
+        assertThat(clientMessage.getVerificationCode()).isNotEmpty();
+        assertThat(clientMessage.getNextMessage()).isNotNull();
+
+        Log.i(EncryptionRunner.TAG,
+                "second client message size:" + clientMessage.getNextMessage().length);
+
+        HandshakeMessage serverMessage =
+                serverRunner.continueHandshake(clientMessage.getNextMessage());
+        assertThat(serverMessage.getHandshakeState())
+                .isEqualTo(HandshakeMessage.HandshakeState.VERIFICATION_NEEDED);
+        assertThat(serverMessage.getKey()).isNull();
+        assertThat(serverMessage.getNextMessage()).isNull();
+
+        Log.i(EncryptionRunner.TAG,
+                "last server message size:" + clientMessage.getNextMessage().length);
+    }
+
+    private void verifyHandshakeReconnect(
+            EncryptionRunner clientRunner, EncryptionRunner serverRunner)
+            throws HandshakeException {
+        clientRunner.setIsReconnect(true);
+        serverRunner.setIsReconnect(true);
+
+        HandshakeMessage initialClientMessage = clientRunner.initHandshake();
+        assertThat(initialClientMessage.getHandshakeState())
+                .isEqualTo(HandshakeMessage.HandshakeState.IN_PROGRESS);
+        assertThat(initialClientMessage.getKey()).isNull();
+        assertThat(initialClientMessage.getNextMessage()).isNotNull();
+
+        // This and the following similar log statements are useful when running this test to
+        // find the payload sizes.
+        Log.i(EncryptionRunner.TAG,
+                "initial client size:" + initialClientMessage.getNextMessage().length);
+
+        HandshakeMessage initialServerMessage =
+                serverRunner.respondToInitRequest(initialClientMessage.getNextMessage());
+
+        assertThat(initialServerMessage.getHandshakeState())
+                .isEqualTo(HandshakeMessage.HandshakeState.IN_PROGRESS);
+        assertThat(initialServerMessage.getKey()).isNull();
+        assertThat(initialServerMessage.getNextMessage()).isNotNull();
+
+        Log.i(EncryptionRunner.TAG,
+                "initial server message size:" + initialServerMessage.getNextMessage().length);
+
+        HandshakeMessage clientMessage =
+                clientRunner.continueHandshake(initialServerMessage.getNextMessage());
+
+        assertThat(clientMessage.getHandshakeState())
+                .isEqualTo(HandshakeMessage.HandshakeState.RESUMING_SESSION);
+        assertThat(clientMessage.getKey()).isNull();
+        assertThat(clientMessage.getNextMessage()).isNotNull();
+
+        HandshakeMessage serverMessage =
+                serverRunner.continueHandshake(clientMessage.getNextMessage());
+        assertThat(serverMessage.getHandshakeState())
+                .isEqualTo(HandshakeMessage.HandshakeState.RESUMING_SESSION);
+        assertThat(serverMessage.getKey()).isNull();
+    }
+
+    @Test
+    public void invalidPin_ukey2() throws Exception {
+        invalidPinTest(EncryptionRunnerFactory::newRunner);
+    }
+
+    @Test
+    public void invalidPin_dummy() throws Exception {
+        invalidPinTest(EncryptionRunnerFactory::newDummyRunner);
+    }
+
+    private void invalidPinTest(RunnerFactory runnerFactory) throws Exception {
+        EncryptionRunner clientRunner = runnerFactory.newRunner();
+        EncryptionRunner serverRunner = runnerFactory.newRunner();
+
+        verifyHandshake(clientRunner, serverRunner);
+        clientRunner.invalidPin();
+        serverRunner.invalidPin();
+
+        try {
+            clientRunner.verifyPin();
+            Assert.fail();
+        } catch (Exception ignored) {
+            // pass
+        }
+
+        try {
+            serverRunner.verifyPin();
+            Assert.fail();
+        } catch (Exception ignored) {
+            // pass
+        }
+    }
+}
diff --git a/tests/carservice_unit_test/src/android/car/encryptionrunner/Ukey2EncryptionRunnerTest.java b/tests/carservice_unit_test/src/android/car/encryptionrunner/Ukey2EncryptionRunnerTest.java
new file mode 100644
index 0000000..71dbb4c
--- /dev/null
+++ b/tests/carservice_unit_test/src/android/car/encryptionrunner/Ukey2EncryptionRunnerTest.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.car.encryptionrunner;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class Ukey2EncryptionRunnerTest {
+
+    private Ukey2EncryptionRunner mRunner;
+
+    @Before
+    public void setup() {
+        mRunner = new Ukey2EncryptionRunner();
+    }
+
+    @Test
+    public void generateReadablePairingCode_modsBytesAcrossRange() throws Exception {
+        // 194 is an example of a value that would fail if using signed instead of unsigned ints
+        // 194 -> 11000010
+        // 11000010 -> 194 (unsigned 8-bit int)
+        // 11000010 -> -62 (signed 8-bit int)
+        byte[] bytes = new byte[]{0, 7, (byte) 161, (byte) 194, (byte) 196, (byte) 255};
+        String pairingCode = mRunner.generateReadablePairingCode(bytes);
+
+        assertThat(pairingCode).isEqualTo("071465");
+    }
+}
diff --git a/tests/carservice_unit_test/src/android/car/userlib/CarUserManagerHelperTest.java b/tests/carservice_unit_test/src/android/car/userlib/CarUserManagerHelperTest.java
index 02a3f13..dbfbbcb 100644
--- a/tests/carservice_unit_test/src/android/car/userlib/CarUserManagerHelperTest.java
+++ b/tests/carservice_unit_test/src/android/car/userlib/CarUserManagerHelperTest.java
@@ -19,38 +19,27 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.Matchers.any;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.app.ActivityManager;
-import android.content.BroadcastReceiver;
 import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
 import android.content.pm.UserInfo;
-import android.graphics.Bitmap;
-import android.graphics.drawable.Drawable;
-import android.os.Bundle;
-import android.os.Handler;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.provider.Settings;
 
 import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.FlakyTest;
 import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
-import org.mockito.Mockito;
-import org.mockito.MockitoAnnotations;
+import org.mockito.junit.MockitoJUnitRunner;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -63,35 +52,24 @@
  * 1. {@link Context} provides system services and resources.
  * 2. {@link UserManager} provides dummy users and user info.
  * 3. {@link ActivityManager} to verify user switch is invoked.
- * 4. {@link CarUserManagerHelper.OnUsersUpdateListener} registers a listener for user updates.
  */
-@RunWith(AndroidJUnit4.class)
+@RunWith(MockitoJUnitRunner.class)
 @SmallTest
 public class CarUserManagerHelperTest {
-    @Mock
-    private Context mContext;
-    @Mock
-    private UserManager mUserManager;
-    @Mock
-    private ActivityManager mActivityManager;
-    @Mock
-    private CarUserManagerHelper.OnUsersUpdateListener mTestListener;
-    @Mock
-    private TestableFrameworkWrapper mTestableFrameworkWrapper;
+    @Mock private Context mContext;
+    @Mock private UserManager mUserManager;
+    @Mock private ActivityManager mActivityManager;
+    @Mock private TestableFrameworkWrapper mTestableFrameworkWrapper;
 
-    private static final String GUEST_USER_NAME = "testGuest";
     private static final String TEST_USER_NAME = "testUser";
-    private static final String DEFAULT_ADMIN_NAME = "defaultAdminName";
 
     private CarUserManagerHelper mCarUserManagerHelper;
     private UserInfo mCurrentProcessUser;
     private UserInfo mSystemUser;
     private int mForegroundUserId;
-    private UserInfo mForegroundUser;
 
     @Before
-    public void setUpMocksAndVariables() throws Exception {
-        MockitoAnnotations.initMocks(this);
+    public void setUpMocksAndVariables() {
         doReturn(mUserManager).when(mContext).getSystemService(Context.USER_SERVICE);
         doReturn(mActivityManager).when(mContext).getSystemService(Context.ACTIVITY_SERVICE);
         doReturn(InstrumentationRegistry.getTargetContext().getResources())
@@ -100,7 +78,6 @@
                 .when(mContext).getContentResolver();
         doReturn(mContext).when(mContext).getApplicationContext();
         mCarUserManagerHelper = new CarUserManagerHelper(mContext, mTestableFrameworkWrapper);
-        mCarUserManagerHelper.setDefaultAdminName(DEFAULT_ADMIN_NAME);
 
         mCurrentProcessUser = createUserInfoForId(UserHandle.myUserId());
         mSystemUser = createUserInfoForId(UserHandle.USER_SYSTEM);
@@ -110,7 +87,6 @@
         // We cannot mock the foreground user since getCurrentUser is static.
         // We cannot rely on foreground_id != system_id, they could be the same user.
         mForegroundUserId = ActivityManager.getCurrentUser();
-        mForegroundUser = createUserInfoForId(mForegroundUserId);
 
         // Clear boot override for every test by returning the default value passed to the method
         when(mTestableFrameworkWrapper.getBootUserOverrideId(anyInt()))
@@ -118,334 +94,6 @@
     }
 
     @Test
-    public void checkHeadlessSystemUserFlag() {
-        // Make sure the headless system user flag is on.
-        assertThat(mCarUserManagerHelper.isHeadlessSystemUser()).isTrue();
-    }
-
-    @Test
-    public void checkIsSystemUser() {
-        UserInfo testInfo = new UserInfo();
-
-        testInfo.id = UserHandle.USER_SYSTEM;
-        assertThat(mCarUserManagerHelper.isSystemUser(testInfo)).isTrue();
-
-        testInfo.id = UserHandle.USER_SYSTEM + 2; // Make it different than system id.
-        assertThat(mCarUserManagerHelper.isSystemUser(testInfo)).isFalse();
-    }
-
-    // System user will not be returned when calling get all users.
-    @Test
-    public void testHeadlessUser0GetAllUsers_NotReturnSystemUser() {
-        UserInfo otherUser1 = createUserInfoForId(10);
-        UserInfo otherUser2 = createUserInfoForId(11);
-        UserInfo otherUser3 = createUserInfoForId(12);
-
-        mockGetUsers(mSystemUser, otherUser1, otherUser2, otherUser3);
-
-        assertThat(mCarUserManagerHelper.getAllUsers())
-                .containsExactly(otherUser1, otherUser2, otherUser3);
-    }
-
-    @Test
-    public void testGetAllSwitchableUsers() {
-        // Create two non-foreground users.
-        UserInfo user1 = createUserInfoForId(mForegroundUserId + 1);
-        UserInfo user2 = createUserInfoForId(mForegroundUserId + 2);
-
-        mockGetUsers(mForegroundUser, user1, user2);
-
-        // Should return all non-foreground users.
-        assertThat(mCarUserManagerHelper.getAllSwitchableUsers()).containsExactly(user1, user2);
-    }
-
-    @Test
-    public void testGetAllPersistentUsers() {
-        // Create two non-ephemeral users.
-        UserInfo user1 = createUserInfoForId(mForegroundUserId);
-        UserInfo user2 = createUserInfoForId(mForegroundUserId + 1);
-        // Create two ephemeral users.
-        UserInfo user3 = new UserInfo(
-                /* id= */mForegroundUserId + 2, /* name = */ "user3", UserInfo.FLAG_EPHEMERAL);
-        UserInfo user4 = new UserInfo(
-                /* id= */mForegroundUserId + 3, /* name = */ "user4", UserInfo.FLAG_EPHEMERAL);
-
-        mockGetUsers(user1, user2, user3, user4);
-
-        // Should return all non-ephemeral users.
-        assertThat(mCarUserManagerHelper.getAllPersistentUsers()).containsExactly(user1, user2);
-    }
-
-    @Test
-    public void testGetAllAdminUsers() {
-        // Create two admin, and two non-admin users.
-        UserInfo user1 = new UserInfo(/* id= */ 10, /* name = */ "user10", UserInfo.FLAG_ADMIN);
-        UserInfo user2 = createUserInfoForId(11);
-        UserInfo user3 = createUserInfoForId(12);
-        UserInfo user4 = new UserInfo(/* id= */ 13, /* name = */ "user13", UserInfo.FLAG_ADMIN);
-
-        mockGetUsers(user1, user2, user3, user4);
-
-        // Should return only admin users.
-        assertThat(mCarUserManagerHelper.getAllAdminUsers()).containsExactly(user1, user4);
-    }
-
-    @Test
-    public void testGetAllUsersExceptGuests() {
-        // Create two users and a guest user.
-        UserInfo user1 = createUserInfoForId(10);
-        UserInfo user2 = createUserInfoForId(12);
-        UserInfo user3 = new UserInfo(/* id= */ 13, /* name = */ "user13", UserInfo.FLAG_GUEST);
-
-        mockGetUsers(user1, user2, user3);
-
-        // Should not return guests.
-        assertThat(mCarUserManagerHelper.getAllUsersExceptGuests())
-                .containsExactly(user1, user2);
-    }
-
-    @Test
-    public void testUserCanBeRemoved() {
-        UserInfo testInfo = new UserInfo();
-
-        // System user cannot be removed.
-        testInfo.id = UserHandle.USER_SYSTEM;
-        assertThat(mCarUserManagerHelper.canUserBeRemoved(testInfo)).isFalse();
-
-        testInfo.id = UserHandle.USER_SYSTEM + 2; // Make it different than system id.
-        assertThat(mCarUserManagerHelper.canUserBeRemoved(testInfo)).isTrue();
-    }
-
-    @Test
-    public void testCurrentProcessCanAddUsers() {
-        doReturn(false).when(mUserManager)
-                .hasUserRestriction(UserManager.DISALLOW_ADD_USER);
-        assertThat(mCarUserManagerHelper.canCurrentProcessAddUsers()).isTrue();
-
-        doReturn(true).when(mUserManager)
-                .hasUserRestriction(UserManager.DISALLOW_ADD_USER);
-        assertThat(mCarUserManagerHelper.canCurrentProcessAddUsers()).isFalse();
-    }
-
-    @Test
-    public void testCurrentProcessCanRemoveUsers() {
-        doReturn(false).when(mUserManager)
-                .hasUserRestriction(UserManager.DISALLOW_REMOVE_USER);
-        assertThat(mCarUserManagerHelper.canCurrentProcessRemoveUsers()).isTrue();
-
-        doReturn(true).when(mUserManager)
-                .hasUserRestriction(UserManager.DISALLOW_REMOVE_USER);
-        assertThat(mCarUserManagerHelper.canCurrentProcessRemoveUsers()).isFalse();
-    }
-
-    @Test
-    public void testCurrentProcessCanSwitchUsers() {
-        doReturn(false).when(mUserManager)
-                .hasUserRestriction(UserManager.DISALLOW_USER_SWITCH);
-        assertThat(mCarUserManagerHelper.canCurrentProcessSwitchUsers()).isTrue();
-
-        doReturn(true).when(mUserManager)
-                .hasUserRestriction(UserManager.DISALLOW_USER_SWITCH);
-        assertThat(mCarUserManagerHelper.canCurrentProcessSwitchUsers()).isFalse();
-    }
-
-    @Test
-    public void testCurrentGuestProcessCannotModifyAccounts() {
-        assertThat(mCarUserManagerHelper.canCurrentProcessModifyAccounts()).isTrue();
-
-        doReturn(true).when(mUserManager).isGuestUser();
-
-        assertThat(mCarUserManagerHelper.canCurrentProcessModifyAccounts()).isFalse();
-    }
-
-    @Test
-    public void testCurrentDemoProcessCannotModifyAccounts() {
-        assertThat(mCarUserManagerHelper.canCurrentProcessModifyAccounts()).isTrue();
-
-        doReturn(true).when(mUserManager).isDemoUser();
-
-        assertThat(mCarUserManagerHelper.canCurrentProcessModifyAccounts()).isFalse();
-    }
-
-    @Test
-    public void testCurrentDisallowModifyAccountsProcessIsEnforced() {
-        assertThat(mCarUserManagerHelper.canCurrentProcessModifyAccounts()).isTrue();
-
-        doReturn(true).when(mUserManager)
-                .hasUserRestriction(UserManager.DISALLOW_MODIFY_ACCOUNTS);
-
-        assertThat(mCarUserManagerHelper.canCurrentProcessModifyAccounts()).isFalse();
-    }
-
-    @Test
-    public void testGetMaxSupportedUsers() {
-        setMaxSupportedUsers(11);
-
-        // Max users - headless system user.
-        assertThat(mCarUserManagerHelper.getMaxSupportedUsers()).isEqualTo(10);
-    }
-
-    @Test
-    public void testGetMaxSupportedRealUsers() {
-        setMaxSupportedUsers(7);
-
-        // Create three managed profiles, and two normal users.
-        UserInfo user1 = createUserInfoForId(10);
-        UserInfo user2 =
-                new UserInfo(/* id= */ 11, /* name = */ "user11", UserInfo.FLAG_MANAGED_PROFILE);
-        UserInfo user3 =
-                new UserInfo(/* id= */ 12, /* name = */ "user12", UserInfo.FLAG_MANAGED_PROFILE);
-        UserInfo user4 = createUserInfoForId(13);
-        UserInfo user5 =
-                new UserInfo(/* id= */ 14, /* name = */ "user14", UserInfo.FLAG_MANAGED_PROFILE);
-
-        mockGetUsers(user1, user2, user3, user4, user5);
-
-        // Max users - # managed profiles - headless system user.
-        assertThat(mCarUserManagerHelper.getMaxSupportedRealUsers()).isEqualTo(3);
-    }
-
-    @Test
-    public void testHeadlessSystemUser_IsUserLimitReached() {
-        UserInfo user1 = createUserInfoForId(10);
-        UserInfo user2 =
-                new UserInfo(/* id= */ 11, /* name = */ "user11", UserInfo.FLAG_MANAGED_PROFILE);
-        UserInfo user3 =
-                new UserInfo(/* id= */ 12, /* name = */ "user12", UserInfo.FLAG_MANAGED_PROFILE);
-        UserInfo user4 = createUserInfoForId(13);
-
-        mockGetUsers(mSystemUser, user1, user2, user3, user4);
-
-        setMaxSupportedUsers(6);
-        assertThat(mCarUserManagerHelper.isUserLimitReached()).isFalse();
-
-        setMaxSupportedUsers(5);
-        assertThat(mCarUserManagerHelper.isUserLimitReached()).isTrue();
-    }
-
-    @Test
-    public void testIsUserLimitReachedIgnoresGuests() {
-        setMaxSupportedUsers(6);
-
-        UserInfo user1 = createUserInfoForId(10);
-        UserInfo user2 =
-                new UserInfo(/* id= */ 11, /* name = */ "user11", UserInfo.FLAG_MANAGED_PROFILE);
-        UserInfo user3 =
-                new UserInfo(/* id= */ 12, /* name = */ "user12", UserInfo.FLAG_MANAGED_PROFILE);
-        UserInfo user4 = createUserInfoForId(13);
-        UserInfo user5 = new UserInfo(/* id= */ 14, /* name = */ "user14", UserInfo.FLAG_GUEST);
-        UserInfo user6 = createUserInfoForId(15);
-
-        mockGetUsers(user1, user2, user3, user4);
-        assertThat(mCarUserManagerHelper.isUserLimitReached()).isFalse();
-
-        // Add guest user. Verify it doesn't affect the limit.
-        mockGetUsers(user1, user2, user3, user4, user5);
-        assertThat(mCarUserManagerHelper.isUserLimitReached()).isFalse();
-
-        // Add normal user. Limit is reached
-        mockGetUsers(user1, user2, user3, user4, user5, user6);
-        assertThat(mCarUserManagerHelper.isUserLimitReached()).isTrue();
-    }
-
-    @Test
-    public void testCreateNewAdminUserCallsCreateUser() {
-        // Make sure current user is admin, since only admins can create other admins.
-        doReturn(true).when(mUserManager).isAdminUser();
-
-        mCarUserManagerHelper.createNewAdminUser(TEST_USER_NAME);
-        verify(mUserManager).createUser(TEST_USER_NAME, UserInfo.FLAG_ADMIN);
-    }
-
-    @Test
-    public void testCreateNewAdminUserReturnsNullUsers() {
-        // Make sure current user is admin, since only admins can create other admins.
-        doReturn(true).when(mUserManager).isAdminUser();
-
-        doReturn(null).when(mUserManager).createUser(TEST_USER_NAME, UserInfo.FLAG_ADMIN);
-        assertThat(mCarUserManagerHelper.createNewAdminUser(TEST_USER_NAME)).isNull();
-    }
-
-    @Test
-    public void testCreateNewAdminUserReturnsCreatedUser() {
-        // Make sure current user is admin, since only admins can create other admins.
-        doReturn(true).when(mUserManager).isAdminUser();
-
-        UserInfo newUser = new UserInfo();
-        newUser.name = TEST_USER_NAME;
-        doReturn(newUser).when(mUserManager).createUser(TEST_USER_NAME, UserInfo.FLAG_ADMIN);
-        assertThat(mCarUserManagerHelper.createNewAdminUser(TEST_USER_NAME)).isEqualTo(newUser);
-    }
-
-    @Test
-    public void testCreateNewAdminUserWithDefaultUserNameCallsCreateUser() {
-        // Make sure current user is admin, since only admins can create other admins.
-        doReturn(true).when(mUserManager).isAdminUser();
-
-        mCarUserManagerHelper.createNewAdminUser();
-        verify(mUserManager).createUser(DEFAULT_ADMIN_NAME, UserInfo.FLAG_ADMIN);
-    }
-
-    @Test
-    public void testCreateNewAdminUserWithDefaultUserNameReturnsNullUsers() {
-        // Make sure current user is admin, since only admins can create other admins.
-        doReturn(true).when(mUserManager).isAdminUser();
-
-        doReturn(null).when(mUserManager).createUser(DEFAULT_ADMIN_NAME, UserInfo.FLAG_ADMIN);
-        assertThat(mCarUserManagerHelper.createNewAdminUser(DEFAULT_ADMIN_NAME)).isNull();
-    }
-
-    @Test
-    public void testCreateNewAdminUserWithDefaultUserNameReturnsCreatedUser() {
-        // Make sure current user is admin, since only admins can create other admins.
-        doReturn(true).when(mUserManager).isAdminUser();
-
-        UserInfo newUser = new UserInfo();
-        newUser.name = DEFAULT_ADMIN_NAME;
-        doReturn(newUser).when(mUserManager).createUser(DEFAULT_ADMIN_NAME, UserInfo.FLAG_ADMIN);
-        assertThat(mCarUserManagerHelper.createNewAdminUser()).isEqualTo(newUser);
-    }
-
-    @Test
-    public void testAdminsCanCreateAdmins() {
-        String newAdminName = "Test new admin";
-        UserInfo expectedAdmin = new UserInfo();
-        expectedAdmin.name = newAdminName;
-        doReturn(expectedAdmin).when(mUserManager).createUser(newAdminName, UserInfo.FLAG_ADMIN);
-
-        // Admins can create other admins.
-        doReturn(true).when(mUserManager).isAdminUser();
-        UserInfo actualAdmin = mCarUserManagerHelper.createNewAdminUser(newAdminName);
-        assertThat(actualAdmin).isEqualTo(expectedAdmin);
-    }
-
-    @Test
-    public void testNonAdminsCanNotCreateAdmins() {
-        String newAdminName = "Test new admin";
-        UserInfo expectedAdmin = new UserInfo();
-        expectedAdmin.name = newAdminName;
-        doReturn(expectedAdmin).when(mUserManager).createUser(newAdminName, UserInfo.FLAG_ADMIN);
-
-        // Test that non-admins cannot create new admins.
-        doReturn(false).when(mUserManager).isAdminUser(); // Current user non-admin.
-        assertThat(mCarUserManagerHelper.createNewAdminUser(newAdminName)).isNull();
-    }
-
-    @Test
-    public void testSystemUserCanCreateAdmins() {
-        String newAdminName = "Test new admin";
-        UserInfo expectedAdmin = new UserInfo();
-        expectedAdmin.name = newAdminName;
-
-        doReturn(expectedAdmin).when(mUserManager).createUser(newAdminName, UserInfo.FLAG_ADMIN);
-
-        // System user can create admins.
-        doReturn(true).when(mUserManager).isSystemUser();
-        UserInfo actualAdmin = mCarUserManagerHelper.createNewAdminUser(newAdminName);
-        assertThat(actualAdmin).isEqualTo(expectedAdmin);
-    }
-
-    @Test
     public void testCreateNewNonAdminUser() {
         // Verify createUser on UserManager gets called.
         mCarUserManagerHelper.createNewNonAdminUser(TEST_USER_NAME);
@@ -461,104 +109,6 @@
     }
 
     @Test
-    public void testCannotRemoveSystemUser() {
-        assertThat(mCarUserManagerHelper.removeUser(mSystemUser, GUEST_USER_NAME)).isFalse();
-    }
-
-    @Test
-    public void testAdminsCanRemoveOtherUsers() {
-        int idToRemove = mCurrentProcessUser.id + 2;
-        UserInfo userToRemove = createUserInfoForId(idToRemove);
-
-        doReturn(true).when(mUserManager).removeUser(idToRemove);
-
-        // If Admin is removing non-current, non-system user, simply calls removeUser.
-        doReturn(true).when(mUserManager).isAdminUser();
-        assertThat(mCarUserManagerHelper.removeUser(userToRemove, GUEST_USER_NAME)).isTrue();
-        verify(mUserManager).removeUser(idToRemove);
-    }
-
-    @Test
-    public void testNonAdminsCanNotRemoveOtherUsers() {
-        UserInfo otherUser = createUserInfoForId(mCurrentProcessUser.id + 2);
-
-        // Make current user non-admin.
-        doReturn(false).when(mUserManager).isAdminUser();
-
-        // Mock so that removeUser always pretends it's successful.
-        doReturn(true).when(mUserManager).removeUser(anyInt());
-
-        // If Non-Admin is trying to remove someone other than themselves, they should fail.
-        assertThat(mCarUserManagerHelper.removeUser(otherUser, GUEST_USER_NAME)).isFalse();
-        verify(mUserManager, never()).removeUser(otherUser.id);
-    }
-
-    @Test
-    public void testRemoveLastActiveUser() {
-        // Cannot remove system user.
-        assertThat(mCarUserManagerHelper.removeUser(mSystemUser, GUEST_USER_NAME)).isFalse();
-
-        UserInfo adminInfo = new UserInfo(/* id= */10, "admin", UserInfo.FLAG_ADMIN);
-        mockGetUsers(adminInfo);
-
-        assertThat(mCarUserManagerHelper.removeUser(adminInfo, GUEST_USER_NAME))
-                .isEqualTo(false);
-    }
-
-    @Test
-    public void testRemoveLastAdminUser() {
-        // Make current user admin.
-        doReturn(true).when(mUserManager).isAdminUser();
-
-        UserInfo adminInfo = new UserInfo(/* id= */10, "admin", UserInfo.FLAG_ADMIN);
-        UserInfo nonAdminInfo = new UserInfo(/* id= */11, "non-admin", 0);
-        mockGetUsers(adminInfo, nonAdminInfo);
-
-        UserInfo newAdminInfo = new UserInfo(/* id= */12, DEFAULT_ADMIN_NAME, UserInfo.FLAG_ADMIN);
-        doReturn(newAdminInfo)
-                .when(mUserManager).createUser(DEFAULT_ADMIN_NAME, UserInfo.FLAG_ADMIN);
-
-        mCarUserManagerHelper.removeUser(adminInfo, GUEST_USER_NAME);
-        verify(mUserManager).createUser(DEFAULT_ADMIN_NAME, UserInfo.FLAG_ADMIN);
-        verify(mActivityManager).switchUser(newAdminInfo.id);
-        verify(mUserManager).removeUser(adminInfo.id);
-    }
-
-    @Test
-    public void testRemoveLastAdminUserFailsToCreateNewUser() {
-        // Make current user admin.
-        doReturn(true).when(mUserManager).isAdminUser();
-
-        UserInfo adminInfo = new UserInfo(/* id= */10, "admin", UserInfo.FLAG_ADMIN);
-        UserInfo nonAdminInfo = new UserInfo(/* id= */11, "non-admin", 0);
-        mockGetUsers(adminInfo, nonAdminInfo);
-
-        UserInfo newAdminInfo = new UserInfo(/* id= */12, DEFAULT_ADMIN_NAME, UserInfo.FLAG_ADMIN);
-        doReturn(newAdminInfo)
-                .when(mUserManager).createUser(DEFAULT_ADMIN_NAME, UserInfo.FLAG_ADMIN);
-
-        // Fail to create a new user to force a failure case
-        doReturn(null)
-                .when(mUserManager).createUser(DEFAULT_ADMIN_NAME, UserInfo.FLAG_ADMIN);
-
-        mCarUserManagerHelper.removeUser(adminInfo, GUEST_USER_NAME);
-        verify(mUserManager).createUser(DEFAULT_ADMIN_NAME, UserInfo.FLAG_ADMIN);
-        verify(mActivityManager, never()).switchUser(anyInt());
-        verify(mUserManager, never()).removeUser(adminInfo.id);
-    }
-
-    @Test
-    public void testSwitchToGuest() {
-        mCarUserManagerHelper.startGuestSession(GUEST_USER_NAME);
-        verify(mUserManager).createGuest(mContext, GUEST_USER_NAME);
-
-        UserInfo guestInfo = new UserInfo(/* id= */21, GUEST_USER_NAME, UserInfo.FLAG_GUEST);
-        doReturn(guestInfo).when(mUserManager).createGuest(mContext, GUEST_USER_NAME);
-        mCarUserManagerHelper.startGuestSession(GUEST_USER_NAME);
-        verify(mActivityManager).switchUser(21);
-    }
-
-    @Test
     public void testSwitchToId() {
         int userIdToSwitchTo = mForegroundUserId + 2;
         doReturn(true).when(mActivityManager).switchUser(userIdToSwitchTo);
@@ -579,43 +129,13 @@
     public void testCannotSwitchIfSwitchingNotAllowed() {
         int userIdToSwitchTo = mForegroundUserId + 2;
         doReturn(true).when(mActivityManager).switchUser(userIdToSwitchTo);
-        doReturn(true).when(mUserManager).hasUserRestriction(UserManager.DISALLOW_USER_SWITCH);
+        doReturn(UserManager.SWITCHABILITY_STATUS_USER_SWITCH_DISALLOWED)
+                .when(mUserManager).getUserSwitchability();
         assertThat(mCarUserManagerHelper.switchToUserId(userIdToSwitchTo)).isFalse();
         verify(mActivityManager, never()).switchUser(userIdToSwitchTo);
     }
 
     @Test
-    public void testGetUserIcon() {
-        mCarUserManagerHelper.getUserIcon(mCurrentProcessUser);
-        verify(mUserManager).getUserIcon(mCurrentProcessUser.id);
-    }
-
-    @Test
-    public void testScaleUserIcon() {
-        Bitmap fakeIcon = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);
-        Drawable scaledIcon = mCarUserManagerHelper.scaleUserIcon(fakeIcon, 300);
-        assertThat(scaledIcon.getIntrinsicWidth()).isEqualTo(300);
-        assertThat(scaledIcon.getIntrinsicHeight()).isEqualTo(300);
-    }
-
-    @Test
-    public void testSetUserName() {
-        UserInfo testInfo = createUserInfoForId(mCurrentProcessUser.id + 3);
-        String newName = "New Test Name";
-        mCarUserManagerHelper.setUserName(testInfo, newName);
-        verify(mUserManager).setUserName(mCurrentProcessUser.id + 3, newName);
-    }
-
-    @Test
-    public void testIsCurrentProcessSystemUser() {
-        doReturn(true).when(mUserManager).isAdminUser();
-        assertThat(mCarUserManagerHelper.isCurrentProcessAdminUser()).isTrue();
-
-        doReturn(false).when(mUserManager).isAdminUser();
-        assertThat(mCarUserManagerHelper.isCurrentProcessAdminUser()).isFalse();
-    }
-
-    @Test
     public void testGrantAdminPermissions() {
         int userId = 30;
         UserInfo testInfo = createUserInfoForId(userId);
@@ -632,22 +152,6 @@
     }
 
     @Test
-    public void testSetUserRestriction() {
-        int userId = 20;
-        UserInfo testInfo = createUserInfoForId(userId);
-
-        mCarUserManagerHelper.setUserRestriction(
-                testInfo, UserManager.DISALLOW_ADD_USER, /* enable= */ true);
-        verify(mUserManager).setUserRestriction(
-                UserManager.DISALLOW_ADD_USER, true, UserHandle.of(userId));
-
-        mCarUserManagerHelper.setUserRestriction(
-                testInfo, UserManager.DISALLOW_REMOVE_USER, /* enable= */ false);
-        verify(mUserManager).setUserRestriction(
-                UserManager.DISALLOW_REMOVE_USER, false, UserHandle.of(userId));
-    }
-
-    @Test
     public void testDefaultNonAdminRestrictions() {
         String testUserName = "Test User";
         int userId = 20;
@@ -659,30 +163,6 @@
 
         verify(mUserManager).setUserRestriction(
                 UserManager.DISALLOW_FACTORY_RESET, /* enable= */ true, UserHandle.of(userId));
-        verify(mUserManager).setUserRestriction(
-                UserManager.DISALLOW_SMS, /* enable= */ false, UserHandle.of(userId));
-        verify(mUserManager).setUserRestriction(
-                UserManager.DISALLOW_OUTGOING_CALLS, /* enable= */ false, UserHandle.of(userId));
-    }
-
-    @Test
-    public void testDefaultGuestRestrictions() {
-        int guestRestrictionsExpectedCount = 6;
-
-        ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
-        mCarUserManagerHelper.initDefaultGuestRestrictions();
-
-        verify(mUserManager).setDefaultGuestRestrictions(bundleCaptor.capture());
-        Bundle guestRestrictions = bundleCaptor.getValue();
-
-        assertThat(guestRestrictions.keySet()).hasSize(guestRestrictionsExpectedCount);
-        assertThat(guestRestrictions.getBoolean(UserManager.DISALLOW_FACTORY_RESET)).isTrue();
-        assertThat(guestRestrictions.getBoolean(UserManager.DISALLOW_REMOVE_USER)).isTrue();
-        assertThat(guestRestrictions.getBoolean(UserManager.DISALLOW_MODIFY_ACCOUNTS)).isTrue();
-        assertThat(guestRestrictions.getBoolean(UserManager.DISALLOW_INSTALL_APPS)).isTrue();
-        assertThat(guestRestrictions.getBoolean(UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES))
-                .isTrue();
-        assertThat(guestRestrictions.getBoolean(UserManager.DISALLOW_UNINSTALL_APPS)).isTrue();
     }
 
     @Test
@@ -701,146 +181,6 @@
     }
 
     @Test
-    public void testRegisterUserChangeReceiver() {
-        mCarUserManagerHelper.registerOnUsersUpdateListener(mTestListener);
-
-        ArgumentCaptor<BroadcastReceiver> receiverCaptor =
-                ArgumentCaptor.forClass(BroadcastReceiver.class);
-        ArgumentCaptor<UserHandle> handleCaptor = ArgumentCaptor.forClass(UserHandle.class);
-        ArgumentCaptor<IntentFilter> filterCaptor = ArgumentCaptor.forClass(IntentFilter.class);
-        ArgumentCaptor<String> permissionCaptor = ArgumentCaptor.forClass(String.class);
-        ArgumentCaptor<Handler> handlerCaptor = ArgumentCaptor.forClass(Handler.class);
-
-        verify(mContext).registerReceiverAsUser(
-                receiverCaptor.capture(),
-                handleCaptor.capture(),
-                filterCaptor.capture(),
-                permissionCaptor.capture(),
-                handlerCaptor.capture());
-
-        // Verify we're listening to Intents from ALL users.
-        assertThat(handleCaptor.getValue()).isEqualTo(UserHandle.ALL);
-
-        // Verify the presence of each intent in the filter.
-        // Verify the exact number of filters. Every time a new intent is added, this test should
-        // get updated.
-        assertThat(filterCaptor.getValue().countActions()).isEqualTo(6);
-        assertThat(filterCaptor.getValue().hasAction(Intent.ACTION_USER_REMOVED)).isTrue();
-        assertThat(filterCaptor.getValue().hasAction(Intent.ACTION_USER_ADDED)).isTrue();
-        assertThat(filterCaptor.getValue().hasAction(Intent.ACTION_USER_INFO_CHANGED)).isTrue();
-        assertThat(filterCaptor.getValue().hasAction(Intent.ACTION_USER_SWITCHED)).isTrue();
-        assertThat(filterCaptor.getValue().hasAction(Intent.ACTION_USER_STOPPED)).isTrue();
-        assertThat(filterCaptor.getValue().hasAction(Intent.ACTION_USER_UNLOCKED)).isTrue();
-
-        // Verify that calling the receiver calls the listener.
-        receiverCaptor.getValue().onReceive(mContext, new Intent());
-        verify(mTestListener).onUsersUpdate();
-
-        assertThat(permissionCaptor.getValue()).isNull();
-        assertThat(handlerCaptor.getValue()).isNull();
-
-        // Unregister the receiver.
-        mCarUserManagerHelper.unregisterOnUsersUpdateListener(mTestListener);
-        verify(mContext).unregisterReceiver(receiverCaptor.getValue());
-    }
-
-    @Test
-    public void testMultipleRegistrationsOfSameListener() {
-        CarUserManagerHelper.OnUsersUpdateListener listener =
-                Mockito.mock(CarUserManagerHelper.OnUsersUpdateListener.class);
-
-        ArgumentCaptor<BroadcastReceiver> receiverCaptor =
-                ArgumentCaptor.forClass(BroadcastReceiver.class);
-
-        mCarUserManagerHelper.registerOnUsersUpdateListener(listener);
-        mCarUserManagerHelper.registerOnUsersUpdateListener(listener);
-        // Even for multiple registrations of the same listener, broadcast receiver registered once.
-        verify(mContext, times(1))
-                .registerReceiverAsUser(receiverCaptor.capture(), any(), any(), any(), any());
-
-        // Verify that calling the receiver calls the listener.
-        receiverCaptor.getValue().onReceive(mContext, new Intent());
-        verify(listener).onUsersUpdate();
-
-        // Verify that a single removal unregisters the listener.
-        mCarUserManagerHelper.unregisterOnUsersUpdateListener(listener);
-        verify(mContext).unregisterReceiver(any());
-    }
-
-    @Test
-    public void testMultipleUnregistrationsOfTheSameListener() {
-        CarUserManagerHelper.OnUsersUpdateListener listener =
-                Mockito.mock(CarUserManagerHelper.OnUsersUpdateListener.class);
-        mCarUserManagerHelper.registerOnUsersUpdateListener(listener);
-
-        // Verify that a multiple unregistrations cause only one unregister for broadcast receiver.
-        mCarUserManagerHelper.unregisterOnUsersUpdateListener(listener);
-        mCarUserManagerHelper.unregisterOnUsersUpdateListener(listener);
-        mCarUserManagerHelper.unregisterOnUsersUpdateListener(listener);
-        verify(mContext, times(1)).unregisterReceiver(any());
-    }
-
-    @Test
-    public void testUnregisterReceiverCalledAfterAllListenersUnregister() {
-        CarUserManagerHelper.OnUsersUpdateListener listener1 =
-                Mockito.mock(CarUserManagerHelper.OnUsersUpdateListener.class);
-        CarUserManagerHelper.OnUsersUpdateListener listener2 =
-                Mockito.mock(CarUserManagerHelper.OnUsersUpdateListener.class);
-
-        mCarUserManagerHelper.registerOnUsersUpdateListener(listener1);
-        mCarUserManagerHelper.registerOnUsersUpdateListener(listener2);
-
-        mCarUserManagerHelper.unregisterOnUsersUpdateListener(listener1);
-        verify(mContext, never()).unregisterReceiver(any());
-
-        mCarUserManagerHelper.unregisterOnUsersUpdateListener(listener2);
-        verify(mContext, times(1)).unregisterReceiver(any());
-    }
-
-    @Test
-    public void testRegisteringMultipleListeners() {
-        CarUserManagerHelper.OnUsersUpdateListener listener1 =
-                Mockito.mock(CarUserManagerHelper.OnUsersUpdateListener.class);
-        CarUserManagerHelper.OnUsersUpdateListener listener2 =
-                Mockito.mock(CarUserManagerHelper.OnUsersUpdateListener.class);
-        ArgumentCaptor<BroadcastReceiver> receiverCaptor =
-                ArgumentCaptor.forClass(BroadcastReceiver.class);
-
-        mCarUserManagerHelper.registerOnUsersUpdateListener(listener1);
-        mCarUserManagerHelper.registerOnUsersUpdateListener(listener2);
-        verify(mContext, times(1))
-                .registerReceiverAsUser(receiverCaptor.capture(), any(), any(), any(), any());
-
-        // Verify that calling the receiver calls both listeners.
-        receiverCaptor.getValue().onReceive(mContext, new Intent());
-        verify(listener1).onUsersUpdate();
-        verify(listener2).onUsersUpdate();
-    }
-
-    @Test
-    public void testUnregisteringListenerStopsUpdatesForListener() {
-        CarUserManagerHelper.OnUsersUpdateListener listener1 =
-                Mockito.mock(CarUserManagerHelper.OnUsersUpdateListener.class);
-        CarUserManagerHelper.OnUsersUpdateListener listener2 =
-                Mockito.mock(CarUserManagerHelper.OnUsersUpdateListener.class);
-        ArgumentCaptor<BroadcastReceiver> receiverCaptor =
-                ArgumentCaptor.forClass(BroadcastReceiver.class);
-
-        mCarUserManagerHelper.registerOnUsersUpdateListener(listener1);
-        mCarUserManagerHelper.registerOnUsersUpdateListener(listener2);
-        verify(mContext, times(1))
-                .registerReceiverAsUser(receiverCaptor.capture(), any(), any(), any(), any());
-
-        // Unregister listener2
-        mCarUserManagerHelper.unregisterOnUsersUpdateListener(listener2);
-
-        // Verify that calling the receiver calls only one listener.
-        receiverCaptor.getValue().onReceive(mContext, new Intent());
-        verify(listener1).onUsersUpdate();
-        verify(listener2, never()).onUsersUpdate();
-    }
-
-    @Test
     public void test_GetInitialUserWithValidLastActiveUser_ReturnsLastActiveUser() {
         int lastActiveUserId = 12;
 
@@ -869,6 +209,7 @@
     }
 
     @Test
+    @FlakyTest
     public void test_GetInitialUserWithOverrideId_ReturnsOverrideId() {
         int lastActiveUserId = 12;
         int overrideUserId = 11;
@@ -917,37 +258,6 @@
         assertThat(mCarUserManagerHelper.getInitialUser()).isEqualTo(minimumUserId);
     }
 
-    @Test
-    public void test_CreateNewOrFindExistingGuest_ReturnsExistingGuest() {
-        // Create two users and a guest user.
-        UserInfo user1 = createUserInfoForId(10);
-        UserInfo user2 = createUserInfoForId(12);
-        UserInfo user3 = new UserInfo(/* id= */ 13, /* name = */ "user13", UserInfo.FLAG_GUEST);
-
-        mockGetUsers(user1, user2, user3);
-        doReturn(null).when(mUserManager).createGuest(any(), any());
-
-        UserInfo guest = mCarUserManagerHelper.createNewOrFindExistingGuest(GUEST_USER_NAME);
-        assertThat(guest).isEqualTo(user3);
-    }
-
-    @Test
-    public void test_CreateNewOrFindExistingGuest_CreatesNewGuest_IfNoExisting() {
-        // Create two users.
-        UserInfo user1 = createUserInfoForId(10);
-        UserInfo user2 = createUserInfoForId(12);
-
-        mockGetUsers(user1, user2);
-
-        // Create a user for the "new guest" user.
-        UserInfo guestInfo = new UserInfo(/* id= */21, GUEST_USER_NAME, UserInfo.FLAG_GUEST);
-        doReturn(guestInfo).when(mUserManager).createGuest(mContext, GUEST_USER_NAME);
-
-        UserInfo guest = mCarUserManagerHelper.createNewOrFindExistingGuest(GUEST_USER_NAME);
-        verify(mUserManager).createGuest(mContext, GUEST_USER_NAME);
-        assertThat(guest).isEqualTo(guestInfo);
-    }
-
     private UserInfo createUserInfoForId(int id) {
         UserInfo userInfo = new UserInfo();
         userInfo.id = id;
@@ -975,4 +285,4 @@
     private void setMaxSupportedUsers(int maxValue) {
         doReturn(maxValue).when(mTestableFrameworkWrapper).userManagerGetMaxSupportedUsers();
     }
-}
\ No newline at end of file
+}
diff --git a/tests/carservice_unit_test/src/android/car/userlib/OWNERS b/tests/carservice_unit_test/src/android/car/userlib/OWNERS
new file mode 100644
index 0000000..2669705
--- /dev/null
+++ b/tests/carservice_unit_test/src/android/car/userlib/OWNERS
@@ -0,0 +1,5 @@
+# Library owners
+ahugh@google.com
+jovanak@google.com
+yizheng@google.com
+
diff --git a/tests/carservice_unit_test/src/android/car/vms/VmsSubscriptionHelperTest.java b/tests/carservice_unit_test/src/android/car/vms/VmsSubscriptionHelperTest.java
new file mode 100644
index 0000000..d12447c
--- /dev/null
+++ b/tests/carservice_unit_test/src/android/car/vms/VmsSubscriptionHelperTest.java
@@ -0,0 +1,311 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.car.vms;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.assertThrows;
+
+import static java.util.Collections.emptySet;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class VmsSubscriptionHelperTest {
+    private static final VmsLayer LAYER1 = new VmsLayer(1, 1, 1);
+    private static final VmsLayer LAYER2 = new VmsLayer(2, 1, 1);
+    private static final VmsLayer LAYER3 = new VmsLayer(3, 1, 1);
+
+    private static final int PROVIDER_ID1 = 12345;
+    private static final int PROVIDER_ID2 = 54321;
+    private static final int PROVIDER_ID3 = 99999;
+
+    private final VmsSubscriptionHelper mSubscriptionHelper =
+            new VmsSubscriptionHelper(this::handleUpdate);
+
+    private Set<VmsAssociatedLayer> mSubscriptionUpdate;
+    private int mSubscriptionUpdateCount;
+    private boolean mUpdateThrowsException;
+
+    @Test
+    public void testSubscribe_SingleLayer() {
+        mSubscriptionHelper.subscribe(LAYER1);
+
+        assertThat(mSubscriptionUpdateCount).isEqualTo(1);
+        assertSubscriptions(new VmsAssociatedLayer(LAYER1, emptySet()));
+    }
+
+    @Test
+    public void testSubscribe_SingleLayer_IgnoreDuplicates() {
+        mSubscriptionHelper.subscribe(LAYER1);
+        mSubscriptionHelper.subscribe(LAYER1);
+
+        assertThat(mSubscriptionUpdateCount).isEqualTo(1);
+        assertSubscriptions(new VmsAssociatedLayer(LAYER1, emptySet()));
+    }
+
+    @Test
+    public void testSubscribe_SingleLayer_RetryAfterException() {
+        mUpdateThrowsException = true;
+        assertThrows(
+                UpdateHandlerException.class,
+                () -> mSubscriptionHelper.subscribe(LAYER1));
+
+        mUpdateThrowsException = false;
+        mSubscriptionHelper.subscribe(LAYER1);
+
+        assertThat(mSubscriptionUpdateCount).isEqualTo(2);
+        assertSubscriptions(new VmsAssociatedLayer(LAYER1, emptySet()));
+    }
+
+    @Test
+    public void testUnsubscribe_SingleLayer() {
+        mSubscriptionHelper.subscribe(LAYER1);
+        mSubscriptionHelper.unsubscribe(LAYER1);
+
+        assertThat(mSubscriptionUpdateCount).isEqualTo(2);
+        assertEmptySubscriptions();
+    }
+
+    @Test
+    public void testUnsubscribe_SingleLayer_IgnoreUnknown() {
+        mSubscriptionHelper.subscribe(LAYER1);
+        mSubscriptionHelper.unsubscribe(LAYER2);
+
+        assertThat(mSubscriptionUpdateCount).isEqualTo(1);
+        assertSubscriptions(new VmsAssociatedLayer(LAYER1, emptySet()));
+    }
+
+    @Test
+    public void testUnsubscribe_SingleLayer_RetryAfterException() {
+        mSubscriptionHelper.subscribe(LAYER1);
+        mUpdateThrowsException = true;
+        assertThrows(
+                UpdateHandlerException.class,
+                () -> mSubscriptionHelper.unsubscribe(LAYER1));
+
+        mUpdateThrowsException = false;
+        mSubscriptionHelper.unsubscribe(LAYER1);
+
+        assertThat(mSubscriptionUpdateCount).isEqualTo(3);
+        assertEmptySubscriptions();
+    }
+
+    @Test
+    public void testSubscribe_MultipleLayers() {
+        mSubscriptionHelper.subscribe(LAYER1);
+        mSubscriptionHelper.subscribe(LAYER2);
+        mSubscriptionHelper.subscribe(LAYER3);
+
+        assertThat(mSubscriptionUpdateCount).isEqualTo(3);
+        assertSubscriptions(
+                new VmsAssociatedLayer(LAYER1, emptySet()),
+                new VmsAssociatedLayer(LAYER2, emptySet()),
+                new VmsAssociatedLayer(LAYER3, emptySet()));
+    }
+
+    @Test
+    public void testUnsubscribe_MultipleLayers() {
+        mSubscriptionHelper.subscribe(LAYER1);
+        mSubscriptionHelper.subscribe(LAYER2);
+        mSubscriptionHelper.subscribe(LAYER3);
+        mSubscriptionHelper.unsubscribe(LAYER2);
+
+        assertThat(mSubscriptionUpdateCount).isEqualTo(4);
+        assertSubscriptions(
+                new VmsAssociatedLayer(LAYER1, emptySet()),
+                new VmsAssociatedLayer(LAYER3, emptySet()));
+    }
+
+    @Test
+    public void testSubscribe_SingleLayerAndProvider() {
+        mSubscriptionHelper.subscribe(LAYER1, PROVIDER_ID1);
+
+        assertThat(mSubscriptionUpdateCount).isEqualTo(1);
+        assertSubscriptions(new VmsAssociatedLayer(LAYER1, asSet(PROVIDER_ID1)));
+    }
+
+    @Test
+    public void testSubscribe_SingleLayerAndProvider_IgnoreDuplicates() {
+        mSubscriptionHelper.subscribe(LAYER1, PROVIDER_ID1);
+        mSubscriptionHelper.subscribe(LAYER1, PROVIDER_ID1);
+
+        assertThat(mSubscriptionUpdateCount).isEqualTo(1);
+        assertSubscriptions(new VmsAssociatedLayer(LAYER1, asSet(PROVIDER_ID1)));
+    }
+
+    @Test
+    public void testSubscribe_SingleLayerAndProvider_RetryAfterException() {
+        mUpdateThrowsException = true;
+        assertThrows(
+                UpdateHandlerException.class,
+                () -> mSubscriptionHelper.subscribe(LAYER1, PROVIDER_ID1));
+
+        mUpdateThrowsException = false;
+        mSubscriptionHelper.subscribe(LAYER1, PROVIDER_ID1);
+
+        assertThat(mSubscriptionUpdateCount).isEqualTo(2);
+        assertSubscriptions(new VmsAssociatedLayer(LAYER1, asSet(PROVIDER_ID1)));
+    }
+
+    @Test
+    public void testUnsubscribe_SingleLayerAndProvider() {
+        mSubscriptionHelper.subscribe(LAYER1, PROVIDER_ID1);
+        mSubscriptionHelper.unsubscribe(LAYER1, PROVIDER_ID1);
+
+        assertThat(mSubscriptionUpdateCount).isEqualTo(2);
+        assertEmptySubscriptions();
+    }
+
+    @Test
+    public void testUnsubscribe_SingleLayerAndProvider_IgnoreUnknown() {
+        mSubscriptionHelper.subscribe(LAYER1, PROVIDER_ID1);
+        mSubscriptionHelper.unsubscribe(LAYER1, PROVIDER_ID2);
+
+        assertThat(mSubscriptionUpdateCount).isEqualTo(1);
+        assertSubscriptions(new VmsAssociatedLayer(LAYER1, asSet(PROVIDER_ID1)));
+    }
+
+    @Test
+    public void testUnubscribe_SingleLayerAndProvider_RetryAfterException() {
+        mSubscriptionHelper.subscribe(LAYER1, PROVIDER_ID1);
+        mUpdateThrowsException = true;
+        assertThrows(
+                UpdateHandlerException.class,
+                () -> mSubscriptionHelper.unsubscribe(LAYER1, PROVIDER_ID1));
+
+        mUpdateThrowsException = false;
+        mSubscriptionHelper.unsubscribe(LAYER1, PROVIDER_ID1);
+
+        assertThat(mSubscriptionUpdateCount).isEqualTo(3);
+        assertEmptySubscriptions();
+    }
+
+    @Test
+    public void testSubscribe_SingleLayerAndMultipleProviders() {
+        mSubscriptionHelper.subscribe(LAYER1, PROVIDER_ID1);
+        mSubscriptionHelper.subscribe(LAYER1, PROVIDER_ID2);
+        mSubscriptionHelper.subscribe(LAYER1, PROVIDER_ID3);
+
+        assertThat(mSubscriptionUpdateCount).isEqualTo(3);
+        assertSubscriptions(
+                new VmsAssociatedLayer(LAYER1, asSet(PROVIDER_ID1, PROVIDER_ID2, PROVIDER_ID3)));
+    }
+
+    @Test
+    public void testUnsubscribe_SingleLayerAndMultipleProviders() {
+        mSubscriptionHelper.subscribe(LAYER1, PROVIDER_ID1);
+        mSubscriptionHelper.subscribe(LAYER1, PROVIDER_ID2);
+        mSubscriptionHelper.subscribe(LAYER1, PROVIDER_ID3);
+        mSubscriptionHelper.unsubscribe(LAYER1, PROVIDER_ID2);
+
+        assertThat(mSubscriptionUpdateCount).isEqualTo(4);
+        assertSubscriptions(new VmsAssociatedLayer(LAYER1, asSet(PROVIDER_ID1, PROVIDER_ID3)));
+    }
+
+    @Test
+    public void testSubscribe_MultipleLayersAndProvider() {
+        mSubscriptionHelper.subscribe(LAYER1, PROVIDER_ID1);
+        mSubscriptionHelper.subscribe(LAYER2, PROVIDER_ID1);
+        mSubscriptionHelper.subscribe(LAYER3, PROVIDER_ID1);
+
+        assertThat(mSubscriptionUpdateCount).isEqualTo(3);
+        assertSubscriptions(
+                new VmsAssociatedLayer(LAYER1, asSet(PROVIDER_ID1)),
+                new VmsAssociatedLayer(LAYER2, asSet(PROVIDER_ID1)),
+                new VmsAssociatedLayer(LAYER3, asSet(PROVIDER_ID1)));
+    }
+
+    @Test
+    public void testUnsubscribe_MultipleLayersAndProvider() {
+        mSubscriptionHelper.subscribe(LAYER1, PROVIDER_ID1);
+        mSubscriptionHelper.subscribe(LAYER2, PROVIDER_ID1);
+        mSubscriptionHelper.subscribe(LAYER3, PROVIDER_ID1);
+        mSubscriptionHelper.unsubscribe(LAYER2, PROVIDER_ID1);
+
+        assertThat(mSubscriptionUpdateCount).isEqualTo(4);
+        assertSubscriptions(
+                new VmsAssociatedLayer(LAYER1, asSet(PROVIDER_ID1)),
+                new VmsAssociatedLayer(LAYER3, asSet(PROVIDER_ID1)));
+    }
+
+    @Test
+    public void testSubscribe_MultipleLayersAndMultipleProviders() {
+        mSubscriptionHelper.subscribe(LAYER1, PROVIDER_ID1);
+        mSubscriptionHelper.subscribe(LAYER1, PROVIDER_ID2);
+        mSubscriptionHelper.subscribe(LAYER1, PROVIDER_ID3);
+        mSubscriptionHelper.subscribe(LAYER2, PROVIDER_ID2);
+        mSubscriptionHelper.subscribe(LAYER3, PROVIDER_ID3);
+
+        assertThat(mSubscriptionUpdateCount).isEqualTo(5);
+        assertSubscriptions(
+                new VmsAssociatedLayer(LAYER1, asSet(PROVIDER_ID1, PROVIDER_ID2, PROVIDER_ID3)),
+                new VmsAssociatedLayer(LAYER2, asSet(PROVIDER_ID2)),
+                new VmsAssociatedLayer(LAYER3, asSet(PROVIDER_ID3))
+        );
+    }
+
+    @Test
+    public void testUnsubscribe_MultipleLayersAndMultipleProviders() {
+        mSubscriptionHelper.subscribe(LAYER1, PROVIDER_ID1);
+        mSubscriptionHelper.subscribe(LAYER1, PROVIDER_ID2);
+        mSubscriptionHelper.subscribe(LAYER1, PROVIDER_ID3);
+        mSubscriptionHelper.subscribe(LAYER2, PROVIDER_ID2);
+        mSubscriptionHelper.subscribe(LAYER3, PROVIDER_ID3);
+        mSubscriptionHelper.unsubscribe(LAYER1, PROVIDER_ID2);
+        mSubscriptionHelper.unsubscribe(LAYER3, PROVIDER_ID3);
+
+        assertThat(mSubscriptionUpdateCount).isEqualTo(7);
+        assertSubscriptions(
+                new VmsAssociatedLayer(LAYER1, asSet(PROVIDER_ID1, PROVIDER_ID3)),
+                new VmsAssociatedLayer(LAYER2, asSet(PROVIDER_ID2)));
+    }
+
+    private void handleUpdate(Set<VmsAssociatedLayer> subscriptionUpdate) {
+        mSubscriptionUpdate = subscriptionUpdate;
+        mSubscriptionUpdateCount++;
+        if (mUpdateThrowsException) {
+            throw new UpdateHandlerException();
+        }
+    }
+
+    private void assertEmptySubscriptions() {
+        assertSubscriptions();
+    }
+
+    private void assertSubscriptions(VmsAssociatedLayer... associatedLayers) {
+        Set<VmsAssociatedLayer> subscriptions = asSet(associatedLayers);
+        assertThat(mSubscriptionUpdate).isEqualTo(subscriptions);
+        assertThat(mSubscriptionHelper.getSubscriptions()).isEqualTo(subscriptions);
+    }
+
+    @SafeVarargs
+    private static <T> Set<T> asSet(T... values) {
+        return new HashSet<>(Arrays.asList(values));
+    }
+
+    private static class UpdateHandlerException extends RuntimeException {}
+}
diff --git a/tests/carservice_unit_test/src/com/android/car/BluetoothDeviceConnectionPolicyTest.java b/tests/carservice_unit_test/src/com/android/car/BluetoothDeviceConnectionPolicyTest.java
index f84d5f4..52b6e36 100644
--- a/tests/carservice_unit_test/src/com/android/car/BluetoothDeviceConnectionPolicyTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/BluetoothDeviceConnectionPolicyTest.java
@@ -16,7 +16,12 @@
 
 package com.android.car;
 
-import static org.mockito.Mockito.*;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
 import android.bluetooth.BluetoothAdapter;
 import android.car.hardware.power.CarPowerManager;
@@ -31,7 +36,7 @@
 import android.test.mock.MockContentProvider;
 import android.test.mock.MockContentResolver;
 
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.filters.RequiresDevice;
 
 import org.junit.After;
 import org.junit.Assert;
@@ -39,8 +44,8 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
 import org.mockito.invocation.InvocationOnMock;
+import org.mockito.junit.MockitoJUnitRunner;
 import org.mockito.stubbing.Answer;
 
 /**
@@ -49,7 +54,8 @@
  * Run:
  * atest BluetoothDeviceConnectionPolicyTest
  */
-@RunWith(AndroidJUnit4.class)
+@RequiresDevice
+@RunWith(MockitoJUnitRunner.class)
 public class BluetoothDeviceConnectionPolicyTest {
     private BluetoothDeviceConnectionPolicy mPolicy;
 
@@ -71,7 +77,7 @@
 
     @Before
     public void setUp() {
-        mMockContentResolver = new MockContentResolver(mMockContext);
+        mMockContentResolver = new MockContentResolver(null);
         mMockContentProvider = new MockContentProvider() {
             @Override
             public Bundle call(String method, String request, Bundle args) {
@@ -80,7 +86,6 @@
         };
         mMockContentResolver.addProvider(Settings.AUTHORITY, mMockContentProvider);
 
-        MockitoAnnotations.initMocks(this);
         when(mMockContext.getResources()).thenReturn(mMockResources);
         when(mMockContext.getContentResolver()).thenReturn(mMockContentResolver);
         when(mMockContext.getApplicationContext()).thenReturn(mMockContext);
@@ -109,17 +114,8 @@
 
     @After
     public void tearDown() {
-        mPowerStateListener = null;
         mPolicy.release();
-        mPolicy = null;
         mBluetoothAdapterHelper.release();
-        mBluetoothAdapterHelper = null;
-        mReceiver = null;
-        mMockBluetoothService = null;
-        mMockResources = null;
-        mMockContext = null;
-        mMockContentProvider = null;
-        mMockContentResolver = null;
     }
 
     //--------------------------------------------------------------------------------------------//
diff --git a/tests/carservice_unit_test/src/com/android/car/BluetoothProfileDeviceManagerTest.java b/tests/carservice_unit_test/src/com/android/car/BluetoothProfileDeviceManagerTest.java
index b5837fe..0cc684f 100644
--- a/tests/carservice_unit_test/src/com/android/car/BluetoothProfileDeviceManagerTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/BluetoothProfileDeviceManagerTest.java
@@ -41,7 +41,7 @@
 import android.text.TextUtils;
 
 import androidx.test.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.filters.RequiresDevice;
 
 import com.android.internal.util.test.BroadcastInterceptingContext;
 import com.android.internal.util.test.FakeSettingsProvider;
@@ -53,8 +53,8 @@
 import org.junit.runner.RunWith;
 import org.mockito.InOrder;
 import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
 import org.mockito.invocation.InvocationOnMock;
+import org.mockito.junit.MockitoJUnitRunner;
 import org.mockito.stubbing.Answer;
 
 import java.util.ArrayList;
@@ -68,7 +68,8 @@
  * Run:
  * atest BluetoothProfileDeviceManagerTest
  */
-@RunWith(AndroidJUnit4.class)
+@RequiresDevice
+@RunWith(MockitoJUnitRunner.class)
 public class BluetoothProfileDeviceManagerTest {
     private static final int CONNECT_LATENCY_MS = 100;
     private static final int CONNECT_TIMEOUT_MS = 8000;
@@ -105,7 +106,7 @@
     private final String mSettingsKey = KEY_BLUETOOTH_HFP_CLIENT_DEVICES;
     private final String mConnectionAction = BluetoothHeadsetClient.ACTION_CONNECTION_STATE_CHANGED;
     private ParcelUuid[] mUuids = new ParcelUuid[] {
-            BluetoothUuid.Handsfree_AG,
+            BluetoothUuid.HFP_AG,
             BluetoothUuid.HSP_AG};
     private ParcelUuid[] mBadUuids = new ParcelUuid[] {
             BluetoothUuid.PANU};
@@ -158,9 +159,6 @@
 
     @Before
     public void setUp() {
-
-        MockitoAnnotations.initMocks(this);
-
         mMockContext = new MockContext(InstrumentationRegistry.getTargetContext());
         setSettingsDeviceList("");
         assertSettingsContains("");
diff --git a/tests/carservice_unit_test/src/com/android/car/CarBluetoothServiceTest.java b/tests/carservice_unit_test/src/com/android/car/CarBluetoothServiceTest.java
index 4b5f9bb..3d95204 100644
--- a/tests/carservice_unit_test/src/com/android/car/CarBluetoothServiceTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/CarBluetoothServiceTest.java
@@ -16,9 +16,11 @@
 
 package com.android.car;
 
-import static org.mockito.Mockito.*;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.when;
 
-import android.car.ICarUserService;
+import android.car.IPerUserCarService;
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.content.res.Resources;
@@ -28,16 +30,14 @@
 import android.test.mock.MockContentProvider;
 import android.test.mock.MockContentResolver;
 
-import androidx.test.runner.AndroidJUnit4;
-
 import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
 import org.mockito.invocation.InvocationOnMock;
+import org.mockito.junit.MockitoJUnitRunner;
 import org.mockito.stubbing.Answer;
 
 /**
@@ -52,9 +52,8 @@
  * 2) Verify that, when the useDefaultConnectionPolicy resource overlay flag is false, we do not
  *    create and use the default connection policy.
  */
-@RunWith(AndroidJUnit4.class)
+@RunWith(MockitoJUnitRunner.class)
 public class CarBluetoothServiceTest {
-
     private CarBluetoothService mCarBluetoothService;
 
     @Mock private Context mMockContext;
@@ -64,7 +63,7 @@
     @Mock private PackageManager mMockPackageManager;
 
     @Mock private PerUserCarServiceHelper mMockUserSwitchService;
-    @Mock private ICarUserService mMockCarUserService;
+    @Mock private IPerUserCarService mMockPerUserCarService;
     @Mock private CarBluetoothUserService mMockBluetoothUserService;
     private PerUserCarServiceHelper.ServiceCallback mUserSwitchCallback;
 
@@ -74,7 +73,7 @@
 
     @Before
     public void setUp() {
-        mMockContentResolver = new MockContentResolver(mMockContext);
+        mMockContentResolver = new MockContentResolver(null);
         mMockContentProvider = new MockContentProvider() {
             @Override
             public Bundle call(String method, String request, Bundle args) {
@@ -83,7 +82,6 @@
         };
         mMockContentResolver.addProvider(Settings.AUTHORITY, mMockContentProvider);
 
-        MockitoAnnotations.initMocks(this);
         when(mMockContext.getResources()).thenReturn(mMockResources);
         when(mMockContext.getContentResolver()).thenReturn(mMockContentResolver);
         when(mMockContext.getApplicationContext()).thenReturn(mMockContext);
@@ -105,7 +103,7 @@
                 PerUserCarServiceHelper.ServiceCallback.class));
 
         try {
-            when(mMockCarUserService.getBluetoothUserService()).thenReturn(
+            when(mMockPerUserCarService.getBluetoothUserService()).thenReturn(
                     mMockBluetoothUserService);
         } catch (RemoteException e) {
             Assert.fail();
@@ -140,7 +138,7 @@
                 R.bool.useDefaultBluetoothConnectionPolicy)).thenReturn(true);
         mCarBluetoothService = new CarBluetoothService(mMockContext, mMockUserSwitchService);
         mCarBluetoothService.init();
-        mUserSwitchCallback.onServiceConnected(mMockCarUserService);
+        mUserSwitchCallback.onServiceConnected(mMockPerUserCarService);
         Assert.assertTrue(mCarBluetoothService.isUsingDefaultConnectionPolicy());
     }
 
@@ -160,7 +158,7 @@
                 R.bool.useDefaultBluetoothConnectionPolicy)).thenReturn(false);
         mCarBluetoothService = new CarBluetoothService(mMockContext, mMockUserSwitchService);
         mCarBluetoothService.init();
-        mUserSwitchCallback.onServiceConnected(mMockCarUserService);
+        mUserSwitchCallback.onServiceConnected(mMockPerUserCarService);
         Assert.assertFalse(mCarBluetoothService.isUsingDefaultConnectionPolicy());
     }
 }
diff --git a/tests/carservice_unit_test/src/com/android/car/CarConfigurationServiceTest.java b/tests/carservice_unit_test/src/com/android/car/CarConfigurationServiceTest.java
index 9254a56..e39f5c8 100644
--- a/tests/carservice_unit_test/src/com/android/car/CarConfigurationServiceTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/CarConfigurationServiceTest.java
@@ -29,25 +29,18 @@
 import android.car.settings.SpeedBumpConfiguration;
 
 import androidx.test.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
 
 import org.json.JSONException;
 import org.json.JSONObject;
-import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.mockito.MockitoAnnotations;
+import org.mockito.junit.MockitoJUnitRunner;
 
 /**
  * Tests for {@link CarConfigurationService}.
  */
-@RunWith(AndroidJUnit4.class)
+@RunWith(MockitoJUnitRunner.class)
 public class CarConfigurationServiceTest {
-    @Before
-    public void setUp() {
-        MockitoAnnotations.initMocks(this);
-    }
-
     @Test
     public void testJsonResourceSuccessfullyRead() {
         // Use the default JsonReader to check that the resource JSON can be retrieved.
diff --git a/tests/carservice_unit_test/src/com/android/car/CarInputServiceTest.java b/tests/carservice_unit_test/src/com/android/car/CarInputServiceTest.java
index b32df7f..9f360e1 100644
--- a/tests/carservice_unit_test/src/com/android/car/CarInputServiceTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/CarInputServiceTest.java
@@ -53,7 +53,6 @@
 import android.view.KeyEvent;
 
 import androidx.test.core.app.ApplicationProvider;
-import androidx.test.runner.AndroidJUnit4;
 
 import com.android.car.hal.InputHalService;
 import com.android.internal.app.AssistUtils;
@@ -61,22 +60,19 @@
 import com.google.common.collect.Range;
 
 import org.junit.Before;
-import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 import org.mockito.Spy;
-import org.mockito.junit.MockitoJUnit;
-import org.mockito.junit.MockitoRule;
+import org.mockito.junit.MockitoJUnitRunner;
 
 import java.util.BitSet;
 import java.util.function.IntSupplier;
 import java.util.function.Supplier;
 
-@RunWith(AndroidJUnit4.class)
+@RunWith(MockitoJUnitRunner.class)
 public class CarInputServiceTest {
-    @Rule public MockitoRule rule = MockitoJUnit.rule();
 
     @Mock InputHalService mInputHalService;
     @Mock TelecomManager mTelecomManager;
diff --git a/tests/carservice_unit_test/src/com/android/car/CarLocationServiceTest.java b/tests/carservice_unit_test/src/com/android/car/CarLocationServiceTest.java
index 5384005..8063d61 100644
--- a/tests/carservice_unit_test/src/com/android/car/CarLocationServiceTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/CarLocationServiceTest.java
@@ -31,11 +31,10 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
-import android.car.ICarUserService;
+import android.car.IPerUserCarService;
 import android.car.drivingstate.CarDrivingStateEvent;
 import android.car.drivingstate.ICarDrivingStateChangeListener;
 import android.car.hardware.power.CarPowerManager.CarPowerStateListener;
-import android.car.userlib.CarUserManagerHelper;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
@@ -43,9 +42,9 @@
 import android.location.Location;
 import android.location.LocationManager;
 import android.os.SystemClock;
+import android.os.UserManager;
 
 import androidx.test.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
 
 import com.android.car.systeminterface.SystemInterface;
 import com.android.car.test.utils.TemporaryDirectory;
@@ -56,7 +55,7 @@
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
+import org.mockito.junit.MockitoJUnitRunner;
 
 import java.io.BufferedReader;
 import java.io.File;
@@ -75,14 +74,13 @@
  *
  * The following mocks are used:
  * 1. {@link Context} registers intent receivers.
- * 2. {@link CarUserManagerHelper} tells whether or not the system user is headless.
- * 3. {@link SystemInterface} tells where to store system files.
- * 4. {@link CarDrivingStateService} tells about driving state changes.
- * 5. {@link PerUserCarServiceHelper} provides a mocked {@link ICarUserService}.
- * 6. {@link ICarUserService} provides a mocked {@link LocationManagerProxy}.
- * 7. {@link LocationManagerProxy} provides dummy {@link Location}s.
+ * 2. {@link SystemInterface} tells where to store system files.
+ * 3. {@link CarDrivingStateService} tells about driving state changes.
+ * 4. {@link PerUserCarServiceHelper} provides a mocked {@link IPerUserCarService}.
+ * 5. {@link IPerUserCarService} provides a mocked {@link LocationManagerProxy}.
+ * 6. {@link LocationManagerProxy} provides dummy {@link Location}s.
  */
-@RunWith(AndroidJUnit4.class)
+@RunWith(MockitoJUnitRunner.class)
 public class CarLocationServiceTest {
     private static final String TAG = "CarLocationServiceTest";
     private static final String TEST_FILENAME = "location_cache.json";
@@ -96,26 +94,23 @@
     @Mock
     private LocationManagerProxy mMockLocationManagerProxy;
     @Mock
-    private CarUserManagerHelper mMockCarUserManagerHelper;
-    @Mock
     private SystemInterface mMockSystemInterface;
     @Mock
     private CarDrivingStateService mMockCarDrivingStateService;
     @Mock
     private PerUserCarServiceHelper mMockPerUserCarServiceHelper;
     @Mock
-    private ICarUserService mMockICarUserService;
+    private IPerUserCarService mMockIPerUserCarService;
 
     /**
      * Initialize all of the objects with the @Mock annotation.
      */
     @Before
     public void setUp() throws Exception {
-        MockitoAnnotations.initMocks(this);
         mContext = InstrumentationRegistry.getTargetContext();
         mTempDirectory = new TemporaryDirectory(TAG).getDirectory();
         mLatch = new CountDownLatch(1);
-        mCarLocationService = new CarLocationService(mMockContext, mMockCarUserManagerHelper) {
+        mCarLocationService = new CarLocationService(mMockContext) {
             @Override
             void asyncOperation(Runnable operation) {
                 super.asyncOperation(() -> {
@@ -131,10 +126,13 @@
         CarLocalServices.removeServiceForTest(PerUserCarServiceHelper.class);
         CarLocalServices.addService(PerUserCarServiceHelper.class, mMockPerUserCarServiceHelper);
         when(mMockSystemInterface.getSystemCarDir()).thenReturn(mTempDirectory);
-        when(mMockICarUserService.getLocationManagerProxy()).thenReturn(mMockLocationManagerProxy);
+        when(mMockIPerUserCarService.getLocationManagerProxy())
+                .thenReturn(mMockLocationManagerProxy);
 
-        // We only support and test the headless system user case.
-        when(mMockCarUserManagerHelper.isHeadlessSystemUser()).thenReturn(true);
+        if (!UserManager.isHeadlessSystemUserMode()) {
+            fail("We only support and test the headless system user case. Ensure the system has "
+                    + "the system property 'ro.fw.mu.headless_system_user' set to true.");
+        }
 
         // Store CarLocationService's user switch callback so we can invoke it in the tests.
         doAnswer((invocation) -> {
@@ -217,7 +215,7 @@
         ArgumentCaptor<Location> argument = ArgumentCaptor.forClass(Location.class);
         when(mMockLocationManagerProxy.injectLocation(argument.capture())).thenReturn(true);
 
-        mUserServiceCallback.onServiceConnected(mMockICarUserService);
+        mUserServiceCallback.onServiceConnected(mMockIPerUserCarService);
         mLatch.await();
 
         Location location = argument.getValue();
@@ -239,7 +237,7 @@
         assertThat(mUserServiceCallback).isNotNull();
         assertThat(getLocationCacheFile().exists()).isFalse();
 
-        mUserServiceCallback.onServiceConnected(mMockICarUserService);
+        mUserServiceCallback.onServiceConnected(mMockIPerUserCarService);
         mLatch.await();
 
         verify(mMockLocationManagerProxy, never()).injectLocation(any());
@@ -254,7 +252,7 @@
         assertThat(mUserServiceCallback).isNotNull();
         writeCacheFile("{\"provider\": \"gps\", \"latitude\": 16.7666, \"longitude\": 3.0026,");
 
-        mUserServiceCallback.onServiceConnected(mMockICarUserService);
+        mUserServiceCallback.onServiceConnected(mMockIPerUserCarService);
         mLatch.await();
 
         verify(mMockLocationManagerProxy, never()).injectLocation(any());
@@ -269,7 +267,7 @@
         assertThat(mUserServiceCallback).isNotNull();
         writeCacheFile("{\"provider\":\"latitude\":16.7666,\"longitude\": \"accuracy\":1.0}");
 
-        mUserServiceCallback.onServiceConnected(mMockICarUserService);
+        mUserServiceCallback.onServiceConnected(mMockIPerUserCarService);
         mLatch.await();
 
         verify(mMockLocationManagerProxy, never()).injectLocation(any());
@@ -285,7 +283,7 @@
         assertThat(mUserServiceCallback).isNotNull();
         writeCacheFile("{\"provider\": \"gps\", \"latitude\": 16.7666, \"longitude\": 3.0026}");
 
-        mUserServiceCallback.onServiceConnected(mMockICarUserService);
+        mUserServiceCallback.onServiceConnected(mMockIPerUserCarService);
         mLatch.await();
 
         verify(mMockLocationManagerProxy, never()).injectLocation(any());
@@ -304,7 +302,7 @@
         writeCacheFile("{\"provider\": \"gps\", \"latitude\": 16.7666, \"longitude\": 3.0026,"
                 + "\"accuracy\":12.3, \"captureTime\": " + oldTime + "}");
 
-        mUserServiceCallback.onServiceConnected(mMockICarUserService);
+        mUserServiceCallback.onServiceConnected(mMockIPerUserCarService);
         mLatch.await();
 
         verify(mMockLocationManagerProxy, never()).injectLocation(any());
@@ -319,7 +317,7 @@
         // We must have a LocationManagerProxy for the current user in order to get a location
         // during shutdown-prepare.
         mCarLocationService.init();
-        mUserServiceCallback.onServiceConnected(mMockICarUserService);
+        mUserServiceCallback.onServiceConnected(mMockIPerUserCarService);
         mLatch.await();
         mLatch = new CountDownLatch(1);
 
@@ -373,7 +371,7 @@
         // We must have a LocationManagerProxy for the current user in order to get a location
         // during shutdown-prepare.
         mCarLocationService.init();
-        mUserServiceCallback.onServiceConnected(mMockICarUserService);
+        mUserServiceCallback.onServiceConnected(mMockIPerUserCarService);
         mLatch.await();
         mLatch = new CountDownLatch(1);
 
@@ -398,7 +396,7 @@
         // We must have a LocationManagerProxy for the current user in order to check whether or
         // not location is enabled.
         mCarLocationService.init();
-        mUserServiceCallback.onServiceConnected(mMockICarUserService);
+        mUserServiceCallback.onServiceConnected(mMockIPerUserCarService);
         mLatch.await();
         mLatch = new CountDownLatch(1);
 
diff --git a/tests/carservice_unit_test/src/com/android/car/CarOccupantZoneServiceTest.java b/tests/carservice_unit_test/src/com/android/car/CarOccupantZoneServiceTest.java
new file mode 100644
index 0000000..691bdf4
--- /dev/null
+++ b/tests/carservice_unit_test/src/com/android/car/CarOccupantZoneServiceTest.java
@@ -0,0 +1,609 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.when;
+import static org.testng.Assert.assertThrows;
+
+import android.app.ActivityManager;
+import android.car.Car;
+import android.car.CarOccupantZoneManager;
+import android.car.CarOccupantZoneManager.OccupantZoneInfo;
+import android.car.VehicleAreaSeat;
+import android.content.Context;
+import android.content.res.Resources;
+import android.hardware.display.DisplayManager;
+import android.os.Looper;
+import android.os.UserHandle;
+import android.view.Display;
+import android.view.DisplayAddress;
+
+import com.android.car.CarOccupantZoneService.DisplayConfig;
+import com.android.car.CarOccupantZoneService.DisplayInfo;
+import com.android.car.CarOccupantZoneService.OccupantConfig;
+import com.android.car.user.CarUserService;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
+
+@RunWith(MockitoJUnitRunner.class)
+public class CarOccupantZoneServiceTest {
+
+    private static final String TAG = CarOccupantZoneServiceTest.class.getSimpleName();
+
+    private CarOccupantZoneService mService;
+    private CarOccupantZoneManager mManager;
+
+    @Mock
+    private CarPropertyService mCarPropertyService;
+
+    @Mock
+    private CarUserService mCarUserService;
+
+    @Mock
+    private Context mContext;
+
+    @Mock
+    private DisplayManager mDisplayManager;
+
+    @Mock
+    private Resources mResources;
+
+    @Mock
+    private Display mDisplay0;
+
+    @Mock
+    private Display mDisplay1;
+
+    @Mock
+    private Display mDisplay2;
+
+    @Mock
+    private Display mDisplay3; // not listed by default
+
+    @Mock
+    private Display mDisplay4;
+
+    @Mock
+    private Display mDisplay5; // outside display config and become unknown display
+
+    private static final String[] DEFAULT_OCCUPANT_ZONES = {
+            "occupantZoneId=0,occupantType=DRIVER,seatRow=1,seatSide=driver",
+            "occupantZoneId=1,occupantType=FRONT_PASSENGER,seatRow=1,seatSide=oppositeDriver",
+            "occupantZoneId=2,occupantType=REAR_PASSENGER,seatRow=2,seatSide=left",
+            "occupantZoneId=3,occupantType=REAR_PASSENGER,seatRow=2,seatSide=right"
+    };
+
+    // LHD : Left Hand Drive
+    private final OccupantZoneInfo mZoneDriverLHD = new OccupantZoneInfo(0,
+            CarOccupantZoneManager.OCCUPANT_TYPE_DRIVER,
+            VehicleAreaSeat.SEAT_ROW_1_LEFT);
+    private final OccupantZoneInfo mZoneFrontPassengerLHD = new OccupantZoneInfo(1,
+            CarOccupantZoneManager.OCCUPANT_TYPE_FRONT_PASSENGER,
+            VehicleAreaSeat.SEAT_ROW_1_RIGHT);
+    private final OccupantZoneInfo mZoneRearLeft = new OccupantZoneInfo(2,
+            CarOccupantZoneManager.OCCUPANT_TYPE_REAR_PASSENGER,
+            VehicleAreaSeat.SEAT_ROW_2_LEFT);
+    private final OccupantZoneInfo mZoneRearRight = new OccupantZoneInfo(3,
+            CarOccupantZoneManager.OCCUPANT_TYPE_REAR_PASSENGER,
+            VehicleAreaSeat.SEAT_ROW_2_RIGHT);
+
+    // port address set to mocked displayid + 10 so that any possible mix of port address and
+    // display id can be detected.
+    private static final String[] DEFAULT_OCCUPANT_DISPLAY_MAPPING = {
+            "displayPort=10,displayType=MAIN,occupantZoneId=0",
+            "displayPort=11,displayType=INSTRUMENT_CLUSTER,occupantZoneId=0",
+            "displayPort=12,displayType=MAIN,occupantZoneId=1",
+            "displayPort=13,displayType=MAIN,occupantZoneId=2",
+            "displayPort=14,displayType=MAIN,occupantZoneId=3"
+    };
+
+    // Stores last changeFlags from onOccupantZoneConfigChanged call.
+    private int mLastChangeFlags;
+    private final Semaphore mChangeEventSignal = new Semaphore(0);
+
+    private final CarOccupantZoneManager.OccupantZoneConfigChangeListener mChangeListener =
+            new CarOccupantZoneManager.OccupantZoneConfigChangeListener() {
+                @Override
+                public void onOccupantZoneConfigChanged(int changeFlags) {
+                    // should be dispatched to main thread.
+                    assertThat(Looper.getMainLooper()).isEqualTo(Looper.myLooper());
+                    mLastChangeFlags = changeFlags;
+                    mChangeEventSignal.release();
+                }
+            };
+
+    private void resetConfigChangeEventWait() {
+        mLastChangeFlags = 0;
+        mChangeEventSignal.drainPermits();
+    }
+
+    private boolean waitForConfigChangeEventAndAssertFlag(long timeoutMs, int expectedFlag) {
+        boolean acquired = false;
+        try {
+            acquired = mChangeEventSignal.tryAcquire(timeoutMs, TimeUnit.MILLISECONDS);
+        } catch (Exception ignored) {
+
+        }
+        if (acquired) {
+            assertThat(expectedFlag).isEqualTo(mLastChangeFlags);
+        }
+        return acquired;
+    }
+
+    private void mockDisplay(DisplayManager displayManager, Display display, int displayId,
+            int portAddress) {
+        when(displayManager.getDisplay(displayId)).thenReturn(display);
+        when(display.getDisplayId()).thenReturn(displayId);
+        when(display.getAddress()).thenReturn(DisplayAddress.fromPhysicalDisplayId(portAddress));
+    }
+
+    @Before
+    public void setUp() {
+        when(mContext.getResources()).thenReturn(mResources);
+        when(mContext.getSystemService(DisplayManager.class)).thenReturn(mDisplayManager);
+        when(mResources.getStringArray(R.array.config_occupant_zones))
+                .thenReturn(DEFAULT_OCCUPANT_ZONES);
+        when(mResources.getStringArray(R.array.config_occupant_display_mapping))
+                .thenReturn(DEFAULT_OCCUPANT_DISPLAY_MAPPING);
+        // Stored as static: Other tests can leave things behind and fail this test in add call.
+        // So just remove as safety guard.
+        CarLocalServices.removeServiceForTest(CarPropertyService.class);
+        CarLocalServices.addService(CarPropertyService.class, mCarPropertyService);
+        CarLocalServices.removeServiceForTest(CarUserService.class);
+        CarLocalServices.addService(CarUserService.class, mCarUserService);
+        mockDisplay(mDisplayManager, mDisplay0, 0, 10);
+        mockDisplay(mDisplayManager, mDisplay1, 1, 11);
+        mockDisplay(mDisplayManager, mDisplay2, 2, 12);
+        mockDisplay(mDisplayManager, mDisplay4, 4, 14);
+        mockDisplay(mDisplayManager, mDisplay5, 5, 15);
+        when(mDisplayManager.getDisplays()).thenReturn(new Display[]{
+                mDisplay0,
+                mDisplay1,
+                mDisplay2,
+                mDisplay4,
+                mDisplay5
+        });
+
+        mService = new CarOccupantZoneService(mContext, mDisplayManager);
+        spyOn(mService);
+        doReturn(VehicleAreaSeat.SEAT_ROW_1_LEFT).when(mService).getDriverSeat();
+        doReturn(ActivityManager.getCurrentUser()).when(mService).getCurrentUser();
+
+        Car car = new Car(mContext, /* service= */null, /* handler= */ null);
+        mManager = new CarOccupantZoneManager(car, mService);
+    }
+
+    @After
+    public void tearDown() {
+        CarLocalServices.removeServiceForTest(CarUserService.class);
+        CarLocalServices.removeServiceForTest(CarPropertyService.class);
+    }
+
+    @Test
+    public void testDefaultOccupantConfig() {
+        mService.init();
+
+        // key : zone id
+        HashMap<Integer, OccupantZoneInfo> configs = mService.getOccupantsConfig();
+        assertThat(configs).hasSize(DEFAULT_OCCUPANT_ZONES.length);
+        assertThat(mZoneDriverLHD).isEqualTo(configs.get(0));
+        assertThat(mZoneFrontPassengerLHD).isEqualTo(configs.get(1));
+        assertThat(mZoneRearLeft).isEqualTo(configs.get(2));
+        assertThat(mZoneRearRight).isEqualTo(configs.get(3));
+    }
+
+    /** RHD: Right Hand Drive */
+    @Test
+    public void testDefaultOccupantConfigForRHD() {
+        // driver is right side and opposite should be left.
+        doReturn(VehicleAreaSeat.SEAT_ROW_1_RIGHT).when(mService).getDriverSeat();
+
+        mService.init();
+
+        // key : zone id
+        HashMap<Integer, OccupantZoneInfo> configs = mService.getOccupantsConfig();
+        assertThat(configs).hasSize(DEFAULT_OCCUPANT_ZONES.length);
+        assertThat(new OccupantZoneInfo(0, CarOccupantZoneManager.OCCUPANT_TYPE_DRIVER,
+                VehicleAreaSeat.SEAT_ROW_1_RIGHT)).isEqualTo(configs.get(0));
+        assertThat(new OccupantZoneInfo(1, CarOccupantZoneManager.OCCUPANT_TYPE_FRONT_PASSENGER,
+                VehicleAreaSeat.SEAT_ROW_1_LEFT)).isEqualTo(configs.get(1));
+        assertThat(mZoneRearLeft).isEqualTo(configs.get(2));
+        assertThat(mZoneRearRight).isEqualTo(configs.get(3));
+    }
+
+    private void assertDisplayConfig(DisplayConfig c, int displayType, int occupantZoneId) {
+        assertThat(displayType).isEqualTo(c.displayType);
+        assertThat(occupantZoneId).isEqualTo(c.occupantZoneId);
+    }
+
+    @Test
+    public void testDefaultOccupantDisplayMapping() {
+        mService.init();
+
+        // key: display port address
+        HashMap<Integer, DisplayConfig> configs = mService.getDisplayConfigs();
+        assertThat(configs).hasSize(DEFAULT_OCCUPANT_DISPLAY_MAPPING.length);
+        assertDisplayConfig(configs.get(10), CarOccupantZoneManager.DISPLAY_TYPE_MAIN, 0);
+        assertDisplayConfig(configs.get(11), CarOccupantZoneManager.DISPLAY_TYPE_INSTRUMENT_CLUSTER,
+                0);
+        assertDisplayConfig(configs.get(12), CarOccupantZoneManager.DISPLAY_TYPE_MAIN, 1);
+        assertDisplayConfig(configs.get(13), CarOccupantZoneManager.DISPLAY_TYPE_MAIN, 2);
+        assertDisplayConfig(configs.get(14), CarOccupantZoneManager.DISPLAY_TYPE_MAIN, 3);
+    }
+
+    private void assertDisplayInfoIncluded(
+            LinkedList<DisplayInfo> displayInfos, Display display, int displayType) {
+        for (DisplayInfo info : displayInfos) {
+            if (info.display == display && info.displayType == displayType) {
+                return;
+            }
+        }
+        fail("Cannot find display:" + display + " type:" + displayType);
+    }
+
+    private void assertOccupantConfig(OccupantConfig c, int userId, Display[] displays,
+            int[] displayTypes) {
+        assertThat(userId).isEqualTo(c.userId);
+        assertThat(c.displayInfos).hasSize(displays.length);
+        assertThat(c.displayInfos).hasSize(displayTypes.length);
+        for (int i = 0; i < displays.length; i++) {
+            assertDisplayInfoIncluded(c.displayInfos, displays[i], displayTypes[i]);
+        }
+    }
+
+    @Test
+    public void testActiveOccupantConfigs() {
+        mService.init();
+
+        // key : zone id
+        HashMap<Integer, OccupantConfig> configs = mService.getActiveOccupantConfigs();
+        assertThat(configs).hasSize(3); // driver, front passenger, one rear
+        int currentUser = ActivityManager.getCurrentUser();
+        assertOccupantConfig(configs.get(0), currentUser, new Display[]{mDisplay0, mDisplay1},
+                new int[]{CarOccupantZoneManager.DISPLAY_TYPE_MAIN,
+                        CarOccupantZoneManager.DISPLAY_TYPE_INSTRUMENT_CLUSTER});
+        assertOccupantConfig(configs.get(1), UserHandle.USER_NULL, new Display[]{mDisplay2},
+                new int[]{CarOccupantZoneManager.DISPLAY_TYPE_MAIN});
+        assertOccupantConfig(configs.get(3), UserHandle.USER_NULL, new Display[]{mDisplay4},
+                new int[]{CarOccupantZoneManager.DISPLAY_TYPE_MAIN});
+    }
+
+    @Test
+    public void testActiveOccupantConfigsAfterDisplayAdd() {
+        mService.init();
+
+        mockDisplay(mDisplayManager, mDisplay3, 3, 13);
+        when(mDisplayManager.getDisplays()).thenReturn(new Display[]{
+                mDisplay0,
+                mDisplay1,
+                mDisplay2,
+                mDisplay3,
+                mDisplay4,
+                mDisplay5
+        });
+        mService.mDisplayListener.onDisplayAdded(3);
+
+        // key : zone id
+        HashMap<Integer, OccupantConfig> configs = mService.getActiveOccupantConfigs();
+        assertThat(configs).hasSize(4); // driver, front passenger, two rear
+        int currentUser = ActivityManager.getCurrentUser();
+        assertOccupantConfig(configs.get(0), currentUser, new Display[]{mDisplay0, mDisplay1},
+                new int[]{CarOccupantZoneManager.DISPLAY_TYPE_MAIN,
+                        CarOccupantZoneManager.DISPLAY_TYPE_INSTRUMENT_CLUSTER});
+        assertOccupantConfig(configs.get(1), UserHandle.USER_NULL, new Display[]{mDisplay2},
+                new int[]{CarOccupantZoneManager.DISPLAY_TYPE_MAIN});
+        assertOccupantConfig(configs.get(2), UserHandle.USER_NULL, new Display[]{mDisplay3},
+                new int[]{CarOccupantZoneManager.DISPLAY_TYPE_MAIN});
+        assertOccupantConfig(configs.get(3), UserHandle.USER_NULL, new Display[]{mDisplay4},
+                new int[]{CarOccupantZoneManager.DISPLAY_TYPE_MAIN});
+    }
+
+    @Test
+    public void testActiveOccupantConfigsAfterDisplayRemoval() {
+        mService.init();
+
+        when(mDisplayManager.getDisplays()).thenReturn(new Display[]{
+                mDisplay0,
+                mDisplay1,
+                mDisplay2,
+        });
+        mService.mDisplayListener.onDisplayRemoved(4);
+
+        // key : zone id
+        HashMap<Integer, OccupantConfig> configs = mService.getActiveOccupantConfigs();
+        assertThat(configs).hasSize(2); // driver, front passenger
+        int currentUser = ActivityManager.getCurrentUser();
+        assertOccupantConfig(configs.get(0), currentUser, new Display[]{mDisplay0, mDisplay1},
+                new int[]{CarOccupantZoneManager.DISPLAY_TYPE_MAIN,
+                        CarOccupantZoneManager.DISPLAY_TYPE_INSTRUMENT_CLUSTER});
+        assertOccupantConfig(configs.get(1), UserHandle.USER_NULL, new Display[]{mDisplay2},
+                new int[]{CarOccupantZoneManager.DISPLAY_TYPE_MAIN});
+    }
+
+    @Test
+    public void testActiveUserAfterUserSwitching() {
+        mService.init();
+
+        final int newUserId = 100;
+        doReturn(newUserId).when(mService).getCurrentUser();
+        mService.mUserCallback.onSwitchUser(newUserId);
+
+        // key : zone id
+        HashMap<Integer, OccupantConfig> configs = mService.getActiveOccupantConfigs();
+        assertThat(configs).hasSize(3); // driver, front passenger, one rear
+        assertOccupantConfig(configs.get(0), newUserId, new Display[]{mDisplay0, mDisplay1},
+                new int[]{CarOccupantZoneManager.DISPLAY_TYPE_MAIN,
+                        CarOccupantZoneManager.DISPLAY_TYPE_INSTRUMENT_CLUSTER});
+        assertOccupantConfig(configs.get(1), UserHandle.USER_NULL, new Display[]{mDisplay2},
+                new int[]{CarOccupantZoneManager.DISPLAY_TYPE_MAIN});
+        assertOccupantConfig(configs.get(3), UserHandle.USER_NULL, new Display[]{mDisplay4},
+                new int[]{CarOccupantZoneManager.DISPLAY_TYPE_MAIN});
+    }
+
+    private void assertParsingFailure() {
+        assertThrows(Exception.class, () -> mService.init());
+        // call release to return it to clean state.
+        mService.release();
+    }
+
+    @Test
+    public void testWrongZoneConfigs() {
+        final String[] wrongZoneConfigs = {
+                "unknownKeyword",
+                "unknownKey=0",
+                "occupantZoneId=0,occupantType=Unknown,seatRow=1,seatSide=driver",
+                "occupantZoneId=0,occupantType=DRIVER,seatRow=0,seatSide=driver", // wrong row
+                "occupantZoneId=0,occupantType=DRIVER,seatRow=1,seatSide=wrongSide"
+        };
+
+        String[] zoneConfig = new String[1];
+        when(mResources.getStringArray(R.array.config_occupant_zones))
+                .thenReturn(zoneConfig);
+        for (int i = 0; i < wrongZoneConfigs.length; i++) {
+            zoneConfig[0] = wrongZoneConfigs[i];
+            assertParsingFailure();
+        }
+    }
+
+    @Test
+    public void testWrongDisplayConfigs() {
+        final String[] wrongDisplayConfigs = {
+                "unknownKeyword",
+                "unknownKey=0",
+                "displayPort=10,displayType=Unknown,occupantZoneId=0",
+                "displayPort=10,displayType=MAIN,occupantZoneId=100" // wrong zone id
+        };
+
+        String[] displayConfig = new String[1];
+        when(mResources.getStringArray(R.array.config_occupant_display_mapping))
+                .thenReturn(displayConfig);
+        for (int i = 0; i < wrongDisplayConfigs.length; i++) {
+            displayConfig[0] = wrongDisplayConfigs[i];
+            assertParsingFailure();
+        }
+    }
+
+    @Test
+    public void testManagerGetAllOccupantZones() {
+        mService.init();
+
+        List<OccupantZoneInfo> infos = mManager.getAllOccupantZones();
+        assertThat(infos).hasSize(3);
+        assertThat(infos).contains(mZoneDriverLHD);
+        assertThat(infos).contains(mZoneFrontPassengerLHD);
+        assertThat(infos).contains(mZoneRearRight);
+    }
+
+    @Test
+    public void testManagerGetAllDisplaysForOccupant() {
+        mService.init();
+
+        List<Display> displaysForDriver = mManager.getAllDisplaysForOccupant(mZoneDriverLHD);
+        assertThat(displaysForDriver).hasSize(2);
+        assertThat(displaysForDriver).contains(mDisplay0);
+        assertThat(displaysForDriver).contains(mDisplay1);
+
+        List<Display> displaysForFrontPassenger = mManager.getAllDisplaysForOccupant(
+                mZoneFrontPassengerLHD);
+        assertThat(displaysForFrontPassenger).hasSize(1);
+        assertThat(displaysForFrontPassenger).contains(mDisplay2);
+
+        List<Display> displaysForRearLeft = mManager.getAllDisplaysForOccupant(
+                mZoneRearLeft);
+        assertThat(displaysForRearLeft).hasSize(0);
+
+        List<Display> displaysForRearRight = mManager.getAllDisplaysForOccupant(
+                mZoneRearRight);
+        assertThat(displaysForRearRight).hasSize(1);
+        assertThat(displaysForRearRight).contains(mDisplay4);
+    }
+
+    @Test
+    public void testManagerGetAllDisplaysForOccupantAfterDisplayAdd() {
+        mService.init();
+
+        mockDisplay(mDisplayManager, mDisplay3, 3, 13);
+        when(mDisplayManager.getDisplays()).thenReturn(new Display[]{
+                mDisplay0,
+                mDisplay1,
+                mDisplay2,
+                mDisplay3,
+                mDisplay4,
+                mDisplay5
+        });
+        mService.mDisplayListener.onDisplayAdded(3);
+
+        List<Display> displaysForDriver = mManager.getAllDisplaysForOccupant(mZoneDriverLHD);
+        assertThat(displaysForDriver).hasSize(2);
+        assertThat(displaysForDriver).contains(mDisplay0);
+        assertThat(displaysForDriver).contains(mDisplay1);
+
+        List<Display> displaysForFrontPassenger = mManager.getAllDisplaysForOccupant(
+                mZoneFrontPassengerLHD);
+        assertThat(displaysForFrontPassenger).hasSize(1);
+        assertThat(displaysForFrontPassenger).contains(mDisplay2);
+
+        List<Display> displaysForRearLeft = mManager.getAllDisplaysForOccupant(
+                mZoneRearLeft);
+        assertThat(displaysForRearLeft).hasSize(1);
+        assertThat(displaysForRearLeft).contains(mDisplay3);
+
+        List<Display> displaysForRearRight = mManager.getAllDisplaysForOccupant(
+                mZoneRearRight);
+        assertThat(displaysForRearRight).hasSize(1);
+        assertThat(displaysForRearRight).contains(mDisplay4);
+    }
+
+    @Test
+    public void testManagerGetAllDisplaysForOccupantAfterDisplayRemoval() {
+        mService.init();
+
+        when(mDisplayManager.getDisplays()).thenReturn(new Display[]{
+                mDisplay0,
+                mDisplay1,
+                mDisplay2,
+        });
+        mService.mDisplayListener.onDisplayRemoved(4);
+
+        List<Display> displaysForDriver = mManager.getAllDisplaysForOccupant(mZoneDriverLHD);
+        assertThat(displaysForDriver).hasSize(2);
+        assertThat(displaysForDriver).contains(mDisplay0);
+        assertThat(displaysForDriver).contains(mDisplay1);
+
+        List<Display> displaysForFrontPassenger = mManager.getAllDisplaysForOccupant(
+                mZoneFrontPassengerLHD);
+        assertThat(displaysForFrontPassenger).hasSize(1);
+        assertThat(displaysForFrontPassenger).contains(mDisplay2);
+
+        List<Display> displaysForRearLeft = mManager.getAllDisplaysForOccupant(
+                mZoneRearLeft);
+        assertThat(displaysForRearLeft).hasSize(0);
+
+        List<Display> displaysForRearRight = mManager.getAllDisplaysForOccupant(
+                mZoneRearRight);
+        assertThat(displaysForRearRight).hasSize(0);
+    }
+
+    @Test
+    public void testManagerGetDisplayForOccupant() {
+        mService.init();
+
+        assertThat(mDisplay0).isEqualTo(mManager.getDisplayForOccupant(mZoneDriverLHD,
+                CarOccupantZoneManager.DISPLAY_TYPE_MAIN));
+        assertThat(mDisplay1).isEqualTo(mManager.getDisplayForOccupant(mZoneDriverLHD,
+                CarOccupantZoneManager.DISPLAY_TYPE_INSTRUMENT_CLUSTER));
+        assertThat(mManager.getDisplayForOccupant(mZoneDriverLHD,
+                CarOccupantZoneManager.DISPLAY_TYPE_HUD)).isNull();
+
+        assertThat(mDisplay2).isEqualTo(mManager.getDisplayForOccupant(mZoneFrontPassengerLHD,
+                CarOccupantZoneManager.DISPLAY_TYPE_MAIN));
+
+        assertThat(mManager.getDisplayForOccupant(mZoneRearLeft,
+                CarOccupantZoneManager.DISPLAY_TYPE_MAIN)).isNull();
+
+        assertThat(mDisplay4).isEqualTo(mManager.getDisplayForOccupant(mZoneRearRight,
+                CarOccupantZoneManager.DISPLAY_TYPE_MAIN));
+    }
+
+    @Test
+    public void testManagerGetDisplayType() {
+        mService.init();
+
+        assertThat(mManager.getDisplayType(mDisplay0)).isEqualTo(
+                CarOccupantZoneManager.DISPLAY_TYPE_MAIN);
+        assertThat(mManager.getDisplayType(mDisplay1)).isEqualTo(
+                CarOccupantZoneManager.DISPLAY_TYPE_INSTRUMENT_CLUSTER);
+        assertThat(mManager.getDisplayType(mDisplay2)).isEqualTo(
+                CarOccupantZoneManager.DISPLAY_TYPE_MAIN);
+        assertThat(mManager.getDisplayType(mDisplay4)).isEqualTo(
+                CarOccupantZoneManager.DISPLAY_TYPE_MAIN);
+        assertThat(mManager.getDisplayType(mDisplay5)).isEqualTo(
+                CarOccupantZoneManager.DISPLAY_TYPE_UNKNOWN);
+    }
+
+    @Test
+    public void testManagerGetUserForOccupant() {
+        mService.init();
+
+        int currentUser = ActivityManager.getCurrentUser();
+        int driverUser = mManager.getUserForOccupant(mZoneDriverLHD);
+        assertThat(currentUser).isEqualTo(driverUser);
+
+        //TODO update this after secondary user handling
+        assertThat(mManager.getUserForOccupant(mZoneFrontPassengerLHD)).isEqualTo(
+                UserHandle.USER_NULL);
+        assertThat(mManager.getUserForOccupant(mZoneRearLeft)).isEqualTo(UserHandle.USER_NULL);
+        assertThat(mManager.getUserForOccupant(mZoneRearRight)).isEqualTo(UserHandle.USER_NULL);
+    }
+
+    @Test
+    public void testManagerGetUserForOccupantAfterUserSwitch() {
+        mService.init();
+
+        final int newUserId = 100;
+        doReturn(newUserId).when(mService).getCurrentUser();
+        mService.mUserCallback.onSwitchUser(newUserId);
+
+        assertThat(newUserId).isEqualTo(mManager.getUserForOccupant(mZoneDriverLHD));
+        //TODO update this after secondary user handling
+        assertThat(mManager.getUserForOccupant(mZoneFrontPassengerLHD)).isEqualTo(
+                UserHandle.USER_NULL);
+        assertThat(mManager.getUserForOccupant(mZoneRearLeft)).isEqualTo(UserHandle.USER_NULL);
+        assertThat(mManager.getUserForOccupant(mZoneRearRight)).isEqualTo(UserHandle.USER_NULL);
+    }
+
+    @Test
+    public void testManagerRegisterUnregister() {
+        mService.init();
+
+        final long eventWaitTimeMs = 300;
+
+        mManager.registerOccupantZoneConfigChangeListener(mChangeListener);
+
+        resetConfigChangeEventWait();
+        mService.mUserCallback.onSwitchUser(0); // user id does not matter.
+        assertThat(waitForConfigChangeEventAndAssertFlag(eventWaitTimeMs,
+                CarOccupantZoneManager.ZONE_CONFIG_CHANGE_FLAG_USER)).isTrue();
+
+        resetConfigChangeEventWait();
+        mService.mDisplayListener.onDisplayAdded(0); // displayid ignored
+        assertThat(waitForConfigChangeEventAndAssertFlag(eventWaitTimeMs,
+                CarOccupantZoneManager.ZONE_CONFIG_CHANGE_FLAG_DISPLAY)).isTrue();
+
+        resetConfigChangeEventWait();
+        mManager.unregisterOccupantZoneConfigChangeListener(mChangeListener);
+        mService.mUserCallback.onSwitchUser(0);
+        assertThat(waitForConfigChangeEventAndAssertFlag(eventWaitTimeMs, 0)).isFalse();
+    }
+}
diff --git a/tests/carservice_unit_test/src/com/android/car/CarPowerManagementServiceTest.java b/tests/carservice_unit_test/src/com/android/car/CarPowerManagementServiceTest.java
index 36f2178..7a54cab 100644
--- a/tests/carservice_unit_test/src/com/android/car/CarPowerManagementServiceTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/CarPowerManagementServiceTest.java
@@ -17,10 +17,12 @@
 package com.android.car;
 
 import static android.content.pm.UserInfo.FLAG_EPHEMERAL;
-import static android.content.pm.UserInfo.FLAG_GUEST;
 import static android.os.UserHandle.USER_SYSTEM;
+import static android.os.UserManager.USER_TYPE_FULL_GUEST;
 
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -31,7 +33,6 @@
 import static org.mockito.ArgumentMatchers.notNull;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import static java.lang.annotation.ElementType.METHOD;
@@ -52,6 +53,7 @@
 import android.test.suitebuilder.annotation.SmallTest;
 import android.util.Log;
 
+import androidx.test.filters.FlakyTest;
 import androidx.test.platform.app.InstrumentationRegistry;
 
 import com.android.car.hal.PowerHalService;
@@ -73,6 +75,7 @@
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
 import org.mockito.MockitoSession;
+import org.mockito.invocation.InvocationOnMock;
 import org.mockito.junit.MockitoJUnitRunner;
 import org.mockito.quality.Strictness;
 
@@ -82,6 +85,8 @@
 import java.lang.annotation.Target;
 import java.lang.reflect.Method;
 import java.time.Duration;
+import java.util.ArrayList;
+import java.util.List;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.Semaphore;
 import java.util.concurrent.TimeUnit;
@@ -121,6 +126,10 @@
     // Value used to set config_disableUserSwitchDuringResume - must be defined before initTest();
     private boolean mDisableUserSwitchDuringResume;
 
+    // Tracks Log.wtf() calls made during code execution / used on verifyWtfNeverLogged()
+    // TODO: move mechanism to common code / custom Rule
+    private final List<UnsupportedOperationException> mWtfs = new ArrayList<>();
+
     @Rule
     public final TestRule setWakeupTimeRule = new TestWatcher() {
         protected void starting(Description description) {
@@ -143,6 +152,7 @@
         mSession = mockitoSession()
                 .strictness(Strictness.LENIENT)
                 .spyStatic(ActivityManager.class)
+                .spyStatic(Log.class)
                 .startMocking();
         mPowerHal = new MockedPowerHalService(true /*isPowerStateSupported*/,
                 true /*isDeepSleepAllowed*/, true /*isTimedWakeupAllowed*/);
@@ -151,6 +161,12 @@
             .withSystemStateInterface(mSystemStateInterface)
             .withWakeLockInterface(mWakeLockInterface)
             .withIOInterface(mIOInterface).build();
+        doAnswer((invocation) -> {
+            return addWtf(invocation);
+        }).when(() -> Log.wtf(anyString(), anyString()));
+        doAnswer((invocation) -> {
+            return addWtf(invocation);
+        }).when(() -> Log.wtf(anyString(), anyString(), notNull()));
     }
 
     @After
@@ -162,6 +178,14 @@
         mSession.finishMocking();
     }
 
+
+    private Object addWtf(InvocationOnMock invocation) {
+        String message = "Called " + invocation;
+        Log.d(TAG, message); // Log always, as some test expect it
+        mWtfs.add(new UnsupportedOperationException(message));
+        return null;
+    }
+
     /**
      * Helper method to create mService and initialize a test case
      */
@@ -179,7 +203,7 @@
         mService = new CarPowerManagementService(mContext, mResources, mPowerHal,
                 mSystemInterface, mCarUserManagerHelper, mUserManager);
         mService.init();
-        CarPowerManagementService.setShutdownPrepareTimeout(0);
+        mService.setShutdownTimersForTest(0, 0);
         mPowerHal.setSignalListener(mPowerSignalListener);
         if (mWakeupTime > 0) {
             registerListenerToService();
@@ -188,9 +212,21 @@
         assertStateReceived(MockedPowerHalService.SET_WAIT_FOR_VHAL, 0);
     }
 
+    /**
+     * Same as {@link #initTest()}, but it also assumes the current and initial users are user 10.
+     */
+    private void initTestForUser10() throws Exception {
+        initTest();
+        setUserInfo(10, NO_USER_INFO_FLAGS);
+        setCurrentUser(10);
+        setInitialUser(10);
+    }
+
     @Test
     public void testBootComplete() throws Exception {
         initTest();
+
+        verifyWtfNeverLogged();
     }
 
     @Test
@@ -204,11 +240,13 @@
 
         // display should be turned on as it started with off state.
         assertThat(mDisplayInterface.waitForDisplayStateChange(WAIT_TIMEOUT_MS)).isTrue();
+
+        verifyWtfNeverLogged();
     }
 
     @Test
     public void testShutdown() throws Exception {
-        initTest();
+        initTestForUser10();
 
         // Transition to ON state
         mPowerHal.setCurrentPowerState(new PowerState(VehicleApPowerStateReq.ON, 0));
@@ -224,11 +262,13 @@
         assertThat(mDisplayInterface.waitForDisplayStateChange(WAIT_TIMEOUT_MS)).isFalse();
         mPowerSignalListener.waitForShutdown(WAIT_TIMEOUT_MS);
         mSystemStateInterface.waitForShutdown(WAIT_TIMEOUT_MS);
+
+        verifyWtfNeverLogged();
     }
 
     @Test
     public void testSuspend() throws Exception {
-        initTest();
+        initTestForUser10();
 
         // Start in the ON state
         mPowerHal.setCurrentPowerState(new PowerState(VehicleApPowerStateReq.ON, 0));
@@ -241,11 +281,13 @@
         // Verify suspend
         assertStateReceivedForShutdownOrSleepWithPostpone(
                 PowerHalService.SET_DEEP_SLEEP_ENTRY, WAIT_TIMEOUT_LONG_MS, mWakeupTime);
+
+        verifyWtfNeverLogged();
     }
 
     @Test
     public void testShutdownOnSuspend() throws Exception {
-        initTest();
+        initTestForUser10();
 
         // Start in the ON state
         mPowerHal.setCurrentPowerState(new PowerState(VehicleApPowerStateReq.ON, 0));
@@ -277,11 +319,12 @@
         // Verify suspend
         assertStateReceivedForShutdownOrSleepWithPostpone(
                 PowerHalService.SET_DEEP_SLEEP_ENTRY, WAIT_TIMEOUT_LONG_MS, mWakeupTime);
+        verifyWtfNeverLogged();
     }
 
     @Test
     public void testShutdownCancel() throws Exception {
-        initTest();
+        initTestForUser10();
 
         // Start in the ON state
         mPowerHal.setCurrentPowerState(new PowerState(VehicleApPowerStateReq.ON, 0));
@@ -304,12 +347,35 @@
                         VehicleApPowerStateShutdownParam.CAN_SLEEP));
         assertStateReceivedForShutdownOrSleepWithPostpone(
                 PowerHalService.SET_DEEP_SLEEP_ENTRY, WAIT_TIMEOUT_LONG_MS, mWakeupTime);
+        verifyWtfNeverLogged();
+    }
+
+    @Test
+    public void testSleepImmediately() throws Exception {
+        initTestForUser10();
+
+        // Transition to ON state
+        mPowerHal.setCurrentPowerState(new PowerState(VehicleApPowerStateReq.ON, 0));
+        assertThat(mDisplayInterface.waitForDisplayStateChange(WAIT_TIMEOUT_MS)).isTrue();
+
+        mPowerHal.setCurrentPowerState(
+                new PowerState(
+                        VehicleApPowerStateReq.SHUTDOWN_PREPARE,
+                        VehicleApPowerStateShutdownParam.SLEEP_IMMEDIATELY));
+        // Since modules have to manually schedule next wakeup, we should not schedule next wakeup
+        // To test module behavior, we need to actually implement mock listener module.
+        assertStateReceived(PowerHalService.SET_SHUTDOWN_START, 0);
+        assertThat(mDisplayInterface.waitForDisplayStateChange(WAIT_TIMEOUT_MS)).isFalse();
+        mPowerSignalListener.waitForShutdown(WAIT_TIMEOUT_MS);
+        mSystemStateInterface.waitForShutdown(WAIT_TIMEOUT_MS);
+        verifyWtfNeverLogged();
     }
 
     @Test
     @WakeupTime(100)
+    @FlakyTest
     public void testShutdownWithProcessing() throws Exception {
-        initTest();
+        initTestForUser10();
         mPowerHal.setCurrentPowerState(new PowerState(VehicleApPowerStateReq.SHUTDOWN_PREPARE, 0));
         assertStateReceivedForShutdownOrSleepWithPostpone(
                 PowerHalService.SET_SHUTDOWN_START, WAIT_TIMEOUT_LONG_MS, mWakeupTime);
@@ -317,6 +383,7 @@
         // Send the finished signal
         mPowerHal.setCurrentPowerState(new PowerState(VehicleApPowerStateReq.FINISHED, 0));
         mSystemStateInterface.waitForShutdown(WAIT_TIMEOUT_MS);
+        verifyWtfNeverLogged();
     }
 
     @Test
@@ -333,9 +400,11 @@
         mSystemStateInterface.waitForSleepEntryAndWakeup(WAIT_TIMEOUT_MS);
         assertStateReceived(PowerHalService.SET_DEEP_SLEEP_EXIT, 0);
         mPowerSignalListener.waitForSleepExit(WAIT_TIMEOUT_MS);
+        verifyWtfNeverLogged();
     }
 
     @Test
+    @FlakyTest
     public void testUserSwitchingOnResume_differentUser() throws Exception {
         initTest();
         setUserInfo(10, NO_USER_INFO_FLAGS);
@@ -346,6 +415,7 @@
         suspendAndResumeForUserSwitchingTests();
 
         verifyUserSwitched(11);
+        verifyWtfNeverLogged();
     }
 
     @Test
@@ -358,6 +428,7 @@
         suspendAndResumeForUserSwitchingTests();
 
         verifyUserNotSwitched();
+        verifyWtfNeverLogged();
     }
 
     @Test
@@ -371,12 +442,13 @@
         suspendAndResumeForUserSwitchingTests();
 
         verifyUserSwitched(11);
+        verifyWtfNeverLogged();
     }
 
     @Test
     public void testUserSwitchingOnResume_sameGuest() throws Exception {
         initTest();
-        setUserInfo(10, "ElGuesto", FLAG_GUEST | FLAG_EPHEMERAL);
+        setUserInfo(10, "ElGuesto", USER_TYPE_FULL_GUEST, FLAG_EPHEMERAL);
         setInitialUser(10);
         setCurrentUser(10);
         expectGuestMarkedForDeletionOk(10);
@@ -386,12 +458,13 @@
 
         verifyUserRemoved(10);
         verifyUserSwitched(11);
+        verifyWtfNeverLogged();
     }
 
     @Test
     public void testUserSwitchingOnResume_differentGuest() throws Exception {
         initTest();
-        setUserInfo(11, "ElGuesto", FLAG_GUEST | FLAG_EPHEMERAL);
+        setUserInfo(11, "ElGuesto", USER_TYPE_FULL_GUEST, FLAG_EPHEMERAL);
         setInitialUser(11);
         setCurrentUser(10);
         expectGuestMarkedForDeletionOk(11);
@@ -401,12 +474,13 @@
 
         verifyUserRemoved(11);
         verifyUserSwitched(12);
+        verifyWtfNeverLogged();
     }
 
     @Test
     public void testUserSwitchingOnResume_guestCreationFailed() throws Exception {
         initTest();
-        setUserInfo(10, "ElGuesto", FLAG_GUEST | FLAG_EPHEMERAL);
+        setUserInfo(10, "ElGuesto", USER_TYPE_FULL_GUEST, FLAG_EPHEMERAL);
         setInitialUser(10);
         setCurrentUser(10);
         expectGuestMarkedForDeletionOk(10);
@@ -416,12 +490,13 @@
 
         verifyUserNotSwitched();
         verifyUserNotRemoved(10);
+        // expects WTF
     }
 
     @Test
     public void testUserSwitchingOnResume_differentPersistentGuest() throws Exception {
         initTest();
-        setUserInfo(11, "ElGuesto", FLAG_GUEST);
+        setUserInfo(11, "ElGuesto", USER_TYPE_FULL_GUEST, NO_USER_INFO_FLAGS);
         setInitialUser(11);
         setCurrentUser(10);
         expectGuestMarkedForDeletionOk(11);
@@ -431,12 +506,13 @@
 
         verifyUserRemoved(11);
         verifyUserSwitched(12);
+        verifyWtfNeverLogged();
     }
 
     @Test
     public void testUserSwitchingOnResume_preDeleteGuestFail() throws Exception {
         initTest();
-        setUserInfo(10, "ElGuesto", FLAG_GUEST | FLAG_EPHEMERAL);
+        setUserInfo(10, "ElGuesto", USER_TYPE_FULL_GUEST, FLAG_EPHEMERAL);
         setInitialUser(10);
         setCurrentUser(10);
         expectGuestMarkedForDeletionFail(10);
@@ -445,6 +521,7 @@
 
         verifyUserNotSwitched();
         verifyNoGuestCreated();
+        verifyWtfNeverLogged();
     }
 
     @Test
@@ -456,6 +533,7 @@
         suspendAndResumeForUserSwitchingTests();
 
         verifyUserNotSwitched();
+        // expects WTF
     }
 
     @Test
@@ -470,6 +548,7 @@
         suspendAndResumeForUserSwitchingTests();
 
         verifyUserNotSwitched();
+        verifyWtfNeverLogged();
     }
 
     @Test
@@ -483,6 +562,7 @@
         suspendAndResumeForUserSwitchingTests();
 
         verifyUserNotSwitched();
+        verifyWtfNeverLogged();
     }
 
     @Test
@@ -497,13 +577,14 @@
         suspendAndResumeForUserSwitchingTests();
 
         verifyUserNotSwitched();
+        verifyWtfNeverLogged();
     }
 
     @Test
     public void testUserSwitchingOnResume_disabledByOEM_sameGuest() throws Exception {
         disableUserSwitchingDuringResume();
         initTest();
-        setUserInfo(10, "ElGuesto", FLAG_GUEST | FLAG_EPHEMERAL);
+        setUserInfo(10, "ElGuesto", USER_TYPE_FULL_GUEST, FLAG_EPHEMERAL);
         setInitialUser(10);
         setCurrentUser(10);
         expectGuestMarkedForDeletionOk(10);
@@ -513,14 +594,14 @@
 
         verifyUserRemoved(10);
         verifyUserSwitched(11);
-
+        verifyWtfNeverLogged();
     }
 
     @Test
     public void testUserSwitchingOnResume_disabledByOEM_differentGuest() throws Exception {
         disableUserSwitchingDuringResume();
         initTest();
-        setUserInfo(11, "ElGuesto", FLAG_GUEST | FLAG_EPHEMERAL);
+        setUserInfo(11, "ElGuesto", USER_TYPE_FULL_GUEST, FLAG_EPHEMERAL);
         setInitialUser(11);
         setCurrentUser(10);
         expectGuestMarkedForDeletionOk(11);
@@ -530,14 +611,14 @@
 
         verifyUserRemoved(11);
         verifyUserSwitched(12);
+        verifyWtfNeverLogged();
     }
 
     @Test
     public void testUserSwitchingOnResume_disabledByOEM_guestCreationFailed() throws Exception {
         disableUserSwitchingDuringResume();
         initTest();
-        initTest();
-        setUserInfo(10, "ElGuesto", FLAG_GUEST | FLAG_EPHEMERAL);
+        setUserInfo(10, "ElGuesto", USER_TYPE_FULL_GUEST, FLAG_EPHEMERAL);
         setInitialUser(10);
         setCurrentUser(10);
         expectGuestMarkedForDeletionOk(10);
@@ -547,6 +628,7 @@
 
         verifyUserNotSwitched();
         verifyUserNotRemoved(10);
+        // expects WTF
     }
 
     @Test
@@ -554,7 +636,7 @@
             throws Exception {
         disableUserSwitchingDuringResume();
         initTest();
-        setUserInfo(11, "ElGuesto", FLAG_GUEST);
+        setUserInfo(11, "ElGuesto", USER_TYPE_FULL_GUEST, NO_USER_INFO_FLAGS);
         setInitialUser(11);
         setCurrentUser(10);
         expectGuestMarkedForDeletionOk(11);
@@ -564,13 +646,14 @@
 
         verifyUserRemoved(11);
         verifyUserSwitched(12);
+        verifyWtfNeverLogged();
     }
 
     @Test
     public void testUserSwitchingOnResume_disabledByOEM_preDeleteGuestFail() throws Exception {
         disableUserSwitchingDuringResume();
         initTest();
-        setUserInfo(10, "ElGuesto", FLAG_GUEST | FLAG_EPHEMERAL);
+        setUserInfo(10, "ElGuesto", USER_TYPE_FULL_GUEST, FLAG_EPHEMERAL);
         setInitialUser(10);
         setCurrentUser(10);
         expectGuestMarkedForDeletionFail(10);
@@ -579,6 +662,7 @@
 
         verifyUserNotSwitched();
         verifyNoGuestCreated();
+        verifyWtfNeverLogged();
     }
 
     @Test
@@ -591,6 +675,7 @@
         suspendAndResumeForUserSwitchingTests();
 
         verifyUserNotSwitched();
+        // expects WTF
     }
 
     private void suspendAndResumeForUserSwitchingTests() throws Exception {
@@ -676,6 +761,23 @@
         }
     }
 
+    // TODO: should be part of @After, but then it would hide the real test failure (if any). We'd
+    // need a custom rule (like CTS's SafeCleaner) for it...
+    private void verifyWtfNeverLogged() {
+        int size = mWtfs.size();
+
+        switch (size) {
+            case 0:
+                return;
+            case 1:
+                throw mWtfs.get(0);
+            default:
+                StringBuilder msg = new StringBuilder("wtf called ").append(size).append(" times")
+                        .append(": ").append(mWtfs);
+                fail(msg.toString());
+        }
+    }
+
     private static void waitForSemaphore(Semaphore semaphore, long timeoutMs)
             throws InterruptedException {
         if (!semaphore.tryAcquire(timeoutMs, TimeUnit.MILLISECONDS)) {
@@ -692,14 +794,18 @@
     }
 
     private void setUserInfo(int userId, int flags) {
-        setUserInfo(userId, /* name= */ null, flags);
+        setUserInfo(userId, /* name= */ null, /* userType= */ null, flags);
     }
 
-    private void setUserInfo(int userId, @Nullable String name, int flags) {
+    private void setUserInfo(int userId, @Nullable String name, @Nullable String userType,
+            int flags) {
         final UserInfo userInfo = new UserInfo();
         userInfo.id = userId;
         userInfo.name = name;
         userInfo.flags = flags;
+        if (userType != null) {
+            userInfo.userType = userType;
+        }
         Log.v(TAG, "UM.getUserInfo("  + userId + ") will return " + userInfo.toFullString());
         when(mUserManager.getUserInfo(userId)).thenReturn(userInfo);
     }
@@ -777,9 +883,6 @@
 
         @Override
         public void refreshDisplayBrightness() {}
-
-        @Override
-        public void reconfigureSecondaryDisplays() {}
     }
 
     private static final class MockSystemStateInterface implements SystemStateInterface {
diff --git a/tests/carservice_unit_test/src/com/android/car/CarProjectionServiceTest.java b/tests/carservice_unit_test/src/com/android/car/CarProjectionServiceTest.java
index 2866d11..0224b40 100644
--- a/tests/carservice_unit_test/src/com/android/car/CarProjectionServiceTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/CarProjectionServiceTest.java
@@ -51,18 +51,16 @@
 import android.os.RemoteException;
 
 import androidx.test.core.app.ApplicationProvider;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.filters.FlakyTest;
 
 import org.junit.Before;
-import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
 import org.mockito.InOrder;
 import org.mockito.Mock;
 import org.mockito.Spy;
-import org.mockito.junit.MockitoJUnit;
-import org.mockito.junit.MockitoRule;
+import org.mockito.junit.MockitoJUnitRunner;
 
 import java.util.Arrays;
 import java.util.BitSet;
@@ -70,7 +68,7 @@
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 
-@RunWith(AndroidJUnit4.class)
+@RunWith(MockitoJUnitRunner.class)
 public class CarProjectionServiceTest {
     private static final int MD_ID1 = 1;
     private static final int MD_ID2 = 2;
@@ -84,9 +82,6 @@
 
     private final IBinder mToken = new Binder();
 
-    @Rule
-    public MockitoRule rule = MockitoJUnit.rule();
-
     private CarProjectionService mService;
 
     @Spy
@@ -251,6 +246,7 @@
     }
 
     @Test
+    @FlakyTest
     public void getWifiChannels() {
         List<Integer> expectedWifiChannels = Arrays.asList(2400, 5600);
         when(mWifiScanner.getAvailableChannels(anyInt())).thenReturn(expectedWifiChannels);
diff --git a/tests/carservice_unit_test/src/com/android/car/LocationManagerProxyTest.java b/tests/carservice_unit_test/src/com/android/car/LocationManagerProxyTest.java
index 6fa4e3b..d298564 100644
--- a/tests/carservice_unit_test/src/com/android/car/LocationManagerProxyTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/LocationManagerProxyTest.java
@@ -27,14 +27,16 @@
 
 import org.junit.Before;
 import org.junit.Test;
+import org.junit.runner.RunWith;
 import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
+import org.mockito.junit.MockitoJUnitRunner;
 
 /**
  * This class contains unit tests for {@link LocationManagerProxy}.
  *
  * Mocks are used for {@link Context} and {@link LocationManager}.
  */
+@RunWith(MockitoJUnitRunner.class)
 public class LocationManagerProxyTest {
     private static final String TAG = "LocationManagerProxyTest";
     private LocationManagerProxy mLocationManagerProxy;
@@ -46,7 +48,6 @@
     /** Initialize all of the objects with the @Mock annotation. */
     @Before
     public void setUp() throws Exception {
-        MockitoAnnotations.initMocks(this);
         when(mMockContext.getSystemService(Context.LOCATION_SERVICE))
                 .thenReturn(mMockLocationManager);
         mLocationManagerProxy = new LocationManagerProxy(mMockContext);
diff --git a/tests/carservice_unit_test/src/com/android/car/SystemStateInterfaceTest.java b/tests/carservice_unit_test/src/com/android/car/SystemStateInterfaceTest.java
index 5f8ac00..2e6344d 100644
--- a/tests/carservice_unit_test/src/com/android/car/SystemStateInterfaceTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/SystemStateInterfaceTest.java
@@ -87,12 +87,24 @@
         public int forceSuspend(int timeoutMs) {
             return 0; // Success
         }
+        @Override
+        public void setDisplayWhitelistForUser(int userId, int[] displayIds) {
+        }
+        @Override
+        public void setPassengerDisplays(int[] displayIdsForPassenger) {
+        }
     }
     private static class TestServiceHelperFails extends ICarServiceHelper.Stub {
         @Override
         public int forceSuspend(int timeoutMs) {
             return 1; // Failure
         }
+        @Override
+        public void setDisplayWhitelistForUser(int userId, int[] displayIds) {
+        }
+        @Override
+        public void setPassengerDisplays(int[] displayIdsForPassenger) {
+        }
     }
     // Invoke enterDeepSleep() before setting the helper.
     // Return the result from enterDeepSleep().
diff --git a/tests/carservice_unit_test/src/com/android/car/VmsPublisherServiceTest.java b/tests/carservice_unit_test/src/com/android/car/VmsPublisherServiceTest.java
deleted file mode 100644
index 2bd09df..0000000
--- a/tests/carservice_unit_test/src/com/android/car/VmsPublisherServiceTest.java
+++ /dev/null
@@ -1,368 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.car;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNotSame;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertSame;
-import static org.mockito.Mockito.doThrow;
-import static org.mockito.Mockito.reset;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyNoMoreInteractions;
-import static org.mockito.Mockito.when;
-
-import android.car.Car;
-import android.car.vms.IVmsPublisherClient;
-import android.car.vms.IVmsPublisherService;
-import android.car.vms.IVmsSubscriberClient;
-import android.car.vms.VmsLayer;
-import android.car.vms.VmsLayersOffering;
-import android.car.vms.VmsSubscriptionState;
-import android.content.Context;
-import android.content.pm.PackageManager;
-import android.os.Binder;
-import android.os.IBinder;
-import android.os.RemoteException;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.car.stats.CarStatsService;
-import com.android.car.stats.VmsClientLogger;
-import com.android.car.vms.VmsBrokerService;
-import com.android.car.vms.VmsClientManager;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Captor;
-import org.mockito.Mock;
-import org.mockito.junit.MockitoJUnit;
-import org.mockito.junit.MockitoRule;
-
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashSet;
-
-@SmallTest
-public class VmsPublisherServiceTest {
-    private static final VmsSubscriptionState SUBSCRIPTION_STATE = new VmsSubscriptionState(12345,
-            Collections.emptySet(), Collections.emptySet());
-    private static final VmsLayersOffering OFFERING = new VmsLayersOffering(Collections.emptySet(),
-            54321);
-    private static final VmsLayer LAYER = new VmsLayer(1, 2, 3);
-
-    private static final int PUBLISHER_ID = 54321;
-    private static final byte[] PAYLOAD = new byte[]{1, 2, 3, 4};
-
-    private static final int PUBLISHER_UID = 10100;
-    private static final int SUBSCRIBER_UID = 10101;
-    private static final int SUBSCRIBER_UID2 = 10102;
-    private static final int NO_SUBSCRIBERS_UID = -1;
-
-    @Rule
-    public MockitoRule mMockitoRule = MockitoJUnit.rule();
-    @Mock
-    private Context mContext;
-    @Mock
-    private CarStatsService mStatsService;
-    @Mock
-    private VmsBrokerService mBrokerService;
-    @Captor
-    private ArgumentCaptor<VmsBrokerService.PublisherListener> mProxyCaptor;
-    @Mock
-    private VmsClientManager mClientManager;
-
-    @Mock
-    private VmsClientLogger mPublisherLog;
-    @Mock
-    private VmsClientLogger mSubscriberLog;
-    @Mock
-    private VmsClientLogger mSubscriberLog2;
-    @Mock
-    private VmsClientLogger mNoSubscribersLog;
-
-    @Mock
-    private IVmsSubscriberClient mSubscriberClient;
-    @Mock
-    private IVmsSubscriberClient mSubscriberClient2;
-
-    private VmsPublisherService mPublisherService;
-    private MockPublisherClient mPublisherClient;
-    private MockPublisherClient mPublisherClient2;
-
-    @Before
-    public void setUp() {
-        mPublisherService = new VmsPublisherService(mContext, mStatsService, mBrokerService,
-                mClientManager, () -> PUBLISHER_UID);
-        verify(mClientManager).setPublisherService(mPublisherService);
-
-        when(mClientManager.getSubscriberUid(mSubscriberClient)).thenReturn(SUBSCRIBER_UID);
-        when(mClientManager.getSubscriberUid(mSubscriberClient2)).thenReturn(SUBSCRIBER_UID2);
-
-        when(mStatsService.getVmsClientLogger(PUBLISHER_UID)).thenReturn(mPublisherLog);
-        when(mStatsService.getVmsClientLogger(SUBSCRIBER_UID)).thenReturn(mSubscriberLog);
-        when(mStatsService.getVmsClientLogger(SUBSCRIBER_UID2)).thenReturn(mSubscriberLog2);
-        when(mStatsService.getVmsClientLogger(NO_SUBSCRIBERS_UID)).thenReturn(mNoSubscribersLog);
-
-        mPublisherClient = new MockPublisherClient();
-        mPublisherClient2 = new MockPublisherClient();
-        when(mBrokerService.getSubscribersForLayerFromPublisher(LAYER, PUBLISHER_ID))
-                .thenReturn(new HashSet<>(Arrays.asList(mSubscriberClient, mSubscriberClient2)));
-    }
-
-    @After
-    public void tearDown() {
-        verifyNoMoreInteractions(mPublisherLog, mSubscriberLog, mSubscriberLog2, mNoSubscribersLog);
-    }
-
-    @Test
-    public void testInit() {
-        mPublisherService.init();
-    }
-
-    @Test
-    public void testOnClientConnected() {
-        mPublisherService.onClientConnected("SomeClient", mPublisherClient);
-        mPublisherService.onClientConnected("SomeOtherClient", mPublisherClient2);
-        verify(mBrokerService, times(2)).addPublisherListener(mProxyCaptor.capture());
-
-        assertNotNull(mPublisherClient.mPublisherService);
-        assertSame(mPublisherClient.mPublisherService, mProxyCaptor.getAllValues().get(0));
-
-        assertNotNull(mPublisherClient2.mPublisherService);
-        assertSame(mPublisherClient2.mPublisherService, mProxyCaptor.getAllValues().get(1));
-        assertNotSame(mPublisherClient2.mPublisherService, mPublisherClient.mPublisherService);
-    }
-
-    @Test
-    public void testOnClientDisconnected() {
-        mPublisherService.onClientConnected("SomeClient", mPublisherClient);
-        mPublisherService.onClientConnected("SomeOtherClient", mPublisherClient2);
-        verify(mBrokerService, times(2)).addPublisherListener(mProxyCaptor.capture());
-
-        reset(mClientManager, mBrokerService);
-        mPublisherService.onClientDisconnected("SomeClient");
-
-        verify(mBrokerService).removeDeadPublisher(mPublisherClient.mToken);
-        verify(mBrokerService).removePublisherListener(mProxyCaptor.getAllValues().get(0));
-        verifyNoMoreInteractions(mBrokerService);
-    }
-
-    @Test
-    public void testSetLayersOffering() throws Exception {
-        mPublisherService.onClientConnected("SomeClient", mPublisherClient);
-
-        mPublisherClient.mPublisherService.setLayersOffering(mPublisherClient.mToken, OFFERING);
-        verify(mBrokerService).setPublisherLayersOffering(mPublisherClient.mToken, OFFERING);
-    }
-
-    @Test(expected = SecurityException.class)
-    public void testSetLayersOffering_InvalidToken() throws Exception {
-        mPublisherService.onClientConnected("SomeClient", mPublisherClient);
-
-        mPublisherClient.mPublisherService.setLayersOffering(new Binder(), OFFERING);
-    }
-
-    @Test(expected = SecurityException.class)
-    public void testSetLayersOffering_Disconnected() throws Exception {
-        mPublisherService.onClientConnected("SomeClient", mPublisherClient);
-        mPublisherService.onClientDisconnected("SomeClient");
-
-        mPublisherClient.mPublisherService.setLayersOffering(mPublisherClient.mToken, OFFERING);
-    }
-
-    @Test(expected = SecurityException.class)
-    public void testSetLayersOffering_PermissionDenied() throws Exception {
-        mPublisherService.onClientConnected("SomeClient", mPublisherClient);
-        when(mContext.checkCallingOrSelfPermission(Car.PERMISSION_VMS_PUBLISHER)).thenReturn(
-                PackageManager.PERMISSION_DENIED);
-
-        mPublisherClient.mPublisherService.setLayersOffering(mPublisherClient.mToken, OFFERING);
-    }
-
-    @Test
-    public void testPublish() throws Exception {
-        mPublisherService.onClientConnected("SomeClient", mPublisherClient);
-
-        mPublisherClient.mPublisherService.publish(mPublisherClient.mToken, LAYER, PUBLISHER_ID,
-                PAYLOAD);
-        verify(mSubscriberClient).onVmsMessageReceived(LAYER, PAYLOAD);
-        verify(mSubscriberClient2).onVmsMessageReceived(LAYER, PAYLOAD);
-
-        verify(mPublisherLog).logPacketSent(LAYER, PAYLOAD.length);
-        verify(mSubscriberLog).logPacketReceived(LAYER, PAYLOAD.length);
-        verify(mSubscriberLog2).logPacketReceived(LAYER, PAYLOAD.length);
-    }
-
-    @Test
-    public void testPublishNullLayerAndNullPayload() throws Exception {
-        mPublisherService.onClientConnected("SomeClient", mPublisherClient);
-
-        // We just want to ensure that no exceptions are thrown here.
-        mPublisherClient.mPublisherService.publish(mPublisherClient.mToken, null, PUBLISHER_ID,
-                null);
-    }
-
-    @Test
-    public void testPublish_NoSubscribers() throws Exception {
-        mPublisherService.onClientConnected("SomeClient", mPublisherClient);
-        when(mBrokerService.getSubscribersForLayerFromPublisher(LAYER, PUBLISHER_ID))
-                .thenReturn(Collections.emptySet());
-
-        mPublisherClient.mPublisherService.publish(mPublisherClient.mToken, LAYER, PUBLISHER_ID,
-                PAYLOAD);
-
-        verify(mPublisherLog).logPacketSent(LAYER, PAYLOAD.length);
-        verify(mNoSubscribersLog).logPacketDropped(LAYER, PAYLOAD.length);
-    }
-
-    @Test
-    public void testPublish_ClientError() throws Exception {
-        mPublisherService.onClientConnected("SomeClient", mPublisherClient);
-        doThrow(new RemoteException()).when(mSubscriberClient).onVmsMessageReceived(LAYER, PAYLOAD);
-
-        mPublisherClient.mPublisherService.publish(mPublisherClient.mToken, LAYER, PUBLISHER_ID,
-                PAYLOAD);
-        verify(mSubscriberClient).onVmsMessageReceived(LAYER, PAYLOAD);
-        verify(mSubscriberClient2).onVmsMessageReceived(LAYER, PAYLOAD);
-
-        verify(mPublisherLog).logPacketSent(LAYER, PAYLOAD.length);
-        verify(mSubscriberLog).logPacketDropped(LAYER, PAYLOAD.length);
-        verify(mSubscriberLog2).logPacketReceived(LAYER, PAYLOAD.length);
-    }
-
-    @Test(expected = SecurityException.class)
-    public void testPublish_InvalidToken() throws Exception {
-        mPublisherService.onClientConnected("SomeClient", mPublisherClient);
-
-        mPublisherClient.mPublisherService.publish(new Binder(), LAYER, PUBLISHER_ID, PAYLOAD);
-    }
-
-    @Test(expected = SecurityException.class)
-    public void testPublish_Disconnected() throws Exception {
-        mPublisherService.onClientConnected("SomeClient", mPublisherClient);
-        mPublisherService.onClientDisconnected("SomeClient");
-
-        mPublisherClient.mPublisherService.publish(mPublisherClient.mToken, LAYER, PUBLISHER_ID,
-                PAYLOAD);
-    }
-
-    @Test(expected = SecurityException.class)
-    public void testPublish_PermissionDenied() throws Exception {
-        mPublisherService.onClientConnected("SomeClient", mPublisherClient);
-        when(mContext.checkCallingOrSelfPermission(Car.PERMISSION_VMS_PUBLISHER)).thenReturn(
-                PackageManager.PERMISSION_DENIED);
-
-        mPublisherClient.mPublisherService.publish(mPublisherClient.mToken, LAYER, PUBLISHER_ID,
-                PAYLOAD);
-    }
-
-    @Test
-    public void testGetSubscriptions() throws Exception {
-        mPublisherService.onClientConnected("SomeClient", mPublisherClient);
-        when(mBrokerService.getSubscriptionState()).thenReturn(SUBSCRIPTION_STATE);
-
-        assertEquals(SUBSCRIPTION_STATE, mPublisherClient.mPublisherService.getSubscriptions());
-    }
-
-    @Test(expected = SecurityException.class)
-    public void testGetSubscriptions_Disconnected() throws Exception {
-        mPublisherService.onClientConnected("SomeClient", mPublisherClient);
-        mPublisherService.onClientDisconnected("SomeClient");
-
-        mPublisherClient.mPublisherService.getSubscriptions();
-    }
-
-    @Test(expected = SecurityException.class)
-    public void testGetSubscriptions_PermissionDenied() throws Exception {
-        mPublisherService.onClientConnected("SomeClient", mPublisherClient);
-        when(mContext.checkCallingOrSelfPermission(Car.PERMISSION_VMS_PUBLISHER)).thenReturn(
-                PackageManager.PERMISSION_DENIED);
-
-        mPublisherClient.mPublisherService.getSubscriptions();
-    }
-
-    @Test
-    public void testGetPublisherId() throws Exception {
-        mPublisherService.onClientConnected("SomeClient", mPublisherClient);
-        when(mBrokerService.getPublisherId(PAYLOAD)).thenReturn(PUBLISHER_ID);
-
-        assertEquals(PUBLISHER_ID, mPublisherClient.mPublisherService.getPublisherId(PAYLOAD));
-    }
-
-    @Test(expected = SecurityException.class)
-    public void testGetPublisherId_Disconnected() throws Exception {
-        mPublisherService.onClientConnected("SomeClient", mPublisherClient);
-        mPublisherService.onClientDisconnected("SomeClient");
-
-        mPublisherClient.mPublisherService.getPublisherId(PAYLOAD);
-    }
-
-    @Test(expected = SecurityException.class)
-    public void testGetPublisherId_PermissionDenied() throws Exception {
-        mPublisherService.onClientConnected("SomeClient", mPublisherClient);
-        when(mContext.checkCallingOrSelfPermission(Car.PERMISSION_VMS_PUBLISHER)).thenReturn(
-                PackageManager.PERMISSION_DENIED);
-
-        mPublisherClient.mPublisherService.getPublisherId(PAYLOAD);
-    }
-
-    @Test
-    public void testOnSubscriptionChange() {
-        mPublisherService.onClientConnected("SomeClient", mPublisherClient);
-        mPublisherService.onClientConnected("SomeOtherClient", mPublisherClient2);
-        verify(mBrokerService, times(2)).addPublisherListener(mProxyCaptor.capture());
-
-        mProxyCaptor.getAllValues().get(0).onSubscriptionChange(SUBSCRIPTION_STATE);
-
-        assertEquals(SUBSCRIPTION_STATE, mPublisherClient.mSubscriptionState);
-        assertNull(mPublisherClient2.mSubscriptionState);
-    }
-
-    @Test
-    public void testRelease() {
-        mPublisherService.release();
-    }
-
-    private class MockPublisherClient extends IVmsPublisherClient.Stub {
-        private IBinder mToken;
-        private IVmsPublisherService mPublisherService;
-        private VmsSubscriptionState mSubscriptionState;
-
-        @Override
-        public void setVmsPublisherService(IBinder token, IVmsPublisherService service) {
-            assertNotNull(token);
-            assertNotNull(service);
-            if (mToken != null) {
-                throw new IllegalArgumentException("Publisher service set multiple times");
-            }
-            this.mToken = token;
-            this.mPublisherService = service;
-        }
-
-        @Override
-        public void onVmsSubscriptionChange(VmsSubscriptionState subscriptionState) {
-            assertNotNull(subscriptionState);
-            this.mSubscriptionState = subscriptionState;
-        }
-    }
-}
diff --git a/tests/carservice_unit_test/src/com/android/car/VmsRoutingTest.java b/tests/carservice_unit_test/src/com/android/car/VmsRoutingTest.java
deleted file mode 100644
index 9e516a8..0000000
--- a/tests/carservice_unit_test/src/com/android/car/VmsRoutingTest.java
+++ /dev/null
@@ -1,892 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.car;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-
-import android.car.vms.IVmsSubscriberClient;
-import android.car.vms.VmsAssociatedLayer;
-import android.car.vms.VmsAvailableLayers;
-import android.car.vms.VmsLayer;
-import android.car.vms.VmsSubscriptionState;
-
-import androidx.test.filters.SmallTest;
-
-import org.junit.Before;
-import org.junit.Test;
-
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashSet;
-
-@SmallTest
-public class VmsRoutingTest {
-    private static final VmsLayer LAYER_1 = new VmsLayer(1, 1, 2);
-    private static final VmsLayer LAYER_2 = new VmsLayer(1, 3, 3);
-    private static final VmsLayer[] LAYERS = {LAYER_1, LAYER_2};
-    private static final int PUBLISHER_ID_1 = 123;
-    private static final int PUBLISHER_ID_2 = 456;
-    private static final int[] PUBLISHER_IDS = {PUBLISHER_ID_1, PUBLISHER_ID_2};
-
-    private VmsRouting mRouting;
-    private IVmsSubscriberClient mSubscriber;
-    private IVmsSubscriberClient mSubscriberRewrapped;
-    private IVmsSubscriberClient mSubscriber2;
-
-    @Before
-    public void setUp() throws Exception {
-        mRouting = new VmsRouting();
-        mSubscriber = new MockVmsSubscriber();
-        mSubscriberRewrapped = IVmsSubscriberClient.Stub.asInterface(mSubscriber.asBinder());
-        mSubscriber2 = new MockVmsSubscriber();
-    }
-
-    @Test
-    public void testDefaultSubscriptionState() {
-        assertNoSubscribers();
-
-        assertEquals(
-                new VmsSubscriptionState(0, Collections.emptySet(), Collections.emptySet()),
-                mRouting.getSubscriptionState());
-    }
-
-    @Test
-    public void testAddPassiveSubscriber() {
-        mRouting.addSubscription(mSubscriber);
-
-        // Receives messages for all layers and publishers
-        assertSubscribers(LAYER_1, mSubscriber);
-        assertSubscribers(LAYER_2, mSubscriber);
-
-        assertEquals(
-                new VmsSubscriptionState(0, Collections.emptySet(), Collections.emptySet()),
-                mRouting.getSubscriptionState());
-    }
-
-    @Test
-    public void testAddPassiveSubscriber_MultipleTimes() {
-        mRouting.addSubscription(mSubscriber);
-        mRouting.addSubscription(mSubscriber);
-
-        assertSubscribers(LAYER_1, mSubscriber);
-        assertSubscribers(LAYER_2, mSubscriber);
-
-        assertEquals(
-                new VmsSubscriptionState(0, Collections.emptySet(), Collections.emptySet()),
-                mRouting.getSubscriptionState());
-    }
-
-    @Test
-    public void testAddPassiveSubscriber_MultipleTimes_Rewrapped() {
-        mRouting.addSubscription(mSubscriber);
-        mRouting.addSubscription(mSubscriberRewrapped);
-
-        assertSubscribers(LAYER_1, mSubscriber);
-        assertSubscribers(LAYER_2, mSubscriber);
-
-        assertEquals(
-                new VmsSubscriptionState(0, Collections.emptySet(), Collections.emptySet()),
-                mRouting.getSubscriptionState());
-    }
-
-    @Test
-    public void testAddPassiveSubscriber_MultipleSubscribers() {
-        mRouting.addSubscription(mSubscriber);
-        mRouting.addSubscription(mSubscriber2);
-
-        assertSubscribers(LAYER_1, mSubscriber, mSubscriber2);
-        assertSubscribers(LAYER_2, mSubscriber, mSubscriber2);
-
-        assertEquals(
-                new VmsSubscriptionState(0, Collections.emptySet(), Collections.emptySet()),
-                mRouting.getSubscriptionState());
-    }
-
-    @Test
-    public void testRemovePassiveSubscriber() {
-        mRouting.addSubscription(mSubscriber);
-        mRouting.removeSubscription(mSubscriber);
-
-        assertNoSubscribers();
-
-        assertEquals(
-                new VmsSubscriptionState(0, Collections.emptySet(), Collections.emptySet()),
-                mRouting.getSubscriptionState());
-    }
-
-    @Test
-    public void testRemovePassiveSubscriber_NoSubscriptions() {
-        mRouting.removeSubscription(mSubscriber);
-
-        assertNoSubscribers();
-
-        assertEquals(
-                new VmsSubscriptionState(0, Collections.emptySet(), Collections.emptySet()),
-                mRouting.getSubscriptionState());
-    }
-
-    @Test
-    public void testRemovePassiveSubscriber_MultipleTimes() {
-        mRouting.addSubscription(mSubscriber);
-        mRouting.removeSubscription(mSubscriber);
-        mRouting.removeSubscription(mSubscriber);
-
-        assertNoSubscribers();
-
-        assertEquals(
-                new VmsSubscriptionState(0, Collections.emptySet(), Collections.emptySet()),
-                mRouting.getSubscriptionState());
-    }
-
-    @Test
-    public void testRemovePassiveSubscriber_Rewrapped() {
-        mRouting.addSubscription(mSubscriber);
-        mRouting.removeSubscription(mSubscriberRewrapped);
-
-        assertNoSubscribers();
-
-        assertEquals(
-                new VmsSubscriptionState(0, Collections.emptySet(), Collections.emptySet()),
-                mRouting.getSubscriptionState());
-    }
-
-    @Test
-    public void testRemovePassiveSubscriber_UnknownSubscriber() {
-        mRouting.addSubscription(mSubscriber);
-        mRouting.removeSubscription(mSubscriber2);
-
-        assertSubscribers(mSubscriber);
-
-        assertEquals(
-                new VmsSubscriptionState(0, Collections.emptySet(), Collections.emptySet()),
-                mRouting.getSubscriptionState());
-    }
-
-    @Test
-    public void testRemovePassiveSubscriber_MultipleSubscribers() {
-        mRouting.addSubscription(mSubscriber);
-        mRouting.addSubscription(mSubscriber2);
-        mRouting.removeSubscription(mSubscriber2);
-
-        assertSubscribers(mSubscriber);
-
-        assertEquals(
-                new VmsSubscriptionState(0, Collections.emptySet(), Collections.emptySet()),
-                mRouting.getSubscriptionState());
-    }
-
-    @Test
-    public void testAddLayerSubscriber() {
-        mRouting.addSubscription(mSubscriber, LAYER_1);
-
-        assertSubscribers(LAYER_1, mSubscriber);
-        assertNoSubscribers(LAYER_2);
-
-        assertEquals(
-                new VmsSubscriptionState(1, Collections.singleton(LAYER_1), Collections.emptySet()),
-                mRouting.getSubscriptionState());
-    }
-
-    @Test
-    public void testAddLayerSubscriber_MultipleTimes() {
-        mRouting.addSubscription(mSubscriber, LAYER_1);
-        mRouting.addSubscription(mSubscriber, LAYER_1);
-
-        assertSubscribers(LAYER_1, mSubscriber);
-        assertNoSubscribers(LAYER_2);
-
-        assertEquals(
-                new VmsSubscriptionState(1, Collections.singleton(LAYER_1), Collections.emptySet()),
-                mRouting.getSubscriptionState());
-    }
-
-    @Test
-    public void testAddLayerSubscriber_MultipleTimes_Rewrapped() {
-        mRouting.addSubscription(mSubscriber, LAYER_1);
-        mRouting.addSubscription(mSubscriberRewrapped, LAYER_1);
-
-        assertSubscribers(LAYER_1, mSubscriber);
-        assertNoSubscribers(LAYER_2);
-
-        assertEquals(
-                new VmsSubscriptionState(1, Collections.singleton(LAYER_1), Collections.emptySet()),
-                mRouting.getSubscriptionState());
-    }
-
-    @Test
-    public void testAddLayerSubscriber_MultipleLayers() {
-        mRouting.addSubscription(mSubscriber, LAYER_1);
-        mRouting.addSubscription(mSubscriber, LAYER_2);
-
-        assertSubscribers(LAYER_1, mSubscriber);
-        assertSubscribers(LAYER_2, mSubscriber);
-
-        assertEquals(
-                new VmsSubscriptionState(2,
-                        new HashSet<>(Arrays.asList(LAYER_1, LAYER_2)), Collections.emptySet()),
-                mRouting.getSubscriptionState());
-    }
-
-    @Test
-    public void testAddLayerSubscriber_MultipleSubscribers() {
-        mRouting.addSubscription(mSubscriber, LAYER_1);
-        mRouting.addSubscription(mSubscriber2, LAYER_1);
-
-        assertSubscribers(LAYER_1, mSubscriber, mSubscriber2);
-        assertNoSubscribers(LAYER_2);
-
-        assertEquals(
-                new VmsSubscriptionState(2, Collections.singleton(LAYER_1), Collections.emptySet()),
-                mRouting.getSubscriptionState());
-    }
-
-    @Test
-    public void testAddLayerSubscriber_MultipleSubscribers_MultipleLayers() {
-        mRouting.addSubscription(mSubscriber, LAYER_1);
-        mRouting.addSubscription(mSubscriber2, LAYER_2);
-
-        assertSubscribers(LAYER_1, mSubscriber);
-        assertSubscribers(LAYER_2, mSubscriber2);
-
-        assertEquals(
-                new VmsSubscriptionState(2,
-                        new HashSet<>(Arrays.asList(LAYER_1, LAYER_2)), Collections.emptySet()),
-                mRouting.getSubscriptionState());
-    }
-
-    @Test
-    public void testRemoveLayerSubscriber() {
-        mRouting.addSubscription(mSubscriber, LAYER_1);
-        mRouting.removeSubscription(mSubscriber, LAYER_1);
-
-        assertNoSubscribers();
-
-        assertEquals(
-                new VmsSubscriptionState(2, Collections.emptySet(), Collections.emptySet()),
-                mRouting.getSubscriptionState());
-    }
-
-    @Test
-    public void testRemoveLayerSubscriber_NoSubscriptions() {
-        mRouting.removeSubscription(mSubscriber, LAYER_1);
-
-        assertNoSubscribers();
-
-        assertEquals(
-                new VmsSubscriptionState(0, Collections.emptySet(), Collections.emptySet()),
-                mRouting.getSubscriptionState());
-    }
-
-    @Test
-    public void testRemoveLayerSubscriber_MultipleTimes() {
-        mRouting.addSubscription(mSubscriber, LAYER_1);
-        mRouting.removeSubscription(mSubscriber, LAYER_1);
-        mRouting.removeSubscription(mSubscriber, LAYER_1);
-
-        assertNoSubscribers();
-
-        assertEquals(
-                new VmsSubscriptionState(2, Collections.emptySet(), Collections.emptySet()),
-                mRouting.getSubscriptionState());
-    }
-
-    @Test
-    public void testRemoveLayerSubscriber_Rewrapped() {
-        mRouting.addSubscription(mSubscriber, LAYER_1);
-        mRouting.removeSubscription(mSubscriberRewrapped, LAYER_1);
-
-        assertNoSubscribers();
-
-        assertEquals(
-                new VmsSubscriptionState(2, Collections.emptySet(), Collections.emptySet()),
-                mRouting.getSubscriptionState());
-    }
-
-    @Test
-    public void testRemoveLayerSubscriber_UnknownSubscriber() {
-        mRouting.addSubscription(mSubscriber, LAYER_1);
-        mRouting.removeSubscription(mSubscriber2, LAYER_1);
-
-        assertSubscribers(LAYER_1, mSubscriber);
-        assertNoSubscribers(LAYER_2);
-
-        assertEquals(
-                new VmsSubscriptionState(1, Collections.singleton(LAYER_1), Collections.emptySet()),
-                mRouting.getSubscriptionState());
-    }
-
-    @Test
-    public void testRemoveLayerSubscriber_MultipleLayers() {
-        mRouting.addSubscription(mSubscriber, LAYER_1);
-        mRouting.addSubscription(mSubscriber, LAYER_2);
-        mRouting.removeSubscription(mSubscriber, LAYER_2);
-
-        assertSubscribers(LAYER_1, mSubscriber);
-        assertNoSubscribers(LAYER_2);
-
-        assertEquals(
-                new VmsSubscriptionState(3, Collections.singleton(LAYER_1), Collections.emptySet()),
-                mRouting.getSubscriptionState());
-    }
-
-    @Test
-    public void testRemoveLayerSubscriber_MultipleSubscribers() {
-        mRouting.addSubscription(mSubscriber, LAYER_1);
-        mRouting.addSubscription(mSubscriber2, LAYER_1);
-        mRouting.removeSubscription(mSubscriber2, LAYER_1);
-
-        assertSubscribers(LAYER_1, mSubscriber);
-        assertNoSubscribers(LAYER_2);
-
-        assertEquals(
-                new VmsSubscriptionState(3, Collections.singleton(LAYER_1), Collections.emptySet()),
-                mRouting.getSubscriptionState());
-    }
-
-    @Test
-    public void testRemoveLayerSubscriber_MultipleSubscribers_MultipleLayers() {
-        mRouting.addSubscription(mSubscriber, LAYER_1);
-        mRouting.addSubscription(mSubscriber2, LAYER_2);
-        mRouting.removeSubscription(mSubscriber2, LAYER_2);
-
-        assertSubscribers(LAYER_1, mSubscriber);
-        assertNoSubscribers(LAYER_2);
-
-        assertEquals(
-                new VmsSubscriptionState(3, Collections.singleton(LAYER_1), Collections.emptySet()),
-                mRouting.getSubscriptionState());
-    }
-
-    @Test
-    public void testAddPublisherSubscriber() {
-        mRouting.addSubscription(mSubscriber, LAYER_1, PUBLISHER_ID_1);
-
-        assertSubscribers(LAYER_1, PUBLISHER_ID_1, mSubscriber);
-        assertNoSubscribers(LAYER_1, PUBLISHER_ID_2);
-        assertNoSubscribers(LAYER_2);
-
-        assertEquals(
-                new VmsSubscriptionState(1, Collections.emptySet(),
-                        Collections.singleton(new VmsAssociatedLayer(
-                                LAYER_1, Collections.singleton(PUBLISHER_ID_1)))),
-                mRouting.getSubscriptionState());
-    }
-
-    @Test
-    public void testAddPublisherSubscriber_MultipleTimes() {
-        mRouting.addSubscription(mSubscriber, LAYER_1, PUBLISHER_ID_1);
-        mRouting.addSubscription(mSubscriber, LAYER_1, PUBLISHER_ID_1);
-
-        assertSubscribers(LAYER_1, PUBLISHER_ID_1, mSubscriber);
-        assertNoSubscribers(LAYER_1, PUBLISHER_ID_2);
-        assertNoSubscribers(LAYER_2);
-
-        assertEquals(
-                new VmsSubscriptionState(1, Collections.emptySet(),
-                        Collections.singleton(new VmsAssociatedLayer(
-                                LAYER_1, Collections.singleton(PUBLISHER_ID_1)))),
-                mRouting.getSubscriptionState());
-    }
-
-    @Test
-    public void testAddPublisherSubscriber_MultipleTimes_Rewrapped() {
-        mRouting.addSubscription(mSubscriber, LAYER_1, PUBLISHER_ID_1);
-        mRouting.addSubscription(mSubscriberRewrapped, LAYER_1, PUBLISHER_ID_1);
-
-        assertSubscribers(LAYER_1, PUBLISHER_ID_1, mSubscriber);
-        assertNoSubscribers(LAYER_1, PUBLISHER_ID_2);
-        assertNoSubscribers(LAYER_2);
-
-        assertEquals(
-                new VmsSubscriptionState(1, Collections.emptySet(),
-                        Collections.singleton(new VmsAssociatedLayer(
-                                LAYER_1, Collections.singleton(PUBLISHER_ID_1)))),
-                mRouting.getSubscriptionState());
-    }
-
-    @Test
-    public void testAddPublisherSubscriber_MultipleSubscribers() {
-        mRouting.addSubscription(mSubscriber, LAYER_1, PUBLISHER_ID_1);
-        mRouting.addSubscription(mSubscriber2, LAYER_1, PUBLISHER_ID_1);
-
-        assertSubscribers(LAYER_1, PUBLISHER_ID_1, mSubscriber, mSubscriber2);
-        assertNoSubscribers(LAYER_1, PUBLISHER_ID_2);
-        assertNoSubscribers(LAYER_2);
-
-        assertEquals(
-                new VmsSubscriptionState(2, Collections.emptySet(),
-                        Collections.singleton(new VmsAssociatedLayer(
-                                LAYER_1, Collections.singleton(PUBLISHER_ID_1)))),
-                mRouting.getSubscriptionState());
-    }
-
-    @Test
-    public void testAddPublisherSubscriber_MultipleLayers() {
-        mRouting.addSubscription(mSubscriber, LAYER_1, PUBLISHER_ID_1);
-        mRouting.addSubscription(mSubscriber, LAYER_2, PUBLISHER_ID_1);
-
-        assertSubscribers(LAYER_1, PUBLISHER_ID_1, mSubscriber);
-        assertSubscribers(LAYER_2, PUBLISHER_ID_1, mSubscriber);
-        assertNoSubscribers(LAYER_1, PUBLISHER_ID_2);
-        assertNoSubscribers(LAYER_2, PUBLISHER_ID_2);
-
-        assertEquals(
-                new VmsSubscriptionState(2, Collections.emptySet(),
-                        new HashSet<>(Arrays.asList(
-                                new VmsAssociatedLayer(
-                                        LAYER_1, Collections.singleton(PUBLISHER_ID_1)),
-                                new VmsAssociatedLayer(
-                                        LAYER_2, Collections.singleton(PUBLISHER_ID_1))
-                        ))),
-                mRouting.getSubscriptionState());
-    }
-
-    @Test
-    public void testAddPublisherSubscriber_MultiplePublishers() {
-        mRouting.addSubscription(mSubscriber, LAYER_1, PUBLISHER_ID_1);
-        mRouting.addSubscription(mSubscriber, LAYER_1, PUBLISHER_ID_2);
-
-        assertSubscribers(LAYER_1, PUBLISHER_ID_1, mSubscriber);
-        assertSubscribers(LAYER_1, PUBLISHER_ID_2, mSubscriber);
-        assertNoSubscribers(LAYER_2);
-
-        assertEquals(
-                new VmsSubscriptionState(2, Collections.emptySet(),
-                        Collections.singleton(new VmsAssociatedLayer(
-                                LAYER_1,
-                                new HashSet<>(Arrays.asList(PUBLISHER_ID_1, PUBLISHER_ID_2))
-                        ))),
-                mRouting.getSubscriptionState());
-    }
-
-    @Test
-    public void testAddPublisherSubscriber_MultipleSubscribers_MultipleLayers() {
-        mRouting.addSubscription(mSubscriber, LAYER_1, PUBLISHER_ID_1);
-        mRouting.addSubscription(mSubscriber2, LAYER_2, PUBLISHER_ID_1);
-
-        assertSubscribers(LAYER_1, PUBLISHER_ID_1, mSubscriber);
-        assertSubscribers(LAYER_2, PUBLISHER_ID_1, mSubscriber2);
-        assertNoSubscribers(LAYER_1, PUBLISHER_ID_2);
-        assertNoSubscribers(LAYER_2, PUBLISHER_ID_2);
-
-        assertEquals(
-                new VmsSubscriptionState(2, Collections.emptySet(),
-                        new HashSet<>(Arrays.asList(
-                                new VmsAssociatedLayer(
-                                        LAYER_1, Collections.singleton(PUBLISHER_ID_1)),
-                                new VmsAssociatedLayer(
-                                        LAYER_2, Collections.singleton(PUBLISHER_ID_1))
-                        ))),
-                mRouting.getSubscriptionState());
-    }
-
-    @Test
-    public void testAddPublisherSubscriber_MultipleSubscribers_MultiplePublishers() {
-        mRouting.addSubscription(mSubscriber, LAYER_1, PUBLISHER_ID_1);
-        mRouting.addSubscription(mSubscriber2, LAYER_1, PUBLISHER_ID_2);
-
-        assertSubscribers(LAYER_1, PUBLISHER_ID_1, mSubscriber);
-        assertSubscribers(LAYER_1, PUBLISHER_ID_2, mSubscriber2);
-        assertNoSubscribers(LAYER_2);
-
-        assertEquals(
-                new VmsSubscriptionState(2, Collections.emptySet(),
-                        Collections.singleton(new VmsAssociatedLayer(
-                                LAYER_1,
-                                new HashSet<>(Arrays.asList(PUBLISHER_ID_1, PUBLISHER_ID_2))
-                        ))),
-                mRouting.getSubscriptionState());
-    }
-
-    @Test
-    public void testRemovePublisherSubscriber() {
-        mRouting.addSubscription(mSubscriber, LAYER_1, PUBLISHER_ID_1);
-        mRouting.removeSubscription(mSubscriber, LAYER_1, PUBLISHER_ID_1);
-
-        assertNoSubscribers();
-
-        assertEquals(
-                new VmsSubscriptionState(2, Collections.emptySet(), Collections.emptySet()),
-                mRouting.getSubscriptionState());
-    }
-
-    @Test
-    public void testRemovePublisherSubscriber_NoSubscribers() {
-        mRouting.removeSubscription(mSubscriber, LAYER_1, PUBLISHER_ID_1);
-
-        assertNoSubscribers();
-
-        assertEquals(
-                new VmsSubscriptionState(0, Collections.emptySet(), Collections.emptySet()),
-                mRouting.getSubscriptionState());
-    }
-
-    @Test
-    public void testRemovePublisherSubscriber_MultipleTimes() {
-        mRouting.addSubscription(mSubscriber, LAYER_1, PUBLISHER_ID_1);
-        mRouting.removeSubscription(mSubscriber, LAYER_1, PUBLISHER_ID_1);
-        mRouting.removeSubscription(mSubscriber, LAYER_1, PUBLISHER_ID_1);
-
-        assertNoSubscribers();
-
-        assertEquals(
-                new VmsSubscriptionState(2, Collections.emptySet(), Collections.emptySet()),
-                mRouting.getSubscriptionState());
-    }
-
-    @Test
-    public void testRemovePublisherSubscriber_Rewrapped() {
-        mRouting.addSubscription(mSubscriber, LAYER_1, PUBLISHER_ID_1);
-        mRouting.removeSubscription(mSubscriberRewrapped, LAYER_1, PUBLISHER_ID_1);
-
-        assertNoSubscribers();
-
-        assertEquals(
-                new VmsSubscriptionState(2, Collections.emptySet(), Collections.emptySet()),
-                mRouting.getSubscriptionState());
-    }
-
-    @Test
-    public void testRemovePublisherSubscriber_UnknownSubscriber() {
-        mRouting.addSubscription(mSubscriber, LAYER_1, PUBLISHER_ID_1);
-        mRouting.removeSubscription(mSubscriber2, LAYER_1, PUBLISHER_ID_1);
-
-        assertSubscribers(LAYER_1, PUBLISHER_ID_1, mSubscriber);
-        assertNoSubscribers(LAYER_1, PUBLISHER_ID_2);
-        assertNoSubscribers(LAYER_2);
-
-        assertEquals(
-                new VmsSubscriptionState(1, Collections.emptySet(),
-                        Collections.singleton(new VmsAssociatedLayer(
-                                LAYER_1, Collections.singleton(PUBLISHER_ID_1)))),
-                mRouting.getSubscriptionState());
-    }
-
-    @Test
-    public void testRemovePublisherSubscriber_MultipleLayers() {
-        mRouting.addSubscription(mSubscriber, LAYER_1, PUBLISHER_ID_1);
-        mRouting.addSubscription(mSubscriber2, LAYER_2, PUBLISHER_ID_1);
-        mRouting.removeSubscription(mSubscriber2, LAYER_2, PUBLISHER_ID_1);
-
-        assertSubscribers(LAYER_1, PUBLISHER_ID_1, mSubscriber);
-        assertNoSubscribers(LAYER_1, PUBLISHER_ID_2);
-        assertNoSubscribers(LAYER_2);
-
-        assertEquals(
-                new VmsSubscriptionState(3, Collections.emptySet(),
-                        Collections.singleton(new VmsAssociatedLayer(
-                                LAYER_1, Collections.singleton(PUBLISHER_ID_1)))),
-                mRouting.getSubscriptionState());
-    }
-
-    @Test
-    public void testRemovePublisherSubscriber_MultiplePublishers() {
-        mRouting.addSubscription(mSubscriber, LAYER_1, PUBLISHER_ID_1);
-        mRouting.addSubscription(mSubscriber2, LAYER_1, PUBLISHER_ID_2);
-        mRouting.removeSubscription(mSubscriber2, LAYER_1, PUBLISHER_ID_2);
-
-        assertSubscribers(LAYER_1, PUBLISHER_ID_1, mSubscriber);
-        assertNoSubscribers(LAYER_1, PUBLISHER_ID_2);
-        assertNoSubscribers(LAYER_2);
-
-        assertEquals(
-                new VmsSubscriptionState(3, Collections.emptySet(),
-                        Collections.singleton(new VmsAssociatedLayer(
-                                LAYER_1, Collections.singleton(PUBLISHER_ID_1)))),
-                mRouting.getSubscriptionState());
-    }
-
-    @Test
-    public void testRemovePublisherSubscriber_MultipleSubscribers() {
-        mRouting.addSubscription(mSubscriber, LAYER_1, PUBLISHER_ID_1);
-        mRouting.addSubscription(mSubscriber2, LAYER_1, PUBLISHER_ID_1);
-        mRouting.removeSubscription(mSubscriber2, LAYER_1, PUBLISHER_ID_1);
-
-        assertSubscribers(LAYER_1, PUBLISHER_ID_1, mSubscriber);
-        assertNoSubscribers(LAYER_1, PUBLISHER_ID_2);
-        assertNoSubscribers(LAYER_2);
-
-        assertEquals(
-                new VmsSubscriptionState(3, Collections.emptySet(),
-                        Collections.singleton(new VmsAssociatedLayer(
-                                LAYER_1, Collections.singleton(PUBLISHER_ID_1)))),
-                mRouting.getSubscriptionState());
-    }
-
-    @Test
-    public void testRemovePublisherSubscriber_MultipleSubscribers_MultipleLayers() {
-        mRouting.addSubscription(mSubscriber, LAYER_1, PUBLISHER_ID_1);
-        mRouting.addSubscription(mSubscriber2, LAYER_2, PUBLISHER_ID_1);
-        mRouting.removeSubscription(mSubscriber2, LAYER_2, PUBLISHER_ID_1);
-
-        assertSubscribers(LAYER_1, PUBLISHER_ID_1, mSubscriber);
-        assertNoSubscribers(LAYER_2);
-
-        assertEquals(
-                new VmsSubscriptionState(3, Collections.emptySet(),
-                        Collections.singleton(new VmsAssociatedLayer(
-                                LAYER_1, Collections.singleton(PUBLISHER_ID_1)))),
-                mRouting.getSubscriptionState());
-    }
-
-    @Test
-    public void testRemovePublisherSubscriber_MultipleSubscribers_MultiplePublishers() {
-        mRouting.addSubscription(mSubscriber, LAYER_1, PUBLISHER_ID_1);
-        mRouting.addSubscription(mSubscriber2, LAYER_1, PUBLISHER_ID_2);
-        mRouting.removeSubscription(mSubscriber2, LAYER_1, PUBLISHER_ID_2);
-
-        assertSubscribers(LAYER_1, PUBLISHER_ID_1, mSubscriber);
-        assertNoSubscribers(LAYER_1, PUBLISHER_ID_2);
-        assertNoSubscribers(LAYER_2);
-
-        assertEquals(
-                new VmsSubscriptionState(3, Collections.emptySet(),
-                        Collections.singleton(new VmsAssociatedLayer(
-                                LAYER_1, Collections.singleton(PUBLISHER_ID_1)))),
-                mRouting.getSubscriptionState());
-    }
-
-    @Test
-    public void testAddingAllTypesOfSubscribers() {
-        IVmsSubscriberClient passiveSubscriber = new MockVmsSubscriber();
-        mRouting.addSubscription(passiveSubscriber);
-        mRouting.addSubscription(mSubscriber, LAYER_1, PUBLISHER_ID_1);
-        mRouting.addSubscription(mSubscriber2, LAYER_1);
-        mRouting.addSubscription(mSubscriber, LAYER_2);
-        mRouting.addSubscription(mSubscriber2, LAYER_2, PUBLISHER_ID_2);
-
-        assertSubscribers(LAYER_1, PUBLISHER_ID_1, passiveSubscriber, mSubscriber, mSubscriber2);
-        assertSubscribers(LAYER_1, PUBLISHER_ID_2, passiveSubscriber, mSubscriber2);
-        assertSubscribers(LAYER_2, PUBLISHER_ID_1, passiveSubscriber, mSubscriber);
-        assertSubscribers(LAYER_2, PUBLISHER_ID_2, passiveSubscriber, mSubscriber, mSubscriber2);
-
-        assertEquals(
-                new VmsSubscriptionState(4,
-                        new HashSet<>(Arrays.asList(LAYER_1, LAYER_2)),
-                        new HashSet<>(Arrays.asList(
-                                new VmsAssociatedLayer(
-                                        LAYER_1, Collections.singleton(PUBLISHER_ID_1)),
-                                new VmsAssociatedLayer(
-                                        LAYER_2, Collections.singleton(PUBLISHER_ID_2))
-                        ))),
-                mRouting.getSubscriptionState());
-    }
-
-    @Test
-    public void testAddingAndRemovingAllTypesOfSubscribers() {
-        IVmsSubscriberClient passiveSubscriber = new MockVmsSubscriber();
-        mRouting.addSubscription(passiveSubscriber);
-        mRouting.addSubscription(mSubscriber, LAYER_1, PUBLISHER_ID_1);
-        mRouting.addSubscription(mSubscriber2, LAYER_1);
-        mRouting.addSubscription(mSubscriber, LAYER_2);
-        mRouting.addSubscription(mSubscriber2, LAYER_2, PUBLISHER_ID_2);
-        mRouting.removeSubscription(mSubscriber, LAYER_1, PUBLISHER_ID_1);
-        mRouting.removeSubscription(mSubscriber, LAYER_2);
-
-        assertSubscribers(LAYER_1, PUBLISHER_ID_1, passiveSubscriber, mSubscriber2);
-        assertSubscribers(LAYER_1, PUBLISHER_ID_2, passiveSubscriber, mSubscriber2);
-        assertSubscribers(LAYER_2, PUBLISHER_ID_1, passiveSubscriber);
-        assertSubscribers(LAYER_2, PUBLISHER_ID_2, passiveSubscriber, mSubscriber2);
-
-        assertEquals(
-                new VmsSubscriptionState(6,
-                        Collections.singleton(LAYER_1),
-                        Collections.singleton(
-                                new VmsAssociatedLayer(
-                                        LAYER_2, Collections.singleton(PUBLISHER_ID_2))
-                        )),
-                mRouting.getSubscriptionState());
-    }
-
-    @Test
-    public void testRemoveDeadSubscriber() {
-        IVmsSubscriberClient passiveSubscriber = new MockVmsSubscriber();
-        mRouting.addSubscription(passiveSubscriber);
-        mRouting.addSubscription(mSubscriber, LAYER_1, PUBLISHER_ID_1);
-        mRouting.addSubscription(mSubscriber2, LAYER_1);
-        mRouting.addSubscription(mSubscriber, LAYER_2);
-        mRouting.addSubscription(mSubscriber2, LAYER_2, PUBLISHER_ID_2);
-        assertTrue(mRouting.removeDeadSubscriber(mSubscriber));
-
-        assertSubscribers(LAYER_1, PUBLISHER_ID_1, passiveSubscriber, mSubscriber2);
-        assertSubscribers(LAYER_1, PUBLISHER_ID_2, passiveSubscriber, mSubscriber2);
-        assertSubscribers(LAYER_2, PUBLISHER_ID_1, passiveSubscriber);
-        assertSubscribers(LAYER_2, PUBLISHER_ID_2, passiveSubscriber, mSubscriber2);
-
-        assertEquals(
-                new VmsSubscriptionState(6,
-                        Collections.singleton(LAYER_1),
-                        Collections.singleton(
-                                new VmsAssociatedLayer(
-                                        LAYER_2, Collections.singleton(PUBLISHER_ID_2))
-                        )),
-                mRouting.getSubscriptionState());
-    }
-
-    @Test
-    public void testRemoveDeadSubscriber_NoSubscriptions() {
-        IVmsSubscriberClient passiveSubscriber = new MockVmsSubscriber();
-        mRouting.addSubscription(passiveSubscriber);
-        mRouting.addSubscription(mSubscriber2, LAYER_1);
-        mRouting.addSubscription(mSubscriber2, LAYER_2, PUBLISHER_ID_2);
-        assertFalse(mRouting.removeDeadSubscriber(mSubscriber));
-
-        assertSubscribers(LAYER_1, PUBLISHER_ID_1, passiveSubscriber, mSubscriber2);
-        assertSubscribers(LAYER_1, PUBLISHER_ID_2, passiveSubscriber, mSubscriber2);
-        assertSubscribers(LAYER_2, PUBLISHER_ID_1, passiveSubscriber);
-        assertSubscribers(LAYER_2, PUBLISHER_ID_2, passiveSubscriber, mSubscriber2);
-
-        assertEquals(
-                new VmsSubscriptionState(2,
-                        Collections.singleton(LAYER_1),
-                        Collections.singleton(
-                                new VmsAssociatedLayer(
-                                        LAYER_2, Collections.singleton(PUBLISHER_ID_2))
-                        )),
-                mRouting.getSubscriptionState());
-    }
-
-    @Test
-    public void testRemoveDeadSubscriber_PassiveSubscriber() {
-        IVmsSubscriberClient passiveSubscriber = new MockVmsSubscriber();
-        mRouting.addSubscription(passiveSubscriber);
-        mRouting.addSubscription(mSubscriber, LAYER_1, PUBLISHER_ID_1);
-        mRouting.addSubscription(mSubscriber2, LAYER_1);
-        mRouting.addSubscription(mSubscriber, LAYER_2);
-        mRouting.addSubscription(mSubscriber2, LAYER_2, PUBLISHER_ID_2);
-        assertFalse(mRouting.removeDeadSubscriber(passiveSubscriber));
-
-        assertSubscribers(LAYER_1, PUBLISHER_ID_1, mSubscriber, mSubscriber2);
-        assertSubscribers(LAYER_1, PUBLISHER_ID_2, mSubscriber2);
-        assertSubscribers(LAYER_2, PUBLISHER_ID_1, mSubscriber);
-        assertSubscribers(LAYER_2, PUBLISHER_ID_2, mSubscriber, mSubscriber2);
-
-        assertEquals(
-                new VmsSubscriptionState(4,
-                        new HashSet<>(Arrays.asList(LAYER_1, LAYER_2)),
-                        new HashSet<>(Arrays.asList(
-                                new VmsAssociatedLayer(
-                                        LAYER_1, Collections.singleton(PUBLISHER_ID_1)),
-                                new VmsAssociatedLayer(
-                                        LAYER_2, Collections.singleton(PUBLISHER_ID_2))
-                        ))),
-                mRouting.getSubscriptionState());
-    }
-
-    @Test
-    public void testHasSubscriptions_Default() {
-        assertFalse(mRouting.hasLayerSubscriptions(LAYER_1));
-        assertFalse(mRouting.hasLayerSubscriptions(LAYER_2));
-        assertFalse(mRouting.hasLayerFromPublisherSubscriptions(LAYER_1, PUBLISHER_ID_1));
-        assertFalse(mRouting.hasLayerFromPublisherSubscriptions(LAYER_1, PUBLISHER_ID_2));
-        assertFalse(mRouting.hasLayerFromPublisherSubscriptions(LAYER_2, PUBLISHER_ID_1));
-        assertFalse(mRouting.hasLayerFromPublisherSubscriptions(LAYER_2, PUBLISHER_ID_2));
-    }
-
-    @Test
-    public void testHasSubscriptions_PassiveSubscriber() {
-        mRouting.addSubscription(mSubscriber);
-
-        testHasSubscriptions_Default();
-    }
-
-    @Test
-    public void testHasSubscriptions_DeadSubscriber() {
-        mRouting.addSubscription(mSubscriber);
-        mRouting.addSubscription(mSubscriber, LAYER_1, PUBLISHER_ID_1);
-        mRouting.removeDeadSubscriber(mSubscriber);
-
-        testHasSubscriptions_Default();
-    }
-
-    @Test
-    public void testHasSubscriptions_Layer() {
-        mRouting.addSubscription(mSubscriber, LAYER_1);
-
-        assertTrue(mRouting.hasLayerSubscriptions(LAYER_1));
-        assertFalse(mRouting.hasLayerSubscriptions(LAYER_2));
-        assertFalse(mRouting.hasLayerFromPublisherSubscriptions(LAYER_1, PUBLISHER_ID_1));
-        assertFalse(mRouting.hasLayerFromPublisherSubscriptions(LAYER_1, PUBLISHER_ID_2));
-        assertFalse(mRouting.hasLayerFromPublisherSubscriptions(LAYER_2, PUBLISHER_ID_1));
-        assertFalse(mRouting.hasLayerFromPublisherSubscriptions(LAYER_2, PUBLISHER_ID_2));
-
-        mRouting.removeSubscription(mSubscriber, LAYER_1);
-
-        assertFalse(mRouting.hasLayerSubscriptions(LAYER_1));
-    }
-
-    @Test
-    public void testHasSubscriptions_LayerFromPublisher() {
-        mRouting.addSubscription(mSubscriber, LAYER_1, PUBLISHER_ID_1);
-
-        assertFalse(mRouting.hasLayerSubscriptions(LAYER_1));
-        assertFalse(mRouting.hasLayerSubscriptions(LAYER_2));
-        assertTrue(mRouting.hasLayerFromPublisherSubscriptions(LAYER_1, PUBLISHER_ID_1));
-        assertFalse(mRouting.hasLayerFromPublisherSubscriptions(LAYER_1, PUBLISHER_ID_2));
-        assertFalse(mRouting.hasLayerFromPublisherSubscriptions(LAYER_2, PUBLISHER_ID_1));
-        assertFalse(mRouting.hasLayerFromPublisherSubscriptions(LAYER_2, PUBLISHER_ID_2));
-
-        mRouting.removeSubscription(mSubscriber, LAYER_1, PUBLISHER_ID_1);
-
-        assertFalse(mRouting.hasLayerFromPublisherSubscriptions(LAYER_1, PUBLISHER_ID_1));
-    }
-
-    class MockVmsSubscriber extends IVmsSubscriberClient.Stub {
-        @Override
-        public void onVmsMessageReceived(VmsLayer layer, byte[] payload) {
-            throw new RuntimeException("Should not be accessed");
-        }
-
-        @Override
-        public void onLayersAvailabilityChanged(VmsAvailableLayers availableLayers) {
-            throw new RuntimeException("Should not be accessed");
-        }
-    }
-
-    private void assertNoSubscribers() {
-        assertSubscribers(); /* subscribers is empty */
-    }
-
-    private void assertNoSubscribers(VmsLayer layer) {
-        assertSubscribers(layer); /* subscribers is empty */
-    }
-
-    private void assertNoSubscribers(VmsLayer layer, int publisherId) {
-        assertSubscribers(layer, publisherId); /* subscribers is empty */
-    }
-
-    private void assertSubscribers(IVmsSubscriberClient... subscribers) {
-        for (VmsLayer layer : LAYERS) {
-            assertSubscribers(layer, subscribers);
-        }
-    }
-
-    private void assertSubscribers(VmsLayer layer, IVmsSubscriberClient... subscribers) {
-        for (int publisherId : PUBLISHER_IDS) {
-            assertSubscribers(layer, publisherId, subscribers);
-        }
-    }
-
-    private void assertSubscribers(VmsLayer layer, int publisherId,
-            IVmsSubscriberClient... subscribers) {
-        assertEquals(
-                new HashSet<>(Arrays.asList(subscribers)),
-                mRouting.getSubscribersForLayerFromPublisher(layer, publisherId));
-    }
-}
\ No newline at end of file
diff --git a/tests/carservice_unit_test/src/com/android/car/VmsSubscriberServiceTest.java b/tests/carservice_unit_test/src/com/android/car/VmsSubscriberServiceTest.java
deleted file mode 100644
index 0208515..0000000
--- a/tests/carservice_unit_test/src/com/android/car/VmsSubscriberServiceTest.java
+++ /dev/null
@@ -1,164 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.car;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyNoMoreInteractions;
-import static org.mockito.Mockito.when;
-
-import android.car.vms.IVmsSubscriberClient;
-import android.car.vms.VmsAvailableLayers;
-import android.car.vms.VmsLayer;
-import android.content.Context;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.car.hal.VmsHalService;
-import com.android.car.vms.VmsBrokerService;
-import com.android.car.vms.VmsClientManager;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.mockito.Mock;
-import org.mockito.junit.MockitoJUnit;
-import org.mockito.junit.MockitoRule;
-
-import java.util.Arrays;
-import java.util.Collections;
-
-@SmallTest
-public class VmsSubscriberServiceTest {
-    private static final VmsLayer LAYER = new VmsLayer(1, 2, 3);
-    private static final int PUBLISHER_ID = 54321;
-    private static final byte[] PUBLISHER_INFO = new byte[]{1, 2, 3, 4};
-    private static final VmsAvailableLayers AVAILABLE_LAYERS =
-            new VmsAvailableLayers(Collections.emptySet(), 0);
-
-    @Rule
-    public MockitoRule mMockitoRule = MockitoJUnit.rule();
-    @Mock
-    private Context mContext;
-    @Mock
-    private VmsBrokerService mBrokerService;
-    @Mock
-    private VmsClientManager mClientManager;
-    @Mock
-    private VmsHalService mHal;
-
-    @Mock
-    private IVmsSubscriberClient mSubscriberClient;
-    @Mock
-    private IVmsSubscriberClient mSubscriberClient2;
-
-    private VmsSubscriberService mSubscriberService;
-
-    @Before
-    public void setUp() {
-        mSubscriberService = new VmsSubscriberService(mContext, mBrokerService, mClientManager,
-                mHal);
-        verify(mBrokerService).addSubscriberListener(eq(mSubscriberService));
-        verify(mHal).setVmsSubscriberService(eq(mSubscriberService));
-    }
-
-    @After
-    public void tearDown() {
-        verifyNoMoreInteractions(mBrokerService, mClientManager);
-    }
-
-    @Test
-    public void testAddVmsSubscriberToNotifications() {
-        mSubscriberService.addVmsSubscriberToNotifications(mSubscriberClient);
-        verify(mClientManager).addSubscriber(mSubscriberClient);
-    }
-
-    @Test
-    public void testRemoveVmsSubscriberToNotifications() {
-        mSubscriberService.removeVmsSubscriberToNotifications(mSubscriberClient);
-        verify(mClientManager).removeSubscriber(mSubscriberClient);
-    }
-
-    @Test
-    public void testAddVmsSubscriber() {
-        mSubscriberService.addVmsSubscriber(mSubscriberClient, LAYER);
-        verify(mClientManager).addSubscriber(mSubscriberClient);
-        verify(mBrokerService).addSubscription(mSubscriberClient, LAYER);
-    }
-
-    @Test
-    public void testRemoveVmsSubscriber() {
-        mSubscriberService.removeVmsSubscriber(mSubscriberClient, LAYER);
-        verify(mBrokerService).removeSubscription(mSubscriberClient, LAYER);
-    }
-
-
-    @Test
-    public void testAddVmsSubscriberToPublisher() {
-        mSubscriberService.addVmsSubscriberToPublisher(mSubscriberClient, LAYER, PUBLISHER_ID);
-        verify(mClientManager).addSubscriber(mSubscriberClient);
-        verify(mBrokerService).addSubscription(mSubscriberClient, LAYER, PUBLISHER_ID);
-    }
-
-    @Test
-    public void testRemoveVmsSubscriberToPublisher() {
-        testAddVmsSubscriberToPublisher();
-
-        mSubscriberService.removeVmsSubscriberToPublisher(mSubscriberClient, LAYER, PUBLISHER_ID);
-        verify(mBrokerService).removeSubscription(mSubscriberClient, LAYER, PUBLISHER_ID);
-    }
-
-    @Test
-    public void testAddVmsSubscriberPassive() {
-        mSubscriberService.addVmsSubscriberPassive(mSubscriberClient);
-        verify(mClientManager).addSubscriber(mSubscriberClient);
-        verify(mBrokerService).addSubscription(mSubscriberClient);
-    }
-
-    @Test
-    public void testRemoveVmsSubscriberPassive() {
-        mSubscriberService.removeVmsSubscriberPassive(mSubscriberClient);
-        verify(mBrokerService).removeSubscription(mSubscriberClient);
-    }
-
-    @Test
-    public void testGetPublisherInfo() {
-        when(mBrokerService.getPublisherInfo(PUBLISHER_ID)).thenReturn(PUBLISHER_INFO);
-        assertThat(mSubscriberService.getPublisherInfo(PUBLISHER_ID)).isSameAs(PUBLISHER_INFO);
-        verify(mBrokerService).getPublisherInfo(PUBLISHER_ID);
-    }
-
-    @Test
-    public void testGetAvailableLayers() {
-        when(mBrokerService.getAvailableLayers()).thenReturn(AVAILABLE_LAYERS);
-        assertThat(mSubscriberService.getAvailableLayers()).isSameAs(AVAILABLE_LAYERS);
-        verify(mBrokerService).getAvailableLayers();
-    }
-
-    @Test
-    public void testOnLayersAvailabilityChange() throws Exception {
-        when(mClientManager.getAllSubscribers())
-                .thenReturn(Arrays.asList(mSubscriberClient, mSubscriberClient2));
-        mSubscriberService.onLayersAvailabilityChange(AVAILABLE_LAYERS);
-        verify(mClientManager).getAllSubscribers();
-        verify(mSubscriberClient).onLayersAvailabilityChanged(AVAILABLE_LAYERS);
-        verify(mSubscriberClient2).onLayersAvailabilityChanged(AVAILABLE_LAYERS);
-    }
-}
diff --git a/tests/carservice_unit_test/src/com/android/car/audio/CarAudioContextTest.java b/tests/carservice_unit_test/src/com/android/car/audio/CarAudioContextTest.java
new file mode 100644
index 0000000..e886652
--- /dev/null
+++ b/tests/carservice_unit_test/src/com/android/car/audio/CarAudioContextTest.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.audio;
+
+import static android.media.AudioAttributes.USAGE_GAME;
+import static android.media.AudioAttributes.USAGE_MEDIA;
+import static android.media.AudioAttributes.USAGE_UNKNOWN;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.assertThrows;
+
+import android.media.AudioAttributes.AttributeUsage;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.car.audio.CarAudioContext.AudioContext;
+
+import com.google.common.primitives.Ints;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.HashSet;
+import java.util.Set;
+
+@RunWith(AndroidJUnit4.class)
+public class CarAudioContextTest {
+    private static final int INVALID_USAGE = -5;
+    private static final int INVALID_CONTEXT = -5;
+
+    @Test
+    public void getContextForUsage_forValidUsage_returnsContext() {
+        assertThat(CarAudioContext.getContextForUsage(USAGE_MEDIA))
+                .isEqualTo(CarAudioContext.MUSIC);
+    }
+
+    @Test
+    public void getContextForUsage_withInvalidUsage_returnsInvalidContext() {
+        assertThat(CarAudioContext.getContextForUsage(INVALID_USAGE)).isEqualTo(
+                CarAudioContext.INVALID);
+    }
+
+    @Test
+    public void getUsagesForContext_withValidContext_returnsUsages() {
+        int[] usages = CarAudioContext.getUsagesForContext(CarAudioContext.MUSIC);
+        assertThat(usages).asList().containsExactly(USAGE_UNKNOWN, USAGE_GAME, USAGE_MEDIA);
+    }
+
+    @Test
+    public void getUsagesForContext_withInvalidContext_throws() {
+        assertThrows(IllegalArgumentException.class, () -> {
+            CarAudioContext.getUsagesForContext(INVALID_CONTEXT);
+        });
+    }
+
+    @Test
+    public void getUsagesForContext_returnsUniqueValuesForAllContexts() {
+        Set<Integer> allUsages = new HashSet<>();
+        for (@AudioContext int audioContext : CarAudioContext.CONTEXTS) {
+            @AttributeUsage int[] usages = CarAudioContext.getUsagesForContext(audioContext);
+            assertThat(allUsages.addAll(Ints.asList(usages))).isTrue();
+        }
+    }
+}
diff --git a/tests/carservice_unit_test/src/com/android/car/audio/CarVolumeCallbackHandlerTest.java b/tests/carservice_unit_test/src/com/android/car/audio/CarVolumeCallbackHandlerTest.java
new file mode 100644
index 0000000..54436af
--- /dev/null
+++ b/tests/carservice_unit_test/src/com/android/car/audio/CarVolumeCallbackHandlerTest.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.car.audio;
+
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+
+import android.car.media.ICarVolumeCallback;
+import android.os.RemoteException;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class CarVolumeCallbackHandlerTest {
+    private static final int ZONE_ID = 2;
+    private static final int GROUP_ID = 5;
+    private static final int FLAGS = 0;
+
+    private CarVolumeCallbackHandler mHandler;
+    private TestCarVolumeCallback mCallback1;
+    private TestCarVolumeCallback mCallback2;
+
+    @Before
+    public void setUp() {
+        mHandler = new CarVolumeCallbackHandler();
+        mCallback1 = new TestCarVolumeCallback(spy(ICarVolumeCallback.class));
+        mHandler.registerCallback(mCallback1.asBinder());
+        mCallback2 = new TestCarVolumeCallback(spy(ICarVolumeCallback.class));
+        mHandler.registerCallback(mCallback2.asBinder());
+    }
+
+    @After
+    public void tearDown() {
+        mHandler.unregisterCallback(mCallback1.asBinder());
+        mHandler.unregisterCallback(mCallback2.asBinder());
+    }
+
+    @Test
+    public void onVolumeGroupChange_callsAllRegisteredCallbacks() throws RemoteException {
+        mHandler.onVolumeGroupChange(ZONE_ID, GROUP_ID, FLAGS);
+
+        verify(mCallback1.getSpy()).onGroupVolumeChanged(ZONE_ID, GROUP_ID, FLAGS);
+        verify(mCallback2.getSpy()).onGroupVolumeChanged(ZONE_ID, GROUP_ID, FLAGS);
+    }
+
+    @Test
+    public void onVolumeGroupChange_doesntCallUnregisteredCallbacks() throws RemoteException {
+        mHandler.unregisterCallback(mCallback1.asBinder());
+        mHandler.onVolumeGroupChange(ZONE_ID, GROUP_ID, FLAGS);
+
+        verify(mCallback1.getSpy(), never()).onGroupVolumeChanged(ZONE_ID, GROUP_ID, FLAGS);
+        verify(mCallback2.getSpy()).onGroupVolumeChanged(ZONE_ID, GROUP_ID, FLAGS);
+    }
+
+    @Test
+    public void onMasterMuteChanged_callsAllRegisteredCallbacks() throws RemoteException {
+        mHandler.onMasterMuteChanged(ZONE_ID, FLAGS);
+
+        verify(mCallback1.getSpy()).onMasterMuteChanged(ZONE_ID, FLAGS);
+        verify(mCallback2.getSpy()).onMasterMuteChanged(ZONE_ID, FLAGS);
+    }
+
+    // Because the binder logic uses transact, spying on the object directly doesn't work. So
+    // instead we pass a mSpy in and have the Test wrapper call it so we can test the behavior
+    private class TestCarVolumeCallback extends ICarVolumeCallback.Stub {
+        private final ICarVolumeCallback mSpy;
+
+        TestCarVolumeCallback(ICarVolumeCallback spy) {
+            this.mSpy = spy;
+        }
+
+        public ICarVolumeCallback getSpy() {
+            return mSpy;
+        }
+
+        @Override
+        public void onGroupVolumeChanged(int zoneId, int groupId, int flags)
+                throws RemoteException {
+            mSpy.onGroupVolumeChanged(zoneId, groupId, flags);
+        }
+
+        @Override
+        public void onMasterMuteChanged(int zoneId, int flags) throws RemoteException {
+            mSpy.onMasterMuteChanged(zoneId, flags);
+        }
+    }
+}
diff --git a/tests/carservice_unit_test/src/com/android/car/audio/FocusEntryTest.java b/tests/carservice_unit_test/src/com/android/car/audio/FocusEntryTest.java
new file mode 100644
index 0000000..1698781
--- /dev/null
+++ b/tests/carservice_unit_test/src/com/android/car/audio/FocusEntryTest.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.audio;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.when;
+
+import android.car.Car;
+import android.car.media.CarAudioManager;
+import android.content.pm.PackageManager;
+import android.media.AudioAttributes;
+import android.media.AudioFocusInfo;
+import android.media.AudioManager;
+import android.os.Bundle;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+@RunWith(AndroidJUnit4.class)
+public class FocusEntryTest {
+
+    private static final int CLIENT_UID = 0;
+    private static final String CLIENT_ID = "0";
+    private static final String PACKAGE_NAME = "PACKAGE_NAME";
+    private static final int LOSS_RECEIVED = 0;
+    private static final int DEFAULT_FLAGS = 0;
+    private static final int SDK = 0;
+
+    @Rule
+    public MockitoRule rule = MockitoJUnit.rule();
+
+    @Mock
+    private PackageManager mMockPM;
+
+    @Test
+    public void wantsPauseInsteadOfDucking_whenFlagIsSet_returnsTrue() {
+        AudioFocusInfo info = getInfoWithFlags(
+                AudioManager.AUDIOFOCUS_FLAG_PAUSES_ON_DUCKABLE_LOSS);
+
+        FocusEntry focusEntry = new FocusEntry(info, CarAudioContext.MUSIC, mMockPM);
+
+        assertThat(focusEntry.wantsPauseInsteadOfDucking()).isTrue();
+    }
+
+    @Test
+    public void wantsPauseInsteadOfDucking_whenFlagIsNotSet_returnsFalse() {
+        AudioFocusInfo info = getInfoWithFlags(0);
+
+        FocusEntry focusEntry = new FocusEntry(info, CarAudioContext.MUSIC, mMockPM);
+
+        assertThat(focusEntry.wantsPauseInsteadOfDucking()).isFalse();
+    }
+
+    @Test
+    public void receivesDuckEvents_whenBundleDoesNotReceiveDuckingEvents_returnsFalse() {
+        AudioFocusInfo info = getInfoThatReceivesDuckingEvents(false);
+        FocusEntry focusEntry = new FocusEntry(info, CarAudioContext.MUSIC, mMockPM);
+
+        assertThat(focusEntry.receivesDuckEvents()).isFalse();
+    }
+
+    @Test
+    public void receivesDuckEvents_withoutReceiveCarAudioDuckingEventsPermission_returnsFalse() {
+        withoutPermission();
+        AudioFocusInfo info = getInfoThatReceivesDuckingEvents(true);
+
+        FocusEntry focusEntry = new FocusEntry(info, CarAudioContext.MUSIC, mMockPM);
+
+        assertThat(focusEntry.receivesDuckEvents()).isFalse();
+    }
+
+    @Test
+    public void receivesDuckEvents_withReceiveCarAudioDuckingEventsPermission_returnsTrue() {
+        withPermission();
+        AudioFocusInfo info = getInfoThatReceivesDuckingEvents(true);
+
+        FocusEntry focusEntry = new FocusEntry(info, CarAudioContext.MUSIC, mMockPM);
+
+        assertThat(focusEntry.receivesDuckEvents()).isTrue();
+    }
+
+    private void withPermission() {
+        when(mMockPM.checkPermission(Car.PERMISSION_RECEIVE_CAR_AUDIO_DUCKING_EVENTS, PACKAGE_NAME))
+                .thenReturn(PackageManager.PERMISSION_GRANTED);
+    }
+
+    private void withoutPermission() {
+        when(mMockPM.checkPermission(Car.PERMISSION_RECEIVE_CAR_AUDIO_DUCKING_EVENTS, PACKAGE_NAME))
+                .thenReturn(PackageManager.PERMISSION_DENIED);
+    }
+
+    private AudioFocusInfo getInfoWithFlags(int flags) {
+        AudioAttributes attributes = new AudioAttributes.Builder().build();
+        return getInfo(attributes, flags);
+    }
+
+    private AudioFocusInfo getInfoThatReceivesDuckingEvents(boolean receivesDuckingEvents) {
+        Bundle bundle = new Bundle();
+        bundle.putBoolean(CarAudioManager.AUDIOFOCUS_EXTRA_RECEIVE_DUCKING_EVENTS,
+                receivesDuckingEvents);
+        AudioAttributes attributes = new AudioAttributes.Builder()
+                .addBundle(bundle)
+                .build();
+        return getInfo(attributes, DEFAULT_FLAGS);
+    }
+
+    private AudioFocusInfo getInfo(AudioAttributes attributes, int flags) {
+        return new AudioFocusInfo(attributes, CLIENT_UID, CLIENT_ID, PACKAGE_NAME,
+                AudioManager.AUDIOFOCUS_GAIN, LOSS_RECEIVED, flags, SDK);
+    }
+}
diff --git a/tests/carservice_unit_test/src/com/android/car/audio/FocusInteractionTest.java b/tests/carservice_unit_test/src/com/android/car/audio/FocusInteractionTest.java
new file mode 100644
index 0000000..fd8efc1
--- /dev/null
+++ b/tests/carservice_unit_test/src/com/android/car/audio/FocusInteractionTest.java
@@ -0,0 +1,190 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.audio;
+
+import static com.android.car.audio.CarAudioContext.AudioContext;
+import static com.android.car.audio.FocusInteraction.INTERACTION_CONCURRENT;
+import static com.android.car.audio.FocusInteraction.INTERACTION_EXCLUSIVE;
+import static com.android.car.audio.FocusInteraction.INTERACTION_REJECT;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import static org.testng.Assert.assertThrows;
+
+import android.media.AudioManager;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+@RunWith(AndroidJUnit4.class)
+public class FocusInteractionTest {
+
+    private static final int UNDEFINED_CONTEXT_VALUE = -10;
+    private final List<FocusEntry> mLosers = new ArrayList<>();
+
+    @Test
+    public void getInteractionMatrix_returnsNByNMatrix() {
+        int n = CarAudioContext.CONTEXTS.length + 1; // One extra for CarAudioContext.INVALID
+        int[][] interactionMatrix = FocusInteraction.getInteractionMatrix();
+
+        assertThat(interactionMatrix.length).isEqualTo(n);
+        for (int i = 0; i < n; i++) {
+            assertWithMessage("Row %s is not of length %s", i, n)
+                    .that(interactionMatrix[i].length).isEqualTo(n);
+        }
+    }
+
+    @Test
+    public void getInteractionMatrix_hasValidInteractionValues() {
+        List<Integer> supportedInteractions = Arrays.asList(INTERACTION_REJECT,
+                INTERACTION_EXCLUSIVE, INTERACTION_CONCURRENT);
+        int[][] interactionMatrix = FocusInteraction.getInteractionMatrix();
+
+        for (int i = 0; i < interactionMatrix.length; i++) {
+            for (int j = 0; j < interactionMatrix[i].length; j++) {
+                assertWithMessage("Row %s column %s has unexpected value %s", i, j,
+                        interactionMatrix[i][j]).that(
+                        interactionMatrix[i][j]).isIn(supportedInteractions);
+            }
+        }
+    }
+
+    @Test
+    public void evaluateResult_forRejectPair_returnsFailed() {
+        FocusEntry focusEntry = newMockFocusEntryWithContext(CarAudioContext.INVALID);
+
+        int result = FocusInteraction.evaluateRequest(CarAudioContext.INVALID, focusEntry, mLosers,
+                false);
+
+        assertThat(result).isEqualTo(AudioManager.AUDIOFOCUS_REQUEST_FAILED);
+    }
+
+    @Test
+    public void evaluateResult_forRejectPair_doesNotAddToLosers() {
+        FocusEntry focusEntry = newMockFocusEntryWithContext(CarAudioContext.INVALID);
+
+        FocusInteraction.evaluateRequest(CarAudioContext.INVALID, focusEntry, mLosers, false);
+
+        assertThat(mLosers).isEmpty();
+    }
+
+    @Test
+    public void evaluateRequest_forExclusivePair_returnsGranted() {
+        FocusEntry focusEntry = newMockFocusEntryWithContext(CarAudioContext.MUSIC);
+
+        int result = FocusInteraction.evaluateRequest(CarAudioContext.MUSIC, focusEntry, mLosers,
+                false);
+
+        assertThat(result).isEqualTo(AudioManager.AUDIOFOCUS_REQUEST_GRANTED);
+    }
+
+    @Test
+    public void evaluateRequest_forExclusivePair_addsEntryToLosers() {
+        FocusEntry focusEntry = newMockFocusEntryWithContext(CarAudioContext.MUSIC);
+
+        FocusInteraction.evaluateRequest(CarAudioContext.MUSIC, focusEntry, mLosers, false);
+
+        assertThat(mLosers).containsExactly(focusEntry);
+    }
+
+    @Test
+    public void evaluateResult_forConcurrentPair_returnsGranted() {
+        FocusEntry focusEntry = newMockFocusEntryWithContext(CarAudioContext.NAVIGATION);
+
+        int result = FocusInteraction.evaluateRequest(CarAudioContext.MUSIC, focusEntry, mLosers,
+                false);
+
+        assertThat(result).isEqualTo(AudioManager.AUDIOFOCUS_REQUEST_GRANTED);
+    }
+
+    @Test
+    public void evaluateResult_forConcurrentPair_andNoDucking_addsToLosers() {
+        FocusEntry focusEntry = newMockFocusEntryWithDuckingBehavior(false, false);
+
+        FocusInteraction.evaluateRequest(CarAudioContext.MUSIC, focusEntry, mLosers, false);
+
+        assertThat(mLosers).containsExactly(focusEntry);
+    }
+
+    @Test
+    public void evaluateResult_forConcurrentPair_andWantsPauseInsteadOfDucking_addsToLosers() {
+        FocusEntry focusEntry = newMockFocusEntryWithDuckingBehavior(true, false);
+
+        FocusInteraction.evaluateRequest(CarAudioContext.MUSIC, focusEntry, mLosers, true);
+
+        assertThat(mLosers).containsExactly(focusEntry);
+    }
+
+    @Test
+    public void evaluateResult_forConcurrentPair_andReceivesDuckEvents_addsToLosers() {
+        FocusEntry focusEntry = newMockFocusEntryWithDuckingBehavior(false, true);
+
+        FocusInteraction.evaluateRequest(CarAudioContext.MUSIC, focusEntry, mLosers, true);
+
+        assertThat(mLosers).containsExactly(focusEntry);
+    }
+
+    @Test
+    public void evaluateResult_forConcurrentPair_andDucking_doesAddsToLosers() {
+        FocusEntry focusEntry = newMockFocusEntryWithDuckingBehavior(false, true);
+
+        FocusInteraction.evaluateRequest(CarAudioContext.MUSIC, focusEntry, mLosers, true);
+
+        assertThat(mLosers).containsExactly(focusEntry);
+    }
+
+    @Test
+    public void evaluateResult_forUndefinedContext_throws() {
+        FocusEntry focusEntry = newMockFocusEntryWithContext(CarAudioContext.NAVIGATION);
+
+        assertThrows(IllegalArgumentException.class,
+                () -> FocusInteraction.evaluateRequest(UNDEFINED_CONTEXT_VALUE, focusEntry, mLosers,
+                        false));
+    }
+
+    @Test
+    public void evaluateResult_forUndefinedFocusHolderContext_throws() {
+        FocusEntry focusEntry = newMockFocusEntryWithContext(UNDEFINED_CONTEXT_VALUE);
+
+        assertThrows(IllegalArgumentException.class,
+                () -> FocusInteraction.evaluateRequest(CarAudioContext.MUSIC, focusEntry,
+                        mLosers, false));
+    }
+
+    private FocusEntry newMockFocusEntryWithContext(@AudioContext int audioContext) {
+        FocusEntry focusEntry = mock(FocusEntry.class);
+        when(focusEntry.getAudioContext()).thenReturn(audioContext);
+        return focusEntry;
+    }
+
+    private FocusEntry newMockFocusEntryWithDuckingBehavior(boolean pauseInsteadOfDucking,
+            boolean receivesDuckingEvents) {
+        FocusEntry focusEntry = newMockFocusEntryWithContext(CarAudioContext.NAVIGATION);
+        when(focusEntry.wantsPauseInsteadOfDucking()).thenReturn(pauseInsteadOfDucking);
+        when(focusEntry.receivesDuckEvents()).thenReturn(receivesDuckingEvents);
+        return focusEntry;
+    }
+}
diff --git a/tests/carservice_unit_test/src/com/android/car/hal/InputHalServiceTest.java b/tests/carservice_unit_test/src/com/android/car/hal/InputHalServiceTest.java
index 5cce922..3d6bcc3 100644
--- a/tests/carservice_unit_test/src/com/android/car/hal/InputHalServiceTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/hal/InputHalServiceTest.java
@@ -23,6 +23,7 @@
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -32,7 +33,7 @@
 import android.hardware.automotive.vehicle.V2_0.VehicleProperty;
 import android.view.KeyEvent;
 
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.filters.RequiresDevice;
 
 import com.android.car.vehiclehal.test.VehiclePropConfigBuilder;
 
@@ -41,13 +42,11 @@
 
 import org.junit.After;
 import org.junit.Before;
-import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
-import org.mockito.junit.MockitoJUnit;
-import org.mockito.junit.MockitoRule;
+import org.mockito.junit.MockitoJUnitRunner;
 
 import java.util.ArrayList;
 import java.util.Collection;
@@ -55,10 +54,8 @@
 import java.util.Set;
 import java.util.function.LongSupplier;
 
-@RunWith(AndroidJUnit4.class)
+@RunWith(MockitoJUnitRunner.class)
 public class InputHalServiceTest {
-    @Rule public MockitoRule mMockitoRule = MockitoJUnit.rule();
-
     @Mock VehicleHal mVehicleHal;
     @Mock InputHalService.InputListener mInputListener;
     @Mock LongSupplier mUptimeSupplier;
@@ -90,7 +87,7 @@
 
         mInputHalService.setInputListener(mInputListener);
 
-        mInputHalService.handleHalEvents(
+        mInputHalService.onHalEvents(
                 ImmutableList.of(makeKeyPropValue(Key.DOWN, KeyEvent.KEYCODE_ENTER)));
         verify(mInputListener, never()).onKeyEvent(any(), anyInt());
     }
@@ -131,7 +128,7 @@
             return null;
         }).when(mInputListener).onKeyEvent(any(), eq(DISPLAY));
 
-        mInputHalService.handleHalEvents(
+        mInputHalService.onHalEvents(
                 ImmutableList.of(
                         makeKeyPropValue(Key.DOWN, KeyEvent.KEYCODE_ENTER),
                         makeKeyPropValue(Key.DOWN, KeyEvent.KEYCODE_MENU)));
@@ -184,6 +181,7 @@
     /**
      * Test for handling rotary knob event.
      */
+    @RequiresDevice
     @Test
     public void handlesRepeatedKeyWithIndents() {
         subscribeListener();
@@ -254,7 +252,7 @@
     private KeyEvent dispatchSingleEvent(Key action, int code) {
         ArgumentCaptor<KeyEvent> captor = ArgumentCaptor.forClass(KeyEvent.class);
         reset(mInputListener);
-        mInputHalService.handleHalEvents(ImmutableList.of(makeKeyPropValue(action, code)));
+        mInputHalService.onHalEvents(ImmutableList.of(makeKeyPropValue(action, code)));
         verify(mInputListener).onKeyEvent(captor.capture(), eq(DISPLAY));
         reset(mInputListener);
         return captor.getValue();
@@ -274,9 +272,9 @@
     private KeyEvent dispatchSingleEventWithIndents(int code, int indents) {
         ArgumentCaptor<KeyEvent> captor = ArgumentCaptor.forClass(KeyEvent.class);
         reset(mInputListener);
-        mInputHalService.handleHalEvents(
+        mInputHalService.onHalEvents(
                 ImmutableList.of(makeKeyPropValueWithIndents(code, indents)));
-        verify(mInputListener).onKeyEvent(captor.capture(), eq(DISPLAY));
+        verify(mInputListener, times(indents)).onKeyEvent(captor.capture(), eq(DISPLAY));
         reset(mInputListener);
         return captor.getValue();
     }
diff --git a/tests/carservice_unit_test/src/com/android/car/hal/PropertyHalServiceIdsTest.java b/tests/carservice_unit_test/src/com/android/car/hal/PropertyHalServiceIdsTest.java
new file mode 100644
index 0000000..59498cb
--- /dev/null
+++ b/tests/carservice_unit_test/src/com/android/car/hal/PropertyHalServiceIdsTest.java
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.hal;
+
+import android.car.Car;
+import android.car.VehiclePropertyIds;
+import android.hardware.automotive.vehicle.V2_0.VehicleVendorPermission;
+import android.util.Log;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@RunWith(AndroidJUnit4.class)
+public class PropertyHalServiceIdsTest {
+    @Rule
+    public MockitoRule mMockitoRule = MockitoJUnit.rule();
+
+    private PropertyHalServiceIds mPropertyHalServiceIds;
+
+    private static final String TAG = PropertyHalServiceIdsTest.class.getSimpleName();
+    private static final int VENDOR_PROPERTY_1 = 0x21e01111;
+    private static final int VENDOR_PROPERTY_2 = 0x21e01112;
+    private static final int VENDOR_PROPERTY_3 = 0x21e01113;
+    private static final int VENDOR_PROPERTY_4 = 0x21e01114;
+    private static final int[] VENDOR_PROPERTY_IDS = {
+            VENDOR_PROPERTY_1, VENDOR_PROPERTY_2, VENDOR_PROPERTY_3, VENDOR_PROPERTY_4};
+    private static final int[] SYSTEM_PROPERTY_IDS = {VehiclePropertyIds.ENGINE_OIL_LEVEL,
+            VehiclePropertyIds.CURRENT_GEAR, VehiclePropertyIds.NIGHT_MODE,
+            VehiclePropertyIds.HVAC_FAN_SPEED, VehiclePropertyIds.DOOR_LOCK};
+    private static final List<Integer> CONFIG_ARRAY = new ArrayList<>();
+    private static final List<Integer> CONFIG_ARRAY_INVALID = new ArrayList<>();
+    @Before
+    public void setUp() {
+        mPropertyHalServiceIds = new PropertyHalServiceIds();
+        // set up read permission and write permission to VENDOR_PROPERTY_1
+        CONFIG_ARRAY.add(VENDOR_PROPERTY_1);
+        CONFIG_ARRAY.add(VehicleVendorPermission.PERMISSION_DEFAULT);
+        CONFIG_ARRAY.add(VehicleVendorPermission.PERMISSION_NOT_ACCESSIBLE);
+        // set up read permission and write permission to VENDOR_PROPERTY_2
+        CONFIG_ARRAY.add(VENDOR_PROPERTY_2);
+        CONFIG_ARRAY.add(VehicleVendorPermission.PERMISSION_GET_VENDOR_CATEGORY_ENGINE);
+        CONFIG_ARRAY.add(VehicleVendorPermission.PERMISSION_SET_VENDOR_CATEGORY_ENGINE);
+        // set up read permission and write permission to VENDOR_PROPERTY_3
+        CONFIG_ARRAY.add(VENDOR_PROPERTY_3);
+        CONFIG_ARRAY.add(VehicleVendorPermission.PERMISSION_GET_VENDOR_CATEGORY_INFO);
+        CONFIG_ARRAY.add(VehicleVendorPermission.PERMISSION_DEFAULT);
+
+        // set a invalid config
+        CONFIG_ARRAY_INVALID.add(VehiclePropertyIds.CURRENT_GEAR);
+        CONFIG_ARRAY_INVALID.add(VehicleVendorPermission.PERMISSION_GET_VENDOR_CATEGORY_ENGINE);
+        CONFIG_ARRAY_INVALID.add(VehicleVendorPermission.PERMISSION_SET_VENDOR_CATEGORY_ENGINE);
+    }
+
+    /**
+     * Test {@link PropertyHalServiceIds#getReadPermission(int)}
+     * and {@link PropertyHalServiceIds#getWritePermission(int)} for system properties
+     */
+    @Test
+    public void checkPermissionForSystemProperty() {
+        Assert.assertEquals(Car.PERMISSION_CAR_ENGINE_DETAILED,
+                mPropertyHalServiceIds.getReadPermission(VehiclePropertyIds.ENGINE_OIL_LEVEL));
+        Assert.assertNull(
+                mPropertyHalServiceIds.getWritePermission(VehiclePropertyIds.ENGINE_OIL_LEVEL));
+        Assert.assertEquals(Car.PERMISSION_CONTROL_CAR_CLIMATE,
+                mPropertyHalServiceIds.getReadPermission(VehiclePropertyIds.HVAC_FAN_SPEED));
+        Assert.assertEquals(Car.PERMISSION_CONTROL_CAR_CLIMATE,
+                mPropertyHalServiceIds.getWritePermission(VehiclePropertyIds.HVAC_FAN_SPEED));
+    }
+
+    /**
+     * Test {@link PropertyHalServiceIds#customizeVendorPermission(List)}
+     */
+    @Test
+    public void checkPermissionForVendorProperty() {
+        // test insert a valid config
+        mPropertyHalServiceIds.customizeVendorPermission(CONFIG_ARRAY);
+
+        Assert.assertEquals(Car.PERMISSION_VENDOR_EXTENSION,
+                mPropertyHalServiceIds.getReadPermission(VENDOR_PROPERTY_1));
+        Assert.assertNull(mPropertyHalServiceIds.getWritePermission(VENDOR_PROPERTY_1));
+
+        Assert.assertEquals(android.car.hardware.property
+                        .VehicleVendorPermission.PERMISSION_GET_CAR_VENDOR_CATEGORY_ENGINE,
+                mPropertyHalServiceIds.getReadPermission(VENDOR_PROPERTY_2));
+        Assert.assertEquals(android.car.hardware.property
+                        .VehicleVendorPermission.PERMISSION_SET_CAR_VENDOR_CATEGORY_ENGINE,
+                mPropertyHalServiceIds.getWritePermission(VENDOR_PROPERTY_2));
+
+        Assert.assertEquals(android.car.hardware.property
+                        .VehicleVendorPermission.PERMISSION_GET_CAR_VENDOR_CATEGORY_INFO,
+                mPropertyHalServiceIds.getReadPermission(VENDOR_PROPERTY_3));
+        Assert.assertEquals(Car.PERMISSION_VENDOR_EXTENSION,
+                mPropertyHalServiceIds.getWritePermission(VENDOR_PROPERTY_3));
+
+        Assert.assertEquals(Car.PERMISSION_VENDOR_EXTENSION,
+                mPropertyHalServiceIds.getReadPermission(VENDOR_PROPERTY_4));
+        Assert.assertEquals(Car.PERMISSION_VENDOR_EXTENSION,
+                mPropertyHalServiceIds.getWritePermission(VENDOR_PROPERTY_4));
+
+        // test insert invalid config
+        try {
+            mPropertyHalServiceIds.customizeVendorPermission(CONFIG_ARRAY_INVALID);
+            Assert.fail("Insert system properties with vendor permissions");
+        } catch (IllegalArgumentException e) {
+            Log.v(TAG, e.getMessage());
+        }
+    }
+
+    /**
+     * Test {@link PropertyHalServiceIds#isSupportedProperty(int)}
+     */
+    @Test
+    public void checkVendorPropertyId() {
+        for (int vendorProp : VENDOR_PROPERTY_IDS) {
+            Assert.assertTrue(mPropertyHalServiceIds.isSupportedProperty(vendorProp));
+        }
+        for (int systemProp : SYSTEM_PROPERTY_IDS) {
+            Assert.assertTrue(mPropertyHalServiceIds.isSupportedProperty(systemProp));
+        }
+    }
+
+}
diff --git a/tests/carservice_unit_test/src/com/android/car/hal/UserHalServiceTest.java b/tests/carservice_unit_test/src/com/android/car/hal/UserHalServiceTest.java
new file mode 100644
index 0000000..d5f4ccc
--- /dev/null
+++ b/tests/carservice_unit_test/src/com/android/car/hal/UserHalServiceTest.java
@@ -0,0 +1,520 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.car.hal;
+
+import static android.car.VehiclePropertyIds.CURRENT_GEAR;
+import static android.car.VehiclePropertyIds.INITIAL_USER_INFO;
+import static android.hardware.automotive.vehicle.V2_0.InitialUserInfoRequestType.COLD_BOOT;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.verify;
+import static org.testng.Assert.assertThrows;
+
+import android.hardware.automotive.vehicle.V2_0.InitialUserInfoResponse;
+import android.hardware.automotive.vehicle.V2_0.InitialUserInfoResponseAction;
+import android.hardware.automotive.vehicle.V2_0.UserFlags;
+import android.hardware.automotive.vehicle.V2_0.UserInfo;
+import android.hardware.automotive.vehicle.V2_0.UsersInfo;
+import android.hardware.automotive.vehicle.V2_0.VehiclePropConfig;
+import android.hardware.automotive.vehicle.V2_0.VehiclePropValue;
+import android.hardware.automotive.vehicle.V2_0.VehiclePropertyAccess;
+import android.hardware.automotive.vehicle.V2_0.VehiclePropertyChangeMode;
+import android.os.UserHandle;
+import android.util.Log;
+
+import com.android.car.hal.UserHalService.HalCallback;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentMatcher;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+
+@RunWith(MockitoJUnitRunner.class)
+public final class UserHalServiceTest {
+
+    private static final String TAG = UserHalServiceTest.class.getSimpleName();
+
+    /**
+     * Timeout passed to {@link UserHalService#getInitialUserInfo(int, int, UsersInfo, HalCallback)}
+     * calls.
+     */
+    private static final int INITIAL_USER_TIMEOUT_MS = 20;
+
+    /**
+     * Timeout for {@link GenericHalCallback#assertCalled()} for tests where the HAL is supposed to
+     * return something - it's a short time so it doesn't impact the test duration.
+     */
+    private static final int INITIAL_USER_CALLBACK_TIMEOUT_SUCCESS = INITIAL_USER_TIMEOUT_MS + 50;
+
+    /**
+     * Timeout for {@link GenericHalCallback#assertCalled()} for tests where the HAL is not supposed
+     * to return anything - it's a slightly longer to make sure the test doesn't fail prematurely.
+     */
+    private static final int INITIAL_USER_CALLBACK_TIMEOUT_TIMEOUT = INITIAL_USER_TIMEOUT_MS + 500;
+
+    // Used when crafting a reqquest property - the real value will be set by the mock.
+    private static final int REQUEST_ID_PLACE_HOLDER = 42;
+
+    private static final int INITIAL_USER_INFO_RESPONSE_ACTION = 108;
+
+    @Mock
+    private VehicleHal mVehicleHal;
+
+    private final UserInfo mUser0 = new UserInfo();
+    private final UserInfo mUser10 = new UserInfo();
+
+    private final UsersInfo mUsersInfo = new UsersInfo();
+
+    private UserHalService mUserHalService;
+
+    @Before
+    public void setFixtures() {
+        mUserHalService = new UserHalService(mVehicleHal);
+        mUserHalService
+                .takeSupportedProperties(Arrays.asList(newSubscribableConfig(INITIAL_USER_INFO)));
+
+        mUser0.userId = 0;
+        mUser0.flags = 100;
+        mUser10.userId = 10;
+        mUser10.flags = 110;
+
+        mUsersInfo.currentUser = mUser0;
+        mUsersInfo.numberUsers = 2;
+        mUsersInfo.existingUsers = new ArrayList<>(2);
+        mUsersInfo.existingUsers.add(mUser0);
+        mUsersInfo.existingUsers.add(mUser10);
+    }
+
+    @Test
+    public void testTakeSupportedProperties_unsupportedOnly() {
+        // Cannot use mUserHalService because it's already set with supported properties
+        UserHalService myHalService = new UserHalService(mVehicleHal);
+
+        List<VehiclePropConfig> input = Arrays.asList(newConfig(CURRENT_GEAR));
+        Collection<VehiclePropConfig> output = myHalService.takeSupportedProperties(input);
+        assertThat(myHalService.isSupported()).isFalse();
+        assertThat(output).isNull();
+    }
+
+    @Test
+    public void testTakeSupportedPropertiesAndInit() {
+        // Cannot use mUserHalService because it's already set with supported properties
+        UserHalService myHalService = new UserHalService(mVehicleHal);
+
+        VehiclePropConfig unsupportedConfig = newConfig(CURRENT_GEAR);
+        VehiclePropConfig userInfoConfig = newSubscribableConfig(INITIAL_USER_INFO);
+        List<VehiclePropConfig> input = Arrays.asList(unsupportedConfig, userInfoConfig);
+        Collection<VehiclePropConfig> output = myHalService.takeSupportedProperties(input);
+        assertThat(mUserHalService.isSupported()).isTrue();
+        assertThat(output).containsExactly(userInfoConfig);
+
+        // Ideally there should be 2 test methods (one for takeSupportedProperties() and one for
+        // init()), but on "real life" VehicleHal calls these 2 methods in sequence, and the latter
+        // depends on the properties set by the former, so it's ok to test both here...
+        myHalService.init();
+        verify(mVehicleHal).subscribeProperty(myHalService, INITIAL_USER_INFO);
+    }
+
+    @Test
+    public void testGetUserInfo_invalidTimeout() {
+        assertThrows(IllegalArgumentException.class,
+                () -> mUserHalService.getInitialUserInfo(COLD_BOOT, 0, mUsersInfo, (i, r) -> {
+                }));
+        assertThrows(IllegalArgumentException.class,
+                () -> mUserHalService.getInitialUserInfo(COLD_BOOT, -1, mUsersInfo, (i, r) -> {
+                }));
+    }
+
+    @Test
+    public void testGetUserInfo_noUsersInfo() {
+        assertThrows(NullPointerException.class,
+                () -> mUserHalService.getInitialUserInfo(COLD_BOOT, INITIAL_USER_TIMEOUT_MS, null,
+                        (i, r) -> {
+                        }));
+    }
+
+    @Test
+    public void testGetUserInfo_noCallback() {
+        assertThrows(NullPointerException.class,
+                () -> mUserHalService.getInitialUserInfo(COLD_BOOT, INITIAL_USER_TIMEOUT_MS,
+                        mUsersInfo, null));
+    }
+
+    @Test
+    public void testGetUserInfo_halSetTimedOut() throws Exception {
+        replySetPropertyWithTimeoutException(INITIAL_USER_INFO);
+
+        GenericHalCallback<InitialUserInfoResponse> callback = new GenericHalCallback<>(
+                INITIAL_USER_CALLBACK_TIMEOUT_TIMEOUT);
+        mUserHalService.getInitialUserInfo(COLD_BOOT, INITIAL_USER_TIMEOUT_MS, mUsersInfo,
+                callback);
+
+        callback.assertCalled();
+        assertCallbackStatus(callback, HalCallback.STATUS_HAL_SET_TIMEOUT);
+        assertThat(callback.response).isNull();
+    }
+
+    @Test
+    public void testGetUserInfo_halDidNotReply() throws Exception {
+        GenericHalCallback<InitialUserInfoResponse> callback = new GenericHalCallback<>(
+                INITIAL_USER_CALLBACK_TIMEOUT_TIMEOUT);
+        mUserHalService.getInitialUserInfo(COLD_BOOT, INITIAL_USER_TIMEOUT_MS, mUsersInfo,
+                callback);
+
+        callback.assertCalled();
+        assertCallbackStatus(callback, HalCallback.STATUS_HAL_RESPONSE_TIMEOUT);
+        assertThat(callback.response).isNull();
+    }
+
+    @Test
+    public void testGetUserInfo_secondCallFailWhilePending() throws Exception {
+        GenericHalCallback<InitialUserInfoResponse> callback1 = new GenericHalCallback<>(
+                INITIAL_USER_CALLBACK_TIMEOUT_TIMEOUT);
+        GenericHalCallback<InitialUserInfoResponse> callback2 = new GenericHalCallback<>(
+                INITIAL_USER_CALLBACK_TIMEOUT_TIMEOUT);
+        mUserHalService.getInitialUserInfo(COLD_BOOT, INITIAL_USER_TIMEOUT_MS, mUsersInfo,
+                callback1);
+        mUserHalService.getInitialUserInfo(COLD_BOOT, INITIAL_USER_TIMEOUT_MS, mUsersInfo,
+                callback2);
+
+        callback1.assertCalled();
+        assertCallbackStatus(callback1, HalCallback.STATUS_HAL_RESPONSE_TIMEOUT);
+        assertThat(callback1.response).isNull();
+
+        callback2.assertCalled();
+        assertCallbackStatus(callback2, HalCallback.STATUS_CONCURRENT_OPERATION);
+        assertThat(callback1.response).isNull();
+    }
+
+    @Test
+    public void testGetUserInfo_halReplyWithWrongRequestId() throws Exception {
+        // TODO(b/146207078): use helper method to convert prop value to proper req
+        VehiclePropValue propResponse = new VehiclePropValue();
+        propResponse.prop = INITIAL_USER_INFO;
+        propResponse.value.int32Values.add(REQUEST_ID_PLACE_HOLDER);
+
+        replySetPropertyWithOnChangeEvent(INITIAL_USER_INFO, propResponse,
+                /* rightRequestId= */ false);
+
+        GenericHalCallback<InitialUserInfoResponse> callback = new GenericHalCallback<>(
+                INITIAL_USER_CALLBACK_TIMEOUT_TIMEOUT);
+        mUserHalService.getInitialUserInfo(COLD_BOOT, INITIAL_USER_TIMEOUT_MS, mUsersInfo,
+                callback);
+
+        callback.assertCalled();
+        assertCallbackStatus(callback, HalCallback.STATUS_HAL_RESPONSE_TIMEOUT);
+        assertThat(callback.response).isNull();
+    }
+
+    @Test
+    public void testGetUserInfo_halReturnedInvalidAction() throws Exception {
+        // TODO(b/146207078): use helper method to convert prop value to proper req
+        VehiclePropValue propResponse = new VehiclePropValue();
+        propResponse.prop = INITIAL_USER_INFO;
+        propResponse.value.int32Values.add(REQUEST_ID_PLACE_HOLDER);
+        propResponse.value.int32Values.add(INITIAL_USER_INFO_RESPONSE_ACTION);
+
+        AtomicReference<VehiclePropValue> reqCaptor = replySetPropertyWithOnChangeEvent(
+                INITIAL_USER_INFO, propResponse, /* rightRequestId= */ true);
+
+        GenericHalCallback<InitialUserInfoResponse> callback = new GenericHalCallback<>(
+                INITIAL_USER_CALLBACK_TIMEOUT_SUCCESS);
+        mUserHalService.getInitialUserInfo(COLD_BOOT, INITIAL_USER_TIMEOUT_MS, mUsersInfo,
+                callback);
+
+        callback.assertCalled();
+
+        // Make sure the arguments were properly converted
+        assertInitialUserInfoSetRequest(reqCaptor.get(), COLD_BOOT);
+
+        // Assert response
+        assertCallbackStatus(callback, HalCallback.STATUS_WRONG_HAL_RESPONSE);
+        assertThat(callback.response).isNull();
+    }
+
+    @Test
+    public void testGetUserInfo_successDefault() throws Exception {
+        // TODO(b/146207078): use helper method to convert prop value to proper req
+        VehiclePropValue propResponse = new VehiclePropValue();
+        propResponse.prop = INITIAL_USER_INFO;
+        propResponse.value.int32Values.add(REQUEST_ID_PLACE_HOLDER);
+        propResponse.value.int32Values.add(InitialUserInfoResponseAction.DEFAULT);
+
+        AtomicReference<VehiclePropValue> reqCaptor = replySetPropertyWithOnChangeEvent(
+                INITIAL_USER_INFO, propResponse, /* rightRequestId= */ true);
+
+        GenericHalCallback<InitialUserInfoResponse> callback = new GenericHalCallback<>(
+                INITIAL_USER_CALLBACK_TIMEOUT_SUCCESS);
+        mUserHalService.getInitialUserInfo(COLD_BOOT, INITIAL_USER_TIMEOUT_MS, mUsersInfo,
+                callback);
+
+        callback.assertCalled();
+
+        // Make sure the arguments were properly converted
+        assertInitialUserInfoSetRequest(reqCaptor.get(), COLD_BOOT);
+
+        // Assert response
+        assertCallbackStatus(callback, HalCallback.STATUS_OK);
+        InitialUserInfoResponse actualResponse = callback.response;
+        assertThat(actualResponse.action).isEqualTo(InitialUserInfoResponseAction.DEFAULT);
+        assertThat(actualResponse.userNameToCreate).isEmpty();
+        assertThat(actualResponse.userToSwitchOrCreate).isNotNull();
+        assertThat(actualResponse.userToSwitchOrCreate.userId).isEqualTo(UserHandle.USER_NULL);
+        assertThat(actualResponse.userToSwitchOrCreate.flags).isEqualTo(UserFlags.NONE);
+    }
+
+    @Test
+    public void testGetUserInfo_successSwitchUser() throws Exception {
+        int userIdToSwitch = 42;
+        // TODO(b/146207078): use helper method to convert prop value to proper req
+        VehiclePropValue propResponse = new VehiclePropValue();
+        propResponse.prop = INITIAL_USER_INFO;
+        propResponse.value.int32Values.add(REQUEST_ID_PLACE_HOLDER);
+        propResponse.value.int32Values.add(InitialUserInfoResponseAction.SWITCH);
+        propResponse.value.int32Values.add(userIdToSwitch);
+
+        AtomicReference<VehiclePropValue> reqCaptor = replySetPropertyWithOnChangeEvent(
+                INITIAL_USER_INFO, propResponse, /* rightRequestId= */ true);
+
+        GenericHalCallback<InitialUserInfoResponse> callback = new GenericHalCallback<>(
+                INITIAL_USER_CALLBACK_TIMEOUT_SUCCESS);
+        mUserHalService.getInitialUserInfo(COLD_BOOT, INITIAL_USER_TIMEOUT_MS, mUsersInfo,
+                callback);
+
+        callback.assertCalled();
+
+        // Make sure the arguments were properly converted
+        assertInitialUserInfoSetRequest(reqCaptor.get(), COLD_BOOT);
+
+        assertCallbackStatus(callback, HalCallback.STATUS_OK);
+        InitialUserInfoResponse actualResponse = callback.response;
+        assertThat(actualResponse.action).isEqualTo(InitialUserInfoResponseAction.SWITCH);
+        assertThat(actualResponse.userNameToCreate).isEmpty();
+        UserInfo userToSwitch = actualResponse.userToSwitchOrCreate;
+        assertThat(userToSwitch).isNotNull();
+        assertThat(userToSwitch.userId).isEqualTo(userIdToSwitch);
+        assertThat(userToSwitch.flags).isEqualTo(UserFlags.NONE);
+    }
+
+    @Test
+    public void testGetUserInfo_successCreateUser() throws Exception {
+        int newUserFlags = 108;
+        String newUserName = "Groot";
+        // TODO(b/146207078): use helper method to convert prop value to proper req
+        VehiclePropValue propResponse = new VehiclePropValue();
+        propResponse.prop = INITIAL_USER_INFO;
+        propResponse.value.int32Values.add(REQUEST_ID_PLACE_HOLDER);
+        propResponse.value.int32Values.add(InitialUserInfoResponseAction.CREATE);
+        propResponse.value.int32Values.add(newUserFlags);
+        propResponse.value.stringValue = newUserName;
+
+        AtomicReference<VehiclePropValue> reqCaptor = replySetPropertyWithOnChangeEvent(
+                INITIAL_USER_INFO, propResponse, /* rightRequestId= */ true);
+
+        GenericHalCallback<InitialUserInfoResponse> callback = new GenericHalCallback<>(
+                INITIAL_USER_CALLBACK_TIMEOUT_SUCCESS);
+        mUserHalService.getInitialUserInfo(COLD_BOOT, INITIAL_USER_TIMEOUT_MS, mUsersInfo,
+                callback);
+
+        callback.assertCalled();
+
+        // Make sure the arguments were properly converted
+        assertInitialUserInfoSetRequest(reqCaptor.get(), COLD_BOOT);
+
+        assertCallbackStatus(callback, HalCallback.STATUS_OK);
+        assertThat(callback.status).isEqualTo(HalCallback.STATUS_OK);
+        InitialUserInfoResponse actualResponse = callback.response;
+        assertThat(actualResponse.action).isEqualTo(InitialUserInfoResponseAction.CREATE);
+        assertThat(actualResponse.userNameToCreate).isEqualTo(newUserName);
+        UserInfo newUser = actualResponse.userToSwitchOrCreate;
+        assertThat(newUser).isNotNull();
+        assertThat(newUser.userId).isEqualTo(UserHandle.USER_NULL);
+        assertThat(newUser.flags).isEqualTo(newUserFlags);
+    }
+
+    /**
+     * Asserts the given {@link UsersInfo} is properly represented in the {@link VehiclePropValue}.
+     *
+     * @param value property containing the info
+     * @param info info to be checked
+     * @param initialIndex first index of the info values in the property's {@code int32Values}
+     */
+    private void assertUsersInfo(VehiclePropValue value, UsersInfo info, int initialIndex) {
+        // TODO(b/146207078): use helper method to convert prop value to proper req to check users
+        ArrayList<Integer> values = value.value.int32Values;
+        assertWithMessage("wrong values size").that(values)
+                .hasSize(initialIndex + 3 + info.numberUsers * 2);
+
+        int i = initialIndex;
+        assertWithMessage("currentUser.id mismatch at index %s", i).that(values.get(i))
+                .isEqualTo(info.currentUser.userId);
+        i++;
+        assertWithMessage("currentUser.flags mismatch at index %s", i).that(values.get(i))
+            .isEqualTo(info.currentUser.flags);
+        i++;
+        assertWithMessage("numberUsers mismatch at index %s", i).that(values.get(i))
+            .isEqualTo(info.numberUsers);
+        i++;
+
+        for (int j = 0; j < info.numberUsers; j++) {
+            int actualUserId = values.get(i++);
+            int actualUserFlags = values.get(i++);
+            UserInfo expectedUser = info.existingUsers.get(j);
+            assertWithMessage("wrong id for existing user#%s at index %s", j, i)
+                .that(actualUserId).isEqualTo(expectedUser.userId);
+            assertWithMessage("wrong flags for existing user#%s at index %s", j, i)
+                .that(actualUserFlags).isEqualTo(expectedUser.flags);
+        }
+    }
+
+    /**
+     * Sets the VHAL mock to emulate a property change event upon a call to set a property.
+     *
+     * @param prop prop to be set
+     * @param response response to be set on event
+     * @param rightRequestId whether the response id should match the request
+     * @return
+     *
+     * @return reference to the value passed to {@code set()}.
+     */
+    private AtomicReference<VehiclePropValue> replySetPropertyWithOnChangeEvent(int prop,
+            VehiclePropValue response, boolean rightRequestId) throws Exception {
+        AtomicReference<VehiclePropValue> ref = new AtomicReference<>();
+        doAnswer((inv) -> {
+            VehiclePropValue request = inv.getArgument(0);
+            ref.set(request);
+            int requestId = request.value.int32Values.get(0);
+            int responseId = rightRequestId ? requestId : requestId + 1000;
+            response.value.int32Values.set(0, responseId);
+            Log.d(TAG, "mockSetPropertyWithOnChange(): resp=" + response + " for req=" + request);
+            mUserHalService.onHalEvents(Arrays.asList(response));
+            return null;
+        }).when(mVehicleHal).set(isProperty(prop));
+        return ref;
+    }
+
+    /**
+     * Sets the VHAL mock to emulate a property timeout exception upon a call to set a property.
+     */
+    private void replySetPropertyWithTimeoutException(int prop) throws Exception {
+        doThrow(new PropertyTimeoutException(prop)).when(mVehicleHal).set(isProperty(prop));
+    }
+
+    private void assertInitialUserInfoSetRequest(VehiclePropValue req, int requestType) {
+        assertThat(req.value.int32Values.get(1)).isEqualTo(requestType);
+        assertUsersInfo(req, mUsersInfo, 2);
+    }
+
+    private void assertCallbackStatus(GenericHalCallback<InitialUserInfoResponse> callback,
+            int expectedStatus) {
+        int actualStatus = callback.status;
+        if (actualStatus == expectedStatus) return;
+
+        fail("Wrong callback status; expected "
+                + UserHalHelper.halCallbackStatusToString(expectedStatus) + ", got "
+                + UserHalHelper.halCallbackStatusToString(actualStatus));
+    }
+
+    private final class GenericHalCallback<R> implements HalCallback<R> {
+
+        private final CountDownLatch mLatch = new CountDownLatch(1);
+        private final int mTimeout;
+
+        public int status;
+        public R response;
+
+        GenericHalCallback(int timeout) {
+            this.mTimeout = timeout;
+        }
+
+        @Override
+        public void onResponse(int status, R response) {
+            Log.d(TAG, "onResponse(): status=" + status + ", response=" +  response);
+            this.status = status;
+            this.response = response;
+            mLatch.countDown();
+        }
+
+        /**
+         * Asserts that the callback was called, or fail if it timed out.
+         */
+        public void assertCalled() throws InterruptedException {
+            Log.d(TAG, "assertCalled(): waiting " + mTimeout + "ms");
+            if (!mLatch.await(mTimeout, TimeUnit.MILLISECONDS)) {
+                throw new AssertionError("callback not called in " + mTimeout + "ms");
+            }
+        }
+    }
+
+    // TODO(b/149099817): move stuff below to common code
+
+    /**
+     * Custom Mockito matcher to check if a {@link VehiclePropValue} has the given {@code prop}.
+     */
+    public static VehiclePropValue isProperty(int prop) {
+        return argThat(new PropertyIdMatcher(prop));
+    }
+
+    private static class PropertyIdMatcher implements ArgumentMatcher<VehiclePropValue> {
+
+        public final int prop;
+
+        private PropertyIdMatcher(int prop) {
+            this.prop = prop;
+        }
+
+        @Override
+        public boolean matches(VehiclePropValue argument) {
+            return argument.prop == prop;
+        }
+    }
+
+    /**
+     * Creates an empty config for the given property.
+     */
+    private static VehiclePropConfig newConfig(int prop) {
+        VehiclePropConfig config = new VehiclePropConfig();
+        config.prop = prop;
+        return config;
+    }
+
+    /**
+     * Creates a config for the given property that passes the
+     * {@link VehicleHal#isPropertySubscribable(VehiclePropConfig)} criteria.
+     */
+    private static VehiclePropConfig newSubscribableConfig(int prop) {
+        VehiclePropConfig config = newConfig(prop);
+        config.access = VehiclePropertyAccess.READ_WRITE;
+        config.changeMode = VehiclePropertyChangeMode.ON_CHANGE;
+        return config;
+    }
+}
diff --git a/tests/carservice_unit_test/src/com/android/car/hal/VmsHalServiceTest.java b/tests/carservice_unit_test/src/com/android/car/hal/VmsHalServiceTest.java
index 093ab9b..4540b6b 100644
--- a/tests/carservice_unit_test/src/com/android/car/hal/VmsHalServiceTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/hal/VmsHalServiceTest.java
@@ -20,21 +20,17 @@
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.doThrow;
-import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyZeroInteractions;
 import static org.mockito.Mockito.when;
 
-import android.car.vms.IVmsPublisherClient;
-import android.car.vms.IVmsPublisherService;
-import android.car.vms.IVmsSubscriberClient;
-import android.car.vms.IVmsSubscriberService;
 import android.car.vms.VmsAssociatedLayer;
 import android.car.vms.VmsAvailableLayers;
+import android.car.vms.VmsClient;
+import android.car.vms.VmsClientManager.VmsClientCallback;
 import android.car.vms.VmsLayer;
 import android.car.vms.VmsLayerDependency;
-import android.car.vms.VmsLayersOffering;
 import android.car.vms.VmsSubscriptionState;
 import android.content.Context;
 import android.content.res.Resources;
@@ -43,33 +39,32 @@
 import android.hardware.automotive.vehicle.V2_0.VehicleProperty;
 import android.hardware.automotive.vehicle.V2_0.VehiclePropertyGroup;
 import android.hardware.automotive.vehicle.V2_0.VmsMessageType;
-import android.os.Binder;
-import android.os.IBinder;
+import android.os.Handler;
 
 import com.android.car.R;
 import com.android.car.test.utils.TemporaryFile;
-import com.android.car.vms.VmsClientManager;
 
 import org.junit.Before;
-import org.junit.Rule;
 import org.junit.Test;
-import org.mockito.ArgumentCaptor;
+import org.junit.runner.RunWith;
 import org.mockito.InOrder;
 import org.mockito.Mock;
 import org.mockito.Mockito;
-import org.mockito.junit.MockitoJUnit;
-import org.mockito.junit.MockitoRule;
+import org.mockito.junit.MockitoJUnitRunner;
 
 import java.io.FileDescriptor;
 import java.io.FileInputStream;
 import java.io.FileOutputStream;
 import java.util.Arrays;
 import java.util.Collections;
+import java.util.HashSet;
 import java.util.LinkedHashSet;
 import java.util.List;
+import java.util.Set;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 
+@RunWith(MockitoJUnitRunner.class)
 public class VmsHalServiceTest {
     private static final int LAYER_TYPE = 1;
     private static final int LAYER_SUBTYPE = 2;
@@ -81,8 +76,6 @@
     private static final int CORE_ID = 54321;
     private static final int CLIENT_ID = 98765;
 
-    @Rule
-    public MockitoRule mockito = MockitoJUnit.rule();
     @Mock
     private Context mContext;
     @Mock
@@ -90,16 +83,11 @@
     @Mock
     private VehicleHal mVehicleHal;
     @Mock
-    private VmsClientManager mClientManager;
-    @Mock
-    private IVmsPublisherService mPublisherService;
-    @Mock
-    private IVmsSubscriberService mSubscriberService;
+    private VmsClient mVmsClient;
 
-    private IBinder mToken;
     private VmsHalService mHalService;
-    private IVmsPublisherClient mPublisherClient;
-    private IVmsSubscriberClient mSubscriberClient;
+    private VmsClientCallback mEventCallback;
+    private int mVmsInitCount;
 
     @Before
     public void setUp() throws Exception {
@@ -107,24 +95,22 @@
     }
 
     private void initHalService(boolean propagatePropertyException) throws Exception {
+        mVmsInitCount = 0;
         when(mContext.getResources()).thenReturn(mResources);
         mHalService = new VmsHalService(mContext, mVehicleHal, () -> (long) CORE_ID,
-            propagatePropertyException);
-        mHalService.setClientManager(mClientManager);
-        mHalService.setVmsSubscriberService(mSubscriberService);
+                this::initVmsClient, propagatePropertyException);
 
         VehiclePropConfig propConfig = new VehiclePropConfig();
         propConfig.prop = VehicleProperty.VEHICLE_MAP_SERVICE;
         mHalService.takeSupportedProperties(Collections.singleton(propConfig));
 
-        when(mSubscriberService.getAvailableLayers()).thenReturn(
+        when(mVmsClient.getAvailableLayers()).thenReturn(
                 new VmsAvailableLayers(Collections.emptySet(), 0));
         mHalService.init();
         waitForHandlerCompletion();
 
         // Verify START_SESSION message was sent
-        InOrder initOrder =
-                Mockito.inOrder(mClientManager, mSubscriberService, mVehicleHal);
+        InOrder initOrder = Mockito.inOrder(mVehicleHal);
         initOrder.verify(mVehicleHal).subscribeProperty(mHalService,
                 VehicleProperty.VEHICLE_MAP_SERVICE);
         initOrder.verify(mVehicleHal).set(createHalMessage(
@@ -143,21 +129,6 @@
         ));
         waitForHandlerCompletion();
 
-        // Verify client is marked as connected
-        ArgumentCaptor<IVmsPublisherClient> publisherCaptor =
-                ArgumentCaptor.forClass(IVmsPublisherClient.class);
-        ArgumentCaptor<IVmsSubscriberClient> subscriberCaptor =
-                ArgumentCaptor.forClass(IVmsSubscriberClient.class);
-        initOrder.verify(mClientManager, never()).onHalDisconnected();
-        initOrder.verify(mClientManager)
-                .onHalConnected(publisherCaptor.capture(), subscriberCaptor.capture());
-        mPublisherClient = publisherCaptor.getValue();
-        mSubscriberClient = subscriberCaptor.getValue();
-
-        mToken = new Binder();
-        mPublisherClient.setVmsPublisherService(mToken, mPublisherService);
-
-        initOrder.verify(mSubscriberService).getAvailableLayers();
         initOrder.verify(mVehicleHal).set(createHalMessage(
                 VmsMessageType.AVAILABILITY_CHANGE, // Message type
                 0,                                  // Sequence number
@@ -166,19 +137,26 @@
 
         waitForHandlerCompletion();
         initOrder.verifyNoMoreInteractions();
-        reset(mClientManager, mSubscriberService, mVehicleHal);
+        assertEquals(1, mVmsInitCount);
+        reset(mVmsClient, mVehicleHal);
+    }
+
+    private VmsClient initVmsClient(Handler handler, VmsClientCallback callback) {
+        mEventCallback = callback;
+        mVmsInitCount++;
+        return mVmsClient;
     }
 
     @Test
     public void testCoreId_IntegerOverflow() throws Exception {
         mHalService = new VmsHalService(mContext, mVehicleHal,
-                () -> (long) Integer.MAX_VALUE + CORE_ID, true);
+                () -> (long) Integer.MAX_VALUE + CORE_ID, this::initVmsClient, true);
 
         VehiclePropConfig propConfig = new VehiclePropConfig();
         propConfig.prop = VehicleProperty.VEHICLE_MAP_SERVICE;
         mHalService.takeSupportedProperties(Collections.singleton(propConfig));
 
-        when(mSubscriberService.getAvailableLayers()).thenReturn(
+        when(mVmsClient.getAvailableLayers()).thenReturn(
                 new VmsAvailableLayers(Collections.emptySet(), 0));
         mHalService.init();
         waitForHandlerCompletion();
@@ -213,7 +191,7 @@
      * </ul>
      */
     @Test
-    public void testHandleDataEvent() throws Exception {
+    public void testHandleDataEvent() {
         VehiclePropValue message = createHalMessage(
                 VmsMessageType.DATA,                       // Message type
                 LAYER_TYPE, LAYER_SUBTYPE, LAYER_VERSION,  // VmsLayer
@@ -222,7 +200,20 @@
         message.value.bytes.addAll(PAYLOAD_AS_LIST);
 
         sendHalMessage(message);
-        verify(mPublisherService).publish(mToken, LAYER, PUBLISHER_ID, PAYLOAD);
+        verify(mVmsClient).publishPacket(PUBLISHER_ID, LAYER, PAYLOAD);
+    }
+
+    @Test
+    public void testOnPacketReceivedEvent() throws Exception {
+        VehiclePropValue message = createHalMessage(
+                VmsMessageType.DATA,                       // Message type
+                LAYER_TYPE, LAYER_SUBTYPE, LAYER_VERSION,  // VmsLayer
+                PUBLISHER_ID                               // PublisherId
+        );
+        message.value.bytes.addAll(PAYLOAD_AS_LIST);
+
+        mEventCallback.onPacketReceived(PUBLISHER_ID, LAYER, PAYLOAD);
+        verify(mVehicleHal).set(message);
     }
 
     /**
@@ -235,14 +226,14 @@
      * </ul>
      */
     @Test
-    public void testHandleSubscribeEvent() throws Exception {
+    public void testHandleSubscribeEvent() {
         VehiclePropValue message = createHalMessage(
                 VmsMessageType.SUBSCRIBE,                 // Message type
                 LAYER_TYPE, LAYER_SUBTYPE, LAYER_VERSION  // VmsLayer
         );
 
         sendHalMessage(message);
-        verify(mSubscriberService).addVmsSubscriber(mSubscriberClient, LAYER);
+        verify(mVmsClient).setSubscriptions(asSet(new VmsAssociatedLayer(LAYER, asSet())));
     }
 
     /**
@@ -256,7 +247,7 @@
      * </ul>
      */
     @Test
-    public void testHandleSubscribeToPublisherEvent() throws Exception {
+    public void testHandleSubscribeToPublisherEvent() {
         VehiclePropValue message = createHalMessage(
                 VmsMessageType.SUBSCRIBE_TO_PUBLISHER,     // Message type
                 LAYER_TYPE, LAYER_SUBTYPE, LAYER_VERSION,  // VmsLayer
@@ -264,8 +255,8 @@
         );
 
         sendHalMessage(message);
-        verify(mSubscriberService).addVmsSubscriberToPublisher(mSubscriberClient, LAYER,
-                PUBLISHER_ID);
+        verify(mVmsClient).setSubscriptions(asSet(
+                new VmsAssociatedLayer(LAYER, asSet(PUBLISHER_ID))));
     }
 
     /**
@@ -278,14 +269,16 @@
      * </ul>
      */
     @Test
-    public void testHandleUnsubscribeEvent() throws Exception {
+    public void testHandleUnsubscribeEvent() {
+        testHandleSubscribeEvent();
+
         VehiclePropValue message = createHalMessage(
                 VmsMessageType.UNSUBSCRIBE,               // Message type
                 LAYER_TYPE, LAYER_SUBTYPE, LAYER_VERSION  // VmsLayer
         );
 
         sendHalMessage(message);
-        verify(mSubscriberService).removeVmsSubscriber(mSubscriberClient, LAYER);
+        verify(mVmsClient).setSubscriptions(asSet());
     }
 
     /**
@@ -299,7 +292,9 @@
      * </ul>
      */
     @Test
-    public void testHandleUnsubscribeFromPublisherEvent() throws Exception {
+    public void testHandleUnsubscribeFromPublisherEvent() {
+        testHandleSubscribeToPublisherEvent();
+
         VehiclePropValue message = createHalMessage(
                 VmsMessageType.UNSUBSCRIBE_TO_PUBLISHER,   // Message type
                 LAYER_TYPE, LAYER_SUBTYPE, LAYER_VERSION,  // VmsLayer
@@ -307,8 +302,7 @@
         );
 
         sendHalMessage(message);
-        verify(mSubscriberService).removeVmsSubscriberToPublisher(mSubscriberClient, LAYER,
-                PUBLISHER_ID);
+        verify(mVmsClient).setSubscriptions(asSet());
     }
 
     /**
@@ -331,7 +325,7 @@
         );
         request.value.bytes.addAll(PAYLOAD_AS_LIST);
 
-        when(mPublisherService.getPublisherId(PAYLOAD)).thenReturn(PUBLISHER_ID);
+        when(mVmsClient.registerProvider(PAYLOAD)).thenReturn(PUBLISHER_ID);
 
         VehiclePropValue response = createHalMessage(
                 VmsMessageType.PUBLISHER_ID_RESPONSE,  // Message type
@@ -362,7 +356,7 @@
                 PUBLISHER_ID                                   // Publisher ID
         );
 
-        when(mSubscriberService.getPublisherInfo(PUBLISHER_ID)).thenReturn(PAYLOAD);
+        when(mVmsClient.getProviderDescription(PUBLISHER_ID)).thenReturn(PAYLOAD);
 
         VehiclePropValue response = createHalMessage(
                 VmsMessageType.PUBLISHER_INFORMATION_RESPONSE  // Message type
@@ -395,7 +389,7 @@
      * </ul>
      */
     @Test
-    public void testHandleOfferingEvent_ZeroOfferings() throws Exception {
+    public void testHandleOfferingEvent_ZeroOfferings() {
         VehiclePropValue message = createHalMessage(
                 VmsMessageType.OFFERING,  // Message type
                 PUBLISHER_ID,             // PublisherId
@@ -403,13 +397,11 @@
         );
 
         sendHalMessage(message);
-        verify(mPublisherService).setLayersOffering(
-                mToken,
-                new VmsLayersOffering(Collections.emptySet(), PUBLISHER_ID));
+        verify(mVmsClient).setProviderOfferings(PUBLISHER_ID, asSet());
     }
 
     @Test
-    public void testHandleOfferingEvent_LayerOnly() throws Exception {
+    public void testHandleOfferingEvent_LayerOnly() {
         VehiclePropValue message = createHalMessage(
                 VmsMessageType.OFFERING,                   // Message type
                 PUBLISHER_ID,                              // PublisherId
@@ -420,15 +412,13 @@
         );
 
         sendHalMessage(message);
-        verify(mPublisherService).setLayersOffering(
-                mToken,
-                new VmsLayersOffering(Collections.singleton(
-                        new VmsLayerDependency(LAYER)),
-                        PUBLISHER_ID));
+        verify(mVmsClient).setProviderOfferings(
+                PUBLISHER_ID,
+                asSet(new VmsLayerDependency(LAYER)));
     }
 
     @Test
-    public void testHandleOfferingEvent_LayerAndDependency() throws Exception {
+    public void testHandleOfferingEvent_LayerAndDependency() {
         VehiclePropValue message = createHalMessage(
                 VmsMessageType.OFFERING,                   // Message type
                 PUBLISHER_ID,                              // PublisherId
@@ -439,16 +429,15 @@
         );
 
         sendHalMessage(message);
-        verify(mPublisherService).setLayersOffering(
-                mToken,
-                new VmsLayersOffering(Collections.singleton(
+        verify(mVmsClient).setProviderOfferings(
+                PUBLISHER_ID,
+                asSet(
                         new VmsLayerDependency(LAYER, Collections.singleton(
-                                new VmsLayer(4, 5, 6)))),
-                        PUBLISHER_ID));
+                                new VmsLayer(4, 5, 6)))));
     }
 
     @Test
-    public void testHandleOfferingEvent_MultipleLayersAndDependencies() throws Exception {
+    public void testHandleOfferingEvent_MultipleLayersAndDependencies() {
         VehiclePropValue message = createHalMessage(
                 VmsMessageType.OFFERING,                   // Message type
                 PUBLISHER_ID,                              // PublisherId
@@ -468,9 +457,9 @@
         );
 
         sendHalMessage(message);
-        verify(mPublisherService).setLayersOffering(
-                mToken,
-                new VmsLayersOffering(new LinkedHashSet<>(Arrays.asList(
+        verify(mVmsClient).setProviderOfferings(
+                PUBLISHER_ID,
+                asSet(
                         new VmsLayerDependency(LAYER, new LinkedHashSet<>(Arrays.asList(
                                 new VmsLayer(4, 5, 6),
                                 new VmsLayer(7, 8, 9)
@@ -478,8 +467,7 @@
                         new VmsLayerDependency(new VmsLayer(3, 2, 1), Collections.emptySet()),
                         new VmsLayerDependency(new VmsLayer(6, 5, 4), Collections.singleton(
                                 new VmsLayer(7, 8, 9)
-                        )))),
-                        PUBLISHER_ID));
+                        ))));
     }
 
     /**
@@ -509,7 +497,7 @@
                 VmsMessageType.AVAILABILITY_REQUEST  // Message type
         );
 
-        when(mSubscriberService.getAvailableLayers()).thenReturn(
+        when(mVmsClient.getAvailableLayers()).thenReturn(
                 new VmsAvailableLayers(Collections.emptySet(), 123));
 
         VehiclePropValue response = createHalMessage(
@@ -528,7 +516,7 @@
                 VmsMessageType.AVAILABILITY_REQUEST  // Message type
         );
 
-        when(mSubscriberService.getAvailableLayers()).thenReturn(
+        when(mVmsClient.getAvailableLayers()).thenReturn(
                 new VmsAvailableLayers(Collections.singleton(
                         new VmsAssociatedLayer(LAYER, Collections.singleton(PUBLISHER_ID))), 123));
 
@@ -552,7 +540,7 @@
                 VmsMessageType.AVAILABILITY_REQUEST  // Message type
         );
 
-        when(mSubscriberService.getAvailableLayers()).thenReturn(
+        when(mVmsClient.getAvailableLayers()).thenReturn(
                 new VmsAvailableLayers(new LinkedHashSet<>(Arrays.asList(
                         new VmsAssociatedLayer(LAYER,
                                 new LinkedHashSet<>(Arrays.asList(PUBLISHER_ID, 54321))),
@@ -595,7 +583,7 @@
      */
     @Test
     public void testHandleStartSessionEvent() throws Exception {
-        when(mSubscriberService.getAvailableLayers()).thenReturn(
+        when(mVmsClient.getAvailableLayers()).thenReturn(
                 new VmsAvailableLayers(Collections.emptySet(), 5));
 
         VehiclePropValue request = createHalMessage(
@@ -612,10 +600,10 @@
 
         sendHalMessage(request);
 
-        InOrder inOrder = Mockito.inOrder(mClientManager, mVehicleHal);
-        inOrder.verify(mClientManager).onHalDisconnected();
+        InOrder inOrder = Mockito.inOrder(mVehicleHal, mVmsClient);
+        inOrder.verify(mVmsClient).unregister();
         inOrder.verify(mVehicleHal).set(response);
-        inOrder.verify(mClientManager).onHalConnected(mPublisherClient, mSubscriberClient);
+        assertEquals(2, mVmsInitCount);
 
         waitForHandlerCompletion();
         inOrder.verify(mVehicleHal).set(createHalMessage(
@@ -643,7 +631,7 @@
      */
     @Test
     public void testOnLayersAvailabilityChanged_ZeroLayers() throws Exception {
-        mSubscriberClient.onLayersAvailabilityChanged(
+        mEventCallback.onLayerAvailabilityChanged(
                 new VmsAvailableLayers(Collections.emptySet(), 123));
 
         VehiclePropValue message = createHalMessage(
@@ -658,7 +646,7 @@
 
     @Test
     public void testOnLayersAvailabilityChanged_OneLayer() throws Exception {
-        mSubscriberClient.onLayersAvailabilityChanged(
+        mEventCallback.onLayerAvailabilityChanged(
                 new VmsAvailableLayers(Collections.singleton(
                         new VmsAssociatedLayer(LAYER, Collections.singleton(PUBLISHER_ID))), 123));
 
@@ -678,7 +666,7 @@
 
     @Test
     public void testOnLayersAvailabilityChanged_MultipleLayers() throws Exception {
-        mSubscriberClient.onLayersAvailabilityChanged(
+        mEventCallback.onLayerAvailabilityChanged(
                 new VmsAvailableLayers(new LinkedHashSet<>(Arrays.asList(
                         new VmsAssociatedLayer(LAYER,
                                 new LinkedHashSet<>(Arrays.asList(PUBLISHER_ID, 54321))),
@@ -745,7 +733,7 @@
                 VmsMessageType.SUBSCRIPTIONS_REQUEST  // Message type
         );
 
-        when(mPublisherService.getSubscriptions()).thenReturn(
+        when(mVmsClient.getSubscriptionState()).thenReturn(
                 new VmsSubscriptionState(123, Collections.emptySet(), Collections.emptySet()));
 
         VehiclePropValue response = createHalMessage(
@@ -766,7 +754,7 @@
                 VmsMessageType.SUBSCRIPTIONS_REQUEST  // Message type
         );
 
-        when(mPublisherService.getSubscriptions()).thenReturn(
+        when(mVmsClient.getSubscriptionState()).thenReturn(
                 new VmsSubscriptionState(123, Collections.singleton(LAYER),
                         Collections.emptySet()));
 
@@ -789,7 +777,7 @@
                 VmsMessageType.SUBSCRIPTIONS_REQUEST  // Message type
         );
 
-        when(mPublisherService.getSubscriptions()).thenReturn(
+        when(mVmsClient.getSubscriptionState()).thenReturn(
                 new VmsSubscriptionState(123, Collections.emptySet(), Collections.singleton(
                         new VmsAssociatedLayer(LAYER, Collections.singleton(PUBLISHER_ID)))));
 
@@ -814,7 +802,7 @@
                 VmsMessageType.SUBSCRIPTIONS_REQUEST  // Message type
         );
 
-        when(mPublisherService.getSubscriptions()).thenReturn(
+        when(mVmsClient.getSubscriptionState()).thenReturn(
                 new VmsSubscriptionState(123,
                         new LinkedHashSet<>(Arrays.asList(
                                 LAYER,
@@ -877,7 +865,7 @@
      */
     @Test
     public void testOnVmsSubscriptionChange_ZeroLayers() throws Exception {
-        mPublisherClient.onVmsSubscriptionChange(
+        mEventCallback.onSubscriptionStateChanged(
                 new VmsSubscriptionState(123, Collections.emptySet(), Collections.emptySet()));
 
         VehiclePropValue response = createHalMessage(
@@ -894,7 +882,7 @@
     @Test
     public void testOnVmsSubscriptionChange_OneLayer_ZeroAssociatedLayers()
             throws Exception {
-        mPublisherClient.onVmsSubscriptionChange(
+        mEventCallback.onSubscriptionStateChanged(
                 new VmsSubscriptionState(123, Collections.singleton(LAYER),
                         Collections.emptySet()));
 
@@ -913,7 +901,7 @@
     @Test
     public void testOnVmsSubscriptionChange_ZeroLayers_OneAssociatedLayer()
             throws Exception {
-        mPublisherClient.onVmsSubscriptionChange(
+        mEventCallback.onSubscriptionStateChanged(
                 new VmsSubscriptionState(123, Collections.emptySet(), Collections.singleton(
                         new VmsAssociatedLayer(LAYER, Collections.singleton(PUBLISHER_ID)))));
 
@@ -934,7 +922,7 @@
     @Test
     public void testOnVmsSubscriptionChange_MultipleLayersAndAssociatedLayers()
             throws Exception {
-        mPublisherClient.onVmsSubscriptionChange(
+        mEventCallback.onSubscriptionStateChanged(
                 new VmsSubscriptionState(123,
                         new LinkedHashSet<>(Arrays.asList(
                                 LAYER,
@@ -977,7 +965,6 @@
         doThrow(new RuntimeException()).when(mVehicleHal).set(any());
         initHalService(false);
 
-        mHalService.init();
         waitForHandlerCompletion();
     }
 
@@ -985,7 +972,7 @@
     public void testPropertySetExceptionNotPropagated_ClientStartSession() throws Exception {
         initHalService(false);
 
-        when(mSubscriberService.getAvailableLayers()).thenReturn(
+        when(mVmsClient.getAvailableLayers()).thenReturn(
                 new VmsAvailableLayers(Collections.emptySet(), 0));
         doThrow(new RuntimeException()).when(mVehicleHal).set(any());
 
@@ -1081,7 +1068,7 @@
     }
 
     private void sendHalMessage(VehiclePropValue message) {
-        mHalService.handleHalEvents(Collections.singletonList(message));
+        mHalService.onHalEvents(Collections.singletonList(message));
     }
 
     private void waitForHandlerCompletion() throws Exception {
@@ -1089,4 +1076,9 @@
         mHalService.getHandler().post(latch::countDown);
         latch.await(5, TimeUnit.SECONDS);
     }
+
+    @SafeVarargs
+    private static <T> Set<T> asSet(T... values) {
+        return new HashSet<>(Arrays.asList(values));
+    }
 }
diff --git a/tests/carservice_unit_test/src/com/android/car/pm/VendorServiceControllerTest.java b/tests/carservice_unit_test/src/com/android/car/pm/VendorServiceControllerTest.java
index 2be7db8..d106674 100644
--- a/tests/carservice_unit_test/src/com/android/car/pm/VendorServiceControllerTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/pm/VendorServiceControllerTest.java
@@ -16,6 +16,8 @@
 
 package com.android.car.pm;
 
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.ArgumentMatchers.any;
@@ -29,6 +31,7 @@
 import android.content.ContextWrapper;
 import android.content.Intent;
 import android.content.ServiceConnection;
+import android.content.pm.UserInfo;
 import android.content.res.Resources;
 import android.os.Handler;
 import android.os.Looper;
@@ -36,25 +39,26 @@
 import android.os.UserManager;
 
 import androidx.test.core.app.ApplicationProvider;
-import androidx.test.runner.AndroidJUnit4;
 
 import com.android.car.CarLocalServices;
+import com.android.car.hal.UserHalService;
 import com.android.car.user.CarUserService;
+import com.android.internal.annotations.GuardedBy;
 
 import org.junit.After;
 import org.junit.Before;
-import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
 import org.mockito.Mockito;
-import org.mockito.junit.MockitoJUnit;
-import org.mockito.junit.MockitoRule;
+import org.mockito.MockitoSession;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.mockito.quality.Strictness;
 
 import java.util.ArrayList;
 import java.util.List;
 
-@RunWith(AndroidJUnit4.class)
+@RunWith(MockitoJUnitRunner.class)
 public class VendorServiceControllerTest {
     private VendorServiceController mController;
     private static final Long DEFAULT_TIMEOUT_MS = 1000L;
@@ -71,34 +75,40 @@
             SERVICE_START_SYSTEM_UNLOCKED + "#bind=start,user=system,trigger=userUnlocked"
     };
 
-    @Rule
-    public MockitoRule rule = MockitoJUnit.rule();
-
     @Mock
     private Resources mResources;
 
     @Mock
     private UserManager mUserManager;
 
-    private ServiceLauncherContext mContext;
+    @Mock
+    private UserHalService mUserHal;
 
+    private MockitoSession mSession;
+    private ServiceLauncherContext mContext;
     private CarUserManagerHelper mUserManagerHelper;
     private CarUserService mCarUserService;
 
     @Before
     public void setUp() {
+        mSession = mockitoSession()
+                .initMocks(this)
+                .strictness(Strictness.LENIENT)
+                .spyStatic(ActivityManager.class)
+                .startMocking();
         mContext = new ServiceLauncherContext(ApplicationProvider.getApplicationContext());
         mUserManagerHelper = Mockito.spy(new CarUserManagerHelper(mContext));
-        mCarUserService = new CarUserService(mContext, mUserManagerHelper,
+        mCarUserService = new CarUserService(mContext, mUserHal, mUserManagerHelper, mUserManager,
                 ActivityManager.getService(), 2 /* max running users */);
         CarLocalServices.addService(CarUserService.class, mCarUserService);
 
-        mController = new VendorServiceController(mContext,
-                Looper.getMainLooper(), mUserManagerHelper);
+        mController = new VendorServiceController(mContext, Looper.getMainLooper());
 
-        when(mUserManagerHelper.isPersistentUser(anyInt())).thenReturn(true);
+        UserInfo persistentFgUser = new UserInfo(FG_USER_ID, "persistent user", 0);
+        when(mUserManager.getUserInfo(FG_USER_ID)).thenReturn(persistentFgUser);
+
         // Let's pretend system is not fully loaded, current user is system.
-        when(mUserManagerHelper.getCurrentForegroundUserId()).thenReturn(UserHandle.USER_SYSTEM);
+        when(ActivityManager.getCurrentUser()).thenReturn(UserHandle.USER_SYSTEM);
         // ..and by default all users are locked
         mockUserUnlock(UserHandle.USER_ALL, false /* unlock */);
         when(mResources.getStringArray(com.android.car.R.array.config_earlyStartupServices))
@@ -108,6 +118,7 @@
     @After
     public void tearDown() {
         CarLocalServices.removeServiceForTest(CarUserService.class);
+        mSession.finishMocking();
     }
 
     @Test
@@ -137,7 +148,8 @@
 
         // Unlock system user
         mockUserUnlock(UserHandle.USER_SYSTEM, true);
-        runOnMainThread(() -> mCarUserService.setUserLockStatus(UserHandle.USER_SYSTEM, true));
+        runOnMainThreadAndWaitForIdle(() -> mCarUserService.setUserLockStatus(
+                UserHandle.USER_SYSTEM, true));
 
         mContext.assertStartedService(SERVICE_START_SYSTEM_UNLOCKED);
         mContext.verifyNoMoreServiceLaunches();
@@ -149,8 +161,8 @@
         mContext.reset();
 
         // Switch user to foreground
-        when(mUserManagerHelper.getCurrentForegroundUserId()).thenReturn(FG_USER_ID);
-        runOnMainThread(() -> mCarUserService.onSwitchUser(FG_USER_ID));
+        when(ActivityManager.getCurrentUser()).thenReturn(FG_USER_ID);
+        runOnMainThreadAndWaitForIdle(() -> mCarUserService.onSwitchUser(FG_USER_ID));
 
         // Expect only services with ASAP trigger to be started
         mContext.assertBoundService(SERVICE_BIND_ALL_USERS_ASAP);
@@ -158,14 +170,16 @@
 
         // Unlock foreground user
         mockUserUnlock(FG_USER_ID, true);
-        runOnMainThread(() -> mCarUserService.setUserLockStatus(FG_USER_ID, true));
+        runOnMainThreadAndWaitForIdle(() -> mCarUserService.setUserLockStatus(FG_USER_ID, true));
 
         mContext.assertBoundService(SERVICE_BIND_FG_USER_UNLOCKED);
         mContext.verifyNoMoreServiceLaunches();
     }
 
-    private void runOnMainThread(Runnable r) {
+    private void runOnMainThreadAndWaitForIdle(Runnable r) {
         Handler.getMain().runWithScissors(r, DEFAULT_TIMEOUT_MS);
+        // Run empty runnable to make sure that all posted handlers are done.
+        Handler.getMain().runWithScissors(() -> { }, DEFAULT_TIMEOUT_MS);
     }
 
     private void mockUserUnlock(int userId, boolean unlock) {
@@ -180,7 +194,11 @@
 
     /** Overrides framework behavior to succeed on binding/starting processes. */
     public class ServiceLauncherContext extends ContextWrapper {
+        private final Object mLock = new Object();
+
+        @GuardedBy("mLock")
         private List<Intent> mBoundIntents = new ArrayList<>();
+        @GuardedBy("mLock")
         private List<Intent> mStartedServicesIntents = new ArrayList<>();
 
         ServiceLauncherContext(Context base) {
@@ -189,14 +207,18 @@
 
         @Override
         public ComponentName startServiceAsUser(Intent service, UserHandle user) {
-            mStartedServicesIntents.add(service);
+            synchronized (mLock) {
+                mStartedServicesIntents.add(service);
+            }
             return service.getComponent();
         }
 
         @Override
         public boolean bindServiceAsUser(Intent service, ServiceConnection conn, int flags,
                 Handler handler, UserHandle user) {
-            mBoundIntents.add(service);
+            synchronized (mLock) {
+                mBoundIntents.add(service);
+            }
             conn.onServiceConnected(service.getComponent(), null);
             return true;
         }
@@ -213,27 +235,35 @@
         }
 
         void assertBoundService(String service) {
-            assertThat(mBoundIntents).hasSize(1);
-            assertThat(mBoundIntents.get(0).getComponent())
-                    .isEqualTo(ComponentName.unflattenFromString(service));
-            mBoundIntents.clear();
+            synchronized (mLock) {
+                assertThat(mBoundIntents).hasSize(1);
+                assertThat(mBoundIntents.get(0).getComponent())
+                        .isEqualTo(ComponentName.unflattenFromString(service));
+                mBoundIntents.clear();
+            }
         }
 
         void assertStartedService(String service) {
-            assertThat(mStartedServicesIntents).hasSize(1);
-            assertThat(mStartedServicesIntents.get(0).getComponent())
-                    .isEqualTo(ComponentName.unflattenFromString(service));
-            mStartedServicesIntents.clear();
+            synchronized (mLock) {
+                assertThat(mStartedServicesIntents).hasSize(1);
+                assertThat(mStartedServicesIntents.get(0).getComponent())
+                        .isEqualTo(ComponentName.unflattenFromString(service));
+                mStartedServicesIntents.clear();
+            }
         }
 
         void verifyNoMoreServiceLaunches() {
-            assertThat(mStartedServicesIntents).isEmpty();
-            assertThat(mBoundIntents).isEmpty();
+            synchronized (mLock) {
+                assertThat(mStartedServicesIntents).isEmpty();
+                assertThat(mBoundIntents).isEmpty();
+            }
         }
 
         void reset() {
-            mStartedServicesIntents.clear();
-            mBoundIntents.clear();
+            synchronized (mLock) {
+                mStartedServicesIntents.clear();
+                mBoundIntents.clear();
+            }
 
         }
 
diff --git a/tests/carservice_unit_test/src/com/android/car/pm/VendorServiceInfoTest.java b/tests/carservice_unit_test/src/com/android/car/pm/VendorServiceInfoTest.java
index 8174add..9f33ba4 100644
--- a/tests/carservice_unit_test/src/com/android/car/pm/VendorServiceInfoTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/pm/VendorServiceInfoTest.java
@@ -20,14 +20,13 @@
 
 import android.content.ComponentName;
 
-import androidx.test.runner.AndroidJUnit4;
-
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.ExpectedException;
 import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
 
-@RunWith(AndroidJUnit4.class)
+@RunWith(JUnit4.class)
 public class VendorServiceInfoTest {
     private static final String SERVICE_NAME = "com.andorid.car/.MyService";
 
diff --git a/tests/carservice_unit_test/src/com/android/car/storagemonitoring/CarStorageMonitoringTest.java b/tests/carservice_unit_test/src/com/android/car/storagemonitoring/CarStorageMonitoringTest.java
index 50490e9..eb5c4ed 100644
--- a/tests/carservice_unit_test/src/com/android/car/storagemonitoring/CarStorageMonitoringTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/storagemonitoring/CarStorageMonitoringTest.java
@@ -16,7 +16,15 @@
 
 package com.android.car.storagemonitoring;
 
-import static org.mockito.Mockito.*;
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doReturn;
 
 import android.car.storagemonitoring.IoStats;
 import android.car.storagemonitoring.IoStatsEntry;
@@ -37,11 +45,11 @@
 import com.android.car.test.utils.TemporaryDirectory;
 import com.android.car.test.utils.TemporaryFile;
 
-import junit.framework.TestCase;
-
 import org.json.JSONObject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
 import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
+import org.mockito.junit.MockitoJUnitRunner;
 
 import java.io.FileWriter;
 import java.io.StringReader;
@@ -53,22 +61,18 @@
 import java.util.Collections;
 import java.util.List;
 
-
 /**
  * Tests the storage monitoring API in CarService.
  */
+@RunWith(MockitoJUnitRunner.class)
 @MediumTest
-public class CarStorageMonitoringTest extends TestCase {
+public class CarStorageMonitoringTest {
     static final String TAG = CarStorageMonitoringTest.class.getSimpleName();
 
     @Mock private IHealth mMockedHal;
     @Mock private HealthServiceWearInfoProvider.IHealthSupplier mHealthServiceSupplier;
 
-    @Override
-    public void setUp() {
-        MockitoAnnotations.initMocks(this);
-    }
-
+    @Test
     public void testEMmcWearInformationProvider() throws Exception {
         try (TemporaryFile lifetimeFile = new TemporaryFile(TAG)) {
             try (TemporaryFile eolFile = new TemporaryFile(TAG)) {
@@ -80,16 +84,17 @@
 
                 WearInformation wearInformation = wearInfoProvider.load();
 
-                assertNotNull(wearInformation);
-                assertEquals(40, wearInformation.lifetimeEstimateA);
-                assertEquals(WearInformation.UNKNOWN_LIFETIME_ESTIMATE,
-                        wearInformation.lifetimeEstimateB);
-
-                assertEquals(WearInformation.PRE_EOL_INFO_NORMAL, wearInformation.preEolInfo);
+                assertThat(wearInformation).isNotNull();
+                assertThat(wearInformation.lifetimeEstimateA).isEqualTo(40);
+                assertThat(wearInformation.lifetimeEstimateB)
+                    .isEqualTo(WearInformation.UNKNOWN_LIFETIME_ESTIMATE);
+                assertThat(wearInformation.preEolInfo)
+                    .isEqualTo(WearInformation.PRE_EOL_INFO_NORMAL);
             }
         }
     }
 
+    @Test
     public void testUfsWearInformationProvider() throws Exception {
         try (TemporaryFile lifetimeFile = new TemporaryFile(TAG)) {
             lifetimeFile.write("ufs version: 1.0\n" +
@@ -104,14 +109,15 @@
 
             WearInformation wearInformation = wearInfoProvider.load();
 
-            assertNotNull(wearInformation);
-            assertEquals(90, wearInformation.lifetimeEstimateB);
-            assertEquals(WearInformation.PRE_EOL_INFO_WARNING, wearInformation.preEolInfo);
-            assertEquals(WearInformation.UNKNOWN_LIFETIME_ESTIMATE,
-                    wearInformation.lifetimeEstimateA);
+            assertThat(wearInformation).isNotNull();
+            assertThat(wearInformation.lifetimeEstimateB).isEqualTo(90);
+            assertThat(wearInformation.preEolInfo).isEqualTo(WearInformation.PRE_EOL_INFO_WARNING);
+            assertThat(wearInformation.lifetimeEstimateA)
+                .isEqualTo(WearInformation.UNKNOWN_LIFETIME_ESTIMATE);
         }
     }
 
+    @Test
     public void testHealthServiceWearInformationProvider() throws Exception {
         StorageInfo storageInfo = new StorageInfo();
         storageInfo.eol = WearInformation.PRE_EOL_INFO_NORMAL;
@@ -132,31 +138,38 @@
         }).when(mMockedHal).getStorageInfo(any(getStorageInfoCallback.class));
         WearInformation wearInformation = wearInfoProvider.load();
 
-        assertNotNull(wearInformation);
-        assertEquals(storageInfo.lifetimeA, wearInformation.lifetimeEstimateA);
-        assertEquals(storageInfo.lifetimeB, wearInformation.lifetimeEstimateB);
-        assertEquals(storageInfo.eol, wearInformation.preEolInfo);
+        assertThat(wearInformation).isNotNull();
+        assertThat(wearInformation.lifetimeEstimateA).isEqualTo(storageInfo.lifetimeA);
+        assertThat(wearInformation.lifetimeEstimateB).isEqualTo(storageInfo.lifetimeB);
+        assertThat(wearInformation.preEolInfo).isEqualTo(storageInfo.eol);
     }
 
+    @Test
+    @SuppressWarnings("TruthSelfEquals")
+    // TODO: use EqualsTester to check equality with itself,
+    // Remove @SuppressWarnings("TruthSelfEquals") at other places too
     public void testWearEstimateEquality() {
         WearEstimate wearEstimate1 = new WearEstimate(10, 20);
         WearEstimate wearEstimate2 = new WearEstimate(10, 20);
         WearEstimate wearEstimate3 = new WearEstimate(20, 30);
-        assertEquals(wearEstimate1, wearEstimate1);
-        assertEquals(wearEstimate1, wearEstimate2);
-        assertNotSame(wearEstimate1, wearEstimate3);
+        assertThat(wearEstimate1).isEqualTo(wearEstimate1);
+        assertThat(wearEstimate2).isEqualTo(wearEstimate1);
+        assertThat(wearEstimate1).isNotSameAs(wearEstimate3);
     }
 
+    @Test
     public void testWearEstimateParcel() throws Exception {
         WearEstimate originalWearEstimate = new WearEstimate(10, 20);
         Parcel p = Parcel.obtain();
         originalWearEstimate.writeToParcel(p, 0);
         p.setDataPosition(0);
         WearEstimate newWearEstimate = new WearEstimate(p);
-        assertEquals(originalWearEstimate, newWearEstimate);
+        assertThat(newWearEstimate).isEqualTo(originalWearEstimate);
         p.recycle();
     }
 
+    @Test
+    @SuppressWarnings("TruthSelfEquals")
     public void testWearEstimateChangeEquality() {
         WearEstimateChange wearEstimateChange1 = new WearEstimateChange(
                 new WearEstimate(10, 20),
@@ -170,17 +183,18 @@
             5000L,
             wearEstimateChange1.dateAtChange,
             false);
-        assertEquals(wearEstimateChange1, wearEstimateChange1);
-        assertEquals(wearEstimateChange1, wearEstimateChange2);
+        assertThat(wearEstimateChange1).isEqualTo(wearEstimateChange1);
+        assertThat(wearEstimateChange2).isEqualTo(wearEstimateChange1);
         WearEstimateChange wearEstimateChange3 = new WearEstimateChange(
             new WearEstimate(10, 30),
             new WearEstimate(20, 30),
             3000L,
             Instant.now(),
             true);
-        assertNotSame(wearEstimateChange1, wearEstimateChange3);
+        assertThat(wearEstimateChange1).isNotSameAs(wearEstimateChange3);
     }
 
+    @Test
     public void testWearEstimateChangeParcel() throws Exception {
         WearEstimateChange originalWearEstimateChange = new WearEstimateChange(
                 new WearEstimate(10, 0),
@@ -192,10 +206,11 @@
         originalWearEstimateChange.writeToParcel(p, 0);
         p.setDataPosition(0);
         WearEstimateChange newWearEstimateChange = new WearEstimateChange(p);
-        assertEquals(originalWearEstimateChange, newWearEstimateChange);
+        assertThat(newWearEstimateChange).isEqualTo(originalWearEstimateChange);
         p.recycle();
     }
 
+    @Test
     public void testWearEstimateJson() throws Exception {
         WearEstimate originalWearEstimate = new WearEstimate(20, 0);
         StringWriter stringWriter = new StringWriter(1024);
@@ -204,9 +219,10 @@
         StringReader stringReader = new StringReader(stringWriter.toString());
         JsonReader jsonReader = new JsonReader(stringReader);
         WearEstimate newWearEstimate = new WearEstimate(jsonReader);
-        assertEquals(originalWearEstimate, newWearEstimate);
+        assertThat(newWearEstimate).isEqualTo(originalWearEstimate);
     }
 
+    @Test
     public void testWearEstimateRecordJson() throws Exception {
         try (TemporaryFile temporaryFile = new TemporaryFile(TAG)) {
             WearEstimateRecord originalWearEstimateRecord = new WearEstimateRecord(new WearEstimate(10, 20),
@@ -217,10 +233,12 @@
             JSONObject jsonObject = new JSONObject(
                     new String(Files.readAllBytes(temporaryFile.getPath())));
             WearEstimateRecord newWearEstimateRecord = new WearEstimateRecord(jsonObject);
-            assertEquals(originalWearEstimateRecord, newWearEstimateRecord);
+            assertThat(newWearEstimateRecord).isEqualTo(originalWearEstimateRecord);
         }
     }
 
+    @Test
+    @SuppressWarnings("TruthSelfEquals")
     public void testWearEstimateRecordEquality() throws Exception {
         WearEstimateRecord wearEstimateRecord1 = new WearEstimateRecord(WearEstimate.UNKNOWN_ESTIMATE,
                 new WearEstimate(10, 20), 5000, Instant.ofEpochMilli(2000));
@@ -229,11 +247,12 @@
         WearEstimateRecord wearEstimateRecord3 = new WearEstimateRecord(WearEstimate.UNKNOWN_ESTIMATE,
             new WearEstimate(10, 40), 5000, Instant.ofEpochMilli(1000));
 
-        assertEquals(wearEstimateRecord1, wearEstimateRecord1);
-        assertEquals(wearEstimateRecord1, wearEstimateRecord2);
-        assertNotSame(wearEstimateRecord1, wearEstimateRecord3);
+        assertThat(wearEstimateRecord1).isEqualTo(wearEstimateRecord1);
+        assertThat(wearEstimateRecord2).isEqualTo(wearEstimateRecord1);
+        assertThat(wearEstimateRecord1).isNotSameAs(wearEstimateRecord3);
     }
 
+    @Test
     public void testWearHistoryJson() throws Exception {
         try (TemporaryFile temporaryFile = new TemporaryFile(TAG)) {
             WearEstimateRecord wearEstimateRecord1 = new WearEstimateRecord(
@@ -253,10 +272,12 @@
             JSONObject jsonObject = new JSONObject(
                 new String(Files.readAllBytes(temporaryFile.getPath())));
             WearHistory newWearHistory = new WearHistory(jsonObject);
-            assertEquals(originalWearHistory, newWearHistory);
+            assertThat(newWearHistory).isEqualTo(originalWearHistory);
         }
     }
 
+    @Test
+    @SuppressWarnings("TruthSelfEquals")
     public void testWearHistoryEquality() throws Exception {
         WearEstimateRecord wearEstimateRecord1 = new WearEstimateRecord(
             WearEstimate.UNKNOWN_ESTIMATE,
@@ -281,11 +302,12 @@
         WearHistory wearHistory3 = WearHistory.fromRecords(wearEstimateRecord1,
             wearEstimateRecord2, wearEstimateRecord3, wearEstimateRecord5);
 
-        assertEquals(wearHistory1, wearHistory1);
-        assertEquals(wearHistory1, wearHistory2);
-        assertNotSame(wearHistory1, wearHistory3);
+        assertThat(wearHistory1).isEqualTo(wearHistory1);
+        assertThat(wearHistory2).isEqualTo(wearHistory1);
+        assertThat(wearHistory1).isNotSameAs(wearHistory3);
     }
 
+    @Test
     public void testWearHistoryToChanges() {
         WearEstimateRecord wearEstimateRecord1 = new WearEstimateRecord(
             WearEstimate.UNKNOWN_ESTIMATE,
@@ -302,30 +324,34 @@
 
         List<WearEstimateChange> wearEstimateChanges = wearHistory.toWearEstimateChanges(1);
 
-        assertEquals(3, wearEstimateChanges.size());
+        assertThat(wearEstimateChanges.size()).isEqualTo(3);
         WearEstimateChange unknownToOne = wearEstimateChanges.get(0);
         WearEstimateChange oneToTwo = wearEstimateChanges.get(1);
         WearEstimateChange twoToThree = wearEstimateChanges.get(2);
 
-        assertEquals(unknownToOne.oldEstimate, wearEstimateRecord1.getOldWearEstimate());
-        assertEquals(unknownToOne.newEstimate, wearEstimateRecord1.getNewWearEstimate());
-        assertEquals(unknownToOne.uptimeAtChange, wearEstimateRecord1.getTotalCarServiceUptime());
-        assertEquals(unknownToOne.dateAtChange, wearEstimateRecord1.getUnixTimestamp());
-        assertTrue(unknownToOne.isAcceptableDegradation);
+        assertThat(wearEstimateRecord1.getOldWearEstimate()).isEqualTo(unknownToOne.oldEstimate);
+        assertThat(wearEstimateRecord1.getNewWearEstimate()).isEqualTo(unknownToOne.newEstimate);
+        assertThat(wearEstimateRecord1.getTotalCarServiceUptime())
+            .isEqualTo(unknownToOne.uptimeAtChange);
+        assertThat(wearEstimateRecord1.getUnixTimestamp()).isEqualTo(unknownToOne.dateAtChange);
+        assertThat(unknownToOne.isAcceptableDegradation).isTrue();
 
-        assertEquals(oneToTwo.oldEstimate, wearEstimateRecord2.getOldWearEstimate());
-        assertEquals(oneToTwo.newEstimate, wearEstimateRecord2.getNewWearEstimate());
-        assertEquals(oneToTwo.uptimeAtChange, wearEstimateRecord2.getTotalCarServiceUptime());
-        assertEquals(oneToTwo.dateAtChange, wearEstimateRecord2.getUnixTimestamp());
-        assertTrue(oneToTwo.isAcceptableDegradation);
+        assertThat(wearEstimateRecord2.getOldWearEstimate()).isEqualTo(oneToTwo.oldEstimate);
+        assertThat(wearEstimateRecord2.getNewWearEstimate()).isEqualTo(oneToTwo.newEstimate);
+        assertThat(wearEstimateRecord2.getTotalCarServiceUptime())
+            .isEqualTo(oneToTwo.uptimeAtChange);
+        assertThat(wearEstimateRecord2.getUnixTimestamp()).isEqualTo(oneToTwo.dateAtChange);
+        assertThat(oneToTwo.isAcceptableDegradation).isTrue();
 
-        assertEquals(twoToThree.oldEstimate, wearEstimateRecord3.getOldWearEstimate());
-        assertEquals(twoToThree.newEstimate, wearEstimateRecord3.getNewWearEstimate());
-        assertEquals(twoToThree.uptimeAtChange, wearEstimateRecord3.getTotalCarServiceUptime());
-        assertEquals(twoToThree.dateAtChange, wearEstimateRecord3.getUnixTimestamp());
-        assertFalse(twoToThree.isAcceptableDegradation);
+        assertThat(wearEstimateRecord3.getOldWearEstimate()).isEqualTo(twoToThree.oldEstimate);
+        assertThat(wearEstimateRecord3.getNewWearEstimate()).isEqualTo(twoToThree.newEstimate);
+        assertThat(wearEstimateRecord3.getTotalCarServiceUptime())
+            .isEqualTo(twoToThree.uptimeAtChange);
+        assertThat(wearEstimateRecord3.getUnixTimestamp()).isEqualTo(twoToThree.dateAtChange);
+        assertThat(twoToThree.isAcceptableDegradation).isFalse();
     }
 
+    @Test
     public void testUidIoStatEntry() throws Exception {
         try (TemporaryFile statsFile = new TemporaryFile(TAG)) {
             statsFile.write("0 256797495 181736102 362132480 947167232 0 0 0 0 250 0\n"
@@ -336,41 +362,42 @@
 
             SparseArray<UidIoRecord> entries = statsProvider.load();
 
-            assertNotNull(entries);
-            assertEquals(2, entries.size());
+            assertThat(entries).isNotNull();
+            assertThat(entries.size()).isEqualTo(2);
 
             IoStatsEntry entry = new IoStatsEntry(entries.get(0), 1234);
-            assertNotNull(entry);
-            assertEquals(0, entry.uid);
-            assertEquals(1234, entry.runtimeMillis);
-            assertEquals(256797495, entry.foreground.bytesRead);
-            assertEquals(181736102, entry.foreground.bytesWritten);
-            assertEquals(362132480, entry.foreground.bytesReadFromStorage);
-            assertEquals(947167232, entry.foreground.bytesWrittenToStorage);
-            assertEquals(250, entry.foreground.fsyncCalls);
-            assertEquals(0, entry.background.bytesRead);
-            assertEquals(0, entry.background.bytesWritten);
-            assertEquals(0, entry.background.bytesReadFromStorage);
-            assertEquals(0, entry.background.bytesWrittenToStorage);
-            assertEquals(0, entry.background.fsyncCalls);
+            assertThat(entry).isNotNull();
+            assertThat(entry.uid).isEqualTo(0);
+            assertThat(entry.runtimeMillis).isEqualTo(1234);
+            assertThat(entry.foreground.bytesRead).isEqualTo(256797495);
+            assertThat(entry.foreground.bytesWritten).isEqualTo(181736102);
+            assertThat(entry.foreground.bytesReadFromStorage).isEqualTo(362132480);
+            assertThat(entry.foreground.bytesWrittenToStorage).isEqualTo(947167232);
+            assertThat(entry.foreground.fsyncCalls).isEqualTo(250);
+            assertThat(entry.background.bytesRead).isEqualTo(0);
+            assertThat(entry.background.bytesWritten).isEqualTo(0);
+            assertThat(entry.background.bytesReadFromStorage).isEqualTo(0);
+            assertThat(entry.background.bytesWrittenToStorage).isEqualTo(0);
+            assertThat(entry.background.fsyncCalls).isEqualTo(0);
 
             entry = new IoStatsEntry(entries.get(1006), 4321);
-            assertNotNull(entry);
-            assertEquals(1006, entry.uid);
-            assertEquals(4321, entry.runtimeMillis);
-            assertEquals(489007, entry.foreground.bytesRead);
-            assertEquals(196802, entry.foreground.bytesWritten);
-            assertEquals(0, entry.foreground.bytesReadFromStorage);
-            assertEquals(20480, entry.foreground.bytesWrittenToStorage);
-            assertEquals(1, entry.foreground.fsyncCalls);
-            assertEquals(51474, entry.background.bytesRead);
-            assertEquals(2048, entry.background.bytesWritten);
-            assertEquals(1024, entry.background.bytesReadFromStorage);
-            assertEquals(2048, entry.background.bytesWrittenToStorage);
-            assertEquals(1, entry.background.fsyncCalls);
+            assertThat(entry).isNotNull();
+            assertThat(entry.uid).isEqualTo(1006);
+            assertThat(entry.runtimeMillis).isEqualTo(4321);
+            assertThat(entry.foreground.bytesRead).isEqualTo(489007);
+            assertThat(entry.foreground.bytesWritten).isEqualTo(196802);
+            assertThat(entry.foreground.bytesReadFromStorage).isEqualTo(0);
+            assertThat(entry.foreground.bytesWrittenToStorage).isEqualTo(20480);
+            assertThat(entry.foreground.fsyncCalls).isEqualTo(1);
+            assertThat(entry.background.bytesRead).isEqualTo(51474);
+            assertThat(entry.background.bytesWritten).isEqualTo(2048);
+            assertThat(entry.background.bytesReadFromStorage).isEqualTo(1024);
+            assertThat(entry.background.bytesWrittenToStorage).isEqualTo(2048);
+            assertThat(entry.background.fsyncCalls).isEqualTo(1);
         }
     }
 
+    @Test
     public void testUidIoStatEntryMissingFields() throws Exception {
         try (TemporaryFile statsFile = new TemporaryFile(TAG)) {
             statsFile.write("0 256797495 181736102 362132480 947167232 0 0 0 0 250 0\n"
@@ -381,10 +408,11 @@
 
             SparseArray<UidIoRecord> entries = statsProvider.load();
 
-            assertNull(entries);
+            assertThat(entries).isNull();
         }
     }
 
+    @Test
     public void testUidIoStatEntryNonNumericFields() throws Exception {
         try (TemporaryFile statsFile = new TemporaryFile(TAG)) {
             statsFile.write("0 256797495 181736102 362132480 947167232 0 0 0 0 250 0\n"
@@ -395,10 +423,12 @@
 
             SparseArray<UidIoRecord> entries = statsProvider.load();
 
-            assertNull(entries);
+            assertThat(entries).isNull();
         }
     }
 
+    @Test
+    @SuppressWarnings("TruthSelfEquals")
     public void testUidIoStatEntryEquality() throws Exception {
         IoStatsEntry statEntry1 = new IoStatsEntry(10, 1234,
             new IoStatsEntry.Metrics(10, 20, 30, 40, 50),
@@ -416,13 +446,14 @@
             new IoStatsEntry.Metrics(10, 20, 30, 40, 0),
             new IoStatsEntry.Metrics(100, 200, 300, 400, 500));
 
-        assertEquals(statEntry1, statEntry1);
-        assertEquals(statEntry1, statEntry2);
-        assertNotSame(statEntry1, statEntry3);
-        assertNotSame(statEntry1, statEntry4);
-        assertNotSame(statEntry1, statEntry5);
+        assertThat(statEntry1).isEqualTo(statEntry1);
+        assertThat(statEntry2).isEqualTo(statEntry1);
+        assertThat(statEntry1).isNotSameAs(statEntry3);
+        assertThat(statEntry1).isNotSameAs(statEntry4);
+        assertThat(statEntry1).isNotSameAs(statEntry5);
     }
 
+    @Test
     public void testUidIoStatEntryParcel() throws Exception {
         IoStatsEntry statEntry = new IoStatsEntry(10, 5000,
             new IoStatsEntry.Metrics(10, 20, 30, 40, 50),
@@ -431,9 +462,10 @@
         statEntry.writeToParcel(p, 0);
         p.setDataPosition(0);
         IoStatsEntry other = new IoStatsEntry(p);
-        assertEquals(other, statEntry);
+        assertThat(statEntry).isEqualTo(other);
     }
 
+    @Test
     public void testUidIoStatEntryJson() throws Exception {
         try (TemporaryFile temporaryFile = new TemporaryFile(TAG)) {
             IoStatsEntry statEntry = new IoStatsEntry(10, 1200,
@@ -445,11 +477,12 @@
             JSONObject jsonObject = new JSONObject(
                 new String(Files.readAllBytes(temporaryFile.getPath())));
             IoStatsEntry other = new IoStatsEntry(jsonObject);
-            assertEquals(statEntry, other);
+            assertThat(other).isEqualTo(statEntry);
         }
     }
 
 
+    @Test
     public void testUidIoStatEntryDelta() throws Exception {
         IoStatsEntry statEntry1 = new IoStatsEntry(10, 1000,
             new IoStatsEntry.Metrics(10, 20, 30, 40, 50),
@@ -465,21 +498,21 @@
 
 
         IoStatsEntry delta21 = statEntry2.delta(statEntry1);
-        assertNotNull(delta21);
-        assertEquals(statEntry1.uid, delta21.uid);
+        assertThat(delta21).isNotNull();
+        assertThat(delta21.uid).isEqualTo(statEntry1.uid);
 
-        assertEquals(1000, delta21.runtimeMillis);
-        assertEquals(100, delta21.foreground.bytesRead);
-        assertEquals(100, delta21.foreground.bytesWritten);
-        assertEquals(100, delta21.foreground.bytesReadFromStorage);
-        assertEquals(100, delta21.foreground.bytesWrittenToStorage);
-        assertEquals(100, delta21.foreground.fsyncCalls);
+        assertThat(delta21.runtimeMillis).isEqualTo(1000);
+        assertThat(delta21.foreground.bytesRead).isEqualTo(100);
+        assertThat(delta21.foreground.bytesWritten).isEqualTo(100);
+        assertThat(delta21.foreground.bytesReadFromStorage).isEqualTo(100);
+        assertThat(delta21.foreground.bytesWrittenToStorage).isEqualTo(100);
+        assertThat(delta21.foreground.fsyncCalls).isEqualTo(100);
 
-        assertEquals(200, delta21.background.bytesRead);
-        assertEquals(300, delta21.background.bytesWritten);
-        assertEquals(400, delta21.background.bytesReadFromStorage);
-        assertEquals(410, delta21.background.bytesWrittenToStorage);
-        assertEquals(10, delta21.background.fsyncCalls);
+        assertThat(delta21.background.bytesRead).isEqualTo(200);
+        assertThat(delta21.background.bytesWritten).isEqualTo(300);
+        assertThat(delta21.background.bytesReadFromStorage).isEqualTo(400);
+        assertThat(delta21.background.bytesWrittenToStorage).isEqualTo(410);
+        assertThat(delta21.background.fsyncCalls).isEqualTo(10);
 
         try {
             IoStatsEntry delta31 = statEntry3.delta(statEntry1);
@@ -489,6 +522,7 @@
         }
     }
 
+    @Test
     public void testUidIoStatsRecordDelta() throws Exception {
         IoStatsEntry statEntry = new IoStatsEntry(10, 1000,
             new IoStatsEntry.Metrics(10, 20, 30, 40, 50),
@@ -500,20 +534,20 @@
 
         UidIoRecord delta = statRecord.delta(statEntry);
 
-        assertNotNull(delta);
-        assertEquals(statRecord.uid, delta.uid);
+        assertThat(delta).isNotNull();
+        assertThat(delta.uid).isEqualTo(statRecord.uid);
 
-        assertEquals(10, delta.foreground_rchar);
-        assertEquals(0, delta.foreground_wchar);
-        assertEquals(0, delta.foreground_read_bytes);
-        assertEquals(10, delta.foreground_write_bytes);
-        assertEquals(20, delta.foreground_fsync);
+        assertThat(delta.foreground_rchar).isEqualTo(10);
+        assertThat(delta.foreground_wchar).isEqualTo(0);
+        assertThat(delta.foreground_read_bytes).isEqualTo(0);
+        assertThat(delta.foreground_write_bytes).isEqualTo(10);
+        assertThat(delta.foreground_fsync).isEqualTo(20);
 
-        assertEquals(20, delta.background_rchar);
-        assertEquals(0, delta.background_wchar);
-        assertEquals(0, delta.background_read_bytes);
-        assertEquals(10, delta.background_write_bytes);
-        assertEquals(10, delta.background_fsync);
+        assertThat(delta.background_rchar).isEqualTo(20);
+        assertThat(delta.background_wchar).isEqualTo(0);
+        assertThat(delta.background_read_bytes).isEqualTo(0);
+        assertThat(delta.background_write_bytes).isEqualTo(10);
+        assertThat(delta.background_fsync).isEqualTo(10);
 
         statRecord = new UidIoRecord(30,
             20, 20, 30, 50, 70,
@@ -527,6 +561,8 @@
         }
     }
 
+    @Test
+    @SuppressWarnings("TruthSelfEquals")
     public void testUidIoStatsDelta() throws Exception {
         IoStatsEntry entry10 = new IoStatsEntry(10, 1000,
             new IoStatsEntry.Metrics(10, 20, 30, 40, 50),
@@ -555,12 +591,13 @@
         IoStats delta3 = new IoStats(statsEntries2, 3000);
         IoStats delta4 = new IoStats(statsEntries1, 5000);
 
-        assertEquals(delta1, delta1);
-        assertEquals(delta1, delta2);
-        assertNotSame(delta1, delta3);
-        assertNotSame(delta3, delta4);
+        assertThat(delta1).isEqualTo(delta1);
+        assertThat(delta2).isEqualTo(delta1);
+        assertThat(delta1).isNotSameAs(delta3);
+        assertThat(delta3).isNotSameAs(delta4);
     }
 
+    @Test
     public void testUidIoStatsTotals() throws Exception {
         IoStatsEntry entry10 = new IoStatsEntry(10, 1000,
             new IoStatsEntry.Metrics(20, 0, 10, 0, 0),
@@ -581,26 +618,27 @@
         IoStatsEntry.Metrics backgroundTotals = delta.getBackgroundTotals();
         IoStatsEntry.Metrics overallTotals = delta.getTotals();
 
-        assertEquals(120, foregroundTotals.bytesRead);
-        assertEquals(200, foregroundTotals.bytesWritten);
-        assertEquals(60, foregroundTotals.bytesReadFromStorage);
-        assertEquals(200, foregroundTotals.bytesWrittenToStorage);
-        assertEquals(1, foregroundTotals.fsyncCalls);
+        assertThat(foregroundTotals.bytesRead).isEqualTo(120);
+        assertThat(foregroundTotals.bytesWritten).isEqualTo(200);
+        assertThat(foregroundTotals.bytesReadFromStorage).isEqualTo(60);
+        assertThat(foregroundTotals.bytesWrittenToStorage).isEqualTo(200);
+        assertThat(foregroundTotals.fsyncCalls).isEqualTo(1);
 
 
-        assertEquals(10, backgroundTotals.bytesRead);
-        assertEquals(80, backgroundTotals.bytesWritten);
-        assertEquals(10, backgroundTotals.bytesReadFromStorage);
-        assertEquals(20, backgroundTotals.bytesWrittenToStorage);
-        assertEquals(3, backgroundTotals.fsyncCalls);
+        assertThat(backgroundTotals.bytesRead).isEqualTo(10);
+        assertThat(backgroundTotals.bytesWritten).isEqualTo(80);
+        assertThat(backgroundTotals.bytesReadFromStorage).isEqualTo(10);
+        assertThat(backgroundTotals.bytesWrittenToStorage).isEqualTo(20);
+        assertThat(backgroundTotals.fsyncCalls).isEqualTo(3);
 
-        assertEquals(130, overallTotals.bytesRead);
-        assertEquals(280, overallTotals.bytesWritten);
-        assertEquals(70, overallTotals.bytesReadFromStorage);
-        assertEquals(220, overallTotals.bytesWrittenToStorage);
-        assertEquals(4, overallTotals.fsyncCalls);
+        assertThat(overallTotals.bytesRead).isEqualTo(130);
+        assertThat(overallTotals.bytesWritten).isEqualTo(280);
+        assertThat(overallTotals.bytesReadFromStorage).isEqualTo(70);
+        assertThat(overallTotals.bytesWrittenToStorage).isEqualTo(220);
+        assertThat(overallTotals.fsyncCalls).isEqualTo(4);
     }
 
+    @Test
     public void testUidIoStatsDeltaParcel() throws Exception {
         IoStatsEntry entry10 = new IoStatsEntry(10, 1000,
             new IoStatsEntry.Metrics(10, 20, 30, 40, 50),
@@ -623,13 +661,14 @@
 
         IoStats parceledStatsDelta = new IoStats(p);
 
-        assertEquals(statsDelta.getTimestamp(), parceledStatsDelta.getTimestamp());
+        assertThat(parceledStatsDelta.getTimestamp()).isEqualTo(statsDelta.getTimestamp());
 
         assertEquals(2, parceledStatsDelta.getStats().stream()
                 .filter(e -> e.equals(entry10) || e.equals(entry20))
                 .count());
     }
 
+    @Test
     public void testUidIoStatsDeltaJson() throws Exception {
         try (TemporaryFile temporaryFile = new TemporaryFile(TAG)) {
             IoStatsEntry entry10 = new IoStatsEntry(10, 1000,
@@ -652,10 +691,11 @@
             JSONObject jsonObject = new JSONObject(
                 new String(Files.readAllBytes(temporaryFile.getPath())));
             IoStats other = new IoStats(jsonObject);
-            assertEquals(statsDelta, other);
+            assertThat(other).isEqualTo(statsDelta);
         }
     }
 
+    @Test
     public void testLifetimeWriteInfo() throws Exception {
         try (TemporaryDirectory temporaryDirectory = new TemporaryDirectory(TAG)) {
             try (TemporaryDirectory ext4 = temporaryDirectory.getSubdirectory("ext4");
@@ -683,8 +723,8 @@
 
                     LifetimeWriteInfo[] writeInfos = sysfsLifetimeWriteInfoProvider.load();
 
-                    assertNotNull(writeInfos);
-                    assertEquals(3, writeInfos.length);
+                    assertThat(writeInfos).isNotNull();
+                    assertThat(writeInfos.length).isEqualTo(3);
                     assertTrue(Arrays.stream(writeInfos).anyMatch(
                             li -> li.equals(expected_ext4_part1)));
                     assertTrue(Arrays.stream(writeInfos).anyMatch(
@@ -696,6 +736,8 @@
         }
     }
 
+    @Test
+    @SuppressWarnings("TruthSelfEquals")
     public void testLifetimeWriteInfoEquality() throws Exception {
         LifetimeWriteInfo writeInfo = new LifetimeWriteInfo("part1", "ext4", 123);
         LifetimeWriteInfo writeInfoEq = new LifetimeWriteInfo("part1", "ext4", 123);
@@ -704,13 +746,14 @@
         LifetimeWriteInfo writeInfoNeq2 = new LifetimeWriteInfo("part1", "f2fs", 123);
         LifetimeWriteInfo writeInfoNeq3 = new LifetimeWriteInfo("part1", "ext4", 100);
 
-        assertEquals(writeInfo, writeInfo);
-        assertEquals(writeInfo, writeInfoEq);
-        assertNotSame(writeInfo, writeInfoNeq1);
-        assertNotSame(writeInfo, writeInfoNeq2);
-        assertNotSame(writeInfo, writeInfoNeq3);
+        assertThat(writeInfo).isEqualTo(writeInfo);
+        assertThat(writeInfoEq).isEqualTo(writeInfo);
+        assertThat(writeInfo).isNotSameAs(writeInfoNeq1);
+        assertThat(writeInfo).isNotSameAs(writeInfoNeq2);
+        assertThat(writeInfo).isNotSameAs(writeInfoNeq3);
     }
 
+    @Test
     public void testLifetimeWriteInfoParcel() throws Exception {
         LifetimeWriteInfo lifetimeWriteInfo = new LifetimeWriteInfo("part1", "ext4", 1024);
 
@@ -720,9 +763,10 @@
 
         LifetimeWriteInfo parceled = new LifetimeWriteInfo(p);
 
-        assertEquals(lifetimeWriteInfo, parceled);
+        assertThat(parceled).isEqualTo(lifetimeWriteInfo);
     }
 
+    @Test
     public void testLifetimeWriteInfoJson() throws Exception {
         try (TemporaryFile temporaryFile = new TemporaryFile(TAG)) {
             LifetimeWriteInfo lifetimeWriteInfo = new LifetimeWriteInfo("part1", "ext4", 1024);
@@ -733,7 +777,7 @@
             JSONObject jsonObject = new JSONObject(
                 new String(Files.readAllBytes(temporaryFile.getPath())));
             LifetimeWriteInfo other = new LifetimeWriteInfo(jsonObject);
-            assertEquals(lifetimeWriteInfo, other);
+            assertThat(other).isEqualTo(lifetimeWriteInfo);
         }
     }
 }
diff --git a/tests/carservice_unit_test/src/com/android/car/trust/BLEMessagePayloadStreamTest.java b/tests/carservice_unit_test/src/com/android/car/trust/BLEMessagePayloadStreamTest.java
index a99b1d7..31e8ca2 100644
--- a/tests/carservice_unit_test/src/com/android/car/trust/BLEMessagePayloadStreamTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/trust/BLEMessagePayloadStreamTest.java
@@ -18,14 +18,13 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
-import androidx.test.runner.AndroidJUnit4;
-
 import com.android.car.BLEStreamProtos.BLEMessageProto.BLEMessage;
 import com.android.car.BLEStreamProtos.BLEOperationProto.OperationType;
 
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
 
 import java.io.IOException;
 import java.util.List;
@@ -36,7 +35,7 @@
  * <p>Run:
  * {@code atest BLEMessagePayloadStreamTest}
  */
-@RunWith(AndroidJUnit4.class)
+@RunWith(JUnit4.class)
 public class BLEMessagePayloadStreamTest {
     private static final boolean IS_MESSAGE_ENCRYPTED = false;
     private static final OperationType OPERATION_TYPE = OperationType.CLIENT_MESSAGE;
diff --git a/tests/carservice_unit_test/src/com/android/car/trust/BleMessageStreamV1Test.java b/tests/carservice_unit_test/src/com/android/car/trust/BleMessageStreamV1Test.java
new file mode 100644
index 0000000..0483616
--- /dev/null
+++ b/tests/carservice_unit_test/src/com/android/car/trust/BleMessageStreamV1Test.java
@@ -0,0 +1,339 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.trust;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Matchers.anyLong;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.os.Handler;
+
+import com.android.car.BLEStreamProtos.BLEMessageProto.BLEMessage;
+import com.android.car.BLEStreamProtos.BLEOperationProto.OperationType;
+import com.android.car.protobuf.InvalidProtocolBufferException;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+import java.util.UUID;
+
+/**
+ * Unit test for the {@link BleMessageStreamV1}.
+ *
+ * <p>Run:
+ * {@code atest CarServiceUnitTest:BleMessageStreamV1Test}
+ */
+@RunWith(MockitoJUnitRunner.class)
+public class BleMessageStreamV1Test {
+    private static final String ADDRESS_MOCK = "00:11:22:33:AA:BB";
+
+    // The UUID values here are arbitrary.
+    private static final UUID WRITE_UUID = UUID.fromString("9a138a69-7c29-400f-9e71-fc29516f9f8b");
+    private static final UUID READ_UUID = UUID.fromString("3e344860-e688-4cce-8411-16161b61ad57");
+
+    private BleMessageStreamV1 mBleMessageStream;
+    private BluetoothDevice mBluetoothDevice;
+
+    @Mock BlePeripheralManager mBlePeripheralManager;
+    @Mock BleMessageStreamCallback mCallbackMock;
+    @Mock Handler mHandlerMock;
+    @Mock BluetoothGattCharacteristic mWriteCharacteristicMock;
+    @Mock BluetoothGattCharacteristic mReadCharacteristicMock;
+
+    @Before
+    public void setUp() {
+        // Mock so that handler will run anything that is posted to it.
+        when(mHandlerMock.post(any(Runnable.class))).thenAnswer(invocation -> {
+            invocation.<Runnable>getArgument(0).run();
+            return null;
+        });
+
+        // Ensure the mock characteristics return valid UUIDs.
+        when(mWriteCharacteristicMock.getUuid()).thenReturn(WRITE_UUID);
+        when(mReadCharacteristicMock.getUuid()).thenReturn(READ_UUID);
+
+        mBluetoothDevice = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(ADDRESS_MOCK);
+        mBleMessageStream = new BleMessageStreamV1(
+                mHandlerMock, mBlePeripheralManager, mBluetoothDevice, mWriteCharacteristicMock,
+                mReadCharacteristicMock);
+        mBleMessageStream.registerCallback(mCallbackMock);
+    }
+
+    @Test
+    public void writeMessage_noChunkingRequired_sendsCorrectMessage() {
+        // Ensure that there is enough space to fit the message.
+        mBleMessageStream.setMaxWriteSize(512);
+
+        byte[] message = "message".getBytes();
+        boolean isPayloadEncrypted = true;
+        OperationType operationType = OperationType.CLIENT_MESSAGE;
+
+        mBleMessageStream.writeMessage(message, operationType, isPayloadEncrypted);
+
+        BLEMessage expectedMessage = BLEMessageV1Factory.makeBLEMessage(
+                message, operationType, isPayloadEncrypted);
+
+        // Verify that the message was written.
+        verify(mWriteCharacteristicMock).setValue(expectedMessage.toByteArray());
+
+        // Verify that there is also a notification of the characteristic change.
+        verify(mBlePeripheralManager).notifyCharacteristicChanged(mBluetoothDevice,
+                mWriteCharacteristicMock, false);
+    }
+
+    @Test
+    public void writeMessage_chunkingRequired_sendsCorrectMessage()
+            throws InvalidProtocolBufferException, IOException {
+        boolean isPayloadEncrypted = true;
+        OperationType operationType = OperationType.CLIENT_MESSAGE;
+
+        int maxSize = 20;
+        int requiredWrites = 9;
+        byte[] message = makeMessage(requiredWrites * maxSize);
+        int headerSize = BLEMessageV1Factory.getProtoHeaderSize(
+                operationType, message.length, isPayloadEncrypted);
+
+        // Make sure the payload can't fit into one chunk.
+        mBleMessageStream.setMaxWriteSize(maxSize + headerSize);
+        mBleMessageStream.writeMessage(message, operationType, isPayloadEncrypted);
+
+        // Each part of the message requires an ACK except the last one.
+        int numOfAcks = requiredWrites - 1;
+
+        for (int i = 0; i < numOfAcks; i++) {
+            mBleMessageStream.onCharacteristicWrite(
+                    mBluetoothDevice,
+                    mReadCharacteristicMock,
+                    BLEMessageV1Factory.makeAcknowledgementMessage().toByteArray());
+        }
+
+        // Each ACK should trigger a canceling of the retry runnable.
+        verify(mHandlerMock, times(numOfAcks)).removeCallbacks(any(Runnable.class));
+
+        ArgumentCaptor<byte[]> messageCaptor = ArgumentCaptor.forClass(byte[].class);
+
+        verify(mWriteCharacteristicMock, times(requiredWrites)).setValue(
+                messageCaptor.capture());
+        verify(mBlePeripheralManager, times(requiredWrites))
+                .notifyCharacteristicChanged(mBluetoothDevice,
+                        mWriteCharacteristicMock, false);
+
+        List<byte[]> writtenBytes = messageCaptor.getAllValues();
+        ByteArrayOutputStream reassembledMessageStream = new ByteArrayOutputStream();
+
+        // Verify the packet numbers.
+        for (int i = 0; i < writtenBytes.size(); i++) {
+            BLEMessage bleMessage = BLEMessage.parseFrom(writtenBytes.get(i));
+
+            assertThat(bleMessage.getPacketNumber()).isEqualTo(i + 1);
+            assertThat(bleMessage.getTotalPackets()).isEqualTo(writtenBytes.size());
+            assertThat(bleMessage.getIsPayloadEncrypted()).isTrue();
+
+            reassembledMessageStream.write(bleMessage.getPayload().toByteArray());
+        }
+
+        // Verify the reassembled message.
+        assertThat(reassembledMessageStream.toByteArray()).isEqualTo(message);
+    }
+
+    @Test
+    public void writeMessage_chunkingRequired_retriesSamePayloadIfNoAckReceived()
+            throws InvalidProtocolBufferException, IOException {
+        // Execute delayed runnables immediately to simulate an ACK timeout.
+        mockHandlerToExecuteDelayedRunnablesImmediately();
+
+        boolean isPayloadEncrypted = true;
+        OperationType operationType = OperationType.CLIENT_MESSAGE;
+
+        int maxSize = 20;
+        int requiredWrites = 9;
+        byte[] message = makeMessage(requiredWrites * maxSize);
+        int headerSize = BLEMessageV1Factory.getProtoHeaderSize(
+                operationType, message.length, isPayloadEncrypted);
+
+        // Make sure the payload can't fit into one chunk.
+        mBleMessageStream.setMaxWriteSize(maxSize + headerSize);
+        mBleMessageStream.writeMessage(message, operationType, isPayloadEncrypted);
+
+        ArgumentCaptor<byte[]> messageCaptor = ArgumentCaptor.forClass(byte[].class);
+
+        // Because there is no ACK, the write should be retried up to the limit.
+        verify(mWriteCharacteristicMock, times(BleMessageStreamV1.BLE_MESSAGE_RETRY_LIMIT))
+                .setValue(messageCaptor.capture());
+        verify(mBlePeripheralManager, times(BleMessageStreamV1.BLE_MESSAGE_RETRY_LIMIT))
+                .notifyCharacteristicChanged(mBluetoothDevice, mWriteCharacteristicMock, false);
+
+        List<byte[]> writtenBytes = messageCaptor.getAllValues();
+        List<byte[]> writtenPayloads = new ArrayList<>();
+
+        for (int i = 0; i < writtenBytes.size(); i++) {
+            BLEMessage bleMessage = BLEMessage.parseFrom(writtenBytes.get(i));
+
+            // The same packet should be written.
+            assertThat(bleMessage.getPacketNumber()).isEqualTo(1);
+            assertThat(bleMessage.getTotalPackets()).isEqualTo(requiredWrites);
+            assertThat(bleMessage.getIsPayloadEncrypted()).isTrue();
+
+            writtenPayloads.add(bleMessage.getPayload().toByteArray());
+        }
+
+        // Verify that the same payload is being written.
+        for (byte[] payload : writtenPayloads) {
+            assertThat(payload).isEqualTo(writtenPayloads.get(0));
+        }
+    }
+
+    @Test
+    public void writeMessage_chunkingRequired_notifiesCallbackIfNoAck()
+            throws InvalidProtocolBufferException, IOException {
+        // Execute delayed runnables immediately to simulate an ACK timeout.
+        mockHandlerToExecuteDelayedRunnablesImmediately();
+
+        boolean isPayloadEncrypted = true;
+        OperationType operationType = OperationType.CLIENT_MESSAGE;
+
+        int maxSize = 20;
+        int requiredWrites = 9;
+        byte[] message = makeMessage(requiredWrites * maxSize);
+        int headerSize = BLEMessageV1Factory.getProtoHeaderSize(
+                operationType, message.length, isPayloadEncrypted);
+
+        // Make sure the payload can't fit into one chunk.
+        mBleMessageStream.setMaxWriteSize(maxSize + headerSize);
+        mBleMessageStream.writeMessage(message, operationType, isPayloadEncrypted);
+
+        // Because there is no ACK, the write should be retried up to the limit.
+        verify(mWriteCharacteristicMock, times(BleMessageStreamV1.BLE_MESSAGE_RETRY_LIMIT))
+                .setValue(any(byte[].class));
+        verify(mBlePeripheralManager, times(BleMessageStreamV1.BLE_MESSAGE_RETRY_LIMIT))
+                .notifyCharacteristicChanged(mBluetoothDevice, mWriteCharacteristicMock, false);
+
+        // But the callback should only be notified once.
+        verify(mCallbackMock).onWriteMessageError();
+    }
+
+    @Test
+    public void processClientMessage_noChunking_notifiesCallbackForCompleteMessage() {
+        byte[] payload = "message".getBytes();
+
+        // This client message is a complete message, meaning it is part 1 of 1.
+        BLEMessage clientMessage = BLEMessageV1Factory.makeBLEMessage(
+                payload, OperationType.CLIENT_MESSAGE, /* isPayloadEncrypted= */ true);
+
+        mBleMessageStream.onCharacteristicWrite(
+                mBluetoothDevice,
+                mReadCharacteristicMock,
+                clientMessage.toByteArray());
+
+        // The callback should be notified with only the payload.
+        verify(mCallbackMock).onMessageReceived(payload, READ_UUID);
+
+        // And the callback should only be notified once.
+        verify(mCallbackMock).onMessageReceived(any(byte[].class), any(UUID.class));
+    }
+
+    @Test
+    public void processClientMessage_chunkingRequired_notifiesCallbackForCompleteMessage() {
+        // The length here is arbitrary.
+        int payloadLength = 1024;
+        byte[] payload = makeMessage(payloadLength);
+
+        // Ensure the max size is smaller than the payload length.
+        int maxSize = 50;
+        List<BLEMessage> clientMessages = BLEMessageV1Factory.makeBLEMessages(
+                payload, OperationType.CLIENT_MESSAGE, maxSize, /* isPayloadEncrypted= */ true);
+
+        for (BLEMessage message : clientMessages) {
+            mBleMessageStream.onCharacteristicWrite(
+                    mBluetoothDevice,
+                    mReadCharacteristicMock,
+                    message.toByteArray());
+        }
+
+        // The callback should be notified with only the payload.
+        verify(mCallbackMock).onMessageReceived(payload, READ_UUID);
+
+        // And the callback should only be notified once.
+        verify(mCallbackMock).onMessageReceived(any(byte[].class), any(UUID.class));
+    }
+
+    @Test
+    public void processClientMessage_chunkingRequired_sendsACKForEachMessagePart() {
+        // The length here is arbitrary.
+        int payloadLength = 1024;
+        byte[] payload = makeMessage(payloadLength);
+
+        // Ensure the max size is smaller than the payload length.
+        int maxSize = 50;
+        List<BLEMessage> clientMessages = BLEMessageV1Factory.makeBLEMessages(
+                payload, OperationType.CLIENT_MESSAGE, maxSize, /* isPayloadEncrypted= */ true);
+
+        for (BLEMessage message : clientMessages) {
+            mBleMessageStream.onCharacteristicWrite(
+                    mBluetoothDevice,
+                    mReadCharacteristicMock,
+                    message.toByteArray());
+        }
+
+        ArgumentCaptor<byte[]> messageCaptor = ArgumentCaptor.forClass(byte[].class);
+
+        // An ACK should be sent for each message received, except the last one.
+        verify(mWriteCharacteristicMock, times(clientMessages.size() - 1))
+                .setValue(messageCaptor.capture());
+        verify(mBlePeripheralManager, times(clientMessages.size() - 1))
+                .notifyCharacteristicChanged(mBluetoothDevice, mWriteCharacteristicMock, false);
+
+        List<byte[]> writtenValues = messageCaptor.getAllValues();
+        byte[] ackMessageBytes = BLEMessageV1Factory.makeAcknowledgementMessage().toByteArray();
+
+        // Now verify that each byte was an ACK message.
+        for (byte[] writtenValue : writtenValues) {
+            assertThat(writtenValue).isEqualTo(ackMessageBytes);
+        }
+    }
+
+    private void mockHandlerToExecuteDelayedRunnablesImmediately() {
+        when(mHandlerMock.postDelayed(any(Runnable.class), anyLong())).thenAnswer(invocation -> {
+            invocation.<Runnable>getArgument(0).run();
+            return null;
+        });
+    }
+
+    /** Returns a random message of the specified length. */
+    private byte[] makeMessage(int length) {
+        byte[] message = new byte[length];
+        new Random().nextBytes(message);
+
+        return message;
+    }
+}
diff --git a/tests/carservice_unit_test/src/com/android/car/trust/CarTrustAgentEnrollmentServiceTest.java b/tests/carservice_unit_test/src/com/android/car/trust/CarTrustAgentEnrollmentServiceTest.java
index 3f21ca6..8a41e32 100644
--- a/tests/carservice_unit_test/src/com/android/car/trust/CarTrustAgentEnrollmentServiceTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/trust/CarTrustAgentEnrollmentServiceTest.java
@@ -19,46 +19,50 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothDevice;
+import android.bluetooth.le.AdvertiseCallback;
 import android.car.encryptionrunner.DummyEncryptionRunner;
 import android.car.encryptionrunner.EncryptionRunnerFactory;
 import android.car.encryptionrunner.HandshakeMessage;
 import android.car.trust.TrustedDeviceInfo;
-import android.car.userlib.CarUserManagerHelper;
 import android.content.Context;
 import android.content.SharedPreferences;
 import android.os.RemoteException;
+import android.os.UserHandle;
 
 import androidx.test.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.filters.FlakyTest;
 
 import com.android.car.Utils;
+import com.android.car.trust.CarTrustAgentBleManager.SendMessageCallback;
 
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
+import org.mockito.junit.MockitoJUnitRunner;
 
 import java.util.UUID;
 
 /**
  * Unit test for {@link CarTrustAgentEnrollmentService} and {@link CarTrustedDeviceService}.
  */
-@RunWith(AndroidJUnit4.class)
+@RunWith(MockitoJUnitRunner.class)
 public class CarTrustAgentEnrollmentServiceTest {
 
     private static final long TEST_HANDLE1 = 1L;
     private static final long TEST_HANDLE2 = 2L;
+    private static final String DEVICE_ID = "device_id";
+    private static final String KEY = "key";
     // Random uuid for test
     private static final UUID TEST_ID1 = UUID.fromString("9a138a69-7c29-400f-9e71-fc29516f9f8b");
-    private static final UUID TEST_ID2 = UUID.fromString("3e344860-e688-4cce-8411-16161b61ad57");
     private static final String TEST_TOKEN = "test_escrow_token";
     private static final String ADDRESS = "00:11:22:33:AA:BB";
     private static final String DEFAULT_NAME = "My Device";
@@ -70,6 +74,7 @@
     private Context mContext;
     private CarTrustedDeviceService mCarTrustedDeviceService;
     private CarTrustAgentEnrollmentService mCarTrustAgentEnrollmentService;
+    private CarCompanionDeviceStorage mCarCompanionDeviceStorage;
     private BluetoothDevice mBluetoothDevice;
     private int mUserId;
     @Mock
@@ -93,17 +98,16 @@
 
     @Before
     public void setUp() throws RemoteException {
-        MockitoAnnotations.initMocks(this);
-
         mBluetoothDevice = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(ADDRESS);
         mContext = InstrumentationRegistry.getTargetContext();
         mCarTrustedDeviceService = new CarTrustedDeviceService(mContext);
         mCarTrustAgentEnrollmentService = new CarTrustAgentEnrollmentService(mContext,
                 mCarTrustedDeviceService, mMockCarTrustAgentBleManager);
+        mCarCompanionDeviceStorage = new CarCompanionDeviceStorage(mContext);
         mCarTrustedDeviceService.init();
         mCarTrustAgentEnrollmentService.init();
         mCarTrustAgentEnrollmentService.setEnrollmentRequestDelegate(mEnrollDelegate);
-        mUserId = new CarUserManagerHelper(mContext).getCurrentProcessUserId();
+        mUserId = UserHandle.myUserId();
         mCarTrustAgentEnrollmentService.onRemoteDeviceConnected(mBluetoothDevice);
     }
 
@@ -115,10 +119,12 @@
     }
 
     @Test
+    @FlakyTest
     public void testDisableEnrollment_startEnrollmentAdvertisingFail() {
         mCarTrustAgentEnrollmentService.setTrustedDeviceEnrollmentEnabled(false);
         mCarTrustAgentEnrollmentService.startEnrollmentAdvertising();
-        verify(mMockCarTrustAgentBleManager, never()).startEnrollmentAdvertising();
+        verify(mMockCarTrustAgentBleManager, never()).startEnrollmentAdvertising(anyString(),
+                any(AdvertiseCallback.class));
     }
 
     @Test
@@ -129,35 +135,39 @@
                 mCarTrustAgentEnrollmentService.ENROLLMENT_STATE_NONE);
         // Have received device unique id.
         UUID uuid = UUID.randomUUID();
-        mCarTrustAgentEnrollmentService.onEnrollmentDataReceived(Utils.uuidToBytes(uuid));
+        mCarTrustAgentEnrollmentService.onDataReceived(Utils.uuidToBytes(uuid));
         assertThat(mCarTrustAgentEnrollmentService.mEnrollmentState).isEqualTo(
                 mCarTrustAgentEnrollmentService.ENROLLMENT_STATE_UNIQUE_ID);
         assertThat(mCarTrustAgentEnrollmentService.mEncryptionState).isEqualTo(
                 HandshakeMessage.HandshakeState.UNKNOWN);
         // send device unique id
-        verify(mMockCarTrustAgentBleManager).sendEnrollmentMessage(eq(mBluetoothDevice),
-                eq(Utils.uuidToBytes(mCarTrustedDeviceService.getUniqueId())), any(), eq(false));
+        verify(mMockCarTrustAgentBleManager).sendMessage(
+                eq(Utils.uuidToBytes(mCarCompanionDeviceStorage.getUniqueId())), any(), eq(false),
+                any(SendMessageCallback.class));
 
         // Have received handshake request.
-        mCarTrustAgentEnrollmentService.onEnrollmentDataReceived(
+        mCarTrustAgentEnrollmentService.onDataReceived(
                 DummyEncryptionRunner.INIT.getBytes());
         assertThat(mCarTrustAgentEnrollmentService.mEncryptionState).isEqualTo(
                 HandshakeMessage.HandshakeState.IN_PROGRESS);
         // Send response to init handshake request
-        verify(mMockCarTrustAgentBleManager).sendEnrollmentMessage(eq(mBluetoothDevice),
-                eq(DummyEncryptionRunner.INIT_RESPONSE.getBytes()), any(), eq(false));
+        verify(mMockCarTrustAgentBleManager).sendMessage(
+                eq(DummyEncryptionRunner.INIT_RESPONSE.getBytes()), any(), eq(false),
+                any(SendMessageCallback.class));
 
-        mCarTrustAgentEnrollmentService.onEnrollmentDataReceived(
+        mCarTrustAgentEnrollmentService.onDataReceived(
                 DummyEncryptionRunner.CLIENT_RESPONSE.getBytes());
         assertThat(mCarTrustAgentEnrollmentService.mEncryptionState).isEqualTo(
                 HandshakeMessage.HandshakeState.VERIFICATION_NEEDED);
-        verify(mMockCarTrustAgentBleManager).sendEnrollmentMessage(eq(mBluetoothDevice),
-                eq(DummyEncryptionRunner.INIT_RESPONSE.getBytes()), any(), eq(false));
+        verify(mMockCarTrustAgentBleManager).sendMessage(
+                eq(DummyEncryptionRunner.INIT_RESPONSE.getBytes()), any(), eq(false),
+                any(SendMessageCallback.class));
 
         // Completed the handshake by confirming the verification code.
         mCarTrustAgentEnrollmentService.enrollmentHandshakeAccepted(mBluetoothDevice);
-        verify(mMockCarTrustAgentBleManager).sendEnrollmentMessage(eq(mBluetoothDevice),
-                eq(mCarTrustAgentEnrollmentService.CONFIRMATION_SIGNAL), any(), eq(false));
+        verify(mMockCarTrustAgentBleManager).sendMessage(
+                eq(mCarTrustAgentEnrollmentService.CONFIRMATION_SIGNAL), any(), eq(false),
+                any(SendMessageCallback.class));
         assertThat(mCarTrustAgentEnrollmentService.mEncryptionState).isEqualTo(
                 HandshakeMessage.HandshakeState.FINISHED);
         assertThat(mCarTrustAgentEnrollmentService.mEnrollmentState).isEqualTo(
@@ -185,8 +195,8 @@
         mCarTrustAgentEnrollmentService.onEscrowTokenActiveStateChanged(
                 TEST_HANDLE1, /* isTokenActive= */ true, mUserId);
 
-        verify(mMockCarTrustAgentBleManager).sendEnrollmentMessage(eq(mBluetoothDevice), any(),
-                any(), eq(true));
+        verify(mMockCarTrustAgentBleManager).sendMessage(any(),
+                any(), eq(true), any(SendMessageCallback.class));
         assertThat(mCarTrustAgentEnrollmentService.getEnrolledDeviceInfosForUser(
                 mUserId)).containsExactly(DEVICE_INFO1);
         assertThat(mCarTrustAgentEnrollmentService.isEscrowTokenActive(TEST_HANDLE1,
@@ -224,14 +234,14 @@
     @Test
     public void testOnEscrowTokenRemoved_removeOneTrustedDevice() {
         setupEncryptionHandshake(TEST_ID1);
-        SharedPreferences sharedPrefs = mCarTrustedDeviceService.getSharedPrefs();
+        SharedPreferences sharedPrefs = mCarCompanionDeviceStorage.getSharedPrefs();
         mCarTrustAgentEnrollmentService.onEscrowTokenActiveStateChanged(
                 TEST_HANDLE1, /* isTokenActive= */ true,
                 mUserId);
 
         assertThat(mCarTrustAgentEnrollmentService.getEnrolledDeviceInfosForUser(
                 mUserId)).containsExactly(DEVICE_INFO1);
-        assertThat(mCarTrustedDeviceService.getUserHandleByTokenHandle(TEST_HANDLE1)).isEqualTo(
+        assertThat(mCarCompanionDeviceStorage.getUserHandleByTokenHandle(TEST_HANDLE1)).isEqualTo(
                 mUserId);
         assertThat(sharedPrefs.getLong(TEST_ID1.toString(), -1)).isEqualTo(TEST_HANDLE1);
 
@@ -241,31 +251,39 @@
 
         assertThat(mCarTrustAgentEnrollmentService.getEnrolledDeviceInfosForUser(
                 mUserId)).containsExactly(DEVICE_INFO1, DEVICE_INFO2);
-        assertThat(mCarTrustedDeviceService.getUserHandleByTokenHandle(TEST_HANDLE2)).isEqualTo(
+        assertThat(mCarCompanionDeviceStorage.getUserHandleByTokenHandle(TEST_HANDLE2)).isEqualTo(
                 mUserId);
         assertThat(sharedPrefs.getLong(TEST_ID1.toString(), -1)).isEqualTo(TEST_HANDLE2);
 
-        // Remove all handles
+        // Remove one handle
         mCarTrustAgentEnrollmentService.onEscrowTokenRemoved(TEST_HANDLE1, mUserId);
 
         assertThat(mCarTrustAgentEnrollmentService.getEnrolledDeviceInfosForUser(
                 mUserId)).containsExactly(DEVICE_INFO2);
-        assertThat(mCarTrustedDeviceService.getUserHandleByTokenHandle(TEST_HANDLE1)).isEqualTo(-1);
-        assertThat(mCarTrustedDeviceService.getUserHandleByTokenHandle(TEST_HANDLE2)).isEqualTo(
+        assertThat(mCarCompanionDeviceStorage
+                .getUserHandleByTokenHandle(TEST_HANDLE1))
+                .isEqualTo(-1);
+        assertThat(mCarCompanionDeviceStorage.getUserHandleByTokenHandle(TEST_HANDLE2)).isEqualTo(
                 mUserId);
         assertThat(sharedPrefs.getLong(TEST_ID1.toString(), -1)).isEqualTo(TEST_HANDLE2);
-
-        mCarTrustAgentEnrollmentService.onEscrowTokenRemoved(TEST_HANDLE2, mUserId);
-
-        assertThat(mCarTrustAgentEnrollmentService.getEnrolledDeviceInfosForUser(
-            mUserId)).isEmpty();
-        assertThat(mCarTrustedDeviceService.getUserHandleByTokenHandle(TEST_HANDLE2)).isEqualTo(-1);
-        assertThat(sharedPrefs.getLong(TEST_ID1.toString(), -1)).isEqualTo(-1);
     }
 
     @Test
     public void testGetUserHandleByTokenHandle_nonExistentHandle() {
-        assertThat(mCarTrustedDeviceService.getUserHandleByTokenHandle(TEST_HANDLE1)).isEqualTo(-1);
+        assertThat(mCarCompanionDeviceStorage
+                .getUserHandleByTokenHandle(TEST_HANDLE1))
+                .isEqualTo(-1);
+    }
+
+    @Test
+    public void testEncryptionKeyStorage() {
+        byte[] encryptionKey = KEY.getBytes();
+        if (mCarCompanionDeviceStorage.saveEncryptionKey(DEVICE_ID, encryptionKey)) {
+            assertThat(mCarCompanionDeviceStorage.getEncryptionKey(DEVICE_ID))
+                .isEqualTo(encryptionKey);
+        }
+        mCarCompanionDeviceStorage.clearEncryptionKey(DEVICE_ID);
+        assertThat(mCarCompanionDeviceStorage.getEncryptionKey(DEVICE_ID) == null).isTrue();
     }
 
     @Test
@@ -273,17 +291,17 @@
         setupEncryptionHandshake(TEST_ID1);
         mCarTrustAgentEnrollmentService.onEscrowTokenActiveStateChanged(
                 TEST_HANDLE1, /* isTokenActive= */ true, mUserId);
-        assertThat(mCarTrustedDeviceService.getUserHandleByTokenHandle(TEST_HANDLE1)).isEqualTo(
+        assertThat(mCarCompanionDeviceStorage.getUserHandleByTokenHandle(TEST_HANDLE1)).isEqualTo(
                 mUserId);
     }
 
     private void setupEncryptionHandshake(UUID uuid) {
         mCarTrustAgentEnrollmentService.setEncryptionRunner(
                 EncryptionRunnerFactory.newDummyRunner());
-        mCarTrustAgentEnrollmentService.onEnrollmentDataReceived(Utils.uuidToBytes(uuid));
-        mCarTrustAgentEnrollmentService.onEnrollmentDataReceived(
+        mCarTrustAgentEnrollmentService.onDataReceived(Utils.uuidToBytes(uuid));
+        mCarTrustAgentEnrollmentService.onDataReceived(
                 DummyEncryptionRunner.INIT.getBytes());
-        mCarTrustAgentEnrollmentService.onEnrollmentDataReceived(
+        mCarTrustAgentEnrollmentService.onDataReceived(
                 DummyEncryptionRunner.CLIENT_RESPONSE.getBytes());
         mCarTrustAgentEnrollmentService.enrollmentHandshakeAccepted(mBluetoothDevice);
     }
diff --git a/tests/carservice_unit_test/src/com/android/car/user/CarUserNoticeServiceTest.java b/tests/carservice_unit_test/src/com/android/car/user/CarUserNoticeServiceTest.java
new file mode 100644
index 0000000..93d9500
--- /dev/null
+++ b/tests/carservice_unit_test/src/com/android/car/user/CarUserNoticeServiceTest.java
@@ -0,0 +1,294 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.user;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.AppOpsManager;
+import android.car.hardware.power.CarPowerManager;
+import android.car.hardware.power.CarPowerManager.CarPowerStateListener;
+import android.car.settings.CarSettings;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.PowerManager;
+import android.os.UserHandle;
+import android.provider.Settings;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.car.CarLocalServices;
+import com.android.car.CarPowerManagementService;
+import com.android.car.R;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoSession;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.mockito.quality.Strictness;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+@RunWith(MockitoJUnitRunner.class)
+public class CarUserNoticeServiceTest {
+
+    @Mock
+    private Context mMockContext;
+    @Mock
+    private Resources mMockedResources;
+    @Mock
+    private CarPowerManagementService mMockCarPowerManagementService;
+    @Mock
+    private CarUserService mMockCarUserService;
+    @Mock
+    private PowerManager mMockPowerManager;
+    @Mock
+    private AppOpsManager mMockAppOpsManager;
+    @Mock
+    private PackageManager mMockPackageManager;
+    @Mock
+    private CarPowerManager mCarPowerManager;
+
+    @Captor
+    private ArgumentCaptor<BroadcastReceiver> mDisplayBroadcastReceiver;
+
+    @Captor
+    private ArgumentCaptor<CarUserService.UserCallback> mUserCallback;
+
+    @Captor
+    private ArgumentCaptor<CarPowerStateListener> mPowerStateListener;
+
+    private MockitoSession mSession;
+    private CarUserNoticeService mCarUserNoticeService;
+
+    private final Handler mHandler = new Handler(Looper.getMainLooper());
+
+    /**
+     * Initialize all of the objects with the @Mock annotation.
+     */
+    @Before
+    public void setUpMocks() throws Exception {
+        mSession = mockitoSession()
+                .initMocks(this)
+                .mockStatic(CarLocalServices.class)
+                .mockStatic(Settings.Secure.class)
+                .strictness(Strictness.LENIENT)
+                .startMocking();
+
+        when(CarLocalServices.createCarPowerManager(mMockContext)).thenReturn(mCarPowerManager);
+        when(Settings.Secure.getIntForUser(any(),
+                eq(CarSettings.Secure.KEY_ENABLE_INITIAL_NOTICE_SCREEN_TO_USER), anyInt(),
+                anyInt())).thenReturn(1);
+
+        doReturn(mMockedResources).when(mMockContext).getResources();
+        doReturn(InstrumentationRegistry.getInstrumentation().getTargetContext()
+                .getContentResolver())
+                        .when(mMockContext).getContentResolver();
+        doReturn("com.foo/.Blah").when(mMockedResources).getString(anyInt());
+        when(CarLocalServices.getService(CarPowerManagementService.class))
+                .thenReturn(mMockCarPowerManagementService);
+        when(CarLocalServices.getService(CarUserService.class)).thenReturn(mMockCarUserService);
+        doReturn(mMockPowerManager).when(mMockContext).getSystemService(PowerManager.class);
+        doReturn(mMockAppOpsManager).when(mMockContext).getSystemService(AppOpsManager.class);
+        doReturn(mMockPackageManager).when(mMockContext).getPackageManager();
+        doReturn(1).when(mMockPackageManager).getPackageUidAsUser(any(), anyInt());
+        mCarUserNoticeService = new CarUserNoticeService(mMockContext, mHandler);
+        mCarUserNoticeService.init();
+        verify(mMockCarUserService).addUserCallback(mUserCallback.capture());
+        verify(mMockContext).registerReceiver(mDisplayBroadcastReceiver.capture(),
+                any(IntentFilter.class));
+        verify(mCarPowerManager).setListener(mPowerStateListener.capture());
+    }
+
+    @After
+    public void tearDown() {
+        mSession.finishMocking();
+    }
+
+    @Test
+    public void featureDisabledTest() {
+        Context mockContext = mock(Context.class);
+        // if feature is disabled, Resources.getString will return an
+        // empty string
+        doReturn("").when(mMockedResources).getString(R.string.config_userNoticeUiService);
+        doReturn(mMockedResources).when(mockContext).getResources();
+        CarUserNoticeService carUserNoticeService = new CarUserNoticeService(mockContext);
+        carUserNoticeService.init();
+        verify(mockContext, never()).registerReceiver(any(), any());
+    }
+
+    @Test
+    public void uiHiddenWhenBroadcastOffReceived() throws Exception {
+        setUser();
+        // reset UI
+        setDisplayOff();
+        CountDownLatch latch = mockUnbindService();
+        sendBroadcast(Intent.ACTION_SCREEN_OFF);
+        assetLatchCalled(latch);
+    }
+
+    @Test
+    public void uiShownWhenBroadcastOnReceived() throws Exception {
+        setUser();
+        // reset UI
+        setDisplayOff();
+        CountDownLatch latch = mockUnbindService();
+        sendBroadcast(Intent.ACTION_SCREEN_OFF);
+        assetLatchCalled(latch);
+
+        // send screen on broadcast
+        setDisplayOn();
+        latch = mockBindService();
+        sendBroadcast(Intent.ACTION_SCREEN_ON);
+        assetLatchCalled(latch);
+    }
+
+    @Test
+    public void uiHiddenWhenPowerShutDown() throws Exception {
+        setUser();
+        // reset UI
+        setDisplayOff();
+        CountDownLatch latch = mockUnbindService();
+        sendPowerStateChange(CarPowerManager.CarPowerStateListener.SHUTDOWN_PREPARE);
+        assetLatchCalled(latch);
+    }
+
+    @Test
+    public void uiShownWhenPowerOn() throws Exception {
+        setUser();
+        // reset UI
+        setDisplayOff();
+        CountDownLatch latch = mockUnbindService();
+        sendPowerStateChange(CarPowerManager.CarPowerStateListener.SHUTDOWN_PREPARE);
+        assetLatchCalled(latch);
+
+        // send Power On
+        setDisplayOn();
+        latch = mockBindService();
+        sendPowerStateChange(CarPowerManager.CarPowerStateListener.ON);
+        assetLatchCalled(latch);
+    }
+
+    @Test
+    public void uiNotShownIfKeyDisabled() throws Exception {
+        setUser();
+        // reset UI
+        setDisplayOff();
+        CountDownLatch latch = mockUnbindService();
+        sendBroadcast(Intent.ACTION_SCREEN_OFF);
+        assetLatchCalled(latch);
+
+        // UI not shown if key is disabled
+        setDisplayOn();
+        latch = mockKeySettings(
+                CarSettings.Secure.KEY_ENABLE_INITIAL_NOTICE_SCREEN_TO_USER, 0);
+        sendBroadcast(Intent.ACTION_SCREEN_ON);
+        assetLatchCalled(latch);
+        // invoked only once, when user switched
+        verify(mMockContext, times(1)).bindServiceAsUser(any(), any(), anyInt(), any());
+    }
+
+    private void assetLatchCalled(CountDownLatch latch) throws Exception {
+        assertThat(latch.await(10, TimeUnit.SECONDS)).isTrue();
+    }
+
+    private void switchUser(int usrId) throws Exception {
+        // Switch User callback
+        mUserCallback.getValue().onSwitchUser(usrId);
+    }
+
+    private CountDownLatch mockBindService() {
+        CountDownLatch latch = new CountDownLatch(1);
+        when(mMockContext.bindServiceAsUser(any(), any(), anyInt(), any()))
+                .thenAnswer(inv -> {
+                    latch.countDown();
+                    return true;
+                });
+        return latch;
+    }
+
+    private CountDownLatch mockUnbindService() {
+        CountDownLatch latch = new CountDownLatch(1);
+        doAnswer(inv -> {
+            latch.countDown();
+            return null;
+        }).when(mMockContext).unbindService(any());
+        return latch;
+    }
+
+    private CountDownLatch mockKeySettings(String key, int value) {
+        CountDownLatch latch = new CountDownLatch(1);
+        when(Settings.Secure.getIntForUser(any(),
+                eq(key), anyInt(),
+                anyInt()))
+                        .thenAnswer(inv -> {
+                            latch.countDown();
+                            return value;
+                        });
+        return latch;
+    }
+
+    private void setDisplayOn() {
+        doReturn(true).when(mMockPowerManager).isInteractive();
+    }
+
+    private void setDisplayOff() {
+        doReturn(false).when(mMockPowerManager).isInteractive();
+    }
+
+    private void sendBroadcast(String action) {
+        Intent intent = new Intent();
+        intent.setAction(action);
+        mHandler.post(() -> mDisplayBroadcastReceiver.getValue().onReceive(mMockContext, intent));
+    }
+
+    private void sendPowerStateChange(int state) {
+        mPowerStateListener.getValue().onStateChanged(state);
+    }
+
+    private void setUser() throws Exception {
+        // switch user (required to set user)
+        setDisplayOn();
+        CountDownLatch latch = mockBindService();
+        switchUser(UserHandle.MIN_SECONDARY_USER_ID);
+        assetLatchCalled(latch);
+    }
+}
diff --git a/tests/carservice_unit_test/src/com/android/car/user/CarUserServiceTest.java b/tests/carservice_unit_test/src/com/android/car/user/CarUserServiceTest.java
index c81f887..2e307b9 100644
--- a/tests/carservice_unit_test/src/com/android/car/user/CarUserServiceTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/user/CarUserServiceTest.java
@@ -16,41 +16,78 @@
 
 package com.android.car.user;
 
+import static android.content.pm.UserInfo.FLAG_EPHEMERAL;
+import static android.content.pm.UserInfo.FLAG_GUEST;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+
 import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Matchers.anyInt;
+import static org.mockito.ArgumentMatchers.notNull;
+import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
+import android.annotation.NonNull;
+import android.annotation.UserIdInt;
 import android.app.ActivityManager;
 import android.app.IActivityManager;
+import android.car.CarOccupantZoneManager.OccupantTypeEnum;
+import android.car.CarOccupantZoneManager.OccupantZoneInfo;
 import android.car.settings.CarSettings;
 import android.car.userlib.CarUserManagerHelper;
 import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
 import android.content.pm.UserInfo;
+import android.content.res.Resources;
+import android.graphics.drawable.Drawable;
+import android.hardware.automotive.vehicle.V2_0.InitialUserInfoRequestType;
+import android.hardware.automotive.vehicle.V2_0.InitialUserInfoResponse;
+import android.hardware.automotive.vehicle.V2_0.InitialUserInfoResponseAction;
+import android.hardware.automotive.vehicle.V2_0.UserFlags;
+import android.hardware.automotive.vehicle.V2_0.UsersInfo;
 import android.location.LocationManager;
+import android.os.Bundle;
+import android.os.RemoteException;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.provider.Settings;
+import android.util.Log;
+import android.util.SparseArray;
 
+import androidx.annotation.Nullable;
 import androidx.test.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
 
+import com.android.car.hal.UserHalService;
+import com.android.car.hal.UserHalService.HalCallback;
+import com.android.internal.R;
+import com.android.internal.os.IResultReceiver;
+import com.android.internal.util.Preconditions;
+
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
+import org.mockito.MockitoSession;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.mockito.quality.Strictness;
 
 import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
 
 /**
  * This class contains unit tests for the {@link CarUserService}.
@@ -58,115 +95,94 @@
  * The following mocks are used:
  * <ol>
  * <li> {@link Context} provides system services and resources.
- * <li> {@link CarUserManagerHelper} provides user info and actions.
+ * <li> {@link IActivityManager} provides current user.
+ * <li> {@link UserManager} provides user creation and user info.
+ * <li> {@link Resources} provides user icon.
+ * <li> {@link Drawable} provides bitmap of user icon.
  * <ol/>
  */
-@RunWith(AndroidJUnit4.class)
+@RunWith(MockitoJUnitRunner.class)
 public class CarUserServiceTest {
+
+    private static final String TAG = CarUserServiceTest.class.getSimpleName();
+    private static final int NO_USER_INFO_FLAGS = 0;
+
+    @Mock private Context mMockContext;
+    @Mock private Context mApplicationContext;
+    @Mock private LocationManager mLocationManager;
+    @Mock private UserHalService mUserHal;
+    @Mock private CarUserManagerHelper mMockedCarUserManagerHelper;
+    @Mock private IActivityManager mMockedIActivityManager;
+    @Mock private UserManager mMockedUserManager;
+    @Mock private Resources mMockedResources;
+    @Mock private Drawable mMockedDrawable;
+
+    private MockitoSession mSession;
     private CarUserService mCarUserService;
-
-    @Mock
-    private Context mMockContext;
-
-    @Mock
-    private Context mApplicationContext;
-
-    @Mock
-    private LocationManager mLocationManager;
-
-    @Mock
-    private CarUserManagerHelper mCarUserManagerHelper;
-
-    private static final String DEFAULT_ADMIN_NAME = "defaultName";
-
-    @Mock
-    private IActivityManager mMockedIActivityManager;
-
-    @Mock
-    private UserManager mMockedUserManager;
-
     private boolean mUser0TaskExecuted;
+    private FakeCarOccupantZoneService mFakeCarOccupantZoneService;
 
+    private final int mGetUserInfoRequestType = InitialUserInfoRequestType.COLD_BOOT;
+    private final int mAsyncCallTimeoutMs = 100;
+    private final BlockingResultReceiver mReceiver =
+            new BlockingResultReceiver(mAsyncCallTimeoutMs);
+    private final InitialUserInfoResponse mGetUserInfoResponse = new InitialUserInfoResponse();
+
+    private final @NonNull UserInfo mSystemUser = UserInfoBuilder.newSystemUserInfo();
+    private final @NonNull UserInfo mAdminUser = new UserInfoBuilder(10)
+            .setAdmin(true)
+            .build();
+    private final @NonNull UserInfo mGuestUser = new UserInfoBuilder(11)
+            .setGuest(true)
+            .setEphemeral(true)
+            .build();
+    private final List<UserInfo> mExistingUsers = Arrays.asList(mSystemUser, mAdminUser,
+            mGuestUser);
 
     /**
      * Initialize all of the objects with the @Mock annotation.
      */
     @Before
-    public void setUpMocks() throws Exception {
-        MockitoAnnotations.initMocks(this);
+    public void setUpMocks() {
+        mSession = mockitoSession()
+                .initMocks(this)
+                .strictness(Strictness.LENIENT)
+                .spyStatic(ActivityManager.class)
+                .mockStatic(Settings.Global.class)
+                .startMocking();
+
         doReturn(mApplicationContext).when(mMockContext).getApplicationContext();
         doReturn(mLocationManager).when(mMockContext).getSystemService(Context.LOCATION_SERVICE);
         doReturn(InstrumentationRegistry.getTargetContext().getContentResolver())
                 .when(mMockContext).getContentResolver();
-        doReturn(mMockedUserManager).when(mMockContext).getSystemService(Context.USER_SERVICE);
         doReturn(false).when(mMockedUserManager).isUserUnlockingOrUnlocked(anyInt());
-        mCarUserService = new CarUserService(mMockContext, mCarUserManagerHelper,
-                mMockedIActivityManager, 3);
+        doReturn(mMockedResources).when(mMockContext).getResources();
+        doReturn(mMockedDrawable).when(mMockedResources)
+                .getDrawable(eq(R.drawable.ic_account_circle), eq(null));
+        doReturn(mMockedDrawable).when(mMockedDrawable).mutate();
+        doReturn(1).when(mMockedDrawable).getIntrinsicWidth();
+        doReturn(1).when(mMockedDrawable).getIntrinsicHeight();
+        mCarUserService =
+                new CarUserService(
+                        mMockContext,
+                        mUserHal,
+                        mMockedCarUserManagerHelper,
+                        mMockedUserManager,
+                        mMockedIActivityManager,
+                        3);
 
-        doReturn(new ArrayList<>()).when(mCarUserManagerHelper).getAllUsers();
-
+        mFakeCarOccupantZoneService = new FakeCarOccupantZoneService(mCarUserService);
         // Restore default value at the beginning of each test.
+        mockSettingsGlobal();
         putSettingsInt(CarSettings.Global.DEFAULT_USER_RESTRICTIONS_SET, 0);
     }
 
     /**
-     * Test that the {@link CarUserService} registers to receive the locked boot completed
-     * intent.
+     *  Clean up before running the next test
      */
-    @Test
-    public void testRegistersToReceiveEvents() {
-        ArgumentCaptor<IntentFilter> argument = ArgumentCaptor.forClass(IntentFilter.class);
-        mCarUserService.init();
-        verify(mMockContext).registerReceiver(eq(mCarUserService), argument.capture());
-        IntentFilter intentFilter = argument.getValue();
-        assertThat(intentFilter.countActions()).isEqualTo(1);
-
-        assertThat(intentFilter.getAction(0)).isEqualTo(Intent.ACTION_USER_SWITCHED);
-    }
-
-    /**
-     * Test that the {@link CarUserService} unregisters its event receivers.
-     */
-    @Test
-    public void testUnregistersEventReceivers() {
-        mCarUserService.release();
-        verify(mMockContext).unregisterReceiver(mCarUserService);
-    }
-
-    /**
-     * Test that the {@link CarUserService} does set the disable modify account permission for
-     * user 0 upon user 0 unlock when user 0 is headless.
-     */
-    @Test
-    public void testDisableModifyAccountsForHeadlessSystemUserOnFirstRun() {
-        // Mock system user.
-        UserInfo systemUser = new UserInfo();
-        systemUser.id = UserHandle.USER_SYSTEM;
-        doReturn(systemUser).when(mCarUserManagerHelper).getSystemUserInfo();
-        doReturn(true).when(mCarUserManagerHelper).isHeadlessSystemUser();
-
-        mCarUserService.setUserLockStatus(UserHandle.USER_SYSTEM, true);
-
-        verify(mCarUserManagerHelper)
-                .setUserRestriction(systemUser, UserManager.DISALLOW_MODIFY_ACCOUNTS, true);
-    }
-
-    /**
-     * Test that the {@link CarUserService} does not set the disable modify account permission for
-     * user 0 upon user 0 unlock when user 0 is not headless.
-     */
-    @Test
-    public void testDisableModifyAccountsForRegularSystemUserOnFirstRun() {
-        // Mock system user.
-        UserInfo systemUser = new UserInfo();
-        systemUser.id = UserHandle.USER_SYSTEM;
-        doReturn(systemUser).when(mCarUserManagerHelper).getSystemUserInfo();
-        doReturn(false).when(mCarUserManagerHelper).isHeadlessSystemUser();
-
-        mCarUserService.setUserLockStatus(UserHandle.USER_SYSTEM, true);
-
-        verify(mCarUserManagerHelper, never())
-                .setUserRestriction(systemUser, UserManager.DISALLOW_MODIFY_ACCOUNTS, true);
+    @After
+    public void tearDown() {
+        mSession.finishMocking();
     }
 
     /**
@@ -175,16 +191,13 @@
      */
     @Test
     public void testDoesNotSetSystemUserRestrictions_IfRestrictionsAlreadySet() {
-        // Mock system user.
-        UserInfo systemUser = new UserInfo();
-        systemUser.id = UserHandle.USER_SYSTEM;
-        doReturn(systemUser).when(mCarUserManagerHelper).getSystemUserInfo();
-
         putSettingsInt(CarSettings.Global.DEFAULT_USER_RESTRICTIONS_SET, 1);
         mCarUserService.setUserLockStatus(UserHandle.USER_SYSTEM, true);
-
-        verify(mCarUserManagerHelper, never())
-                .setUserRestriction(systemUser, UserManager.DISALLOW_MODIFY_ACCOUNTS, true);
+        verify(mMockedUserManager, never())
+                .setUserRestriction(
+                        UserManager.DISALLOW_MODIFY_ACCOUNTS,
+                        true,
+                        UserHandle.of(UserHandle.USER_SYSTEM));
     }
 
     /**
@@ -193,41 +206,24 @@
      */
     @Test
     public void testDisableLocationForHeadlessSystemUserOnFirstRun() {
-        doReturn(true).when(mCarUserManagerHelper).isHeadlessSystemUser();
         mCarUserService.setUserLockStatus(UserHandle.USER_SYSTEM, true);
-
         verify(mLocationManager).setLocationEnabledForUser(
                 /* enabled= */ false, UserHandle.of(UserHandle.USER_SYSTEM));
     }
 
     /**
-     * Test that the {@link CarUserService} does not disable the location service for regular user 0
-     * upon first run.
-     */
-    @Test
-    public void testDisableLocationForRegularSystemUserOnFirstRun() {
-        doReturn(false).when(mCarUserManagerHelper).isHeadlessSystemUser();
-        mCarUserService.setUserLockStatus(UserHandle.USER_SYSTEM, true);
-
-        verify(mLocationManager, never()).setLocationEnabledForUser(
-                /* enabled= */ false, UserHandle.of(UserHandle.USER_SYSTEM));
-    }
-
-    /**
-     * Test that the {@link CarUserService} updates last active user on user switch intent.
+     * Test that the {@link CarUserService} updates last active user on user switch.
      */
     @Test
     public void testLastActiveUserUpdatedOnUserSwitch() {
-        int lastActiveUserId = 11;
+        int lastActiveUserId = 99;
+        UserInfo persistentUser = new UserInfo(lastActiveUserId, "persistent user",
+                NO_USER_INFO_FLAGS);
+        doReturn(persistentUser).when(mMockedUserManager).getUserInfo(lastActiveUserId);
 
-        Intent intent = new Intent(Intent.ACTION_USER_SWITCHED);
-        intent.putExtra(Intent.EXTRA_USER_HANDLE, lastActiveUserId);
+        mCarUserService.onSwitchUser(lastActiveUserId);
 
-        doReturn(true).when(mCarUserManagerHelper).isPersistentUser(lastActiveUserId);
-
-        mCarUserService.onReceive(mMockContext, intent);
-
-        verify(mCarUserManagerHelper).setLastActiveUser(lastActiveUserId);
+        verify(mMockedCarUserManagerHelper).setLastActiveUser(lastActiveUserId);
     }
 
     /**
@@ -236,7 +232,6 @@
     @Test
     public void testInitializeGuestRestrictions_IfNotAlreadySet() {
         mCarUserService.setUserLockStatus(UserHandle.USER_SYSTEM, true);
-        verify(mCarUserManagerHelper).initDefaultGuestRestrictions();
         assertThat(getSettingsInt(CarSettings.Global.DEFAULT_USER_RESTRICTIONS_SET)).isEqualTo(1);
     }
 
@@ -247,7 +242,7 @@
     public void test_DoesNotInitializeGuestRestrictions_IfAlreadySet() {
         putSettingsInt(CarSettings.Global.DEFAULT_USER_RESTRICTIONS_SET, 1);
         mCarUserService.setUserLockStatus(UserHandle.USER_SYSTEM, true);
-        verify(mCarUserManagerHelper, never()).initDefaultGuestRestrictions();
+        verify(mMockedUserManager, never()).setDefaultGuestRestrictions(any(Bundle.class));
     }
 
     @Test
@@ -275,18 +270,27 @@
      * Test is lengthy as it is testing LRU logic.
      */
     @Test
-    public void testBackgroundUserList() {
-        final int user1 = 101;
-        final int user2 = 102;
-        final int user3 = 103;
-        final int user4Guest = 104;
-        final int user5 = 105;
-        doReturn(true).when(mCarUserManagerHelper).isPersistentUser(user1);
-        doReturn(true).when(mCarUserManagerHelper).isPersistentUser(user2);
-        doReturn(true).when(mCarUserManagerHelper).isPersistentUser(user3);
-        doReturn(true).when(mCarUserManagerHelper).isPersistentUser(user5);
+    public void testBackgroundUserList() throws RemoteException {
+        int user1 = 101;
+        int user2 = 102;
+        int user3 = 103;
+        int user4Guest = 104;
+        int user5 = 105;
 
-        doReturn(user1).when(mCarUserManagerHelper).getCurrentForegroundUserId();
+        UserInfo user1Info = new UserInfo(user1, "user1", NO_USER_INFO_FLAGS);
+        UserInfo user2Info = new UserInfo(user2, "user2", NO_USER_INFO_FLAGS);
+        UserInfo user3Info = new UserInfo(user3, "user3", NO_USER_INFO_FLAGS);
+        UserInfo user4GuestInfo = new UserInfo(
+                user4Guest, "user4Guest", FLAG_EPHEMERAL | FLAG_GUEST);
+        UserInfo user5Info = new UserInfo(user5, "user5", NO_USER_INFO_FLAGS);
+
+        doReturn(user1Info).when(mMockedUserManager).getUserInfo(user1);
+        doReturn(user2Info).when(mMockedUserManager).getUserInfo(user2);
+        doReturn(user3Info).when(mMockedUserManager).getUserInfo(user3);
+        doReturn(user4GuestInfo).when(mMockedUserManager).getUserInfo(user4Guest);
+        doReturn(user5Info).when(mMockedUserManager).getUserInfo(user5);
+
+        when(ActivityManager.getCurrentUser()).thenReturn(user1);
         mCarUserService.setUserLockStatus(UserHandle.USER_SYSTEM, true);
         // user 0 should never go to that list.
         assertTrue(mCarUserService.getBackgroundUsersToRestart().isEmpty());
@@ -301,19 +305,19 @@
         assertEquals(new Integer[]{user1},
                 mCarUserService.getBackgroundUsersToRestart().toArray());
 
-        doReturn(user3).when(mCarUserManagerHelper).getCurrentForegroundUserId();
+        when(ActivityManager.getCurrentUser()).thenReturn(user3);
         mCarUserService.setUserLockStatus(user3, true);
         mCarUserService.setUserLockStatus(user2, false);
         assertEquals(new Integer[]{user3, user1},
                 mCarUserService.getBackgroundUsersToRestart().toArray());
 
-        doReturn(user4Guest).when(mCarUserManagerHelper).getCurrentForegroundUserId();
+        when(ActivityManager.getCurrentUser()).thenReturn(user4Guest);
         mCarUserService.setUserLockStatus(user4Guest, true);
         mCarUserService.setUserLockStatus(user3, false);
         assertEquals(new Integer[]{user3, user1},
                 mCarUserService.getBackgroundUsersToRestart().toArray());
 
-        doReturn(user5).when(mCarUserManagerHelper).getCurrentForegroundUserId();
+        when(ActivityManager.getCurrentUser()).thenReturn(user5);
         mCarUserService.setUserLockStatus(user5, true);
         mCarUserService.setUserLockStatus(user4Guest, false);
         assertEquals(new Integer[]{user5, user3},
@@ -325,20 +329,25 @@
      */
     @Test
     public void testBackgroundUsersStartStopKeepBackgroundUserList() throws Exception {
-        final int user1 = 101;
-        final int user2 = 102;
-        final int user3 = 103;
+        int user1 = 101;
+        int user2 = 102;
+        int user3 = 103;
 
-        doReturn(true).when(mCarUserManagerHelper).isPersistentUser(user1);
-        doReturn(true).when(mCarUserManagerHelper).isPersistentUser(user2);
-        doReturn(true).when(mCarUserManagerHelper).isPersistentUser(user3);
-        doReturn(user1).when(mCarUserManagerHelper).getCurrentForegroundUserId();
+        UserInfo user1Info = new UserInfo(user1, "user1", NO_USER_INFO_FLAGS);
+        UserInfo user2Info = new UserInfo(user2, "user2", NO_USER_INFO_FLAGS);
+        UserInfo user3Info = new UserInfo(user3, "user3", NO_USER_INFO_FLAGS);
+
+        doReturn(user1Info).when(mMockedUserManager).getUserInfo(user1);
+        doReturn(user2Info).when(mMockedUserManager).getUserInfo(user2);
+        doReturn(user3Info).when(mMockedUserManager).getUserInfo(user3);
+
+        when(ActivityManager.getCurrentUser()).thenReturn(user1);
         mCarUserService.setUserLockStatus(UserHandle.USER_SYSTEM, true);
         mCarUserService.setUserLockStatus(user1, true);
-        doReturn(user2).when(mCarUserManagerHelper).getCurrentForegroundUserId();
+        when(ActivityManager.getCurrentUser()).thenReturn(user2);
         mCarUserService.setUserLockStatus(user2, true);
         mCarUserService.setUserLockStatus(user1, false);
-        doReturn(user3).when(mCarUserManagerHelper).getCurrentForegroundUserId();
+        when(ActivityManager.getCurrentUser()).thenReturn(user3);
         mCarUserService.setUserLockStatus(user3, true);
         mCarUserService.setUserLockStatus(user2, false);
 
@@ -372,12 +381,414 @@
     }
 
     @Test
-    public void testStopBackgroundUserForFgUser() {
-        final int user1 = 101;
-        doReturn(user1).when(mCarUserManagerHelper).getCurrentForegroundUserId();
+    public void testStopBackgroundUserForFgUser() throws RemoteException {
+        int user1 = 101;
+        when(ActivityManager.getCurrentUser()).thenReturn(user1);
         assertFalse(mCarUserService.stopBackgroundUser(UserHandle.USER_SYSTEM));
     }
 
+    @Test
+    public void testCreateAdminDriver_IfCurrentUserIsAdminUser() {
+        doReturn(true).when(mMockedUserManager).isSystemUser();
+        String userName = "testUser";
+        UserInfo userInfo = new UserInfo();
+        doReturn(userInfo).when(mMockedUserManager).createUser(userName, UserInfo.FLAG_ADMIN);
+        assertEquals(userInfo, mCarUserService.createDriver(userName, true));
+    }
+
+    @Test
+    public void testCreateAdminDriver_IfCurrentUserIsNotSystemUser() {
+        doReturn(false).when(mMockedUserManager).isSystemUser();
+        assertEquals(null, mCarUserService.createDriver("testUser", true));
+    }
+
+    @Test
+    public void testCreateNonAdminDriver() {
+        String userName = "testUser";
+        UserInfo userInfo = new UserInfo();
+        doReturn(userInfo).when(mMockedCarUserManagerHelper).createNewNonAdminUser(userName);
+        assertEquals(userInfo, mCarUserService.createDriver(userName, false));
+    }
+
+    @Test
+    public void testCreateNonAdminDriver_IfMaximumUserAlreadyCreated() {
+        String userName = "testUser";
+        doReturn(null).when(mMockedUserManager).createUser(userName, NO_USER_INFO_FLAGS);
+        assertEquals(null, mCarUserService.createDriver(userName, false));
+    }
+
+    @Test
+    public void testCreatePassenger() {
+        int driverId = 90;
+        int passengerId = 99;
+        String userName = "testUser";
+        UserInfo userInfo = new UserInfo(passengerId, userName, NO_USER_INFO_FLAGS);
+        doReturn(userInfo).when(mMockedUserManager).createProfileForUser(eq(userName),
+                eq(UserManager.USER_TYPE_PROFILE_MANAGED), eq(0), eq(driverId));
+        UserInfo driverInfo = new UserInfo(driverId, "driver", NO_USER_INFO_FLAGS);
+        doReturn(driverInfo).when(mMockedUserManager).getUserInfo(driverId);
+        assertEquals(userInfo, mCarUserService.createPassenger(userName, driverId));
+    }
+
+    @Test
+    public void testCreatePassenger_IfMaximumProfileAlreadyCreated() {
+        int driverId = 90;
+        String userName = "testUser";
+        doReturn(null).when(mMockedUserManager).createProfileForUser(eq(userName),
+                eq(UserManager.USER_TYPE_PROFILE_MANAGED), anyInt(), anyInt());
+        UserInfo driverInfo = new UserInfo(driverId, "driver", NO_USER_INFO_FLAGS);
+        doReturn(driverInfo).when(mMockedUserManager).getUserInfo(driverId);
+        assertEquals(null, mCarUserService.createPassenger(userName, driverId));
+    }
+
+    @Test
+    public void testCreatePassenger_IfDriverIsGuest() {
+        int driverId = 90;
+        String userName = "testUser";
+        UserInfo driverInfo = new UserInfo(driverId, "driver", UserInfo.FLAG_GUEST);
+        doReturn(driverInfo).when(mMockedUserManager).getUserInfo(driverId);
+        assertEquals(null, mCarUserService.createPassenger(userName, driverId));
+    }
+
+    @Test
+    public void testSwitchDriver() throws RemoteException {
+        int currentId = 11;
+        int targetId = 12;
+        when(ActivityManager.getCurrentUser()).thenReturn(currentId);
+        doReturn(true).when(mMockedIActivityManager).switchUser(targetId);
+        doReturn(false).when(mMockedUserManager)
+                .hasUserRestriction(UserManager.DISALLOW_USER_SWITCH);
+        assertTrue(mCarUserService.switchDriver(targetId));
+    }
+
+    @Test
+    public void testSwitchDriver_IfUserSwitchIsNotAllowed() throws RemoteException {
+        int currentId = 11;
+        int targetId = 12;
+        when(ActivityManager.getCurrentUser()).thenReturn(currentId);
+        doReturn(true).when(mMockedIActivityManager).switchUser(targetId);
+        doReturn(UserManager.SWITCHABILITY_STATUS_USER_SWITCH_DISALLOWED).when(mMockedUserManager)
+                .getUserSwitchability();
+        assertFalse(mCarUserService.switchDriver(targetId));
+    }
+
+    @Test
+    public void testSwitchDriver_IfSwitchedToCurrentUser() throws RemoteException {
+        int currentId = 11;
+        when(ActivityManager.getCurrentUser()).thenReturn(currentId);
+        doReturn(false).when(mMockedUserManager)
+                .hasUserRestriction(UserManager.DISALLOW_USER_SWITCH);
+        assertTrue(mCarUserService.switchDriver(11));
+    }
+
+    @Test
+    public void testStartPassenger() throws RemoteException {
+        int passenger1Id = 91;
+        int passenger2Id = 92;
+        int passenger3Id = 93;
+        int zone1Id = 1;
+        int zone2Id = 2;
+        doReturn(true).when(mMockedIActivityManager)
+                .startUserInBackgroundWithListener(anyInt(), eq(null));
+        assertTrue(mCarUserService.startPassenger(passenger1Id, zone1Id));
+        assertTrue(mCarUserService.startPassenger(passenger2Id, zone2Id));
+        assertFalse(mCarUserService.startPassenger(passenger3Id, zone2Id));
+    }
+
+    @Test
+    public void testStopPassenger() throws RemoteException {
+        int user1Id = 11;
+        int passenger1Id = 91;
+        int passenger2Id = 92;
+        int zoneId = 1;
+        UserInfo user1Info = new UserInfo(user1Id, "user1", NO_USER_INFO_FLAGS);
+        UserInfo passenger1Info =
+                new UserInfo(passenger1Id, "passenger1", UserInfo.FLAG_MANAGED_PROFILE);
+        associateParentChild(user1Info, passenger1Info);
+        doReturn(passenger1Info).when(mMockedUserManager).getUserInfo(passenger1Id);
+        doReturn(null).when(mMockedUserManager).getUserInfo(passenger2Id);
+        when(ActivityManager.getCurrentUser()).thenReturn(user1Id);
+        doReturn(true).when(mMockedIActivityManager)
+                .startUserInBackgroundWithListener(anyInt(), eq(null));
+        assertTrue(mCarUserService.startPassenger(passenger1Id, zoneId));
+        assertTrue(mCarUserService.stopPassenger(passenger1Id));
+        // Test of stopping an already stopped passenger.
+        assertTrue(mCarUserService.stopPassenger(passenger1Id));
+        // Test of stopping a non-existing passenger.
+        assertFalse(mCarUserService.stopPassenger(passenger2Id));
+    }
+
+    private static void associateParentChild(UserInfo parent, UserInfo child) {
+        parent.profileGroupId = parent.id;
+        child.profileGroupId = parent.id;
+    }
+
+    private static List<UserInfo> prepareUserList() {
+        List<UserInfo> users = new ArrayList<>(Arrays.asList(
+                new UserInfo(10, "test10", UserInfo.FLAG_PRIMARY | UserInfo.FLAG_ADMIN),
+                new UserInfo(11, "test11", NO_USER_INFO_FLAGS),
+                new UserInfo(12, "test12", UserInfo.FLAG_MANAGED_PROFILE),
+                new UserInfo(13, "test13", NO_USER_INFO_FLAGS),
+                new UserInfo(14, "test14", UserInfo.FLAG_GUEST),
+                new UserInfo(15, "test15", UserInfo.FLAG_EPHEMERAL),
+                new UserInfo(16, "test16", UserInfo.FLAG_DISABLED),
+                new UserInfo(17, "test17", UserInfo.FLAG_MANAGED_PROFILE),
+                new UserInfo(18, "test18", UserInfo.FLAG_MANAGED_PROFILE)
+        ));
+        // Parent: test10, child: test12
+        associateParentChild(users.get(0), users.get(2));
+        // Parent: test13, child: test17
+        associateParentChild(users.get(3), users.get(7));
+        // Parent: test13, child: test18
+        associateParentChild(users.get(3), users.get(8));
+        return users;
+    }
+
+    @Test
+    public void testGetAllPossibleDrivers() {
+        Set<Integer> expected = new HashSet<Integer>(Arrays.asList(10, 11, 13, 14));
+        doReturn(prepareUserList()).when(mMockedUserManager).getUsers(true);
+        for (UserInfo user : mCarUserService.getAllDrivers()) {
+            assertTrue(expected.contains(user.id));
+            expected.remove(user.id);
+        }
+        assertEquals(0, expected.size());
+    }
+
+    @Test
+    public void testGetAllPassengers() {
+        SparseArray<HashSet<Integer>> testCases = new SparseArray<HashSet<Integer>>() {
+            {
+                put(0, new HashSet<Integer>());
+                put(10, new HashSet<Integer>(Arrays.asList(12)));
+                put(11, new HashSet<Integer>());
+                put(13, new HashSet<Integer>(Arrays.asList(17, 18)));
+            }
+        };
+        for (int i = 0; i < testCases.size(); i++) {
+            doReturn(prepareUserList()).when(mMockedUserManager).getUsers(true);
+            List<UserInfo> passengers = mCarUserService.getPassengers(testCases.keyAt(i));
+            HashSet<Integer> expected = testCases.valueAt(i);
+            for (UserInfo user : passengers) {
+                assertTrue(expected.contains(user.id));
+                expected.remove(user.id);
+            }
+            assertEquals(0, expected.size());
+        }
+    }
+
+    @Test
+    public void testGetUserInfo_defaultResponse() throws Exception {
+        mockCurrentUsers(mAdminUser);
+
+        mGetUserInfoResponse.action = InitialUserInfoResponseAction.DEFAULT;
+        mockGetInitialInfo(mAdminUser.id, mGetUserInfoResponse);
+
+        mCarUserService.getInitialUserInfo(mGetUserInfoRequestType, mAsyncCallTimeoutMs, mReceiver);
+
+        assertThat(mReceiver.getResultCode()).isEqualTo(HalCallback.STATUS_OK);
+        assertThat(mReceiver.getResultData()).isNull();
+    }
+
+    @Test
+    public void testGetUserInfo_switchUserResponse() throws Exception {
+        int switchUserId = mGuestUser.id;
+        mockCurrentUsers(mAdminUser);
+
+        mGetUserInfoResponse.action = InitialUserInfoResponseAction.SWITCH;
+        mGetUserInfoResponse.userToSwitchOrCreate.userId = switchUserId;
+        mockGetInitialInfo(mAdminUser.id, mGetUserInfoResponse);
+
+        mCarUserService.getInitialUserInfo(mGetUserInfoRequestType, mAsyncCallTimeoutMs, mReceiver);
+
+        assertThat(mReceiver.getResultCode()).isEqualTo(HalCallback.STATUS_OK);
+        Bundle resultData = mReceiver.getResultData();
+        assertThat(resultData).isNotNull();
+        assertInitialInfoAction(resultData, mGetUserInfoResponse.action);
+        assertUserId(resultData, switchUserId);
+        assertNoUserFlags(resultData);
+        assertNoUserName(resultData);
+    }
+
+
+    @Test
+    public void testGetUserInfo_createUserResponse() throws Exception {
+        int newUserFlags = 42;
+        String newUserName = "TheDude";
+
+        mockCurrentUsers(mAdminUser);
+
+        mGetUserInfoResponse.action = InitialUserInfoResponseAction.CREATE;
+        mGetUserInfoResponse.userToSwitchOrCreate.flags = newUserFlags;
+        mGetUserInfoResponse.userNameToCreate = newUserName;
+        mockGetInitialInfo(mAdminUser.id, mGetUserInfoResponse);
+
+        mCarUserService.getInitialUserInfo(mGetUserInfoRequestType, mAsyncCallTimeoutMs, mReceiver);
+
+        assertThat(mReceiver.getResultCode()).isEqualTo(HalCallback.STATUS_OK);
+        Bundle resultData = mReceiver.getResultData();
+        assertThat(resultData).isNotNull();
+        assertInitialInfoAction(resultData, mGetUserInfoResponse.action);
+        assertNoUserId(resultData);
+        assertUserFlags(resultData, newUserFlags);
+        assertUserName(resultData, newUserName);
+    }
+
+    /**
+     * Mock calls that generate a {@code UsersInfo}.
+     */
+    private void mockCurrentUsers(@NonNull UserInfo user) throws Exception {
+        when(mMockedIActivityManager.getCurrentUser()).thenReturn(user);
+        when(mMockedUserManager.getUsers()).thenReturn(mExistingUsers);
+    }
+
+    private void mockGetInitialInfo(@UserIdInt int currentUserId,
+            @NonNull InitialUserInfoResponse response) {
+        UsersInfo usersInfo = newUsersInfo(currentUserId);
+        doAnswer((invocation) -> {
+            Log.d(TAG, "Answering " + invocation + " with " + response);
+            @SuppressWarnings("unchecked")
+            HalCallback<InitialUserInfoResponse> callback =
+                    (HalCallback<InitialUserInfoResponse>) invocation.getArguments()[3];
+            callback.onResponse(HalCallback.STATUS_OK, response);
+            return null;
+        }).when(mUserHal).getInitialUserInfo(eq(mGetUserInfoRequestType), eq(mAsyncCallTimeoutMs),
+                eq(usersInfo), notNull());
+    }
+
+    @NonNull
+    private UsersInfo newUsersInfo(@UserIdInt int currentUserId) {
+        UsersInfo infos = new UsersInfo();
+        infos.numberUsers = mExistingUsers.size();
+        boolean foundCurrentUser = false;
+        for (UserInfo info : mExistingUsers) {
+            android.hardware.automotive.vehicle.V2_0.UserInfo existingUser =
+                    new android.hardware.automotive.vehicle.V2_0.UserInfo();
+            int flags = UserFlags.NONE;
+            if (info.id == UserHandle.USER_SYSTEM) {
+                flags |= UserFlags.SYSTEM;
+            }
+            if (info.isAdmin()) {
+                flags |= UserFlags.ADMIN;
+            }
+            if (info.isGuest()) {
+                flags |= UserFlags.GUEST;
+            }
+            if (info.isEphemeral()) {
+                flags |= UserFlags.EPHEMERAL;
+            }
+            existingUser.userId = info.id;
+            existingUser.flags = flags;
+            if (info.id == currentUserId) {
+                foundCurrentUser = true;
+                infos.currentUser.userId = info.id;
+                infos.currentUser.flags = flags;
+            }
+            infos.existingUsers.add(existingUser);
+        }
+        Preconditions.checkArgument(foundCurrentUser,
+                "no user with id " + currentUserId + " on " + mExistingUsers);
+        return infos;
+    }
+
+    private void assertUserId(@NonNull Bundle resultData, int expectedUserId) {
+        int actualUserId = resultData.getInt(CarUserService.BUNDLE_USER_ID);
+        assertWithMessage("wrong user id on bundle extra %s", CarUserService.BUNDLE_USER_ID)
+                .that(actualUserId).isEqualTo(expectedUserId);
+    }
+
+    private void assertNoUserId(@NonNull Bundle resultData) {
+        assertNoExtra(resultData, CarUserService.BUNDLE_USER_ID);
+    }
+
+    private void assertUserFlags(@NonNull Bundle resultData, int expectedUserFlags) {
+        int actualUserFlags = resultData.getInt(CarUserService.BUNDLE_USER_FLAGS);
+        assertWithMessage("wrong user flags on bundle extra %s", CarUserService.BUNDLE_USER_FLAGS)
+                .that(actualUserFlags).isEqualTo(expectedUserFlags);
+    }
+
+    private void assertNoUserFlags(@NonNull Bundle resultData) {
+        assertNoExtra(resultData, CarUserService.BUNDLE_USER_FLAGS);
+    }
+
+    private void assertUserName(@NonNull Bundle resultData, @NonNull String expectedName) {
+        String actualName = resultData.getString(CarUserService.BUNDLE_USER_NAME);
+        assertWithMessage("wrong user name on bundle extra %s",
+                CarUserService.BUNDLE_USER_FLAGS).that(actualName).isEqualTo(expectedName);
+    }
+
+    private void assertNoUserName(@NonNull Bundle resultData) {
+        assertNoExtra(resultData, CarUserService.BUNDLE_USER_NAME);
+    }
+
+    private void assertNoExtra(@NonNull Bundle resultData, @NonNull String extra) {
+        Object value = resultData.get(extra);
+        assertWithMessage("should not have extra %s", extra).that(value).isNull();
+    }
+
+    private void assertInitialInfoAction(@NonNull Bundle resultData, int expectedAction) {
+        int actualAction = resultData.getInt(CarUserService.BUNDLE_INITIAL_INFO_ACTION);
+        assertWithMessage("wrong request type on bundle extra %s",
+                CarUserService.BUNDLE_INITIAL_INFO_ACTION).that(actualAction)
+            .isEqualTo(expectedAction);
+    }
+
+    static final class FakeCarOccupantZoneService {
+        private final SparseArray<Integer> mZoneUserMap = new SparseArray<Integer>();
+        private final CarUserService.ZoneUserBindingHelper mZoneUserBindigHelper =
+                new CarUserService.ZoneUserBindingHelper() {
+                    @Override
+                    @NonNull
+                    public List<OccupantZoneInfo> getOccupantZones(
+                            @OccupantTypeEnum int occupantType) {
+                        return null;
+                    }
+
+                    @Override
+                    public boolean assignUserToOccupantZone(@UserIdInt int userId, int zoneId) {
+                        if (mZoneUserMap.get(zoneId) != null) {
+                            return false;
+                        }
+                        mZoneUserMap.put(zoneId, userId);
+                        return true;
+                    }
+
+                    @Override
+                    public boolean unassignUserFromOccupantZone(@UserIdInt int userId) {
+                        for (int index = 0; index < mZoneUserMap.size(); index++) {
+                            if (mZoneUserMap.valueAt(index) == userId) {
+                                mZoneUserMap.removeAt(index);
+                                break;
+                            }
+                        }
+                        return true;
+                    }
+
+                    @Override
+                    public boolean isPassengerDisplayAvailable() {
+                        return true;
+                    }
+                };
+
+        FakeCarOccupantZoneService(CarUserService carUserService) {
+            carUserService.setZoneUserBindingHelper(mZoneUserBindigHelper);
+        }
+    }
+
+
+    // TODO(b/148403316): Refactor to use common fake settings provider
+    private void mockSettingsGlobal() {
+        when(Settings.Global.putInt(any(), eq(CarSettings.Global.DEFAULT_USER_RESTRICTIONS_SET),
+                anyInt())).thenAnswer(invocation -> {
+                            int value = (int) invocation.getArguments()[2];
+                            when(Settings.Global.getInt(any(),
+                                    eq(CarSettings.Global.DEFAULT_USER_RESTRICTIONS_SET), anyInt()))
+                                    .thenReturn(value);
+                            return null;
+                        }
+        );
+    }
+
     private void putSettingsInt(String key, int value) {
         Settings.Global.putInt(InstrumentationRegistry.getTargetContext().getContentResolver(),
                 key, value);
@@ -389,11 +800,149 @@
                 key, /* default= */ 0);
     }
 
-    private UserInfo mockAdmin(int adminId) {
-        UserInfo admin = new UserInfo(adminId, DEFAULT_ADMIN_NAME,
-                UserInfo.FLAG_ADMIN);
-        doReturn(admin).when(mCarUserManagerHelper).createNewAdminUser();
+    // TODO(b/149099817): move stuff below to common code
 
-        return admin;
+    /**
+     * Builder for {@link UserInfo} objects.
+     *
+     */
+    public static final class UserInfoBuilder {
+
+        @UserIdInt
+        private final int mUserId;
+
+        @Nullable
+        private String mName;
+
+        private boolean mGuest;
+        private boolean mEphemeral;
+        private boolean mAdmin;
+
+        /**
+         * Default constructor.
+         */
+        public UserInfoBuilder(@UserIdInt int userId) {
+            mUserId = userId;
+        }
+
+        /**
+         * Sets the user name.
+         */
+        @NonNull
+        public UserInfoBuilder setName(@Nullable String name) {
+            mName = name;
+            return this;
+        }
+
+        /**
+         * Sets whether the user is a guest.
+         */
+        @NonNull
+        public UserInfoBuilder setGuest(boolean guest) {
+            mGuest = guest;
+            return this;
+        }
+
+        /**
+         * Sets whether the user is ephemeral.
+         */
+        @NonNull
+        public UserInfoBuilder setEphemeral(boolean ephemeral) {
+            mEphemeral = ephemeral;
+            return this;
+        }
+
+        /**
+         * Sets whether the user is an admin.
+         */
+        @NonNull
+        public UserInfoBuilder setAdmin(boolean admin) {
+            mAdmin = admin;
+            return this;
+        }
+
+        /**
+         * Creates a new {@link UserInfo}.
+         */
+        @NonNull
+        public UserInfo build() {
+            int flags = 0;
+            if (mEphemeral) {
+                flags |= UserInfo.FLAG_EPHEMERAL;
+            }
+            if (mAdmin) {
+                flags |= UserInfo.FLAG_ADMIN;
+            }
+            UserInfo info = new UserInfo(mUserId, mName, flags);
+            if (mGuest) {
+                info.userType = UserManager.USER_TYPE_FULL_GUEST;
+            }
+            return info;
+        }
+
+        /**
+         * Creates a new {@link UserInfo} for a system user.
+         */
+        @NonNull
+        public static UserInfo newSystemUserInfo() {
+            UserInfo info = new UserInfo();
+            info.id = UserHandle.USER_SYSTEM;
+            return info;
+        }
+    }
+
+    /**
+     * Implementation of {@link IResultReceiver} that blocks waiting for the result.
+     */
+    public static final class BlockingResultReceiver extends IResultReceiver.Stub {
+
+        private final CountDownLatch mLatch = new CountDownLatch(1);
+        private final long mTimeoutMs;
+
+        private int mResultCode;
+        @Nullable private Bundle mResultData;
+
+        /**
+         * Default constructor.
+         *
+         * @param timeoutMs how long to wait for before failing.
+         */
+        public BlockingResultReceiver(long timeoutMs) {
+            mTimeoutMs = timeoutMs;
+        }
+
+        @Override
+        public void send(int resultCode, Bundle resultData) {
+            Log.d(TAG, "send() received: code=" + resultCode + ", data=" + resultData + ", count="
+                    + mLatch.getCount());
+            Preconditions.checkState(mLatch.getCount() == 1,
+                    "send() already called (code=" + mResultCode + ", data=" + mResultData);
+            mResultCode = resultCode;
+            mResultData = resultData;
+            mLatch.countDown();
+        }
+
+        private void assertCalled() throws InterruptedException {
+            boolean called = mLatch.await(mTimeoutMs, TimeUnit.MILLISECONDS);
+            Log.d(TAG, "assertCalled(): " + called);
+            assertWithMessage("receiver not called in %sms", mTimeoutMs).that(called).isTrue();
+        }
+
+        /**
+         * Gets the {@code resultCode} or fails if it times out before {@code send()} is called.
+         */
+        public int getResultCode() throws InterruptedException {
+            assertCalled();
+            return mResultCode;
+        }
+
+        /**
+         * Gets the {@code resultData} or fails if it times out before {@code send()} is called.
+         */
+        @Nullable
+        public Bundle getResultData() throws InterruptedException {
+            assertCalled();
+            return mResultData;
+        }
     }
 }
diff --git a/tests/carservice_unit_test/src/com/android/car/vms/VmsBrokerServiceTest.java b/tests/carservice_unit_test/src/com/android/car/vms/VmsBrokerServiceTest.java
new file mode 100644
index 0000000..ffc3ac7
--- /dev/null
+++ b/tests/carservice_unit_test/src/com/android/car/vms/VmsBrokerServiceTest.java
@@ -0,0 +1,2836 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.vms;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.testng.Assert.assertThrows;
+
+import static java.util.Arrays.asList;
+import static java.util.Collections.emptySet;
+
+import android.car.vms.IVmsClientCallback;
+import android.car.vms.VmsAssociatedLayer;
+import android.car.vms.VmsAvailableLayers;
+import android.car.vms.VmsLayer;
+import android.car.vms.VmsLayerDependency;
+import android.car.vms.VmsProviderInfo;
+import android.car.vms.VmsRegistrationInfo;
+import android.car.vms.VmsSubscriptionState;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.UserHandle;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.car.stats.CarStatsService;
+import com.android.car.stats.VmsClientLogger;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+@RunWith(MockitoJUnitRunner.class)
+@SmallTest
+public class VmsBrokerServiceTest {
+    private static final int USER_ID = 10;
+
+    private static final String TEST_PACKAGE1 = "test.package1";
+    private static final String TEST_PACKAGE2 = "test.package2";
+
+    private static final int TEST_APP_ID1 = 12345;
+    private static final int TEST_APP_ID2 = 54321;
+    private static final int TEST_APP_UID1 = UserHandle.getUid(USER_ID, TEST_APP_ID1);
+    private static final int TEST_APP_UID2 = UserHandle.getUid(USER_ID, TEST_APP_ID2);
+    private static final int NO_SUBSCRIBERS_UID = -1;
+
+    private static final VmsProviderInfo PROVIDER_INFO1 =
+            new VmsProviderInfo(new byte[]{1, 2, 3, 4, 5});
+    private static final VmsProviderInfo PROVIDER_INFO2 =
+            new VmsProviderInfo(new byte[]{5, 4, 3, 2, 1});
+
+    private static final VmsAvailableLayers DEFAULT_AVAILABLE_LAYERS =
+            new VmsAvailableLayers(0, emptySet());
+    private static final VmsSubscriptionState DEFAULT_SUBSCRIPTION_STATE =
+            new VmsSubscriptionState(0, emptySet(), emptySet());
+
+    private static final VmsLayer LAYER1 = new VmsLayer(1, 1, 1);
+    private static final VmsLayer LAYER2 = new VmsLayer(2, 1, 1);
+    private static final VmsLayer LAYER3 = new VmsLayer(3, 1, 1);
+
+    private static final byte[] PAYLOAD = {1, 2, 3, 4, 5, 6, 7, 8};
+
+    @Mock
+    private Context mContext;
+    @Mock
+    private PackageManager mPackageManager;
+    @Mock
+    private CarStatsService mStatsService;
+
+    @Mock
+    private IVmsClientCallback mClientCallback1;
+    @Mock
+    private Binder mClientBinder1;
+    @Mock
+    private VmsClientLogger mClientLog1;
+
+    @Mock
+    private IVmsClientCallback mClientCallback2;
+    @Mock
+    private Binder mClientBinder2;
+    @Mock
+    private VmsClientLogger mClientLog2;
+
+    @Mock
+    private VmsClientLogger mNoSubscribersLog;
+
+
+    private final IBinder mClientToken1 = new Binder();
+    private final IBinder mClientToken2 = new Binder();
+
+    private VmsNewBrokerService mBrokerService;
+    private int mCallingAppUid;
+
+    @Before
+    public void setUp() throws Exception {
+        when(mContext.getPackageManager()).thenReturn(mPackageManager);
+        mBrokerService = new VmsNewBrokerService(mContext, mStatsService, () -> mCallingAppUid);
+
+        when(mPackageManager.getNameForUid(TEST_APP_UID1)).thenReturn(TEST_PACKAGE1);
+        when(mPackageManager.getNameForUid(TEST_APP_UID2)).thenReturn(TEST_PACKAGE2);
+
+        when(mStatsService.getVmsClientLogger(TEST_APP_UID1)).thenReturn(mClientLog1);
+        when(mStatsService.getVmsClientLogger(TEST_APP_UID2)).thenReturn(mClientLog2);
+        when(mStatsService.getVmsClientLogger(NO_SUBSCRIBERS_UID)).thenReturn(mNoSubscribersLog);
+
+        when(mClientCallback1.asBinder()).thenReturn(mClientBinder1);
+        when(mClientCallback2.asBinder()).thenReturn(mClientBinder2);
+
+        mCallingAppUid = TEST_APP_UID1;
+    }
+
+    @Test
+    public void testRegister() {
+        VmsRegistrationInfo registrationInfo =
+                mBrokerService.registerClient(mClientToken1, mClientCallback1, false);
+
+        verify(mClientLog1).logConnectionState(VmsClientLogger.ConnectionState.CONNECTED);
+        assertThat(registrationInfo.getAvailableLayers()).isEqualTo(DEFAULT_AVAILABLE_LAYERS);
+        assertThat(registrationInfo.getSubscriptionState()).isEqualTo(DEFAULT_SUBSCRIPTION_STATE);
+    }
+
+    @Test
+    public void testRegister_LegacyClient() {
+        VmsRegistrationInfo registrationInfo =
+                mBrokerService.registerClient(mClientToken1, mClientCallback1, true);
+
+        verify(mClientLog1).logConnectionState(VmsClientLogger.ConnectionState.CONNECTED);
+        assertThat(registrationInfo.getAvailableLayers()).isEqualTo(DEFAULT_AVAILABLE_LAYERS);
+        assertThat(registrationInfo.getSubscriptionState()).isEqualTo(DEFAULT_SUBSCRIPTION_STATE);
+    }
+
+    @Test
+    public void testRegister_TwoClients_OneProcess() {
+        VmsRegistrationInfo registrationInfo =
+                mBrokerService.registerClient(mClientToken1, mClientCallback1, false);
+        VmsRegistrationInfo registrationInfo2 =
+                mBrokerService.registerClient(mClientToken2, mClientCallback2, false);
+
+        verify(mClientLog1, times(2))
+                .logConnectionState(VmsClientLogger.ConnectionState.CONNECTED);
+        assertThat(registrationInfo).isEqualTo(registrationInfo2);
+    }
+
+    @Test
+    public void testRegister_TwoClients_TwoProcesses() {
+        VmsRegistrationInfo registrationInfo =
+                mBrokerService.registerClient(mClientToken1, mClientCallback1, false);
+        mCallingAppUid = TEST_APP_UID2;
+        VmsRegistrationInfo registrationInfo2 =
+                mBrokerService.registerClient(mClientToken2, mClientCallback2, false);
+
+        verify(mClientLog1).logConnectionState(VmsClientLogger.ConnectionState.CONNECTED);
+        verify(mClientLog2).logConnectionState(VmsClientLogger.ConnectionState.CONNECTED);
+        assertThat(registrationInfo).isEqualTo(registrationInfo2);
+    }
+
+    @Test
+    public void testRegister_ReceivesCurrentLayerAvailabilityAndSubscriptions() {
+        registerClient(mClientToken1, mClientCallback1);
+        int providerId = mBrokerService.registerProvider(mClientToken1, PROVIDER_INFO1);
+        mBrokerService.setProviderOfferings(mClientToken1, providerId, asList(
+                new VmsLayerDependency(LAYER1)));
+        mBrokerService.setSubscriptions(mClientToken1, asList(
+                new VmsAssociatedLayer(LAYER1, emptySet()),
+                new VmsAssociatedLayer(LAYER2, asSet(12345))
+        ));
+
+        VmsRegistrationInfo registrationInfo =
+                mBrokerService.registerClient(mClientToken2, mClientCallback2, false);
+
+        VmsAvailableLayers expectedLayers = new VmsAvailableLayers(1, asSet(
+                new VmsAssociatedLayer(LAYER1,
+                        asSet(providerId)))
+        );
+        VmsSubscriptionState expectedSubscriptions = new VmsSubscriptionState(1,
+                asSet(LAYER1),
+                asSet(new VmsAssociatedLayer(LAYER2, asSet(12345)))
+        );
+        VmsRegistrationInfo expectedRegistrationInfo =
+                new VmsRegistrationInfo(expectedLayers, expectedSubscriptions);
+        assertThat(registrationInfo).isEqualTo(expectedRegistrationInfo);
+    }
+
+    @Test
+    public void testRegisterProvider_UnknownClient() {
+        assertThrows(
+                IllegalStateException.class,
+                () -> mBrokerService.registerProvider(new Binder(), PROVIDER_INFO1));
+    }
+
+    @Test
+    public void testRegisterProvider_SameIdForSameInfo() {
+        registerClient(mClientToken1, mClientCallback1);
+
+        int providerId = mBrokerService.registerProvider(mClientToken1, PROVIDER_INFO1);
+        int providerId2 = mBrokerService.registerProvider(mClientToken1, PROVIDER_INFO1);
+
+        assertThat(providerId).isEqualTo(providerId2);
+    }
+
+    @Test
+    public void testRegisterProvider_SameIdForSameInfo_MultipleClients() {
+        registerClient(mClientToken1, mClientCallback1);
+        registerClient(mClientToken2, mClientCallback2);
+
+        int providerId = mBrokerService.registerProvider(mClientToken1, PROVIDER_INFO1);
+        int providerId2 = mBrokerService.registerProvider(mClientToken2, PROVIDER_INFO1);
+
+        assertThat(providerId).isEqualTo(providerId2);
+    }
+
+    @Test
+    public void testRegisterProvider_DifferentIdForDifferentInfo() {
+        registerClient(mClientToken1, mClientCallback1);
+
+        int providerId = mBrokerService.registerProvider(mClientToken1, PROVIDER_INFO1);
+        int providerId2 = mBrokerService.registerProvider(mClientToken1, PROVIDER_INFO2);
+
+        assertThat(providerId).isNotEqualTo(providerId2);
+    }
+
+    @Test
+    public void testGetProviderInfo_UnknownClient() {
+        registerClient(mClientToken1, mClientCallback1);
+        int providerId = mBrokerService.registerProvider(mClientToken1, PROVIDER_INFO1);
+
+        assertThrows(
+                IllegalStateException.class,
+                () -> mBrokerService.getProviderInfo(new Binder(), providerId));
+    }
+
+    @Test
+    public void testGetProviderInfo_UnknownId() {
+        registerClient(mClientToken1, mClientCallback1);
+
+        assertThat(mBrokerService.getProviderInfo(mClientToken1, 12345).getDescription()).isNull();
+    }
+
+    @Test
+    public void testGetProviderInfo_RegisteredProvider() {
+        registerClient(mClientToken1, mClientCallback1);
+
+        int providerId = mBrokerService.registerProvider(mClientToken1, PROVIDER_INFO1);
+
+        assertThat(mBrokerService.getProviderInfo(mClientToken1, providerId))
+                .isEqualTo(PROVIDER_INFO1);
+    }
+
+    @Test
+    public void testSetSubscriptions_UnknownClient() {
+        assertThrows(
+                IllegalStateException.class,
+                () -> mBrokerService.setSubscriptions(new Binder(), asList()));
+    }
+
+    @Test
+    public void testSetSubscriptions() throws Exception {
+        registerClient(mClientToken1, mClientCallback1);
+
+        mBrokerService.setSubscriptions(mClientToken1, asList(
+                new VmsAssociatedLayer(LAYER1, emptySet())
+        ));
+
+        VmsSubscriptionState expectedSubscriptions = new VmsSubscriptionState(1,
+                asSet(LAYER1),
+                emptySet());
+        verifySubscriptionState(mClientCallback1, expectedSubscriptions);
+    }
+
+    @Test
+    public void testSetSubscriptions_MultipleClients() throws Exception {
+        registerClient(mClientToken1, mClientCallback1);
+        registerClient(mClientToken2, mClientCallback2);
+
+        mBrokerService.setSubscriptions(mClientToken1, asList(
+                new VmsAssociatedLayer(LAYER1, emptySet())
+        ));
+        mBrokerService.setSubscriptions(mClientToken2, asList(
+                new VmsAssociatedLayer(LAYER1, emptySet())
+        ));
+
+        VmsSubscriptionState expectedSubscriptions = new VmsSubscriptionState(1,
+                asSet(LAYER1),
+                emptySet());
+        verifySubscriptionState(mClientCallback1, expectedSubscriptions);
+        verifySubscriptionState(mClientCallback2, expectedSubscriptions);
+    }
+
+    @Test
+    public void testSetSubscriptions_OverwriteSubscription() throws Exception {
+        registerClient(mClientToken1, mClientCallback1);
+
+        mBrokerService.setSubscriptions(mClientToken1, asList(
+                new VmsAssociatedLayer(LAYER1, emptySet())
+        ));
+        mBrokerService.setSubscriptions(mClientToken1, asList(
+                new VmsAssociatedLayer(LAYER2, emptySet())
+        ));
+
+        VmsSubscriptionState expectedSubscriptions = new VmsSubscriptionState(2,
+                asSet(LAYER2),
+                emptySet());
+        verifySubscriptionState(mClientCallback1, expectedSubscriptions);
+    }
+
+    @Test
+    public void testSetSubscriptions_OverwriteSubscription_MultipleClients() throws Exception {
+        registerClient(mClientToken1, mClientCallback1);
+        registerClient(mClientToken2, mClientCallback2);
+
+        mBrokerService.setSubscriptions(mClientToken1, asList(
+                new VmsAssociatedLayer(LAYER1, emptySet())
+        ));
+        mBrokerService.setSubscriptions(mClientToken2, asList(
+                new VmsAssociatedLayer(LAYER2, emptySet())
+        ));
+        mBrokerService.setSubscriptions(mClientToken2, asList(
+                new VmsAssociatedLayer(LAYER3, emptySet())
+        ));
+
+        VmsSubscriptionState expectedSubscriptions = new VmsSubscriptionState(3,
+                asSet(LAYER1, LAYER3),
+                emptySet());
+        verifySubscriptionState(mClientCallback1, expectedSubscriptions);
+        verifySubscriptionState(mClientCallback2, expectedSubscriptions);
+    }
+
+    @Test
+    public void testSetSubscriptions_RemoveSubscription() throws Exception {
+        registerClient(mClientToken1, mClientCallback1);
+
+        mBrokerService.setSubscriptions(mClientToken1, asList(
+                new VmsAssociatedLayer(LAYER1, emptySet())
+        ));
+        mBrokerService.setSubscriptions(mClientToken1, asList());
+
+        VmsSubscriptionState expectedSubscriptions = new VmsSubscriptionState(2,
+                emptySet(),
+                emptySet());
+        verifySubscriptionState(mClientCallback1, expectedSubscriptions);
+    }
+
+    @Test
+    public void testSetSubscriptions_RemoveSubscription_MultipleClients() throws Exception {
+        registerClient(mClientToken1, mClientCallback1);
+        registerClient(mClientToken2, mClientCallback2);
+
+        mBrokerService.setSubscriptions(mClientToken1, asList(
+                new VmsAssociatedLayer(LAYER1, emptySet())
+        ));
+        mBrokerService.setSubscriptions(mClientToken2, asList(
+                new VmsAssociatedLayer(LAYER2, emptySet())
+        ));
+        mBrokerService.setSubscriptions(mClientToken2, asList());
+
+        VmsSubscriptionState expectedSubscriptions = new VmsSubscriptionState(3,
+                asSet(LAYER1),
+                emptySet());
+        verifySubscriptionState(mClientCallback1, expectedSubscriptions);
+        verifySubscriptionState(mClientCallback2, expectedSubscriptions);
+    }
+
+    @Test
+    public void testSetSubscriptions_RemoveSubscription_MultipleClients_SameLayer()
+            throws Exception {
+        registerClient(mClientToken1, mClientCallback1);
+        registerClient(mClientToken2, mClientCallback2);
+
+        mBrokerService.setSubscriptions(mClientToken1, asList(
+                new VmsAssociatedLayer(LAYER1, emptySet())
+        ));
+        mBrokerService.setSubscriptions(mClientToken2, asList(
+                new VmsAssociatedLayer(LAYER1, emptySet())
+        ));
+        mBrokerService.setSubscriptions(mClientToken2, asList());
+
+        VmsSubscriptionState expectedSubscriptions = new VmsSubscriptionState(1,
+                asSet(LAYER1),
+                emptySet());
+        verifySubscriptionState(mClientCallback1, expectedSubscriptions);
+        verifySubscriptionState(mClientCallback2, expectedSubscriptions);
+    }
+
+    @Test
+    public void testSetSubscriptions_RemoveSubscription_OnUnregister_MultipleClients()
+            throws Exception {
+        registerClient(mClientToken1, mClientCallback1);
+        registerClient(mClientToken2, mClientCallback2);
+
+        mBrokerService.setSubscriptions(mClientToken1, asList(
+                new VmsAssociatedLayer(LAYER1, emptySet())
+        ));
+        mBrokerService.setSubscriptions(mClientToken2, asList(
+                new VmsAssociatedLayer(LAYER2, emptySet())
+        ));
+        mBrokerService.unregisterClient(mClientToken2);
+
+        VmsSubscriptionState expectedSubscriptions = new VmsSubscriptionState(3,
+                asSet(LAYER1),
+                emptySet());
+        verifySubscriptionState(mClientCallback1, expectedSubscriptions);
+    }
+
+    @Test
+    public void testSetSubscriptions_RemoveSubscription_OnUnregister_MultipleClients_SameLayer()
+            throws Exception {
+        registerClient(mClientToken1, mClientCallback1);
+        registerClient(mClientToken2, mClientCallback2);
+
+        mBrokerService.setSubscriptions(mClientToken1, asList(
+                new VmsAssociatedLayer(LAYER1, emptySet())
+        ));
+        mBrokerService.setSubscriptions(mClientToken2, asList(
+                new VmsAssociatedLayer(LAYER1, emptySet())
+        ));
+        mBrokerService.unregisterClient(mClientToken2);
+
+        VmsSubscriptionState expectedSubscriptions = new VmsSubscriptionState(1,
+                asSet(LAYER1),
+                emptySet());
+        verifySubscriptionState(mClientCallback1, expectedSubscriptions);
+    }
+
+    @Test
+    public void testSetSubscriptions_RemoveSubscription_OnDisconnect_MultipleClients()
+            throws Exception {
+        registerClient(mClientToken1, mClientCallback1);
+        registerClient(mClientToken2, mClientCallback2);
+
+        mBrokerService.setSubscriptions(mClientToken1, asList(
+                new VmsAssociatedLayer(LAYER1, emptySet())
+        ));
+        mBrokerService.setSubscriptions(mClientToken2, asList(
+                new VmsAssociatedLayer(LAYER2, emptySet())
+        ));
+        disconnectClient(mClientCallback2);
+
+        VmsSubscriptionState expectedSubscriptions = new VmsSubscriptionState(3,
+                asSet(LAYER1),
+                emptySet());
+        verifySubscriptionState(mClientCallback1, expectedSubscriptions);
+    }
+
+    @Test
+    public void testSetSubscriptions_RemoveSubscription_OnDisconnect_MultipleClients_SameLayer()
+            throws Exception {
+        registerClient(mClientToken1, mClientCallback1);
+        registerClient(mClientToken2, mClientCallback2);
+
+        mBrokerService.setSubscriptions(mClientToken1, asList(
+                new VmsAssociatedLayer(LAYER1, emptySet())
+        ));
+        mBrokerService.setSubscriptions(mClientToken2, asList(
+                new VmsAssociatedLayer(LAYER1, emptySet())
+        ));
+        disconnectClient(mClientCallback2);
+
+        VmsSubscriptionState expectedSubscriptions = new VmsSubscriptionState(1,
+                asSet(LAYER1),
+                emptySet());
+        verifySubscriptionState(mClientCallback1, expectedSubscriptions);
+    }
+
+
+    @Test
+    public void testSetSubscriptions_MultipleLayers() throws Exception {
+        registerClient(mClientToken1, mClientCallback1);
+
+        mBrokerService.setSubscriptions(mClientToken1, asList(
+                new VmsAssociatedLayer(LAYER1, emptySet()),
+                new VmsAssociatedLayer(LAYER2, emptySet())
+        ));
+
+        VmsSubscriptionState expectedSubscriptions = new VmsSubscriptionState(1,
+                asSet(LAYER1, LAYER2),
+                emptySet());
+        verifySubscriptionState(mClientCallback1, expectedSubscriptions);
+    }
+
+    @Test
+    public void testSetSubscriptions_MultipleLayers_MultipleClients() throws Exception {
+        registerClient(mClientToken1, mClientCallback1);
+        registerClient(mClientToken2, mClientCallback2);
+
+        mBrokerService.setSubscriptions(mClientToken1, asList(
+                new VmsAssociatedLayer(LAYER1, emptySet())
+        ));
+        mBrokerService.setSubscriptions(mClientToken2, asList(
+                new VmsAssociatedLayer(LAYER2, emptySet()),
+                new VmsAssociatedLayer(LAYER3, emptySet())
+        ));
+
+        VmsSubscriptionState expectedSubscriptions = new VmsSubscriptionState(2,
+                asSet(LAYER1, LAYER2, LAYER3),
+                emptySet());
+        verifySubscriptionState(mClientCallback1, expectedSubscriptions);
+        verifySubscriptionState(mClientCallback2, expectedSubscriptions);
+    }
+
+    @Test
+    public void testSetSubscriptions_LayerAndProvider() throws Exception {
+        registerClient(mClientToken1, mClientCallback1);
+
+        mBrokerService.setSubscriptions(mClientToken1, asList(
+                new VmsAssociatedLayer(LAYER1, asSet(12345))
+        ));
+
+        VmsSubscriptionState expectedSubscriptions = new VmsSubscriptionState(1,
+                emptySet(),
+                asSet(new VmsAssociatedLayer(LAYER1, asSet(12345))));
+        verifySubscriptionState(mClientCallback1, expectedSubscriptions);
+    }
+
+    @Test
+    public void testSetSubscriptions_LayerAndProvider_MultipleClients() throws Exception {
+        registerClient(mClientToken1, mClientCallback1);
+        registerClient(mClientToken2, mClientCallback2);
+
+        mBrokerService.setSubscriptions(mClientToken1, asList(
+                new VmsAssociatedLayer(LAYER1, asSet(12345))
+        ));
+        mBrokerService.setSubscriptions(mClientToken2, asList(
+                new VmsAssociatedLayer(LAYER1, asSet(54321))
+        ));
+
+        VmsSubscriptionState expectedSubscriptions = new VmsSubscriptionState(2,
+                emptySet(),
+                asSet(new VmsAssociatedLayer(LAYER1, asSet(12345, 54321))));
+        verifySubscriptionState(mClientCallback1, expectedSubscriptions);
+        verifySubscriptionState(mClientCallback2, expectedSubscriptions);
+    }
+
+    @Test
+    public void testSetSubscriptions_LayerAndProvider_OverwriteSubscription() throws Exception {
+        registerClient(mClientToken1, mClientCallback1);
+
+        mBrokerService.setSubscriptions(mClientToken1, asList(
+                new VmsAssociatedLayer(LAYER1, asSet(12345))
+        ));
+        mBrokerService.setSubscriptions(mClientToken1, asList(
+                new VmsAssociatedLayer(LAYER2, asSet(54321))
+        ));
+
+        VmsSubscriptionState expectedSubscriptions = new VmsSubscriptionState(2,
+                emptySet(),
+                asSet(new VmsAssociatedLayer(LAYER2, asSet(54321))));
+        verifySubscriptionState(mClientCallback1, expectedSubscriptions);
+    }
+
+    @Test
+    public void testSetSubscriptions_LayerAndProvider_OverwriteSubscription_MultipleClients()
+            throws Exception {
+        registerClient(mClientToken1, mClientCallback1);
+        registerClient(mClientToken2, mClientCallback2);
+
+        mBrokerService.setSubscriptions(mClientToken1, asList(
+                new VmsAssociatedLayer(LAYER1, asSet(12345))
+        ));
+        mBrokerService.setSubscriptions(mClientToken2, asList(
+                new VmsAssociatedLayer(LAYER2, asSet(54321))
+        ));
+        mBrokerService.setSubscriptions(mClientToken2, asList(
+                new VmsAssociatedLayer(LAYER3, asSet(98765))
+        ));
+
+        VmsSubscriptionState expectedSubscriptions = new VmsSubscriptionState(3,
+                emptySet(),
+                asSet(
+                        new VmsAssociatedLayer(LAYER1, asSet(12345)),
+                        new VmsAssociatedLayer(LAYER3, asSet(98765))));
+        verifySubscriptionState(mClientCallback1, expectedSubscriptions);
+        verifySubscriptionState(mClientCallback2, expectedSubscriptions);
+    }
+
+    @Test
+    public void testSetSubscriptions_LayerAndProvider_OverwriteSubscription_MultipleClients_SameLayer()
+            throws Exception {
+        registerClient(mClientToken1, mClientCallback1);
+        registerClient(mClientToken2, mClientCallback2);
+
+        mBrokerService.setSubscriptions(mClientToken1, asList(
+                new VmsAssociatedLayer(LAYER1, asSet(12345))
+        ));
+        mBrokerService.setSubscriptions(mClientToken2, asList(
+                new VmsAssociatedLayer(LAYER2, asSet(54321))
+        ));
+        mBrokerService.setSubscriptions(mClientToken2, asList(
+                new VmsAssociatedLayer(LAYER1, asSet(98765))
+        ));
+
+        VmsSubscriptionState expectedSubscriptions = new VmsSubscriptionState(3,
+                emptySet(),
+                asSet(new VmsAssociatedLayer(LAYER1, asSet(12345, 98765))));
+        verifySubscriptionState(mClientCallback1, expectedSubscriptions);
+        verifySubscriptionState(mClientCallback2, expectedSubscriptions);
+    }
+
+    @Test
+    public void testSetSubscriptions_LayerAndProvider_OverwriteSubscription_MultipleClients_SameLayerAndProvider()
+            throws Exception {
+        registerClient(mClientToken1, mClientCallback1);
+        registerClient(mClientToken2, mClientCallback2);
+
+        mBrokerService.setSubscriptions(mClientToken1, asList(
+                new VmsAssociatedLayer(LAYER1, asSet(12345))
+        ));
+        mBrokerService.setSubscriptions(mClientToken2, asList(
+                new VmsAssociatedLayer(LAYER2, asSet(54321))
+        ));
+        mBrokerService.setSubscriptions(mClientToken2, asList(
+                new VmsAssociatedLayer(LAYER1, asSet(12345))
+        ));
+
+        VmsSubscriptionState expectedSubscriptions = new VmsSubscriptionState(3,
+                emptySet(),
+                asSet(new VmsAssociatedLayer(LAYER1, asSet(12345))));
+        verifySubscriptionState(mClientCallback1, expectedSubscriptions);
+        verifySubscriptionState(mClientCallback2, expectedSubscriptions);
+    }
+
+    @Test
+    public void testSetSubscriptions_LayerAndProvider_RemoveSubscription() throws Exception {
+        registerClient(mClientToken1, mClientCallback1);
+
+        mBrokerService.setSubscriptions(mClientToken1, asList(
+                new VmsAssociatedLayer(LAYER1, asSet(12345))
+        ));
+        mBrokerService.setSubscriptions(mClientToken1, asList());
+
+        VmsSubscriptionState expectedSubscriptions = new VmsSubscriptionState(2,
+                emptySet(),
+                emptySet());
+        verifySubscriptionState(mClientCallback1, expectedSubscriptions);
+    }
+
+    @Test
+    public void testSetSubscriptions_LayerAndProvider_RemoveSubscription_MultipleClients()
+            throws Exception {
+        registerClient(mClientToken1, mClientCallback1);
+        registerClient(mClientToken2, mClientCallback2);
+
+        mBrokerService.setSubscriptions(mClientToken1, asList(
+                new VmsAssociatedLayer(LAYER1, asSet(12345))
+        ));
+        mBrokerService.setSubscriptions(mClientToken2, asList(
+                new VmsAssociatedLayer(LAYER2, asSet(54321))
+        ));
+        mBrokerService.setSubscriptions(mClientToken2, asList());
+
+        VmsSubscriptionState expectedSubscriptions = new VmsSubscriptionState(3,
+                emptySet(),
+                asSet(new VmsAssociatedLayer(LAYER1, asSet(12345))));
+        verifySubscriptionState(mClientCallback1, expectedSubscriptions);
+        verifySubscriptionState(mClientCallback2, expectedSubscriptions);
+    }
+
+    @Test
+    public void testSetSubscriptions_LayerAndProvider_RemoveSubscription_MultipleClients_SameLayer()
+            throws Exception {
+        registerClient(mClientToken1, mClientCallback1);
+        registerClient(mClientToken2, mClientCallback2);
+
+        mBrokerService.setSubscriptions(mClientToken1, asList(
+                new VmsAssociatedLayer(LAYER1, asSet(12345))
+        ));
+        mBrokerService.setSubscriptions(mClientToken2, asList(
+                new VmsAssociatedLayer(LAYER1, asSet(54321))
+        ));
+        mBrokerService.setSubscriptions(mClientToken2, asList());
+
+        VmsSubscriptionState expectedSubscriptions = new VmsSubscriptionState(3,
+                emptySet(),
+                asSet(new VmsAssociatedLayer(LAYER1, asSet(12345))));
+        verifySubscriptionState(mClientCallback1, expectedSubscriptions);
+        verifySubscriptionState(mClientCallback2, expectedSubscriptions);
+    }
+
+    @Test
+    public void testSetSubscriptions_LayerAndProvider_RemoveSubscription_MultipleClients_SameLayerAndProvider()
+            throws Exception {
+        registerClient(mClientToken1, mClientCallback1);
+        registerClient(mClientToken2, mClientCallback2);
+
+        mBrokerService.setSubscriptions(mClientToken1, asList(
+                new VmsAssociatedLayer(LAYER1, asSet(12345))
+        ));
+        mBrokerService.setSubscriptions(mClientToken2, asList(
+                new VmsAssociatedLayer(LAYER1, asSet(12345))
+        ));
+        mBrokerService.setSubscriptions(mClientToken2, asList());
+
+        VmsSubscriptionState expectedSubscriptions = new VmsSubscriptionState(1,
+                emptySet(),
+                asSet(new VmsAssociatedLayer(LAYER1, asSet(12345))));
+        verifySubscriptionState(mClientCallback1, expectedSubscriptions);
+        verifySubscriptionState(mClientCallback2, expectedSubscriptions);
+    }
+
+    @Test
+    public void testSetSubscriptions_LayerAndProvider_RemoveSubscription_OnUnregister_MultipleClients()
+            throws Exception {
+        registerClient(mClientToken1, mClientCallback1);
+        registerClient(mClientToken2, mClientCallback2);
+
+        mBrokerService.setSubscriptions(mClientToken1, asList(
+                new VmsAssociatedLayer(LAYER1, asSet(12345))
+        ));
+        mBrokerService.setSubscriptions(mClientToken2, asList(
+                new VmsAssociatedLayer(LAYER2, asSet(54321))
+        ));
+        mBrokerService.unregisterClient(mClientToken2);
+
+        VmsSubscriptionState expectedSubscriptions = new VmsSubscriptionState(3,
+                emptySet(),
+                asSet(new VmsAssociatedLayer(LAYER1, asSet(12345))));
+        verifySubscriptionState(mClientCallback1, expectedSubscriptions);
+    }
+
+    @Test
+    public void testSetSubscriptions_LayerAndProvider_RemoveSubscription_OnUnregister_MultipleClients_SameLayer()
+            throws Exception {
+        registerClient(mClientToken1, mClientCallback1);
+        registerClient(mClientToken2, mClientCallback2);
+
+        mBrokerService.setSubscriptions(mClientToken1, asList(
+                new VmsAssociatedLayer(LAYER1, asSet(12345))
+        ));
+        mBrokerService.setSubscriptions(mClientToken2, asList(
+                new VmsAssociatedLayer(LAYER1, asSet(54321))
+        ));
+        mBrokerService.unregisterClient(mClientToken2);
+
+        VmsSubscriptionState expectedSubscriptions = new VmsSubscriptionState(3,
+                emptySet(),
+                asSet(new VmsAssociatedLayer(LAYER1, asSet(12345))));
+        verifySubscriptionState(mClientCallback1, expectedSubscriptions);
+    }
+
+    @Test
+    public void testSetSubscriptions_LayerAndProvider_RemoveSubscription_OnUnregister_MultipleClients_SameLayerAndProvider()
+            throws Exception {
+        registerClient(mClientToken1, mClientCallback1);
+        registerClient(mClientToken2, mClientCallback2);
+
+        mBrokerService.setSubscriptions(mClientToken1, asList(
+                new VmsAssociatedLayer(LAYER1, asSet(12345))
+        ));
+        mBrokerService.setSubscriptions(mClientToken2, asList(
+                new VmsAssociatedLayer(LAYER1, asSet(12345))
+        ));
+        mBrokerService.unregisterClient(mClientToken2);
+
+        VmsSubscriptionState expectedSubscriptions = new VmsSubscriptionState(1,
+                emptySet(),
+                asSet(new VmsAssociatedLayer(LAYER1, asSet(12345))));
+        verifySubscriptionState(mClientCallback1, expectedSubscriptions);
+    }
+
+    @Test
+    public void testSetSubscriptions_LayerAndProvider_RemoveSubscription_OnDisconnect_MultipleClients()
+            throws Exception {
+        registerClient(mClientToken1, mClientCallback1);
+        registerClient(mClientToken2, mClientCallback2);
+
+        mBrokerService.setSubscriptions(mClientToken1, asList(
+                new VmsAssociatedLayer(LAYER1, asSet(12345))
+        ));
+        mBrokerService.setSubscriptions(mClientToken2, asList(
+                new VmsAssociatedLayer(LAYER2, asSet(54321))
+        ));
+        disconnectClient(mClientCallback2);
+
+        VmsSubscriptionState expectedSubscriptions = new VmsSubscriptionState(3,
+                emptySet(),
+                asSet(new VmsAssociatedLayer(LAYER1, asSet(12345))));
+        verifySubscriptionState(mClientCallback1, expectedSubscriptions);
+    }
+
+    @Test
+    public void testSetSubscriptions_LayerAndProvider_RemoveSubscription_OnDisconnect_MultipleClients_SameLayer()
+            throws Exception {
+        registerClient(mClientToken1, mClientCallback1);
+        registerClient(mClientToken2, mClientCallback2);
+
+        mBrokerService.setSubscriptions(mClientToken1, asList(
+                new VmsAssociatedLayer(LAYER1, asSet(12345))
+        ));
+        mBrokerService.setSubscriptions(mClientToken2, asList(
+                new VmsAssociatedLayer(LAYER1, asSet(54321))
+        ));
+        disconnectClient(mClientCallback2);
+
+        VmsSubscriptionState expectedSubscriptions = new VmsSubscriptionState(3,
+                emptySet(),
+                asSet(new VmsAssociatedLayer(LAYER1, asSet(12345))));
+        verifySubscriptionState(mClientCallback1, expectedSubscriptions);
+    }
+
+    @Test
+    public void testSetSubscriptions_LayerAndProvider_RemoveSubscription_OnDisconnect_MultipleClients_SameLayerAndProvider()
+            throws Exception {
+        registerClient(mClientToken1, mClientCallback1);
+        registerClient(mClientToken2, mClientCallback2);
+
+        mBrokerService.setSubscriptions(mClientToken1, asList(
+                new VmsAssociatedLayer(LAYER1, asSet(12345))
+        ));
+        mBrokerService.setSubscriptions(mClientToken2, asList(
+                new VmsAssociatedLayer(LAYER1, asSet(12345))
+        ));
+        disconnectClient(mClientCallback2);
+
+        VmsSubscriptionState expectedSubscriptions = new VmsSubscriptionState(1,
+                emptySet(),
+                asSet(new VmsAssociatedLayer(LAYER1, asSet(12345))));
+        verifySubscriptionState(mClientCallback1, expectedSubscriptions);
+    }
+
+    @Test
+    public void testSetSubscriptions_LayerAndMultipleProviders() throws Exception {
+        registerClient(mClientToken1, mClientCallback1);
+
+        mBrokerService.setSubscriptions(mClientToken1, asList(
+                new VmsAssociatedLayer(LAYER1, asSet(12345, 54321))
+        ));
+
+        VmsSubscriptionState expectedSubscriptions = new VmsSubscriptionState(1,
+                emptySet(),
+                asSet(new VmsAssociatedLayer(LAYER1, asSet(12345, 54321))));
+        verifySubscriptionState(mClientCallback1, expectedSubscriptions);
+    }
+
+    @Test
+    public void testSetSubscriptions_LayerAndMultipleProviders_MultipleClients() throws Exception {
+        registerClient(mClientToken1, mClientCallback1);
+        registerClient(mClientToken2, mClientCallback2);
+
+        mBrokerService.setSubscriptions(mClientToken1, asList(
+                new VmsAssociatedLayer(LAYER1, asSet(12345))
+        ));
+        mBrokerService.setSubscriptions(mClientToken2, asList(
+                new VmsAssociatedLayer(LAYER1, asSet(54321))
+        ));
+
+        VmsSubscriptionState expectedSubscriptions = new VmsSubscriptionState(2,
+                emptySet(),
+                asSet(new VmsAssociatedLayer(LAYER1, asSet(12345, 54321))));
+        verifySubscriptionState(mClientCallback1, expectedSubscriptions);
+        verifySubscriptionState(mClientCallback2, expectedSubscriptions);
+    }
+
+    @Test
+    public void testSetSubscriptions_MultipleLayersAndProvider() throws Exception {
+        registerClient(mClientToken1, mClientCallback1);
+
+        mBrokerService.setSubscriptions(mClientToken1, asList(
+                new VmsAssociatedLayer(LAYER1, emptySet()),
+                new VmsAssociatedLayer(LAYER2, asSet(12345))
+        ));
+
+        VmsSubscriptionState expectedSubscriptions = new VmsSubscriptionState(1,
+                asSet(LAYER1),
+                asSet(new VmsAssociatedLayer(LAYER2, asSet(12345))));
+        verifySubscriptionState(mClientCallback1, expectedSubscriptions);
+    }
+
+    @Test
+    public void testSetSubscriptions_MultipleLayersAndProvider_MultipleClients() throws Exception {
+        registerClient(mClientToken1, mClientCallback1);
+        registerClient(mClientToken2, mClientCallback2);
+
+        mBrokerService.setSubscriptions(mClientToken1, asList(
+                new VmsAssociatedLayer(LAYER1, emptySet()),
+                new VmsAssociatedLayer(LAYER2, asSet(12345))
+        ));
+        mBrokerService.setSubscriptions(mClientToken2, asList(
+                new VmsAssociatedLayer(LAYER3, emptySet()),
+                new VmsAssociatedLayer(LAYER2, asSet(54321))
+        ));
+
+        VmsSubscriptionState expectedSubscriptions = new VmsSubscriptionState(2,
+                asSet(LAYER1, LAYER3),
+                asSet(new VmsAssociatedLayer(LAYER2, asSet(12345, 54321))));
+        verifySubscriptionState(mClientCallback1, expectedSubscriptions);
+        verifySubscriptionState(mClientCallback2, expectedSubscriptions);
+    }
+
+    @Test
+    public void testSetSubscriptions_MultipleLayersAndProvider_OverwriteSubscription()
+            throws Exception {
+        registerClient(mClientToken1, mClientCallback1);
+
+        mBrokerService.setSubscriptions(mClientToken1, asList(
+                new VmsAssociatedLayer(LAYER1, emptySet()),
+                new VmsAssociatedLayer(LAYER2, asSet(12345))
+        ));
+        mBrokerService.setSubscriptions(mClientToken1, asList(
+                new VmsAssociatedLayer(LAYER2, emptySet()),
+                new VmsAssociatedLayer(LAYER1, asSet(54321))
+        ));
+
+        VmsSubscriptionState expectedSubscriptions = new VmsSubscriptionState(2,
+                asSet(LAYER2),
+                asSet(new VmsAssociatedLayer(LAYER1, asSet(54321))));
+        verifySubscriptionState(mClientCallback1, expectedSubscriptions);
+    }
+
+    @Test
+    public void testSetSubscriptions_MultipleLayersAndProvider_OverwriteSubscription_MultipleClients()
+            throws Exception {
+        registerClient(mClientToken1, mClientCallback1);
+        registerClient(mClientToken2, mClientCallback2);
+
+        mBrokerService.setSubscriptions(mClientToken1, asList(
+                new VmsAssociatedLayer(LAYER1, emptySet()),
+                new VmsAssociatedLayer(LAYER2, asSet(12345))
+        ));
+        mBrokerService.setSubscriptions(mClientToken2, asList(
+                new VmsAssociatedLayer(LAYER2, emptySet()),
+                new VmsAssociatedLayer(LAYER1, asSet(54321))
+        ));
+        mBrokerService.setSubscriptions(mClientToken2, asList(
+                new VmsAssociatedLayer(LAYER3, emptySet()),
+                new VmsAssociatedLayer(LAYER2, asSet(54321))
+        ));
+
+        VmsSubscriptionState expectedSubscriptions = new VmsSubscriptionState(3,
+                asSet(LAYER1, LAYER3),
+                asSet(new VmsAssociatedLayer(LAYER2, asSet(12345, 54321))));
+        verifySubscriptionState(mClientCallback1, expectedSubscriptions);
+        verifySubscriptionState(mClientCallback2, expectedSubscriptions);
+    }
+
+    @Test
+    public void testSetSubscriptions_MultipleLayersAndProvider_RemoveSubscription()
+            throws Exception {
+        registerClient(mClientToken1, mClientCallback1);
+
+        mBrokerService.setSubscriptions(mClientToken1, asList(
+                new VmsAssociatedLayer(LAYER1, emptySet()),
+                new VmsAssociatedLayer(LAYER2, asSet(12345))
+        ));
+        mBrokerService.setSubscriptions(mClientToken1, asList());
+
+        VmsSubscriptionState expectedSubscriptions = new VmsSubscriptionState(2,
+                emptySet(),
+                emptySet());
+        verifySubscriptionState(mClientCallback1, expectedSubscriptions);
+    }
+
+    @Test
+    public void testSetSubscriptions_MultipleLayersAndProvider_RemoveSubscription_MultipleClients()
+            throws Exception {
+        registerClient(mClientToken1, mClientCallback1);
+        registerClient(mClientToken2, mClientCallback2);
+
+        mBrokerService.setSubscriptions(mClientToken1, asList(
+                new VmsAssociatedLayer(LAYER1, emptySet()),
+                new VmsAssociatedLayer(LAYER2, asSet(12345))
+        ));
+        mBrokerService.setSubscriptions(mClientToken2, asList(
+                new VmsAssociatedLayer(LAYER2, emptySet()),
+                new VmsAssociatedLayer(LAYER1, asSet(54321))
+        ));
+        mBrokerService.setSubscriptions(mClientToken2, asList());
+
+        VmsSubscriptionState expectedSubscriptions = new VmsSubscriptionState(3,
+                asSet(LAYER1),
+                asSet(new VmsAssociatedLayer(LAYER2, asSet(12345))));
+        verifySubscriptionState(mClientCallback1, expectedSubscriptions);
+        verifySubscriptionState(mClientCallback2, expectedSubscriptions);
+    }
+
+    @Test
+    public void testSetSubscriptions_MultipleLayersAndProvider_RemoveSubscription_OnUnregister_MultipleClients()
+            throws Exception {
+        registerClient(mClientToken1, mClientCallback1);
+        registerClient(mClientToken2, mClientCallback2);
+
+        mBrokerService.setSubscriptions(mClientToken1, asList(
+                new VmsAssociatedLayer(LAYER1, emptySet()),
+                new VmsAssociatedLayer(LAYER2, asSet(12345))
+        ));
+        mBrokerService.setSubscriptions(mClientToken2, asList(
+                new VmsAssociatedLayer(LAYER2, emptySet()),
+                new VmsAssociatedLayer(LAYER1, asSet(54321))
+        ));
+        mBrokerService.unregisterClient(mClientToken2);
+
+        VmsSubscriptionState expectedSubscriptions = new VmsSubscriptionState(3,
+                asSet(LAYER1),
+                asSet(new VmsAssociatedLayer(LAYER2, asSet(12345))));
+        verifySubscriptionState(mClientCallback1, expectedSubscriptions);
+    }
+
+    @Test
+    public void testSetSubscriptions_MultipleLayersAndProvider_RemoveSubscription_OnDisconnect_MultipleClients()
+            throws Exception {
+        registerClient(mClientToken1, mClientCallback1);
+        registerClient(mClientToken2, mClientCallback2);
+
+        mBrokerService.setSubscriptions(mClientToken1, asList(
+                new VmsAssociatedLayer(LAYER1, emptySet()),
+                new VmsAssociatedLayer(LAYER2, asSet(12345))
+        ));
+        mBrokerService.setSubscriptions(mClientToken2, asList(
+                new VmsAssociatedLayer(LAYER2, emptySet()),
+                new VmsAssociatedLayer(LAYER1, asSet(54321))
+        ));
+        disconnectClient(mClientCallback2);
+
+        VmsSubscriptionState expectedSubscriptions = new VmsSubscriptionState(3,
+                asSet(LAYER1),
+                asSet(new VmsAssociatedLayer(LAYER2, asSet(12345))));
+        verifySubscriptionState(mClientCallback1, expectedSubscriptions);
+    }
+
+    @Test
+    public void testSetSubscriptions_MultipleLayersAndMultipleProviders() throws Exception {
+        registerClient(mClientToken1, mClientCallback1);
+
+        mBrokerService.setSubscriptions(mClientToken1, asList(
+                new VmsAssociatedLayer(LAYER1, asSet(54321)),
+                new VmsAssociatedLayer(LAYER2, asSet(12345))
+        ));
+
+        VmsSubscriptionState expectedSubscriptions = new VmsSubscriptionState(1,
+                emptySet(),
+                asSet(
+                        new VmsAssociatedLayer(LAYER1, asSet(54321)),
+                        new VmsAssociatedLayer(LAYER2, asSet(12345))));
+        verifySubscriptionState(mClientCallback1, expectedSubscriptions);
+    }
+
+    @Test
+    public void testSetSubscriptions_MultipleLayersAndMultipleProviders_MultipleClients()
+            throws Exception {
+        registerClient(mClientToken1, mClientCallback1);
+        registerClient(mClientToken2, mClientCallback2);
+
+        mBrokerService.setSubscriptions(mClientToken1, asList(
+                new VmsAssociatedLayer(LAYER1, asSet(54321))
+        ));
+        mBrokerService.setSubscriptions(mClientToken2, asList(
+                new VmsAssociatedLayer(LAYER2, asSet(12345))
+        ));
+
+        VmsSubscriptionState expectedSubscriptions = new VmsSubscriptionState(2,
+                emptySet(),
+                asSet(
+                        new VmsAssociatedLayer(LAYER1, asSet(54321)),
+                        new VmsAssociatedLayer(LAYER2, asSet(12345))));
+        verifySubscriptionState(mClientCallback1, expectedSubscriptions);
+    }
+
+    @Test
+    public void testSetSubscriptions_LayerOnlySupersedesLayerAndProvider() throws Exception {
+        registerClient(mClientToken1, mClientCallback1);
+
+        mBrokerService.setSubscriptions(mClientToken1, asList(
+                new VmsAssociatedLayer(LAYER1, emptySet()),
+                new VmsAssociatedLayer(LAYER1, asSet(12345))
+        ));
+
+        VmsSubscriptionState expectedSubscriptions = new VmsSubscriptionState(1,
+                asSet(LAYER1),
+                emptySet());
+        verifySubscriptionState(mClientCallback1, expectedSubscriptions);
+    }
+
+    @Test
+    public void testSetSubscriptions_LayerOnlySupersedesLayerAndProvider_MultipleClients()
+            throws Exception {
+        registerClient(mClientToken1, mClientCallback1);
+        registerClient(mClientToken2, mClientCallback2);
+
+        mBrokerService.setSubscriptions(mClientToken1, asList(
+                new VmsAssociatedLayer(LAYER1, emptySet())
+        ));
+        mBrokerService.setSubscriptions(mClientToken2, asList(
+                new VmsAssociatedLayer(LAYER1, asSet(12345))
+        ));
+
+        VmsSubscriptionState expectedSubscriptions = new VmsSubscriptionState(1,
+                asSet(LAYER1),
+                emptySet());
+        verifySubscriptionState(mClientCallback1, expectedSubscriptions);
+        verifySubscriptionState(mClientCallback2, expectedSubscriptions);
+    }
+
+    @Test
+    public void testSetSubscriptions_LayerOnlySupersedesLayerAndProvider_RemoveLayerSubscription()
+            throws Exception {
+        registerClient(mClientToken1, mClientCallback1);
+
+        mBrokerService.setSubscriptions(mClientToken1, asList(
+                new VmsAssociatedLayer(LAYER1, emptySet()),
+                new VmsAssociatedLayer(LAYER1, asSet(12345))
+        ));
+        mBrokerService.setSubscriptions(mClientToken1, asList(
+                new VmsAssociatedLayer(LAYER1, asSet(12345))
+        ));
+
+        VmsSubscriptionState expectedSubscriptions = new VmsSubscriptionState(2,
+                emptySet(),
+                asSet(new VmsAssociatedLayer(LAYER1, asSet(12345))));
+        verifySubscriptionState(mClientCallback1, expectedSubscriptions);
+    }
+
+    @Test
+    public void testSetSubscriptions_LayerOnlySupersedesLayerAndProvider_RemoveLayerSubscription_MultipleClients()
+            throws Exception {
+        registerClient(mClientToken1, mClientCallback1);
+        registerClient(mClientToken2, mClientCallback2);
+
+        mBrokerService.setSubscriptions(mClientToken1, asList(
+                new VmsAssociatedLayer(LAYER1, asSet(12345))
+        ));
+        mBrokerService.setSubscriptions(mClientToken2, asList(
+                new VmsAssociatedLayer(LAYER1, emptySet())
+        ));
+        mBrokerService.setSubscriptions(mClientToken2, asList());
+
+        VmsSubscriptionState expectedSubscriptions = new VmsSubscriptionState(3,
+                emptySet(),
+                asSet(new VmsAssociatedLayer(LAYER1, asSet(12345))));
+        verifySubscriptionState(mClientCallback1, expectedSubscriptions);
+        verifySubscriptionState(mClientCallback2, expectedSubscriptions);
+    }
+
+    @Test
+    public void testSetMonitoringEnabled_UnknownClient() {
+        assertThrows(
+                IllegalStateException.class,
+                () -> mBrokerService.setMonitoringEnabled(new Binder(), true));
+    }
+
+    @Test
+    public void testSetMonitoringEnabled_Enable_NoSubscriptionChange() throws Exception {
+        registerClient(mClientToken1, mClientCallback1);
+
+        mBrokerService.setMonitoringEnabled(mClientToken1, true);
+
+        verify(mClientCallback1, never()).onSubscriptionStateChanged(any());
+    }
+
+    @Test
+    public void testSetMonitoringEnabled_Disable_NoSubscriptionChange() throws Exception {
+        registerClient(mClientToken1, mClientCallback1);
+
+        mBrokerService.setMonitoringEnabled(mClientToken1, false);
+
+        verify(mClientCallback1, never()).onSubscriptionStateChanged(any());
+    }
+
+    @Test
+    public void testSetProviderOfferings_UnknownClient() {
+        registerClient(mClientToken1, mClientCallback1);
+        int providerId = mBrokerService.registerProvider(mClientToken1, PROVIDER_INFO1);
+
+        assertThrows(
+                IllegalStateException.class,
+                () -> mBrokerService.setProviderOfferings(new Binder(), providerId, asList()));
+    }
+
+    @Test
+    public void testSetProviderOfferings_UnknownProviderId() {
+        registerClient(mClientToken1, mClientCallback1);
+
+        assertThrows(
+                IllegalArgumentException.class,
+                () -> mBrokerService.setProviderOfferings(mClientToken1, 12345, asList()));
+    }
+
+    @Test
+    public void testSetProviderOfferings_UnknownProviderId_LegacyClient() throws Exception {
+        mBrokerService.registerClient(mClientToken1, mClientCallback1, true);
+
+        mBrokerService.setProviderOfferings(mClientToken1, 12345, asList(
+                new VmsLayerDependency(LAYER1)
+        ));
+
+        VmsAvailableLayers expectedLayers = new VmsAvailableLayers(1, asSet(
+                new VmsAssociatedLayer(LAYER1, asSet(12345)))
+        );
+        verifyLayerAvailability(mClientCallback1, expectedLayers);
+    }
+
+    @Test
+    public void testSetProviderOfferings_OtherClientsProviderId() {
+        registerClient(mClientToken1, mClientCallback1);
+        int providerId = mBrokerService.registerProvider(mClientToken1, PROVIDER_INFO1);
+        registerClient(mClientToken2, mClientCallback2);
+
+        assertThrows(
+                IllegalArgumentException.class,
+                () -> mBrokerService.setProviderOfferings(mClientToken2, providerId, asList()));
+    }
+
+    @Test
+    public void testSetProviderOfferings_OtherClientsProviderId_LegacyClient() throws Exception {
+        registerClient(mClientToken1, mClientCallback1);
+        int providerId = mBrokerService.registerProvider(mClientToken1, PROVIDER_INFO1);
+        mBrokerService.registerClient(mClientToken2, mClientCallback2, true);
+
+        mBrokerService.setProviderOfferings(mClientToken2, providerId, asList(
+                new VmsLayerDependency(LAYER1)
+        ));
+
+        VmsAvailableLayers expectedLayers = new VmsAvailableLayers(1, asSet(
+                new VmsAssociatedLayer(LAYER1, asSet(providerId)))
+        );
+        verifyLayerAvailability(mClientCallback1, expectedLayers);
+    }
+
+    @Test
+    public void testSetProviderOfferings_SingleProvider() throws Exception {
+        registerClient(mClientToken1, mClientCallback1);
+        int providerId = mBrokerService.registerProvider(mClientToken1, PROVIDER_INFO1);
+
+        mBrokerService.setProviderOfferings(mClientToken1, providerId, asList(
+                new VmsLayerDependency(LAYER1)
+        ));
+
+        VmsAvailableLayers expectedLayers = new VmsAvailableLayers(1, asSet(
+                new VmsAssociatedLayer(LAYER1, asSet(providerId)))
+        );
+        verifyLayerAvailability(mClientCallback1, expectedLayers);
+    }
+
+    @Test
+    public void testSetProviderOfferings_MultipleProviders() throws Exception {
+        registerClient(mClientToken1, mClientCallback1);
+        int providerId = mBrokerService.registerProvider(mClientToken1, PROVIDER_INFO1);
+        int providerId2 = mBrokerService.registerProvider(mClientToken1, PROVIDER_INFO2);
+
+        mBrokerService.setProviderOfferings(mClientToken1, providerId, asList(
+                new VmsLayerDependency(LAYER1)
+        ));
+        mBrokerService.setProviderOfferings(mClientToken1, providerId2, asList(
+                new VmsLayerDependency(LAYER1)
+        ));
+
+        VmsAvailableLayers expectedLayers = new VmsAvailableLayers(2, asSet(
+                new VmsAssociatedLayer(LAYER1, asSet(providerId, providerId2)))
+        );
+        verifyLayerAvailability(mClientCallback1, expectedLayers);
+    }
+
+    @Test
+    public void testSetProviderOfferings_MultipleClients() throws Exception {
+        registerClient(mClientToken1, mClientCallback1);
+        int providerId = mBrokerService.registerProvider(mClientToken1, PROVIDER_INFO1);
+
+        registerClient(mClientToken2, mClientCallback2);
+        int providerId2 = mBrokerService.registerProvider(mClientToken2, PROVIDER_INFO2);
+
+        mBrokerService.setProviderOfferings(mClientToken1, providerId, asList(
+                new VmsLayerDependency(LAYER1)
+        ));
+        mBrokerService.setProviderOfferings(mClientToken2, providerId2, asList(
+                new VmsLayerDependency(LAYER1)
+        ));
+
+        VmsAvailableLayers expectedLayers = new VmsAvailableLayers(2, asSet(
+                new VmsAssociatedLayer(LAYER1, asSet(providerId, providerId2)))
+        );
+        verifyLayerAvailability(mClientCallback1, expectedLayers);
+        verifyLayerAvailability(mClientCallback2, expectedLayers);
+    }
+
+    @Test
+    public void testSetProviderOfferings_MultipleClients_SingleProvider() throws Exception {
+        registerClient(mClientToken1, mClientCallback1);
+        int providerId = mBrokerService.registerProvider(mClientToken1, PROVIDER_INFO1);
+
+        registerClient(mClientToken2, mClientCallback2);
+        mBrokerService.registerProvider(mClientToken2, PROVIDER_INFO1);
+
+        mBrokerService.setProviderOfferings(mClientToken1, providerId, asList(
+                new VmsLayerDependency(LAYER1)
+        ));
+        mBrokerService.setProviderOfferings(mClientToken2, providerId, asList(
+                new VmsLayerDependency(LAYER1)
+        ));
+
+        VmsAvailableLayers expectedLayers = new VmsAvailableLayers(1, asSet(
+                new VmsAssociatedLayer(LAYER1, asSet(providerId)))
+        );
+        verifyLayerAvailability(mClientCallback1, expectedLayers);
+        verifyLayerAvailability(mClientCallback2, expectedLayers);
+    }
+
+    @Test
+    public void testSetProviderOfferings_MultipleLayers_SingleProvider() throws Exception {
+        registerClient(mClientToken1, mClientCallback1);
+        int providerId = mBrokerService.registerProvider(mClientToken1, PROVIDER_INFO1);
+
+        mBrokerService.setProviderOfferings(mClientToken1, providerId, asList(
+                new VmsLayerDependency(LAYER1),
+                new VmsLayerDependency(LAYER2)
+        ));
+
+        VmsAvailableLayers expectedLayers = new VmsAvailableLayers(1, asSet(
+                new VmsAssociatedLayer(LAYER1, asSet(providerId)),
+                new VmsAssociatedLayer(LAYER2, asSet(providerId)))
+        );
+        verifyLayerAvailability(mClientCallback1, expectedLayers);
+    }
+
+    @Test
+    public void testSetProviderOfferings_MultipleLayers_MultipleProviders() throws Exception {
+        registerClient(mClientToken1, mClientCallback1);
+        int providerId = mBrokerService.registerProvider(mClientToken1, PROVIDER_INFO1);
+        int providerId2 = mBrokerService.registerProvider(mClientToken1, PROVIDER_INFO2);
+
+        mBrokerService.setProviderOfferings(mClientToken1, providerId, asList(
+                new VmsLayerDependency(LAYER1)
+        ));
+        mBrokerService.setProviderOfferings(mClientToken1, providerId2, asList(
+                new VmsLayerDependency(LAYER2)
+        ));
+
+        VmsAvailableLayers expectedLayers = new VmsAvailableLayers(2, asSet(
+                new VmsAssociatedLayer(LAYER1, asSet(providerId)),
+                new VmsAssociatedLayer(LAYER2, asSet(providerId2)))
+        );
+        verifyLayerAvailability(mClientCallback1, expectedLayers);
+    }
+
+    @Test
+    public void testSetProviderOfferings_MultipleLayers_MultipleClients() throws Exception {
+        registerClient(mClientToken1, mClientCallback1);
+        int providerId = mBrokerService.registerProvider(mClientToken1, PROVIDER_INFO1);
+
+        registerClient(mClientToken2, mClientCallback2);
+        int providerId2 = mBrokerService.registerProvider(mClientToken2, PROVIDER_INFO2);
+
+        mBrokerService.setProviderOfferings(mClientToken1, providerId, asList(
+                new VmsLayerDependency(LAYER1)
+        ));
+        mBrokerService.setProviderOfferings(mClientToken2, providerId2, asList(
+                new VmsLayerDependency(LAYER2)
+        ));
+
+        VmsAvailableLayers expectedLayers = new VmsAvailableLayers(2, asSet(
+                new VmsAssociatedLayer(LAYER1, asSet(providerId)),
+                new VmsAssociatedLayer(LAYER2, asSet(providerId2)))
+        );
+        verifyLayerAvailability(mClientCallback1, expectedLayers);
+        verifyLayerAvailability(mClientCallback2, expectedLayers);
+    }
+
+    @Test
+    public void testSetProviderOfferings_MultipleLayers_MultipleClients_SingleProvider()
+            throws Exception {
+        registerClient(mClientToken1, mClientCallback1);
+        int providerId = mBrokerService.registerProvider(mClientToken1, PROVIDER_INFO1);
+
+        registerClient(mClientToken2, mClientCallback2);
+        mBrokerService.registerProvider(mClientToken2, PROVIDER_INFO1);
+
+        mBrokerService.setProviderOfferings(mClientToken1, providerId, asList(
+                new VmsLayerDependency(LAYER1)
+        ));
+        mBrokerService.setProviderOfferings(mClientToken2, providerId, asList(
+                new VmsLayerDependency(LAYER2)
+        ));
+
+        VmsAvailableLayers expectedLayers = new VmsAvailableLayers(2, asSet(
+                new VmsAssociatedLayer(LAYER1, asSet(providerId)),
+                new VmsAssociatedLayer(LAYER2, asSet(providerId)))
+        );
+        verifyLayerAvailability(mClientCallback1, expectedLayers);
+        verifyLayerAvailability(mClientCallback2, expectedLayers);
+    }
+
+    @Test
+    public void testSetProviderOfferings_OverwriteOffering_SingleProvider() throws Exception {
+        registerClient(mClientToken1, mClientCallback1);
+        int providerId = mBrokerService.registerProvider(mClientToken1, PROVIDER_INFO1);
+
+        mBrokerService.setProviderOfferings(mClientToken1, providerId, asList(
+                new VmsLayerDependency(LAYER1)
+        ));
+        mBrokerService.setProviderOfferings(mClientToken1, providerId, asList(
+                new VmsLayerDependency(LAYER2)
+        ));
+
+        VmsAvailableLayers expectedLayers = new VmsAvailableLayers(2, asSet(
+                new VmsAssociatedLayer(LAYER2, asSet(providerId)))
+        );
+        verifyLayerAvailability(mClientCallback1, expectedLayers);
+    }
+
+    @Test
+    public void testSetProviderOfferings_OverwriteOffering_MultipleProviders() throws Exception {
+        registerClient(mClientToken1, mClientCallback1);
+        int providerId = mBrokerService.registerProvider(mClientToken1, PROVIDER_INFO1);
+        int providerId2 = mBrokerService.registerProvider(mClientToken1, PROVIDER_INFO2);
+
+        mBrokerService.setProviderOfferings(mClientToken1, providerId, asList(
+                new VmsLayerDependency(LAYER1)
+        ));
+        mBrokerService.setProviderOfferings(mClientToken1, providerId2, asList(
+                new VmsLayerDependency(LAYER1)
+        ));
+        mBrokerService.setProviderOfferings(mClientToken1, providerId2, asList(
+                new VmsLayerDependency(LAYER2)
+        ));
+
+        VmsAvailableLayers expectedLayers = new VmsAvailableLayers(3, asSet(
+                new VmsAssociatedLayer(LAYER1, asSet(providerId)),
+                new VmsAssociatedLayer(LAYER2, asSet(providerId2)))
+        );
+        verifyLayerAvailability(mClientCallback1, expectedLayers);
+    }
+
+    @Test
+    public void testSetProviderOfferings_OverwriteOffering_MultipleClients() throws Exception {
+        registerClient(mClientToken1, mClientCallback1);
+        int providerId = mBrokerService.registerProvider(mClientToken1, PROVIDER_INFO1);
+
+        registerClient(mClientToken2, mClientCallback2);
+        int providerId2 = mBrokerService.registerProvider(mClientToken2, PROVIDER_INFO2);
+
+        mBrokerService.setProviderOfferings(mClientToken1, providerId, asList(
+                new VmsLayerDependency(LAYER1)
+        ));
+        mBrokerService.setProviderOfferings(mClientToken2, providerId2, asList(
+                new VmsLayerDependency(LAYER1)
+        ));
+        mBrokerService.setProviderOfferings(mClientToken2, providerId2, asList(
+                new VmsLayerDependency(LAYER2)
+        ));
+
+        VmsAvailableLayers expectedLayers = new VmsAvailableLayers(3, asSet(
+                new VmsAssociatedLayer(LAYER1, asSet(providerId)),
+                new VmsAssociatedLayer(LAYER2, asSet(providerId2)))
+        );
+        verifyLayerAvailability(mClientCallback1, expectedLayers);
+        verifyLayerAvailability(mClientCallback2, expectedLayers);
+    }
+
+    @Test
+    public void testSetProviderOfferings_OverwriteOffering_MultipleClients_SingleProvider()
+            throws Exception {
+        registerClient(mClientToken1, mClientCallback1);
+        int providerId = mBrokerService.registerProvider(mClientToken1, PROVIDER_INFO1);
+
+        registerClient(mClientToken2, mClientCallback2);
+        mBrokerService.registerProvider(mClientToken2, PROVIDER_INFO1);
+
+        mBrokerService.setProviderOfferings(mClientToken1, providerId, asList(
+                new VmsLayerDependency(LAYER1)
+        ));
+        mBrokerService.setProviderOfferings(mClientToken2, providerId, asList(
+                new VmsLayerDependency(LAYER1)
+        ));
+        mBrokerService.setProviderOfferings(mClientToken2, providerId, asList(
+                new VmsLayerDependency(LAYER2)
+        ));
+
+        VmsAvailableLayers expectedLayers = new VmsAvailableLayers(2, asSet(
+                new VmsAssociatedLayer(LAYER1, asSet(providerId)),
+                new VmsAssociatedLayer(LAYER2, asSet(providerId)))
+        );
+        verifyLayerAvailability(mClientCallback1, expectedLayers);
+        verifyLayerAvailability(mClientCallback2, expectedLayers);
+    }
+
+    @Test
+    public void testSetProviderOfferings_RemoveOfferings_SingleProvider() throws Exception {
+        registerClient(mClientToken1, mClientCallback1);
+        int providerId = mBrokerService.registerProvider(mClientToken1, PROVIDER_INFO1);
+
+        mBrokerService.setProviderOfferings(mClientToken1, providerId, asList(
+                new VmsLayerDependency(LAYER1)
+        ));
+        mBrokerService.setProviderOfferings(mClientToken1, providerId, asList());
+
+        VmsAvailableLayers expectedLayers = new VmsAvailableLayers(2, emptySet());
+        verifyLayerAvailability(mClientCallback1, expectedLayers);
+    }
+
+    @Test
+    public void testSetProviderOfferings_RemoveOfferings_MultipleProviders() throws Exception {
+        registerClient(mClientToken1, mClientCallback1);
+        int providerId = mBrokerService.registerProvider(mClientToken1, PROVIDER_INFO1);
+        int providerId2 = mBrokerService.registerProvider(mClientToken1, PROVIDER_INFO2);
+
+        mBrokerService.setProviderOfferings(mClientToken1, providerId, asList(
+                new VmsLayerDependency(LAYER1)
+        ));
+        mBrokerService.setProviderOfferings(mClientToken1, providerId2, asList(
+                new VmsLayerDependency(LAYER1)
+        ));
+        mBrokerService.setProviderOfferings(mClientToken1, providerId2, asList());
+
+        VmsAvailableLayers expectedLayers = new VmsAvailableLayers(3, asSet(
+                new VmsAssociatedLayer(LAYER1, asSet(providerId)))
+        );
+        verifyLayerAvailability(mClientCallback1, expectedLayers);
+    }
+
+    @Test
+    public void testSetProviderOfferings_RemoveOfferings_MultipleClients() throws Exception {
+        registerClient(mClientToken1, mClientCallback1);
+        int providerId = mBrokerService.registerProvider(mClientToken1, PROVIDER_INFO1);
+
+        registerClient(mClientToken2, mClientCallback2);
+        int providerId2 = mBrokerService.registerProvider(mClientToken2, PROVIDER_INFO2);
+
+        mBrokerService.setProviderOfferings(mClientToken1, providerId, asList(
+                new VmsLayerDependency(LAYER1)
+        ));
+        mBrokerService.setProviderOfferings(mClientToken2, providerId2, asList(
+                new VmsLayerDependency(LAYER1)
+        ));
+        mBrokerService.setProviderOfferings(mClientToken2, providerId2, asList());
+
+        VmsAvailableLayers expectedLayers = new VmsAvailableLayers(3, asSet(
+                new VmsAssociatedLayer(LAYER1, asSet(providerId)))
+        );
+        verifyLayerAvailability(mClientCallback1, expectedLayers);
+        verifyLayerAvailability(mClientCallback2, expectedLayers);
+    }
+
+    @Test
+    public void testSetProviderOfferings_RemoveOfferings_MultipleClients_SingleProvider()
+            throws Exception {
+        registerClient(mClientToken1, mClientCallback1);
+        int providerId = mBrokerService.registerProvider(mClientToken1, PROVIDER_INFO1);
+
+        registerClient(mClientToken2, mClientCallback2);
+        mBrokerService.registerProvider(mClientToken2, PROVIDER_INFO1);
+
+        mBrokerService.setProviderOfferings(mClientToken1, providerId, asList(
+                new VmsLayerDependency(LAYER1)
+        ));
+        mBrokerService.setProviderOfferings(mClientToken2, providerId, asList(
+                new VmsLayerDependency(LAYER1)
+        ));
+        mBrokerService.setProviderOfferings(mClientToken2, providerId, asList());
+
+        VmsAvailableLayers expectedLayers = new VmsAvailableLayers(2, asSet(
+                new VmsAssociatedLayer(LAYER1, asSet(providerId)))
+        );
+        verifyLayerAvailability(mClientCallback1, expectedLayers);
+        verifyLayerAvailability(mClientCallback2, expectedLayers);
+    }
+
+    @Test
+    public void testSetProviderOfferings_RemoveOfferings_OnUnregister_SingleProvider()
+            throws Exception {
+        registerClient(mClientToken1, mClientCallback1);
+        int providerId = mBrokerService.registerProvider(mClientToken1, PROVIDER_INFO1);
+
+        // Register second client to verify layer availability after first client disconnects
+        registerClient(mClientToken2, mClientCallback2);
+
+        mBrokerService.setProviderOfferings(mClientToken1, providerId, asList(
+                new VmsLayerDependency(LAYER1)
+        ));
+        mBrokerService.unregisterClient(mClientToken1);
+
+        VmsAvailableLayers expectedLayers1 = new VmsAvailableLayers(1, asSet(
+                new VmsAssociatedLayer(LAYER1, asSet(providerId)))
+        );
+        VmsAvailableLayers expectedLayers2 = new VmsAvailableLayers(2, emptySet());
+        verifyLayerAvailability(mClientCallback1, expectedLayers1);
+        verifyLayerAvailability(mClientCallback2, expectedLayers2);
+    }
+
+    @Test
+    public void testSetProviderOfferings_RemoveOfferings_OnUnregister_MultipleProviders()
+            throws Exception {
+        registerClient(mClientToken1, mClientCallback1);
+        int providerId = mBrokerService.registerProvider(mClientToken1, PROVIDER_INFO1);
+        int providerId2 = mBrokerService.registerProvider(mClientToken1, PROVIDER_INFO2);
+
+        // Register second client to verify layer availability after first client disconnects
+        registerClient(mClientToken2, mClientCallback2);
+
+        mBrokerService.setProviderOfferings(mClientToken1, providerId, asList(
+                new VmsLayerDependency(LAYER1)
+        ));
+        mBrokerService.setProviderOfferings(mClientToken1, providerId2, asList(
+                new VmsLayerDependency(LAYER1)
+        ));
+        mBrokerService.unregisterClient(mClientToken1);
+
+        VmsAvailableLayers expectedLayers1 = new VmsAvailableLayers(2, asSet(
+                new VmsAssociatedLayer(LAYER1, asSet(providerId, providerId2)))
+        );
+        VmsAvailableLayers expectedLayers2 = new VmsAvailableLayers(3, emptySet());
+        verifyLayerAvailability(mClientCallback1, expectedLayers1);
+        verifyLayerAvailability(mClientCallback2, expectedLayers2);
+    }
+
+    @Test
+    public void testSetProviderOfferings_RemoveOfferings_OnUnregister_MultipleClients()
+            throws Exception {
+        registerClient(mClientToken1, mClientCallback1);
+        int providerId = mBrokerService.registerProvider(mClientToken1, PROVIDER_INFO1);
+
+        registerClient(mClientToken2, mClientCallback2);
+        int providerId2 = mBrokerService.registerProvider(mClientToken2, PROVIDER_INFO2);
+
+        mBrokerService.setProviderOfferings(mClientToken1, providerId, asList(
+                new VmsLayerDependency(LAYER1)
+        ));
+        mBrokerService.setProviderOfferings(mClientToken2, providerId2, asList(
+                new VmsLayerDependency(LAYER1)
+        ));
+        mBrokerService.unregisterClient(mClientToken1);
+
+        VmsAvailableLayers expectedLayers1 = new VmsAvailableLayers(2, asSet(
+                new VmsAssociatedLayer(LAYER1, asSet(providerId, providerId2)))
+        );
+        VmsAvailableLayers expectedLayers2 = new VmsAvailableLayers(3, asSet(
+                new VmsAssociatedLayer(LAYER1, asSet(providerId2)))
+        );
+        verifyLayerAvailability(mClientCallback1, expectedLayers1);
+        verifyLayerAvailability(mClientCallback2, expectedLayers2);
+    }
+
+    @Test
+    public void testSetProviderOfferings_RemoveOfferings_OnUnregister_MultipleClients_SingleProvider()
+            throws Exception {
+        registerClient(mClientToken1, mClientCallback1);
+        int providerId = mBrokerService.registerProvider(mClientToken1, PROVIDER_INFO1);
+
+        registerClient(mClientToken2, mClientCallback2);
+        mBrokerService.registerProvider(mClientToken2, PROVIDER_INFO1);
+
+        mBrokerService.setProviderOfferings(mClientToken1, providerId, asList(
+                new VmsLayerDependency(LAYER1)
+        ));
+        mBrokerService.setProviderOfferings(mClientToken2, providerId, asList(
+                new VmsLayerDependency(LAYER1)
+        ));
+        disconnectClient(mClientCallback1);
+
+        VmsAvailableLayers expectedLayers1 = new VmsAvailableLayers(1, asSet(
+                new VmsAssociatedLayer(LAYER1, asSet(providerId)))
+        );
+        VmsAvailableLayers expectedLayers2 = new VmsAvailableLayers(1, asSet(
+                new VmsAssociatedLayer(LAYER1, asSet(providerId)))
+        );
+        verifyLayerAvailability(mClientCallback1, expectedLayers1);
+        verifyLayerAvailability(mClientCallback2, expectedLayers2);
+    }
+
+    @Test
+    public void testSetProviderOfferings_RemoveOfferings_OnDisconnect_SingleProvider()
+            throws Exception {
+        registerClient(mClientToken1, mClientCallback1);
+        int providerId = mBrokerService.registerProvider(mClientToken1, PROVIDER_INFO1);
+
+        // Register second client to verify layer availability after first client disconnects
+        registerClient(mClientToken2, mClientCallback2);
+
+        mBrokerService.setProviderOfferings(mClientToken1, providerId, asList(
+                new VmsLayerDependency(LAYER1)
+        ));
+        disconnectClient(mClientCallback1);
+
+        VmsAvailableLayers expectedLayers1 = new VmsAvailableLayers(1, asSet(
+                new VmsAssociatedLayer(LAYER1, asSet(providerId)))
+        );
+        VmsAvailableLayers expectedLayers2 = new VmsAvailableLayers(2, emptySet());
+        verifyLayerAvailability(mClientCallback1, expectedLayers1);
+        verifyLayerAvailability(mClientCallback2, expectedLayers2);
+    }
+
+    @Test
+    public void testSetProviderOfferings_RemoveOfferings_OnDisconnect_MultipleProviders()
+            throws Exception {
+        registerClient(mClientToken1, mClientCallback1);
+        int providerId = mBrokerService.registerProvider(mClientToken1, PROVIDER_INFO1);
+        int providerId2 = mBrokerService.registerProvider(mClientToken1, PROVIDER_INFO2);
+
+        // Register second client to verify layer availability after first client disconnects
+        registerClient(mClientToken2, mClientCallback2);
+
+        mBrokerService.setProviderOfferings(mClientToken1, providerId, asList(
+                new VmsLayerDependency(LAYER1)
+        ));
+        mBrokerService.setProviderOfferings(mClientToken1, providerId2, asList(
+                new VmsLayerDependency(LAYER1)
+        ));
+        disconnectClient(mClientCallback1);
+
+        VmsAvailableLayers expectedLayers1 = new VmsAvailableLayers(2, asSet(
+                new VmsAssociatedLayer(LAYER1, asSet(providerId, providerId2)))
+        );
+        VmsAvailableLayers expectedLayers2 = new VmsAvailableLayers(3, emptySet());
+        verifyLayerAvailability(mClientCallback1, expectedLayers1);
+        verifyLayerAvailability(mClientCallback2, expectedLayers2);
+    }
+
+    @Test
+    public void testSetProviderOfferings_RemoveOfferings_OnDisconnect_MultipleClients()
+            throws Exception {
+        registerClient(mClientToken1, mClientCallback1);
+        int providerId = mBrokerService.registerProvider(mClientToken1, PROVIDER_INFO1);
+
+        registerClient(mClientToken2, mClientCallback2);
+        int providerId2 = mBrokerService.registerProvider(mClientToken2, PROVIDER_INFO2);
+
+        mBrokerService.setProviderOfferings(mClientToken1, providerId, asList(
+                new VmsLayerDependency(LAYER1)
+        ));
+        mBrokerService.setProviderOfferings(mClientToken2, providerId2, asList(
+                new VmsLayerDependency(LAYER1)
+        ));
+        disconnectClient(mClientCallback1);
+
+        VmsAvailableLayers expectedLayers1 = new VmsAvailableLayers(2, asSet(
+                new VmsAssociatedLayer(LAYER1, asSet(providerId, providerId2)))
+        );
+        VmsAvailableLayers expectedLayers2 = new VmsAvailableLayers(3, asSet(
+                new VmsAssociatedLayer(LAYER1, asSet(providerId2)))
+        );
+        verifyLayerAvailability(mClientCallback1, expectedLayers1);
+        verifyLayerAvailability(mClientCallback2, expectedLayers2);
+    }
+
+    @Test
+    public void testSetProviderOfferings_RemoveOfferings_OnDisconnect_MultipleClients_SingleProvider()
+            throws Exception {
+        registerClient(mClientToken1, mClientCallback1);
+        int providerId = mBrokerService.registerProvider(mClientToken1, PROVIDER_INFO1);
+
+        registerClient(mClientToken2, mClientCallback2);
+        mBrokerService.registerProvider(mClientToken2, PROVIDER_INFO1);
+
+        mBrokerService.setProviderOfferings(mClientToken1, providerId, asList(
+                new VmsLayerDependency(LAYER1)
+        ));
+        mBrokerService.setProviderOfferings(mClientToken2, providerId, asList(
+                new VmsLayerDependency(LAYER1)
+        ));
+        disconnectClient(mClientCallback1);
+
+        VmsAvailableLayers expectedLayers1 = new VmsAvailableLayers(1, asSet(
+                new VmsAssociatedLayer(LAYER1, asSet(providerId)))
+        );
+        VmsAvailableLayers expectedLayers2 = new VmsAvailableLayers(1, asSet(
+                new VmsAssociatedLayer(LAYER1, asSet(providerId)))
+        );
+        verifyLayerAvailability(mClientCallback1, expectedLayers1);
+        verifyLayerAvailability(mClientCallback2, expectedLayers2);
+    }
+
+    @Test
+    public void testSetProviderOfferings_DependencyMet_SingleProvider() throws Exception {
+        registerClient(mClientToken1, mClientCallback1);
+        int providerId = mBrokerService.registerProvider(mClientToken1, PROVIDER_INFO1);
+
+        mBrokerService.setProviderOfferings(mClientToken1, providerId, asList(
+                new VmsLayerDependency(LAYER1, asSet(LAYER2)),
+                new VmsLayerDependency(LAYER2)
+        ));
+
+        VmsAvailableLayers expectedLayers = new VmsAvailableLayers(1, asSet(
+                new VmsAssociatedLayer(LAYER1, asSet(providerId)),
+                new VmsAssociatedLayer(LAYER2, asSet(providerId)))
+        );
+        verifyLayerAvailability(mClientCallback1, expectedLayers);
+    }
+
+    @Test
+    public void testSetProviderOfferings_DependencyMet_MultipleProviders() throws Exception {
+        registerClient(mClientToken1, mClientCallback1);
+        int providerId = mBrokerService.registerProvider(mClientToken1, PROVIDER_INFO1);
+        int providerId2 = mBrokerService.registerProvider(mClientToken1, PROVIDER_INFO2);
+
+        mBrokerService.setProviderOfferings(mClientToken1, providerId, asList(
+                new VmsLayerDependency(LAYER1, asSet(LAYER2))
+        ));
+        mBrokerService.setProviderOfferings(mClientToken1, providerId2, asList(
+                new VmsLayerDependency(LAYER2)
+        ));
+
+        VmsAvailableLayers expectedLayers = new VmsAvailableLayers(2, asSet(
+                new VmsAssociatedLayer(LAYER1, asSet(providerId)),
+                new VmsAssociatedLayer(LAYER2, asSet(providerId2)))
+        );
+        verifyLayerAvailability(mClientCallback1, expectedLayers);
+    }
+
+    @Test
+    public void testSetProviderOfferings_DependencyMet_MultipleClients() throws Exception {
+        registerClient(mClientToken1, mClientCallback1);
+        int providerId = mBrokerService.registerProvider(mClientToken1, PROVIDER_INFO1);
+
+        registerClient(mClientToken2, mClientCallback2);
+        int providerId2 = mBrokerService.registerProvider(mClientToken2, PROVIDER_INFO2);
+
+        mBrokerService.setProviderOfferings(mClientToken1, providerId, asList(
+                new VmsLayerDependency(LAYER1, asSet(LAYER2))
+        ));
+        mBrokerService.setProviderOfferings(mClientToken2, providerId2, asList(
+                new VmsLayerDependency(LAYER2)
+        ));
+
+        VmsAvailableLayers expectedLayers = new VmsAvailableLayers(2, asSet(
+                new VmsAssociatedLayer(LAYER1, asSet(providerId)),
+                new VmsAssociatedLayer(LAYER2, asSet(providerId2)))
+        );
+        verifyLayerAvailability(mClientCallback1, expectedLayers);
+        verifyLayerAvailability(mClientCallback2, expectedLayers);
+    }
+
+    @Test
+    public void testSetProviderOfferings_DependencyMet_MultipleClients_SingleProvider()
+            throws Exception {
+        registerClient(mClientToken1, mClientCallback1);
+        int providerId = mBrokerService.registerProvider(mClientToken1, PROVIDER_INFO1);
+
+        registerClient(mClientToken2, mClientCallback2);
+        mBrokerService.registerProvider(mClientToken2, PROVIDER_INFO1);
+
+        mBrokerService.setProviderOfferings(mClientToken1, providerId, asList(
+                new VmsLayerDependency(LAYER1, asSet(LAYER2))
+        ));
+        mBrokerService.setProviderOfferings(mClientToken2, providerId, asList(
+                new VmsLayerDependency(LAYER2)
+        ));
+
+        VmsAvailableLayers expectedLayers = new VmsAvailableLayers(2, asSet(
+                new VmsAssociatedLayer(LAYER1, asSet(providerId)),
+                new VmsAssociatedLayer(LAYER2, asSet(providerId)))
+        );
+        verifyLayerAvailability(mClientCallback1, expectedLayers);
+        verifyLayerAvailability(mClientCallback2, expectedLayers);
+    }
+
+    @Test
+    public void testSetProviderOfferings_DependencyMet_MultipleDependencies_SingleProvider()
+            throws Exception {
+        registerClient(mClientToken1, mClientCallback1);
+        int providerId = mBrokerService.registerProvider(mClientToken1, PROVIDER_INFO1);
+
+        mBrokerService.setProviderOfferings(mClientToken1, providerId, asList(
+                new VmsLayerDependency(LAYER1, asSet(LAYER2, LAYER3)),
+                new VmsLayerDependency(LAYER2),
+                new VmsLayerDependency(LAYER3)
+        ));
+
+        VmsAvailableLayers expectedLayers = new VmsAvailableLayers(1, asSet(
+                new VmsAssociatedLayer(LAYER1, asSet(providerId)),
+                new VmsAssociatedLayer(LAYER2, asSet(providerId)),
+                new VmsAssociatedLayer(LAYER3, asSet(providerId)))
+        );
+        verifyLayerAvailability(mClientCallback1, expectedLayers);
+    }
+
+    @Test
+    public void testSetProviderOfferings_DependencyMet_MultipleDependencies_MultipleProviders()
+            throws Exception {
+        registerClient(mClientToken1, mClientCallback1);
+        int providerId = mBrokerService.registerProvider(mClientToken1, PROVIDER_INFO1);
+        int providerId2 = mBrokerService.registerProvider(mClientToken1, PROVIDER_INFO2);
+
+        mBrokerService.setProviderOfferings(mClientToken1, providerId, asList(
+                new VmsLayerDependency(LAYER1, asSet(LAYER2, LAYER3)),
+                new VmsLayerDependency(LAYER2)
+        ));
+        mBrokerService.setProviderOfferings(mClientToken1, providerId2, asList(
+                new VmsLayerDependency(LAYER3)
+        ));
+
+        VmsAvailableLayers expectedLayers = new VmsAvailableLayers(2, asSet(
+                new VmsAssociatedLayer(LAYER1, asSet(providerId)),
+                new VmsAssociatedLayer(LAYER2, asSet(providerId)),
+                new VmsAssociatedLayer(LAYER3, asSet(providerId2)))
+        );
+        verifyLayerAvailability(mClientCallback1, expectedLayers);
+    }
+
+    @Test
+    public void testSetProviderOfferings_DependencyMet_MultipleDependencies_MultipleClients()
+            throws Exception {
+        registerClient(mClientToken1, mClientCallback1);
+        int providerId = mBrokerService.registerProvider(mClientToken1, PROVIDER_INFO1);
+
+        registerClient(mClientToken2, mClientCallback2);
+        int providerId2 = mBrokerService.registerProvider(mClientToken2, PROVIDER_INFO2);
+
+        mBrokerService.setProviderOfferings(mClientToken1, providerId, asList(
+                new VmsLayerDependency(LAYER1, asSet(LAYER2, LAYER3)),
+                new VmsLayerDependency(LAYER2)
+        ));
+        mBrokerService.setProviderOfferings(mClientToken2, providerId2, asList(
+                new VmsLayerDependency(LAYER3)
+        ));
+
+        VmsAvailableLayers expectedLayers = new VmsAvailableLayers(2, asSet(
+                new VmsAssociatedLayer(LAYER1, asSet(providerId)),
+                new VmsAssociatedLayer(LAYER2, asSet(providerId)),
+                new VmsAssociatedLayer(LAYER3, asSet(providerId2)))
+        );
+        verifyLayerAvailability(mClientCallback1, expectedLayers);
+        verifyLayerAvailability(mClientCallback2, expectedLayers);
+    }
+
+    @Test
+    public void testSetProviderOfferings_DependencyMet_MultipleDependencies_MultipleClients_SingleProvider()
+            throws Exception {
+        registerClient(mClientToken1, mClientCallback1);
+        int providerId = mBrokerService.registerProvider(mClientToken1, PROVIDER_INFO1);
+
+        registerClient(mClientToken2, mClientCallback2);
+        mBrokerService.registerProvider(mClientToken2, PROVIDER_INFO1);
+
+        mBrokerService.setProviderOfferings(mClientToken1, providerId, asList(
+                new VmsLayerDependency(LAYER1, asSet(LAYER2, LAYER3)),
+                new VmsLayerDependency(LAYER2)
+        ));
+        mBrokerService.setProviderOfferings(mClientToken2, providerId, asList(
+                new VmsLayerDependency(LAYER3)
+        ));
+
+        VmsAvailableLayers expectedLayers = new VmsAvailableLayers(2, asSet(
+                new VmsAssociatedLayer(LAYER1, asSet(providerId)),
+                new VmsAssociatedLayer(LAYER2, asSet(providerId)),
+                new VmsAssociatedLayer(LAYER3, asSet(providerId)))
+        );
+        verifyLayerAvailability(mClientCallback1, expectedLayers);
+        verifyLayerAvailability(mClientCallback2, expectedLayers);
+    }
+
+    @Test
+    public void testSetProviderOfferings_DependencyMet_ChainedDependencies_SingleProvider()
+            throws Exception {
+        registerClient(mClientToken1, mClientCallback1);
+        int providerId = mBrokerService.registerProvider(mClientToken1, PROVIDER_INFO1);
+
+        mBrokerService.setProviderOfferings(mClientToken1, providerId, asList(
+                new VmsLayerDependency(LAYER1, asSet(LAYER2)),
+                new VmsLayerDependency(LAYER2, asSet(LAYER3)),
+                new VmsLayerDependency(LAYER3)
+        ));
+
+        VmsAvailableLayers expectedLayers = new VmsAvailableLayers(1, asSet(
+                new VmsAssociatedLayer(LAYER1, asSet(providerId)),
+                new VmsAssociatedLayer(LAYER2, asSet(providerId)),
+                new VmsAssociatedLayer(LAYER3, asSet(providerId)))
+        );
+        verifyLayerAvailability(mClientCallback1, expectedLayers);
+    }
+
+    @Test
+    public void testSetProviderOfferings_DependencyMet_ChainedDependencies_MultipleProviders()
+            throws Exception {
+        registerClient(mClientToken1, mClientCallback1);
+        int providerId = mBrokerService.registerProvider(mClientToken1, PROVIDER_INFO1);
+        int providerId2 = mBrokerService.registerProvider(mClientToken1, PROVIDER_INFO2);
+
+        mBrokerService.setProviderOfferings(mClientToken1, providerId, asList(
+                new VmsLayerDependency(LAYER1, asSet(LAYER2)),
+                new VmsLayerDependency(LAYER3)
+        ));
+        mBrokerService.setProviderOfferings(mClientToken1, providerId2, asList(
+                new VmsLayerDependency(LAYER2, asSet(LAYER3))
+        ));
+
+        VmsAvailableLayers expectedLayers = new VmsAvailableLayers(2, asSet(
+                new VmsAssociatedLayer(LAYER1, asSet(providerId)),
+                new VmsAssociatedLayer(LAYER2, asSet(providerId2)),
+                new VmsAssociatedLayer(LAYER3, asSet(providerId)))
+        );
+        verifyLayerAvailability(mClientCallback1, expectedLayers);
+    }
+
+    @Test
+    public void testSetProviderOfferings_DependencyMet_ChainedDependencies_MultipleClients()
+            throws Exception {
+        registerClient(mClientToken1, mClientCallback1);
+        int providerId = mBrokerService.registerProvider(mClientToken1, PROVIDER_INFO1);
+
+        registerClient(mClientToken2, mClientCallback2);
+        int providerId2 = mBrokerService.registerProvider(mClientToken2, PROVIDER_INFO2);
+
+        mBrokerService.setProviderOfferings(mClientToken1, providerId, asList(
+                new VmsLayerDependency(LAYER1, asSet(LAYER2)),
+                new VmsLayerDependency(LAYER3)
+        ));
+        mBrokerService.setProviderOfferings(mClientToken2, providerId2, asList(
+                new VmsLayerDependency(LAYER2, asSet(LAYER3))
+        ));
+
+        VmsAvailableLayers expectedLayers = new VmsAvailableLayers(2, asSet(
+                new VmsAssociatedLayer(LAYER1, asSet(providerId)),
+                new VmsAssociatedLayer(LAYER2, asSet(providerId2)),
+                new VmsAssociatedLayer(LAYER3, asSet(providerId)))
+        );
+        verifyLayerAvailability(mClientCallback1, expectedLayers);
+        verifyLayerAvailability(mClientCallback2, expectedLayers);
+    }
+
+    @Test
+    public void testSetProviderOfferings_DependencyMet_ChainedDependencies_MultipleClients_SingleProvider()
+            throws Exception {
+        registerClient(mClientToken1, mClientCallback1);
+        int providerId = mBrokerService.registerProvider(mClientToken1, PROVIDER_INFO1);
+
+        registerClient(mClientToken2, mClientCallback2);
+        mBrokerService.registerProvider(mClientToken2, PROVIDER_INFO1);
+
+        mBrokerService.setProviderOfferings(mClientToken1, providerId, asList(
+                new VmsLayerDependency(LAYER1, asSet(LAYER2)),
+                new VmsLayerDependency(LAYER3)
+        ));
+        mBrokerService.setProviderOfferings(mClientToken2, providerId, asList(
+                new VmsLayerDependency(LAYER2, asSet(LAYER3))
+        ));
+
+        VmsAvailableLayers expectedLayers = new VmsAvailableLayers(2, asSet(
+                new VmsAssociatedLayer(LAYER1, asSet(providerId)),
+                new VmsAssociatedLayer(LAYER2, asSet(providerId)),
+                new VmsAssociatedLayer(LAYER3, asSet(providerId)))
+        );
+        verifyLayerAvailability(mClientCallback1, expectedLayers);
+    }
+
+    @Test
+    public void testSetProviderOfferings_DependencyCircular_SingleProvider() throws Exception {
+        registerClient(mClientToken1, mClientCallback1);
+        int providerId = mBrokerService.registerProvider(mClientToken1, PROVIDER_INFO1);
+
+        mBrokerService.setProviderOfferings(mClientToken1, providerId, asList(
+                new VmsLayerDependency(LAYER1, asSet(LAYER2)),
+                new VmsLayerDependency(LAYER2, asSet(LAYER1))
+        ));
+
+        VmsAvailableLayers expectedLayers = new VmsAvailableLayers(1, emptySet());
+        verifyLayerAvailability(mClientCallback1, expectedLayers);
+    }
+
+    @Test
+    public void testSetProviderOfferings_DependencyCircular_MultipleProviders() throws Exception {
+        registerClient(mClientToken1, mClientCallback1);
+        int providerId = mBrokerService.registerProvider(mClientToken1, PROVIDER_INFO1);
+        int providerId2 = mBrokerService.registerProvider(mClientToken1, PROVIDER_INFO2);
+
+        mBrokerService.setProviderOfferings(mClientToken1, providerId, asList(
+                new VmsLayerDependency(LAYER1, asSet(LAYER2))
+        ));
+        mBrokerService.setProviderOfferings(mClientToken1, providerId2, asList(
+                new VmsLayerDependency(LAYER2, asSet(LAYER1))
+        ));
+
+        VmsAvailableLayers expectedLayers = new VmsAvailableLayers(2, emptySet());
+        verifyLayerAvailability(mClientCallback1, expectedLayers);
+    }
+
+    @Test
+    public void testSetProviderOfferings_DependencyCircular_MultipleClients() throws Exception {
+        registerClient(mClientToken1, mClientCallback1);
+        int providerId = mBrokerService.registerProvider(mClientToken1, PROVIDER_INFO1);
+
+        registerClient(mClientToken2, mClientCallback2);
+        int providerId2 = mBrokerService.registerProvider(mClientToken2, PROVIDER_INFO2);
+
+        mBrokerService.setProviderOfferings(mClientToken1, providerId, asList(
+                new VmsLayerDependency(LAYER1, asSet(LAYER2))
+        ));
+        mBrokerService.setProviderOfferings(mClientToken2, providerId2, asList(
+                new VmsLayerDependency(LAYER2, asSet(LAYER1))
+        ));
+
+        VmsAvailableLayers expectedLayers = new VmsAvailableLayers(2, emptySet());
+        verifyLayerAvailability(mClientCallback1, expectedLayers);
+        verifyLayerAvailability(mClientCallback2, expectedLayers);
+    }
+
+    @Test
+    public void testSetProviderOfferings_DependencyCircular_MultipleClients_SingleProvider()
+            throws Exception {
+        registerClient(mClientToken1, mClientCallback1);
+        int providerId = mBrokerService.registerProvider(mClientToken1, PROVIDER_INFO1);
+
+        registerClient(mClientToken2, mClientCallback2);
+        mBrokerService.registerProvider(mClientToken2, PROVIDER_INFO1);
+
+        mBrokerService.setProviderOfferings(mClientToken1, providerId, asList(
+                new VmsLayerDependency(LAYER1, asSet(LAYER2))
+        ));
+        mBrokerService.setProviderOfferings(mClientToken2, providerId, asList(
+                new VmsLayerDependency(LAYER2, asSet(LAYER1))
+        ));
+
+        VmsAvailableLayers expectedLayers = new VmsAvailableLayers(2, emptySet());
+        verifyLayerAvailability(mClientCallback1, expectedLayers);
+        verifyLayerAvailability(mClientCallback2, expectedLayers);
+    }
+
+    @Test
+    public void testSetProviderOfferings_DependencyCircular_MultipleDependencies_SingleProvider()
+            throws Exception {
+        registerClient(mClientToken1, mClientCallback1);
+        int providerId = mBrokerService.registerProvider(mClientToken1, PROVIDER_INFO1);
+
+        mBrokerService.setProviderOfferings(mClientToken1, providerId, asList(
+                new VmsLayerDependency(LAYER1, asSet(LAYER2, LAYER3)),
+                new VmsLayerDependency(LAYER2),
+                new VmsLayerDependency(LAYER3, asSet(LAYER1))
+        ));
+
+        VmsAvailableLayers expectedLayers = new VmsAvailableLayers(1, asSet(
+                new VmsAssociatedLayer(LAYER2, asSet(providerId)))
+        );
+        verifyLayerAvailability(mClientCallback1, expectedLayers);
+    }
+
+    @Test
+    public void testSetProviderOfferings_DependencyCircular_MultipleDependencies_MultipleProviders()
+            throws Exception {
+        registerClient(mClientToken1, mClientCallback1);
+        int providerId = mBrokerService.registerProvider(mClientToken1, PROVIDER_INFO1);
+        int providerId2 = mBrokerService.registerProvider(mClientToken1, PROVIDER_INFO2);
+
+        mBrokerService.setProviderOfferings(mClientToken1, providerId, asList(
+                new VmsLayerDependency(LAYER1, asSet(LAYER2, LAYER3)),
+                new VmsLayerDependency(LAYER2)
+        ));
+        mBrokerService.setProviderOfferings(mClientToken1, providerId2, asList(
+                new VmsLayerDependency(LAYER3, asSet(LAYER1))
+        ));
+
+        VmsAvailableLayers expectedLayers = new VmsAvailableLayers(2, asSet(
+                new VmsAssociatedLayer(LAYER2, asSet(providerId)))
+        );
+        verifyLayerAvailability(mClientCallback1, expectedLayers);
+    }
+
+    @Test
+    public void testSetProviderOfferings_DependencyCircular_MultipleDependencies_MultipleClients()
+            throws Exception {
+        registerClient(mClientToken1, mClientCallback1);
+        int providerId = mBrokerService.registerProvider(mClientToken1, PROVIDER_INFO1);
+
+        registerClient(mClientToken2, mClientCallback2);
+        int providerId2 = mBrokerService.registerProvider(mClientToken2, PROVIDER_INFO2);
+
+        mBrokerService.setProviderOfferings(mClientToken1, providerId, asList(
+                new VmsLayerDependency(LAYER1, asSet(LAYER2, LAYER3)),
+                new VmsLayerDependency(LAYER2)
+        ));
+        mBrokerService.setProviderOfferings(mClientToken2, providerId2, asList(
+                new VmsLayerDependency(LAYER3, asSet(LAYER1))
+        ));
+
+        VmsAvailableLayers expectedLayers = new VmsAvailableLayers(2, asSet(
+                new VmsAssociatedLayer(LAYER2, asSet(providerId)))
+        );
+        verifyLayerAvailability(mClientCallback1, expectedLayers);
+        verifyLayerAvailability(mClientCallback2, expectedLayers);
+    }
+
+    @Test
+    public void testSetProviderOfferings_DependencyCircular_MultipleDependencies_MultipleClients_SingleProvider()
+            throws Exception {
+        registerClient(mClientToken1, mClientCallback1);
+        int providerId = mBrokerService.registerProvider(mClientToken1, PROVIDER_INFO1);
+
+        registerClient(mClientToken2, mClientCallback2);
+        mBrokerService.registerProvider(mClientToken2, PROVIDER_INFO1);
+
+        mBrokerService.setProviderOfferings(mClientToken1, providerId, asList(
+                new VmsLayerDependency(LAYER1, asSet(LAYER2, LAYER3)),
+                new VmsLayerDependency(LAYER2)
+        ));
+        mBrokerService.setProviderOfferings(mClientToken2, providerId, asList(
+                new VmsLayerDependency(LAYER3, asSet(LAYER1))
+        ));
+
+        VmsAvailableLayers expectedLayers = new VmsAvailableLayers(2, asSet(
+                new VmsAssociatedLayer(LAYER2, asSet(providerId)))
+        );
+        verifyLayerAvailability(mClientCallback1, expectedLayers);
+        verifyLayerAvailability(mClientCallback2, expectedLayers);
+    }
+
+    @Test
+    public void testSetProviderOfferings_DependencyCircular_ChainedDependencies_SingleProvider()
+            throws Exception {
+        registerClient(mClientToken1, mClientCallback1);
+        int providerId = mBrokerService.registerProvider(mClientToken1, PROVIDER_INFO1);
+
+        mBrokerService.setProviderOfferings(mClientToken1, providerId, asList(
+                new VmsLayerDependency(LAYER1, asSet(LAYER2)),
+                new VmsLayerDependency(LAYER2, asSet(LAYER3)),
+                new VmsLayerDependency(LAYER3, asSet(LAYER1))
+        ));
+
+        VmsAvailableLayers expectedLayers = new VmsAvailableLayers(1, emptySet());
+        verifyLayerAvailability(mClientCallback1, expectedLayers);
+    }
+
+    @Test
+    public void testSetProviderOfferings_DependencyCircular_ChainedDependencies_MultipleProviders()
+            throws Exception {
+        registerClient(mClientToken1, mClientCallback1);
+        int providerId = mBrokerService.registerProvider(mClientToken1, PROVIDER_INFO1);
+        int providerId2 = mBrokerService.registerProvider(mClientToken1, PROVIDER_INFO2);
+
+        mBrokerService.setProviderOfferings(mClientToken1, providerId, asList(
+                new VmsLayerDependency(LAYER1, asSet(LAYER2)),
+                new VmsLayerDependency(LAYER3, asSet(LAYER1))
+        ));
+        mBrokerService.setProviderOfferings(mClientToken1, providerId2, asList(
+                new VmsLayerDependency(LAYER2, asSet(LAYER3))
+        ));
+
+        VmsAvailableLayers expectedLayers = new VmsAvailableLayers(2, emptySet());
+        verifyLayerAvailability(mClientCallback1, expectedLayers);
+    }
+
+    @Test
+    public void testSetProviderOfferings_DependencyCircular_ChainedDependencies_MultipleClients()
+            throws Exception {
+        registerClient(mClientToken1, mClientCallback1);
+        int providerId = mBrokerService.registerProvider(mClientToken1, PROVIDER_INFO1);
+
+        registerClient(mClientToken2, mClientCallback2);
+        int providerId2 = mBrokerService.registerProvider(mClientToken2, PROVIDER_INFO2);
+
+        mBrokerService.setProviderOfferings(mClientToken1, providerId, asList(
+                new VmsLayerDependency(LAYER1, asSet(LAYER2)),
+                new VmsLayerDependency(LAYER3, asSet(LAYER1))
+        ));
+        mBrokerService.setProviderOfferings(mClientToken2, providerId2, asList(
+                new VmsLayerDependency(LAYER2, asSet(LAYER3))
+        ));
+
+        VmsAvailableLayers expectedLayers = new VmsAvailableLayers(2, emptySet());
+        verifyLayerAvailability(mClientCallback1, expectedLayers);
+        verifyLayerAvailability(mClientCallback2, expectedLayers);
+    }
+
+    @Test
+    public void testSetProviderOfferings_DependencyCircular_ChainedDependencies_MultipleClients_SingleProvider()
+            throws Exception {
+        registerClient(mClientToken1, mClientCallback1);
+        int providerId = mBrokerService.registerProvider(mClientToken1, PROVIDER_INFO1);
+
+        registerClient(mClientToken2, mClientCallback2);
+        mBrokerService.registerProvider(mClientToken2, PROVIDER_INFO1);
+
+        mBrokerService.setProviderOfferings(mClientToken1, providerId, asList(
+                new VmsLayerDependency(LAYER1, asSet(LAYER2)),
+                new VmsLayerDependency(LAYER3, asSet(LAYER1))
+        ));
+        mBrokerService.setProviderOfferings(mClientToken2, providerId, asList(
+                new VmsLayerDependency(LAYER2, asSet(LAYER3))
+        ));
+
+        VmsAvailableLayers expectedLayers = new VmsAvailableLayers(2, emptySet());
+        verifyLayerAvailability(mClientCallback1, expectedLayers);
+        verifyLayerAvailability(mClientCallback2, expectedLayers);
+    }
+
+    @Test
+    public void testSetProviderOfferings_DependencyUnmet_SingleProvider() throws Exception {
+        registerClient(mClientToken1, mClientCallback1);
+        int providerId = mBrokerService.registerProvider(mClientToken1, PROVIDER_INFO1);
+
+        mBrokerService.setProviderOfferings(mClientToken1, providerId, asList(
+                new VmsLayerDependency(LAYER1, asSet(LAYER2))
+        ));
+
+        VmsAvailableLayers expectedLayers = new VmsAvailableLayers(1, emptySet());
+        verifyLayerAvailability(mClientCallback1, expectedLayers);
+    }
+
+    @Test
+    public void testSetProviderOfferings_DependencyUnmet_MultipleDependencies_SingleProvider()
+            throws Exception {
+        registerClient(mClientToken1, mClientCallback1);
+        int providerId = mBrokerService.registerProvider(mClientToken1, PROVIDER_INFO1);
+
+        mBrokerService.setProviderOfferings(mClientToken1, providerId, asList(
+                new VmsLayerDependency(LAYER1, asSet(LAYER2, LAYER3)),
+                new VmsLayerDependency(LAYER2)
+        ));
+
+        VmsAvailableLayers expectedLayers = new VmsAvailableLayers(1, asSet(
+                new VmsAssociatedLayer(LAYER2, asSet(providerId)))
+        );
+        verifyLayerAvailability(mClientCallback1, expectedLayers);
+    }
+
+    @Test
+    public void testSetProviderOfferings_DependencyUnmet_MultipleDependencies_MultipleProviders()
+            throws Exception {
+        registerClient(mClientToken1, mClientCallback1);
+        int providerId = mBrokerService.registerProvider(mClientToken1, PROVIDER_INFO1);
+        int providerId2 = mBrokerService.registerProvider(mClientToken1, PROVIDER_INFO2);
+
+        mBrokerService.setProviderOfferings(mClientToken1, providerId, asList(
+                new VmsLayerDependency(LAYER1, asSet(LAYER2, LAYER3))
+        ));
+        mBrokerService.setProviderOfferings(mClientToken1, providerId2, asList(
+                new VmsLayerDependency(LAYER3)
+        ));
+
+        VmsAvailableLayers expectedLayers = new VmsAvailableLayers(2, asSet(
+                new VmsAssociatedLayer(LAYER3, asSet(providerId2)))
+        );
+        verifyLayerAvailability(mClientCallback1, expectedLayers);
+    }
+
+    @Test
+    public void testSetProviderOfferings_DependencyUnmet_MultipleDependencies_MultipleClients()
+            throws Exception {
+        registerClient(mClientToken1, mClientCallback1);
+        int providerId = mBrokerService.registerProvider(mClientToken1, PROVIDER_INFO1);
+
+        registerClient(mClientToken2, mClientCallback2);
+        int providerId2 = mBrokerService.registerProvider(mClientToken2, PROVIDER_INFO2);
+
+        mBrokerService.setProviderOfferings(mClientToken1, providerId, asList(
+                new VmsLayerDependency(LAYER1, asSet(LAYER2, LAYER3))
+        ));
+        mBrokerService.setProviderOfferings(mClientToken2, providerId2, asList(
+                new VmsLayerDependency(LAYER3)
+        ));
+
+        VmsAvailableLayers expectedLayers = new VmsAvailableLayers(2, asSet(
+                new VmsAssociatedLayer(LAYER3, asSet(providerId2)))
+        );
+        verifyLayerAvailability(mClientCallback1, expectedLayers);
+        verifyLayerAvailability(mClientCallback2, expectedLayers);
+    }
+
+    @Test
+    public void testSetProviderOfferings_DependencyUnmet_MultipleDependencies_MultipleClients_SingleProvider()
+            throws Exception {
+        registerClient(mClientToken1, mClientCallback1);
+        int providerId = mBrokerService.registerProvider(mClientToken1, PROVIDER_INFO1);
+
+        registerClient(mClientToken2, mClientCallback2);
+        mBrokerService.registerProvider(mClientToken2, PROVIDER_INFO1);
+
+        mBrokerService.setProviderOfferings(mClientToken1, providerId, asList(
+                new VmsLayerDependency(LAYER1, asSet(LAYER2, LAYER3))
+        ));
+        mBrokerService.setProviderOfferings(mClientToken2, providerId, asList(
+                new VmsLayerDependency(LAYER3)
+        ));
+
+        VmsAvailableLayers expectedLayers = new VmsAvailableLayers(2, asSet(
+                new VmsAssociatedLayer(LAYER3, asSet(providerId)))
+        );
+        verifyLayerAvailability(mClientCallback1, expectedLayers);
+        verifyLayerAvailability(mClientCallback2, expectedLayers);
+    }
+
+    @Test
+    public void testSetProviderOfferings_DependencyUnmet_ChainedDependencies_SingleProvider()
+            throws Exception {
+        registerClient(mClientToken1, mClientCallback1);
+        int providerId = mBrokerService.registerProvider(mClientToken1, PROVIDER_INFO1);
+
+        mBrokerService.setProviderOfferings(mClientToken1, providerId, asList(
+                new VmsLayerDependency(LAYER1, asSet(LAYER2)),
+                new VmsLayerDependency(LAYER2, asSet(LAYER3))
+        ));
+
+        VmsAvailableLayers expectedLayers = new VmsAvailableLayers(1, emptySet());
+        verifyLayerAvailability(mClientCallback1, expectedLayers);
+    }
+
+    @Test
+    public void testSetProviderOfferings_DependencyUnmet_ChainedDependencies_MultipleProviders()
+            throws Exception {
+        registerClient(mClientToken1, mClientCallback1);
+        int providerId = mBrokerService.registerProvider(mClientToken1, PROVIDER_INFO1);
+        int providerId2 = mBrokerService.registerProvider(mClientToken1, PROVIDER_INFO2);
+
+        mBrokerService.setProviderOfferings(mClientToken1, providerId, asList(
+                new VmsLayerDependency(LAYER1, asSet(LAYER2))
+        ));
+        mBrokerService.setProviderOfferings(mClientToken1, providerId2, asList(
+                new VmsLayerDependency(LAYER2, asSet(LAYER3))
+        ));
+
+        VmsAvailableLayers expectedLayers = new VmsAvailableLayers(2, emptySet());
+        verifyLayerAvailability(mClientCallback1, expectedLayers);
+    }
+
+    @Test
+    public void testSetProviderOfferings_DependencyUnmet_ChainedDependencies_MultipleClients()
+            throws Exception {
+        registerClient(mClientToken1, mClientCallback1);
+        int providerId = mBrokerService.registerProvider(mClientToken1, PROVIDER_INFO1);
+
+        registerClient(mClientToken2, mClientCallback2);
+        int providerId2 = mBrokerService.registerProvider(mClientToken2, PROVIDER_INFO2);
+
+        mBrokerService.setProviderOfferings(mClientToken1, providerId, asList(
+                new VmsLayerDependency(LAYER1, asSet(LAYER2))
+        ));
+        mBrokerService.setProviderOfferings(mClientToken2, providerId2, asList(
+                new VmsLayerDependency(LAYER2, asSet(LAYER3))
+        ));
+
+        VmsAvailableLayers expectedLayers = new VmsAvailableLayers(2, emptySet());
+        verifyLayerAvailability(mClientCallback1, expectedLayers);
+        verifyLayerAvailability(mClientCallback2, expectedLayers);
+    }
+
+    @Test
+    public void testSetProviderOfferings_DependencyUnmet_ChainedDependencies_MultipleClients_SingleProvider()
+            throws Exception {
+        registerClient(mClientToken1, mClientCallback1);
+        int providerId = mBrokerService.registerProvider(mClientToken1, PROVIDER_INFO1);
+
+        registerClient(mClientToken2, mClientCallback2);
+        mBrokerService.registerProvider(mClientToken2, PROVIDER_INFO1);
+
+        mBrokerService.setProviderOfferings(mClientToken1, providerId, asList(
+                new VmsLayerDependency(LAYER1, asSet(LAYER2))
+        ));
+        mBrokerService.setProviderOfferings(mClientToken2, providerId, asList(
+                new VmsLayerDependency(LAYER2, asSet(LAYER3))
+        ));
+
+        VmsAvailableLayers expectedLayers = new VmsAvailableLayers(2, emptySet());
+        verifyLayerAvailability(mClientCallback1, expectedLayers);
+        verifyLayerAvailability(mClientCallback2, expectedLayers);
+    }
+
+    @Test
+    public void testPublishPacket_UnknownClient() {
+        registerClient(mClientToken1, mClientCallback1);
+        int providerId = mBrokerService.registerProvider(mClientToken1, PROVIDER_INFO1);
+
+        assertThrows(
+                IllegalStateException.class,
+                () -> mBrokerService.publishPacket(new Binder(), providerId, LAYER1, PAYLOAD));
+    }
+
+    @Test
+    public void testPublishPacket_UnknownOffering() {
+        registerClient(mClientToken1, mClientCallback1);
+        int providerId = mBrokerService.registerProvider(mClientToken1, PROVIDER_INFO1);
+
+        assertThrows(
+                IllegalArgumentException.class,
+                () -> mBrokerService.publishPacket(mClientToken1, providerId, LAYER1, PAYLOAD));
+    }
+
+    @Test
+    public void testPublishPacket_UnknownOffering_LegacyClient() throws Exception {
+        mBrokerService.registerClient(mClientToken1, mClientCallback1, true);
+
+        mBrokerService.setSubscriptions(mClientToken1, asList(
+                new VmsAssociatedLayer(LAYER1, emptySet())
+        ));
+        mBrokerService.publishPacket(mClientToken1, 12345, LAYER1, PAYLOAD);
+
+        verify(mClientLog1).logPacketSent(LAYER1, PAYLOAD.length);
+        verify(mClientLog1).logPacketReceived(LAYER1, PAYLOAD.length);
+        verifyPacketReceived(mClientCallback1, 12345, LAYER1, PAYLOAD);
+    }
+
+
+    @Test
+    public void testPublishPacket_NoSubscribers() throws Exception {
+        registerClient(mClientToken1, mClientCallback1);
+        int providerId = mBrokerService.registerProvider(mClientToken1, PROVIDER_INFO1);
+        mBrokerService.setProviderOfferings(mClientToken1, providerId, asList(
+                new VmsLayerDependency(LAYER1)
+        ));
+        registerClient(mClientToken2, mClientCallback2);
+
+        mBrokerService.publishPacket(mClientToken1, providerId, LAYER1, PAYLOAD);
+
+        verify(mClientLog1).logPacketSent(LAYER1, PAYLOAD.length);
+        verify(mNoSubscribersLog).logPacketDropped(LAYER1, PAYLOAD.length);
+        verifyNoPacketsReceived(mClientCallback1, providerId, LAYER1);
+        verifyNoPacketsReceived(mClientCallback2, providerId, LAYER1);
+    }
+
+    @Test
+    public void testPublishPacket_MonitorSubscriber_Enabled() throws Exception {
+        registerClient(mClientToken1, mClientCallback1);
+        int providerId = mBrokerService.registerProvider(mClientToken1, PROVIDER_INFO1);
+
+        mBrokerService.setProviderOfferings(mClientToken1, providerId, asList(
+                new VmsLayerDependency(LAYER1)
+        ));
+        registerClient(mClientToken2, mClientCallback2);
+
+        mBrokerService.setMonitoringEnabled(mClientToken1, true);
+        mBrokerService.publishPacket(mClientToken1, providerId, LAYER1, PAYLOAD);
+
+        verify(mClientLog1).logPacketSent(LAYER1, PAYLOAD.length);
+        verify(mClientLog1).logPacketReceived(LAYER1, PAYLOAD.length);
+        verifyPacketReceived(mClientCallback1, providerId, LAYER1, PAYLOAD);
+        verifyNoPacketsReceived(mClientCallback2, providerId, LAYER1);
+    }
+
+    @Test
+    public void testPublishPacket_MonitorSubscriber_EnabledAndDisabled() throws Exception {
+        registerClient(mClientToken1, mClientCallback1);
+        int providerId = mBrokerService.registerProvider(mClientToken1, PROVIDER_INFO1);
+
+        mBrokerService.setProviderOfferings(mClientToken1, providerId, asList(
+                new VmsLayerDependency(LAYER1)
+        ));
+        registerClient(mClientToken2, mClientCallback2);
+
+        mBrokerService.setMonitoringEnabled(mClientToken1, true);
+        mBrokerService.setMonitoringEnabled(mClientToken1, false);
+        mBrokerService.publishPacket(mClientToken1, providerId, LAYER1, PAYLOAD);
+
+        verify(mClientLog1).logPacketSent(LAYER1, PAYLOAD.length);
+        verify(mNoSubscribersLog).logPacketDropped(LAYER1, PAYLOAD.length);
+        verifyNoPacketsReceived(mClientCallback1, providerId, LAYER1);
+        verifyNoPacketsReceived(mClientCallback2, providerId, LAYER1);
+    }
+
+    @Test
+    public void testPublishPacket_LayerSubscriber() throws Exception {
+        registerClient(mClientToken1, mClientCallback1);
+        int providerId = mBrokerService.registerProvider(mClientToken1, PROVIDER_INFO1);
+
+        mBrokerService.setProviderOfferings(mClientToken1, providerId, asList(
+                new VmsLayerDependency(LAYER1)
+        ));
+        registerClient(mClientToken2, mClientCallback2);
+
+        mBrokerService.setSubscriptions(mClientToken1, asList(
+                new VmsAssociatedLayer(LAYER1, emptySet())
+        ));
+        mBrokerService.publishPacket(mClientToken1, providerId, LAYER1, PAYLOAD);
+
+        verify(mClientLog1).logPacketSent(LAYER1, PAYLOAD.length);
+        verify(mClientLog1).logPacketReceived(LAYER1, PAYLOAD.length);
+        verifyPacketReceived(mClientCallback1, providerId, LAYER1, PAYLOAD);
+        verifyNoPacketsReceived(mClientCallback2, providerId, LAYER1);
+    }
+
+    @Test
+    public void testPublishPacket_LayerSubscriber_Unsubscribe() throws Exception {
+        registerClient(mClientToken1, mClientCallback1);
+        int providerId = mBrokerService.registerProvider(mClientToken1, PROVIDER_INFO1);
+
+        mBrokerService.setProviderOfferings(mClientToken1, providerId, asList(
+                new VmsLayerDependency(LAYER1)
+        ));
+        registerClient(mClientToken2, mClientCallback2);
+
+        mBrokerService.setSubscriptions(mClientToken1, asList(
+                new VmsAssociatedLayer(LAYER1, emptySet())
+        ));
+        mBrokerService.setSubscriptions(mClientToken1, asList());
+        mBrokerService.publishPacket(mClientToken1, providerId, LAYER1, PAYLOAD);
+
+        verify(mClientLog1).logPacketSent(LAYER1, PAYLOAD.length);
+        verify(mNoSubscribersLog).logPacketDropped(LAYER1, PAYLOAD.length);
+        verifyNoPacketsReceived(mClientCallback1, providerId, LAYER1);
+        verifyNoPacketsReceived(mClientCallback2, providerId, LAYER1);
+    }
+
+    @Test
+    public void testPublishPacket_LayerSubscriber_DifferentLayer() throws Exception {
+        registerClient(mClientToken1, mClientCallback1);
+        int providerId = mBrokerService.registerProvider(mClientToken1, PROVIDER_INFO1);
+
+        mBrokerService.setProviderOfferings(mClientToken1, providerId, asList(
+                new VmsLayerDependency(LAYER1)
+        ));
+        registerClient(mClientToken2, mClientCallback2);
+
+        mBrokerService.setSubscriptions(mClientToken1, asList(
+                new VmsAssociatedLayer(LAYER2, emptySet())
+        ));
+        mBrokerService.publishPacket(mClientToken1, providerId, LAYER1, PAYLOAD);
+
+        verify(mClientLog1).logPacketSent(LAYER1, PAYLOAD.length);
+        verify(mNoSubscribersLog).logPacketDropped(LAYER1, PAYLOAD.length);
+        verifyNoPacketsReceived(mClientCallback1, providerId, LAYER1);
+        verifyNoPacketsReceived(mClientCallback2, providerId, LAYER1);
+    }
+
+    @Test
+    public void testPublishPacket_MultipleLayerSubscribers() throws Exception {
+        registerClient(mClientToken1, mClientCallback1);
+        int providerId = mBrokerService.registerProvider(mClientToken1, PROVIDER_INFO1);
+
+        mBrokerService.setProviderOfferings(mClientToken1, providerId, asList(
+                new VmsLayerDependency(LAYER1)
+        ));
+        registerClient(mClientToken2, mClientCallback2);
+
+        mBrokerService.setSubscriptions(mClientToken1, asList(
+                new VmsAssociatedLayer(LAYER1, emptySet())
+        ));
+        mBrokerService.setSubscriptions(mClientToken2, asList(
+                new VmsAssociatedLayer(LAYER1, emptySet())
+        ));
+        mBrokerService.publishPacket(mClientToken1, providerId, LAYER1, PAYLOAD);
+
+        verify(mClientLog1).logPacketSent(LAYER1, PAYLOAD.length);
+        verify(mClientLog1, times(2)).logPacketReceived(LAYER1, PAYLOAD.length);
+        verifyPacketReceived(mClientCallback1, providerId, LAYER1, PAYLOAD);
+        verifyPacketReceived(mClientCallback2, providerId, LAYER1, PAYLOAD);
+    }
+
+    @Test
+    public void testPublishPacket_MultipleLayerSubscribers_DifferentProcesses() throws Exception {
+        registerClient(mClientToken1, mClientCallback1);
+        int providerId = mBrokerService.registerProvider(mClientToken1, PROVIDER_INFO1);
+
+        mBrokerService.setProviderOfferings(mClientToken1, providerId, asList(
+                new VmsLayerDependency(LAYER1)
+        ));
+        mCallingAppUid = TEST_APP_UID2;
+        registerClient(mClientToken2, mClientCallback2);
+
+        mBrokerService.setSubscriptions(mClientToken1, asList(
+                new VmsAssociatedLayer(LAYER1, emptySet())
+        ));
+        mBrokerService.setSubscriptions(mClientToken2, asList(
+                new VmsAssociatedLayer(LAYER1, emptySet())
+        ));
+        mBrokerService.publishPacket(mClientToken1, providerId, LAYER1, PAYLOAD);
+
+        verify(mClientLog1).logPacketSent(LAYER1, PAYLOAD.length);
+        verify(mClientLog1).logPacketReceived(LAYER1, PAYLOAD.length);
+        verify(mClientLog2).logPacketReceived(LAYER1, PAYLOAD.length);
+        verifyPacketReceived(mClientCallback1, providerId, LAYER1, PAYLOAD);
+        verifyPacketReceived(mClientCallback2, providerId, LAYER1, PAYLOAD);
+    }
+
+    @Test
+    public void testPublishPacket_LayerAndProviderSubscriber() throws Exception {
+        registerClient(mClientToken1, mClientCallback1);
+        int providerId = mBrokerService.registerProvider(mClientToken1, PROVIDER_INFO1);
+
+        mBrokerService.setProviderOfferings(mClientToken1, providerId, asList(
+                new VmsLayerDependency(LAYER1)
+        ));
+        registerClient(mClientToken2, mClientCallback2);
+
+        mBrokerService.setSubscriptions(mClientToken1, asList(
+                new VmsAssociatedLayer(LAYER1, asSet(providerId))
+        ));
+        mBrokerService.publishPacket(mClientToken1, providerId, LAYER1, PAYLOAD);
+
+        verify(mClientLog1).logPacketSent(LAYER1, PAYLOAD.length);
+        verify(mClientLog1).logPacketReceived(LAYER1, PAYLOAD.length);
+        verifyPacketReceived(mClientCallback1, providerId, LAYER1, PAYLOAD);
+        verifyNoPacketsReceived(mClientCallback2, providerId, LAYER1);
+    }
+
+    @Test
+    public void testPublishPacket_LayerAndProviderSubscriber_Unsubscribe() throws Exception {
+        registerClient(mClientToken1, mClientCallback1);
+        int providerId = mBrokerService.registerProvider(mClientToken1, PROVIDER_INFO1);
+
+        mBrokerService.setProviderOfferings(mClientToken1, providerId, asList(
+                new VmsLayerDependency(LAYER1)
+        ));
+        registerClient(mClientToken2, mClientCallback2);
+
+        mBrokerService.setSubscriptions(mClientToken1, asList(
+                new VmsAssociatedLayer(LAYER1, asSet(providerId))
+        ));
+        mBrokerService.setSubscriptions(mClientToken1, asList());
+        mBrokerService.publishPacket(mClientToken1, providerId, LAYER1, PAYLOAD);
+
+        verify(mClientLog1).logPacketSent(LAYER1, PAYLOAD.length);
+        verify(mNoSubscribersLog).logPacketDropped(LAYER1, PAYLOAD.length);
+        verifyNoPacketsReceived(mClientCallback1, providerId, LAYER1);
+        verifyNoPacketsReceived(mClientCallback2, providerId, LAYER1);
+    }
+
+    @Test
+    public void testPublishPacket_LayerAndProviderSubscriber_DifferentProvider() throws Exception {
+        registerClient(mClientToken1, mClientCallback1);
+        int providerId = mBrokerService.registerProvider(mClientToken1, PROVIDER_INFO1);
+        int providerId2 = mBrokerService.registerProvider(mClientToken1, PROVIDER_INFO2);
+
+        mBrokerService.setProviderOfferings(mClientToken1, providerId, asList(
+                new VmsLayerDependency(LAYER1)
+        ));
+        registerClient(mClientToken2, mClientCallback2);
+
+        mBrokerService.setSubscriptions(mClientToken1, asList(
+                new VmsAssociatedLayer(LAYER1, asSet(providerId2))
+        ));
+        mBrokerService.publishPacket(mClientToken1, providerId, LAYER1, PAYLOAD);
+
+        verify(mClientLog1).logPacketSent(LAYER1, PAYLOAD.length);
+        verify(mNoSubscribersLog).logPacketDropped(LAYER1, PAYLOAD.length);
+        verifyNoPacketsReceived(mClientCallback1, providerId, LAYER1);
+        verifyNoPacketsReceived(mClientCallback2, providerId, LAYER1);
+    }
+
+    @Test
+    public void testPublishPacket_MultipleLayerAndProviderSubscribers() throws Exception {
+        registerClient(mClientToken1, mClientCallback1);
+        int providerId = mBrokerService.registerProvider(mClientToken1, PROVIDER_INFO1);
+
+        mBrokerService.setProviderOfferings(mClientToken1, providerId, asList(
+                new VmsLayerDependency(LAYER1)
+        ));
+        registerClient(mClientToken2, mClientCallback2);
+
+        mBrokerService.setSubscriptions(mClientToken1, asList(
+                new VmsAssociatedLayer(LAYER1, asSet(providerId))
+        ));
+        mBrokerService.setSubscriptions(mClientToken2, asList(
+                new VmsAssociatedLayer(LAYER1, asSet(providerId))
+        ));
+        mBrokerService.publishPacket(mClientToken1, providerId, LAYER1, PAYLOAD);
+
+        verify(mClientLog1).logPacketSent(LAYER1, PAYLOAD.length);
+        verify(mClientLog1, times(2)).logPacketReceived(LAYER1, PAYLOAD.length);
+        verifyPacketReceived(mClientCallback1, providerId, LAYER1, PAYLOAD);
+        verifyPacketReceived(mClientCallback2, providerId, LAYER1, PAYLOAD);
+    }
+
+    @Test
+    public void testPublishPacket_MultipleLayerAndProviderSubscribers_DifferentProcesses()
+            throws Exception {
+        registerClient(mClientToken1, mClientCallback1);
+        int providerId = mBrokerService.registerProvider(mClientToken1, PROVIDER_INFO1);
+
+        mBrokerService.setProviderOfferings(mClientToken1, providerId, asList(
+                new VmsLayerDependency(LAYER1)
+        ));
+        mCallingAppUid = TEST_APP_UID2;
+        registerClient(mClientToken2, mClientCallback2);
+
+        mBrokerService.setSubscriptions(mClientToken1, asList(
+                new VmsAssociatedLayer(LAYER1, asSet(providerId))
+        ));
+        mBrokerService.setSubscriptions(mClientToken2, asList(
+                new VmsAssociatedLayer(LAYER1, asSet(providerId))
+        ));
+        mBrokerService.publishPacket(mClientToken1, providerId, LAYER1, PAYLOAD);
+
+        verify(mClientLog1).logPacketSent(LAYER1, PAYLOAD.length);
+        verify(mClientLog1).logPacketReceived(LAYER1, PAYLOAD.length);
+        verify(mClientLog2).logPacketReceived(LAYER1, PAYLOAD.length);
+        verifyPacketReceived(mClientCallback1, providerId, LAYER1, PAYLOAD);
+        verifyPacketReceived(mClientCallback2, providerId, LAYER1, PAYLOAD);
+    }
+
+    private void registerClient(IBinder token, IVmsClientCallback callback) {
+        mBrokerService.registerClient(token, callback, false);
+    }
+
+    private static void disconnectClient(IVmsClientCallback callback) throws Exception {
+        ArgumentCaptor<IBinder.DeathRecipient> deathRecipient =
+                ArgumentCaptor.forClass(IBinder.DeathRecipient.class);
+        verify(callback.asBinder()).linkToDeath(deathRecipient.capture(), eq(0));
+        deathRecipient.getValue().binderDied();
+    }
+
+    private static void verifyLayerAvailability(
+            IVmsClientCallback callback,
+            VmsAvailableLayers availableLayers) throws RemoteException {
+        ArgumentCaptor<VmsAvailableLayers> availableLayersCaptor =
+                ArgumentCaptor.forClass(VmsAvailableLayers.class);
+        verify(callback, times(availableLayers.getSequenceNumber()))
+                .onLayerAvailabilityChanged(availableLayersCaptor.capture());
+        assertThat(availableLayersCaptor.getValue()).isEqualTo(availableLayers);
+    }
+
+    private static void verifySubscriptionState(
+            IVmsClientCallback callback,
+            VmsSubscriptionState subscriptionState) throws RemoteException {
+        ArgumentCaptor<VmsSubscriptionState> subscriptionStateCaptor =
+                ArgumentCaptor.forClass(VmsSubscriptionState.class);
+        verify(callback, times(subscriptionState.getSequenceNumber()))
+                .onSubscriptionStateChanged(subscriptionStateCaptor.capture());
+        assertThat(subscriptionStateCaptor.getValue()).isEqualTo(subscriptionState);
+    }
+
+    private static void verifyNoPacketsReceived(
+            IVmsClientCallback callback,
+            int providerId, VmsLayer layer) throws RemoteException {
+        verify(callback, never()).onPacketReceived(eq(providerId), eq(layer), any());
+    }
+
+    private static void verifyPacketReceived(
+            IVmsClientCallback callback,
+            int providerId, VmsLayer layer, byte[] payload) throws RemoteException {
+        verify(callback).onPacketReceived(providerId, layer, payload);
+    }
+
+    private static <T> Set<T> asSet(T... values) {
+        return new HashSet<T>(Arrays.asList(values));
+    }
+}
diff --git a/tests/carservice_unit_test/src/com/android/car/vms/VmsClientManagerTest.java b/tests/carservice_unit_test/src/com/android/car/vms/VmsClientManagerTest.java
deleted file mode 100644
index e1c9d9a..0000000
--- a/tests/carservice_unit_test/src/com/android/car/vms/VmsClientManagerTest.java
+++ /dev/null
@@ -1,1116 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.car.vms;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertSame;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.argThat;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.ArgumentMatchers.same;
-import static org.mockito.Mockito.atLeast;
-import static org.mockito.Mockito.atMost;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.reset;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyNoMoreInteractions;
-import static org.mockito.Mockito.verifyZeroInteractions;
-import static org.mockito.Mockito.when;
-
-import android.car.Car;
-import android.car.vms.IVmsPublisherClient;
-import android.car.vms.IVmsPublisherService;
-import android.car.vms.IVmsSubscriberClient;
-import android.car.vms.VmsAvailableLayers;
-import android.car.vms.VmsLayer;
-import android.car.vms.VmsSubscriptionState;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.ServiceConnection;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.ServiceInfo;
-import android.content.res.Resources;
-import android.os.Binder;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.Process;
-import android.os.UserHandle;
-import android.os.UserManager;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.car.VmsPublisherService;
-import com.android.car.hal.VmsHalService;
-import com.android.car.stats.CarStatsService;
-import com.android.car.stats.VmsClientLogger;
-import com.android.car.stats.VmsClientLogger.ConnectionState;
-import com.android.car.user.CarUserService;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Captor;
-import org.mockito.Mock;
-import org.mockito.junit.MockitoJUnit;
-import org.mockito.junit.MockitoRule;
-
-@SmallTest
-public class VmsClientManagerTest {
-    private static final String SYSTEM_CLIENT = "com.google.android.apps.vms.test/.VmsSystemClient";
-    private static final ComponentName SYSTEM_CLIENT_COMPONENT =
-            ComponentName.unflattenFromString(SYSTEM_CLIENT);
-    private static final String SYSTEM_CLIENT_NAME =
-            "com.google.android.apps.vms.test/com.google.android.apps.vms.test.VmsSystemClient U=0";
-
-    private static final String USER_CLIENT = "com.google.android.apps.vms.test/.VmsUserClient";
-    private static final ComponentName USER_CLIENT_COMPONENT =
-            ComponentName.unflattenFromString(USER_CLIENT);
-    private static final int USER_ID = 10;
-    private static final String USER_CLIENT_NAME =
-            "com.google.android.apps.vms.test/com.google.android.apps.vms.test.VmsUserClient U=10";
-    private static final int USER_ID_U11 = 11;
-
-    private static final String TEST_PACKAGE = "test.package1";
-    private static final String HAL_CLIENT_NAME = "HalClient";
-    private static final String UNKNOWN_PACKAGE = "UnknownPackage";
-
-    private static final int TEST_APP_ID = 12345;
-    private static final int TEST_SYSTEM_UID = 12345;
-    private static final int TEST_USER_UID = 1012345;
-    private static final int TEST_USER_UID_U11 = 1112345;
-
-    private static final long MILLIS_BEFORE_REBIND = 100;
-
-    @Rule
-    public MockitoRule mMockitoRule = MockitoJUnit.rule();
-    @Mock
-    private Context mContext;
-    @Mock
-    private PackageManager mPackageManager;
-    @Mock
-    private Resources mResources;
-    @Mock
-    private CarStatsService mStatsService;
-    @Mock
-    private UserManager mUserManager;
-    @Mock
-    private CarUserService mUserService;
-
-    @Mock
-    private VmsBrokerService mBrokerService;
-
-    @Mock
-    private VmsHalService mHal;
-
-    @Mock
-    private Handler mHandler;
-
-    @Captor
-    private ArgumentCaptor<Runnable> mRebindCaptor;
-
-    @Mock
-    private VmsPublisherService mPublisherService;
-
-    @Mock
-    private IVmsSubscriberClient mSubscriberClient1;
-    @Mock
-    private Binder mSubscriberBinder1;
-
-    @Captor
-    private ArgumentCaptor<IBinder.DeathRecipient> mDeathRecipient;
-
-    @Mock
-    private IVmsSubscriberClient mSubscriberClient2;
-    @Mock
-    private Binder mSubscriberBinder2;
-
-    @Captor
-    private ArgumentCaptor<ServiceConnection> mConnectionCaptor;
-
-    @Mock
-    private VmsClientLogger mSystemClientLog;
-    @Mock
-    private VmsClientLogger mUserClientLog;
-    @Mock
-    private VmsClientLogger mUserClientLog2;
-    @Mock
-    private VmsClientLogger mHalClientLog;
-
-    private VmsClientManager mClientManager;
-
-    private int mForegroundUserId;
-    private int mCallingAppUid;
-
-    private ServiceInfo mSystemServiceInfo;
-    private ServiceInfo mUserServiceInfo;
-
-    @Before
-    public void setUp() throws Exception {
-        resetContext();
-        mSystemServiceInfo = new ServiceInfo();
-        mSystemServiceInfo.permission = Car.PERMISSION_BIND_VMS_CLIENT;
-        mSystemServiceInfo.applicationInfo = new ApplicationInfo();
-        mSystemServiceInfo.applicationInfo.uid = TEST_APP_ID;
-        when(mPackageManager.getServiceInfo(eq(SYSTEM_CLIENT_COMPONENT), anyInt()))
-                .thenReturn(mSystemServiceInfo);
-        when(mStatsService.getVmsClientLogger(TEST_SYSTEM_UID)).thenReturn(mSystemClientLog);
-
-        mUserServiceInfo = new ServiceInfo();
-        mUserServiceInfo.permission = Car.PERMISSION_BIND_VMS_CLIENT;
-        mUserServiceInfo.applicationInfo = new ApplicationInfo();
-        mUserServiceInfo.applicationInfo.uid = TEST_APP_ID;
-        when(mPackageManager.getServiceInfo(eq(USER_CLIENT_COMPONENT), anyInt()))
-                .thenReturn(mUserServiceInfo);
-        when(mStatsService.getVmsClientLogger(TEST_USER_UID)).thenReturn(mUserClientLog);
-        when(mStatsService.getVmsClientLogger(TEST_USER_UID_U11)).thenReturn(mUserClientLog2);
-
-        when(mStatsService.getVmsClientLogger(Process.myUid())).thenReturn(mHalClientLog);
-
-        when(mResources.getInteger(
-                com.android.car.R.integer.millisecondsBeforeRebindToVmsPublisher)).thenReturn(
-                (int) MILLIS_BEFORE_REBIND);
-        when(mResources.getStringArray(
-                com.android.car.R.array.vmsPublisherSystemClients)).thenReturn(
-                new String[]{ SYSTEM_CLIENT });
-        when(mResources.getStringArray(
-                com.android.car.R.array.vmsPublisherUserClients)).thenReturn(
-                new String[]{ USER_CLIENT });
-
-        when(mContext.getSystemService(eq(Context.USER_SERVICE))).thenReturn(mUserManager);
-
-        mClientManager = new VmsClientManager(mContext, mStatsService, mUserService,
-                mBrokerService, mHal, mHandler, () -> mCallingAppUid);
-        verify(mHal).setClientManager(mClientManager);
-        mClientManager.setPublisherService(mPublisherService);
-
-        notifyUserSwitched(USER_ID, false);
-        mCallingAppUid = UserHandle.getUid(USER_ID, 0);
-
-        when(mSubscriberClient1.asBinder()).thenReturn(mSubscriberBinder1);
-        when(mSubscriberClient2.asBinder()).thenReturn(mSubscriberBinder2);
-
-        when(mPackageManager.getNameForUid(mCallingAppUid)).thenReturn(TEST_PACKAGE);
-    }
-
-    @After
-    public void tearDown() {
-        verify(mContext, atLeast(0)).getSystemService(eq(Context.USER_SERVICE));
-        verify(mContext, atLeast(0)).getResources();
-        verify(mContext, atLeast(0)).getPackageManager();
-        verifyNoMoreInteractions(mContext, mBrokerService, mHal, mPublisherService, mHandler);
-        verifyNoMoreInteractions(mSystemClientLog, mUserClientLog, mUserClientLog2, mHalClientLog);
-    }
-
-    @Test
-    public void testInit() {
-        mClientManager.init();
-
-        // Verify registration of system user unlock listener
-        verify(mUserService).runOnUser0Unlock(mClientManager.mSystemUserUnlockedListener);
-        // Verify user callback is added
-        verify(mUserService).addUserCallback(eq(mClientManager.mUserCallback));
-    }
-
-    @Test
-    public void testRelease() {
-        mClientManager.release();
-
-        // Verify user switch receiver is unregistered
-        verify(mUserService).removeUserCallback(mClientManager.mUserCallback);
-    }
-
-    @Test
-    public void testSystemUserUnlocked() {
-        notifySystemUserUnlocked();
-        notifySystemUserUnlocked();
-
-        // Multiple events should only trigger a single bind, when successful
-        verifySystemBind(1);
-    }
-
-    @Test
-    public void testSystemUserUnlocked_ClientNotFound() throws Exception {
-        when(mPackageManager.getServiceInfo(eq(SYSTEM_CLIENT_COMPONENT), anyInt()))
-                .thenThrow(new PackageManager.NameNotFoundException());
-        notifySystemUserUnlocked();
-
-        // Process will not be bound
-        verifySystemBind(0);
-    }
-
-    @Test
-    public void testSystemUserUnlocked_WrongPermission() throws Exception {
-        mSystemServiceInfo.permission = Car.PERMISSION_VMS_PUBLISHER;
-        notifySystemUserUnlocked();
-
-        // Process will not be bound
-        verifySystemBind(0);
-        verify(mSystemClientLog).logConnectionState(ConnectionState.CONNECTION_ERROR);
-    }
-
-    @Test
-    public void testSystemUserUnlocked_BindFailed() {
-        when(mContext.bindServiceAsUser(any(), any(), anyInt(), any(), any())).thenReturn(false);
-        notifySystemUserUnlocked();
-        notifySystemUserUnlocked();
-
-        // Failure state will trigger another attempt on event
-        verifySystemBind(2);
-        verify(mSystemClientLog, times(2)).logConnectionState(ConnectionState.CONNECTION_ERROR);
-    }
-
-    @Test
-    public void testSystemUserUnlocked_BindException() {
-        when(mContext.bindServiceAsUser(any(), any(), anyInt(), any(), any())).thenThrow(
-                new SecurityException());
-        notifySystemUserUnlocked();
-        notifySystemUserUnlocked();
-
-        // Failure state will trigger another attempt on event
-        verifySystemBind(2);
-        verify(mSystemClientLog, times(2)).logConnectionState(ConnectionState.CONNECTION_ERROR);
-    }
-
-    @Test
-    public void testUserUnlocked() {
-        notifyUserUnlocked(USER_ID, true);
-        notifyUserUnlocked(USER_ID, true);
-
-        // Multiple events should only trigger a single bind, when successful
-        verifyUserBind(1);
-    }
-
-    @Test
-    public void testUserUnlocked_ForegroundUserNotUnlocked() {
-        notifyUserUnlocked(USER_ID, false);
-
-        // Process will not be bound
-        verifyUserBind(0);
-    }
-
-    @Test
-    public void testUserUnlocked_OtherUserUnlocked() {
-        notifyUserUnlocked(USER_ID_U11, true);
-
-        // Process will not be bound
-        verifyUserBind(0);
-    }
-
-    @Test
-    public void testUserUnlocked_ClientNotFound() throws Exception {
-        when(mPackageManager.getServiceInfo(eq(USER_CLIENT_COMPONENT), anyInt()))
-                .thenThrow(new PackageManager.NameNotFoundException());
-        notifyUserUnlocked(USER_ID, true);
-
-        // Process will not be bound
-        verifyUserBind(0);
-    }
-
-    @Test
-    public void testUserUnlocked_WrongPermission() throws Exception {
-        mUserServiceInfo.permission = Car.PERMISSION_VMS_PUBLISHER;
-        notifyUserUnlocked(USER_ID, true);
-
-        // Process will not be bound
-        verifyUserBind(0);
-        verify(mUserClientLog).logConnectionState(ConnectionState.CONNECTION_ERROR);
-    }
-
-    @Test
-    public void testUserUnlocked_BindFailed() {
-        when(mContext.bindServiceAsUser(any(), any(), anyInt(), any(), any()))
-                .thenReturn(false);
-        notifyUserUnlocked(USER_ID, true);
-        notifyUserUnlocked(USER_ID, true);
-
-        // Failure state will trigger another attempt
-        verifyUserBind(2);
-        verify(mUserClientLog, times(2)).logConnectionState(ConnectionState.CONNECTION_ERROR);
-    }
-
-    @Test
-    public void testUserUnlocked_UserBindFailed() {
-        when(mContext.bindServiceAsUser(any(), any(), anyInt(), any(), eq(UserHandle.of(USER_ID))))
-                .thenReturn(false);
-        notifyUserUnlocked(USER_ID, true);
-        notifyUserUnlocked(USER_ID, true);
-
-        // Failure state will trigger another attempt
-        verifyUserBind(2);
-        verify(mUserClientLog, times(2)).logConnectionState(ConnectionState.CONNECTION_ERROR);
-    }
-
-    @Test
-    public void testUserUnlocked_BindException() {
-        when(mContext.bindServiceAsUser(any(), any(), anyInt(), any(), any()))
-                .thenThrow(new SecurityException());
-        notifyUserUnlocked(USER_ID, true);
-        notifyUserUnlocked(USER_ID, true);
-
-        // Failure state will trigger another attempt
-        verifyUserBind(2);
-        verify(mUserClientLog, times(2)).logConnectionState(ConnectionState.CONNECTION_ERROR);
-    }
-
-    @Test
-    public void testUserUnlocked_SystemRebind() {
-        when(mContext.bindServiceAsUser(any(), any(), anyInt(), any(), eq(UserHandle.SYSTEM)))
-                .thenReturn(false);
-        notifySystemUserUnlocked();
-        verifySystemBind(1);
-        verify(mSystemClientLog).logConnectionState(ConnectionState.CONNECTION_ERROR);
-        resetContext();
-
-        when(mContext.bindServiceAsUser(any(), any(), anyInt(), any(), eq(UserHandle.SYSTEM)))
-                .thenReturn(true);
-        notifyUserUnlocked(USER_ID, true);
-        verifySystemBind(1);
-        verifyUserBind(1);
-    }
-
-    @Test
-    public void testUserUnlocked_SystemRebind_BindFailed() {
-        when(mContext.bindServiceAsUser(any(), any(), anyInt(), any(), eq(UserHandle.SYSTEM)))
-                .thenReturn(false);
-        notifySystemUserUnlocked();
-        verifySystemBind(1);
-        verify(mSystemClientLog).logConnectionState(ConnectionState.CONNECTION_ERROR);
-        resetContext();
-
-        when(mContext.bindServiceAsUser(any(), any(), anyInt(), any(), eq(UserHandle.SYSTEM)))
-                .thenReturn(false);
-        notifyUserUnlocked(USER_ID, true);
-        notifyUserUnlocked(USER_ID, true);
-
-        verifySystemBind(2); // Failure state will trigger another attempt
-        verify(mSystemClientLog, times(2)).logConnectionState(ConnectionState.CONNECTION_ERROR);
-        verifyUserBind(1);
-    }
-
-    @Test
-    public void testUserUnlocked_SystemRebind_BindException() {
-        when(mContext.bindServiceAsUser(any(), any(), anyInt(), any(), eq(UserHandle.SYSTEM)))
-                .thenThrow(new SecurityException());
-        notifySystemUserUnlocked();
-        verifySystemBind(1);
-        verify(mSystemClientLog).logConnectionState(ConnectionState.CONNECTION_ERROR);
-        resetContext();
-
-        when(mContext.bindServiceAsUser(any(), any(), anyInt(), any(), eq(UserHandle.SYSTEM)))
-                .thenThrow(new SecurityException());
-        notifyUserUnlocked(USER_ID, true);
-        notifyUserUnlocked(USER_ID, true);
-
-        verifySystemBind(2); // Failure state will trigger another attempt
-        verify(mSystemClientLog, times(2)).logConnectionState(ConnectionState.CONNECTION_ERROR);
-        verifyUserBind(1);
-    }
-
-    @Test
-    public void testUserSwitched() {
-        notifyUserSwitched(USER_ID, true);
-        notifyUserSwitched(USER_ID, true);
-
-        // Multiple events should only trigger a single bind, when successful
-        verifyUserBind(1);
-    }
-
-    @Test
-    public void testUserSwitchedAndUnlocked() {
-        notifyUserSwitched(USER_ID, true);
-        notifyUserUnlocked(USER_ID, true);
-
-        // Multiple events should only trigger a single bind, when successful
-        verifyUserBind(1);
-    }
-
-    @Test
-    public void testUserSwitched_ForegroundUserNotUnlocked() {
-        notifyUserSwitched(USER_ID, false);
-
-        // Process will not be bound
-        verifyUserBind(0);
-    }
-
-    @Test
-    public void testUserSwitchedToSystemUser() {
-        notifyUserSwitched(UserHandle.USER_SYSTEM, true);
-
-        // Neither user nor system processes will be bound for system user intent
-        verifySystemBind(0);
-        verifyUserBind(0);
-    }
-
-    @Test
-    public void testOnSystemServiceConnected() {
-        IBinder binder = bindSystemClient();
-        verifyOnClientConnected(SYSTEM_CLIENT_NAME, binder);
-        verify(mSystemClientLog).logConnectionState(ConnectionState.CONNECTED);
-    }
-
-    private IBinder bindSystemClient() {
-        notifySystemUserUnlocked();
-        verifySystemBind(1);
-        resetContext();
-
-        IBinder binder = createPublisherBinder();
-        ServiceConnection connection = mConnectionCaptor.getValue();
-        connection.onServiceConnected(null, binder);
-        return binder;
-    }
-
-    @Test
-    public void testOnUserServiceConnected() {
-        IBinder binder = bindUserClient();
-        verifyOnClientConnected(USER_CLIENT_NAME, binder);
-        verify(mUserClientLog).logConnectionState(ConnectionState.CONNECTED);
-    }
-
-    private IBinder bindUserClient() {
-        notifyUserUnlocked(USER_ID, true);
-        verifyUserBind(1);
-        resetContext();
-
-        IBinder binder = createPublisherBinder();
-        ServiceConnection connection = mConnectionCaptor.getValue();
-        connection.onServiceConnected(null, binder);
-        return binder;
-    }
-
-    @Test
-    public void testOnSystemServiceDisconnected() throws Exception {
-        notifySystemUserUnlocked();
-        verifySystemBind(1);
-        resetContext();
-
-        ServiceConnection connection = mConnectionCaptor.getValue();
-        connection.onServiceConnected(null, createPublisherBinder());
-        verify(mSystemClientLog).logConnectionState(ConnectionState.CONNECTED);
-        reset(mPublisherService);
-
-        connection.onServiceDisconnected(null);
-        verify(mPublisherService).onClientDisconnected(eq(SYSTEM_CLIENT_NAME));
-        verify(mSystemClientLog).logConnectionState(ConnectionState.DISCONNECTED);
-
-        verifyAndRunRebindTask();
-        verify(mContext).unbindService(connection);
-        verifySystemBind(1);
-    }
-
-    @Test
-    public void testOnSystemServiceDisconnected_ServiceReboundByAndroid() throws Exception {
-        notifySystemUserUnlocked();
-        verifySystemBind(1);
-        resetContext();
-
-        ServiceConnection connection = mConnectionCaptor.getValue();
-        IBinder binder = createPublisherBinder();
-        connection.onServiceConnected(null, binder);
-        verifyOnClientConnected(SYSTEM_CLIENT_NAME, binder);
-        verify(mSystemClientLog).logConnectionState(ConnectionState.CONNECTED);
-        reset(mPublisherService, mSystemClientLog);
-
-        connection.onServiceDisconnected(null);
-        verify(mPublisherService).onClientDisconnected(eq(SYSTEM_CLIENT_NAME));
-        verify(mSystemClientLog).logConnectionState(ConnectionState.DISCONNECTED);
-
-        binder = createPublisherBinder();
-        connection.onServiceConnected(null, binder);
-        verifyOnClientConnected(SYSTEM_CLIENT_NAME, binder);
-        verify(mSystemClientLog).logConnectionState(ConnectionState.CONNECTED);
-
-        verifyAndRunRebindTask();
-        // No more interactions (verified by tearDown)
-    }
-
-
-    @Test
-    public void testOnSystemServiceBindingDied() throws Exception {
-        notifySystemUserUnlocked();
-        verifySystemBind(1);
-        resetContext();
-
-        ServiceConnection connection = mConnectionCaptor.getValue();
-        connection.onServiceConnected(null, createPublisherBinder());
-        verify(mSystemClientLog).logConnectionState(ConnectionState.CONNECTED);
-        reset(mPublisherService);
-
-        connection.onBindingDied(null);
-        verify(mPublisherService).onClientDisconnected(eq(SYSTEM_CLIENT_NAME));
-        verify(mSystemClientLog).logConnectionState(ConnectionState.DISCONNECTED);
-
-        verifyAndRunRebindTask();
-        verify(mContext).unbindService(connection);
-        verifySystemBind(1);
-    }
-
-    @Test
-    public void testOnSystemServiceBindingDied_ServiceNotConnected() throws Exception {
-        notifySystemUserUnlocked();
-        verifySystemBind(1);
-        resetContext();
-
-        ServiceConnection connection = mConnectionCaptor.getValue();
-        connection.onBindingDied(null);
-
-        verifyZeroInteractions(mPublisherService);
-
-        verifyAndRunRebindTask();
-        verify(mContext).unbindService(connection);
-        verifySystemBind(1);
-    }
-
-    @Test
-    public void testOnUserServiceDisconnected() throws Exception {
-        notifyUserUnlocked(USER_ID, true);
-        verifyUserBind(1);
-        resetContext();
-
-        ServiceConnection connection = mConnectionCaptor.getValue();
-        connection.onServiceConnected(null, createPublisherBinder());
-        verify(mUserClientLog).logConnectionState(ConnectionState.CONNECTED);
-        reset(mPublisherService);
-
-        connection.onServiceDisconnected(null);
-        verify(mPublisherService).onClientDisconnected(eq(USER_CLIENT_NAME));
-        verify(mUserClientLog).logConnectionState(ConnectionState.DISCONNECTED);
-
-        verifyAndRunRebindTask();
-        verify(mContext).unbindService(connection);
-        verifyUserBind(1);
-    }
-
-    @Test
-    public void testOnUserServiceDisconnected_ServiceReboundByAndroid() throws Exception {
-        notifyUserUnlocked(USER_ID, true);
-        verifyUserBind(1);
-        resetContext();
-
-        ServiceConnection connection = mConnectionCaptor.getValue();
-        IBinder binder = createPublisherBinder();
-        connection.onServiceConnected(null, binder);
-        verifyOnClientConnected(USER_CLIENT_NAME, binder);
-        verify(mUserClientLog).logConnectionState(ConnectionState.CONNECTED);
-        reset(mPublisherService, mUserClientLog);
-
-        connection.onServiceDisconnected(null);
-        verify(mPublisherService).onClientDisconnected(eq(USER_CLIENT_NAME));
-        verify(mUserClientLog).logConnectionState(ConnectionState.DISCONNECTED);
-
-        binder = createPublisherBinder();
-        connection.onServiceConnected(null, binder);
-        verifyOnClientConnected(USER_CLIENT_NAME, binder);
-        verify(mUserClientLog).logConnectionState(ConnectionState.CONNECTED);
-
-        verifyAndRunRebindTask();
-        // No more interactions (verified by tearDown)
-    }
-
-    @Test
-    public void testOnUserServiceBindingDied() throws Exception {
-        notifyUserUnlocked(USER_ID, true);
-        verifyUserBind(1);
-        resetContext();
-
-        ServiceConnection connection = mConnectionCaptor.getValue();
-        connection.onServiceConnected(null, createPublisherBinder());
-        verify(mUserClientLog).logConnectionState(ConnectionState.CONNECTED);
-        reset(mPublisherService);
-
-        connection.onBindingDied(null);
-        verify(mPublisherService).onClientDisconnected(eq(USER_CLIENT_NAME));
-        verify(mUserClientLog).logConnectionState(ConnectionState.DISCONNECTED);
-
-        verifyAndRunRebindTask();
-        verify(mContext).unbindService(connection);
-        verifyUserBind(1);
-    }
-
-    @Test
-    public void testOnUserServiceBindingDied_ServiceNotConnected() throws Exception {
-        notifyUserUnlocked(USER_ID, true);
-        verifyUserBind(1);
-        resetContext();
-
-        ServiceConnection connection = mConnectionCaptor.getValue();
-        connection.onBindingDied(null);
-
-        verifyZeroInteractions(mPublisherService);
-
-        verifyAndRunRebindTask();
-        verify(mContext).unbindService(connection);
-        verifyUserBind(1);
-    }
-
-    @Test
-    public void testOnUserSwitched_UserChange() {
-        notifyUserUnlocked(USER_ID, true);
-        verifyUserBind(1);
-        resetContext();
-
-        ServiceConnection connection = mConnectionCaptor.getValue();
-        connection.onServiceConnected(null, createPublisherBinder());
-        verify(mUserClientLog).logConnectionState(ConnectionState.CONNECTED);
-        reset(mPublisherService);
-
-        notifyUserSwitched(USER_ID_U11, true);
-
-        verify(mContext).unbindService(connection);
-        verify(mPublisherService).onClientDisconnected(eq(USER_CLIENT_NAME));
-        verify(mUserClientLog).logConnectionState(ConnectionState.TERMINATED);
-        verifyUserBind(1);
-    }
-
-    @Test
-    public void testOnUserSwitched_UserChange_ForegroundUserNotUnlocked() {
-        notifyUserUnlocked(USER_ID, true);
-        verifyUserBind(1);
-        ServiceConnection connection = mConnectionCaptor.getValue();
-        connection.onServiceConnected(null, createPublisherBinder());
-        resetContext();
-        reset(mPublisherService);
-
-        notifyUserSwitched(USER_ID_U11, false);
-
-        verify(mContext).unbindService(connection);
-        verify(mPublisherService).onClientDisconnected(eq(USER_CLIENT_NAME));
-        verify(mUserClientLog).logConnectionState(ConnectionState.TERMINATED);
-        verifyUserBind(0);
-    }
-
-    @Test
-    public void testOnUserSwitched_UserChange_ToSystemUser() {
-        notifyUserUnlocked(USER_ID, true);
-        verifyUserBind(1);
-        ServiceConnection connection = mConnectionCaptor.getValue();
-        connection.onServiceConnected(null, createPublisherBinder());
-        resetContext();
-        reset(mPublisherService);
-
-        notifyUserSwitched(UserHandle.USER_SYSTEM, true);
-
-        verify(mContext).unbindService(connection);
-        verify(mPublisherService).onClientDisconnected(eq(USER_CLIENT_NAME));
-        verify(mUserClientLog).logConnectionState(ConnectionState.TERMINATED);
-        verifyUserBind(0);
-    }
-
-    @Test
-    public void testOnUserSwitched_UserChange_ServiceNotConnected() {
-        notifyUserUnlocked(USER_ID, true);
-        verifyUserBind(1);
-        ServiceConnection connection = mConnectionCaptor.getValue();
-        resetContext();
-
-        notifyUserSwitched(USER_ID_U11, true);
-
-        verify(mContext).unbindService(connection);
-        verifyUserBind(1);
-    }
-
-    @Test
-    public void testOnUserSwitched_UserChange_ServiceNotConnected_ForegroundUserNotUnlocked() {
-        notifyUserUnlocked(USER_ID, true);
-        verifyUserBind(1);
-        ServiceConnection connection = mConnectionCaptor.getValue();
-        resetContext();
-
-        notifyUserSwitched(USER_ID_U11, false);
-
-        verify(mContext).unbindService(connection);
-        verifyUserBind(0);
-    }
-
-    @Test
-    public void testOnUserUnlocked_UserChange() {
-        notifyUserUnlocked(USER_ID, true);
-        verifyUserBind(1);
-        ServiceConnection connection = mConnectionCaptor.getValue();
-        connection.onServiceConnected(null, createPublisherBinder());
-        resetContext();
-        reset(mPublisherService);
-
-        notifyUserSwitched(USER_ID_U11, false);
-        notifyUserUnlocked(USER_ID_U11, true);
-
-        verify(mContext).unbindService(connection);
-        verify(mPublisherService).onClientDisconnected(eq(USER_CLIENT_NAME));
-        verify(mUserClientLog).logConnectionState(ConnectionState.TERMINATED);
-        verifyUserBind(1);
-    }
-
-    @Test
-    public void testOnUserUnlocked_UserChange_ToSystemUser() {
-        notifySystemUserUnlocked();
-        verifySystemBind(1);
-        notifyUserUnlocked(USER_ID, true);
-        verifyUserBind(1);
-        ServiceConnection connection = mConnectionCaptor.getValue();
-        connection.onServiceConnected(null, createPublisherBinder());
-        resetContext();
-        reset(mPublisherService);
-
-        notifyUserSwitched(USER_ID_U11, false);
-        notifyUserUnlocked(UserHandle.USER_SYSTEM, true);
-
-        verify(mContext).unbindService(connection);
-        verify(mPublisherService).onClientDisconnected(eq(USER_CLIENT_NAME));
-        verify(mUserClientLog).logConnectionState(ConnectionState.TERMINATED);
-        // User processes will not be bound for system user
-        verifyUserBind(0);
-    }
-
-    @Test
-    public void testOnUserUnlocked_UserChange_ServiceNotConnected() {
-        notifyUserUnlocked(USER_ID, true);
-        verifyUserBind(1);
-        ServiceConnection connection = mConnectionCaptor.getValue();
-        resetContext();
-
-        notifyUserSwitched(USER_ID_U11, false);
-        notifyUserUnlocked(USER_ID_U11, true);
-
-        verify(mContext).unbindService(connection);
-        verifyUserBind(1);
-    }
-
-    @Test
-    public void testAddSubscriber() {
-        mClientManager.addSubscriber(mSubscriberClient1);
-        assertEquals(TEST_PACKAGE, mClientManager.getPackageName(mSubscriberClient1));
-        assertEquals(mCallingAppUid, mClientManager.getSubscriberUid(mSubscriberClient1));
-        assertEquals(UNKNOWN_PACKAGE, mClientManager.getPackageName(mSubscriberClient2));
-        assertEquals(-1, mClientManager.getSubscriberUid(mSubscriberClient2));
-    }
-
-    @Test
-    public void testAddSubscriber_SystemUser() {
-        mCallingAppUid = UserHandle.getUid(UserHandle.USER_SYSTEM, 0);
-        when(mPackageManager.getNameForUid(mCallingAppUid)).thenReturn(TEST_PACKAGE);
-
-        mClientManager.addSubscriber(mSubscriberClient1);
-        assertEquals(TEST_PACKAGE, mClientManager.getPackageName(mSubscriberClient1));
-        assertEquals(mCallingAppUid, mClientManager.getSubscriberUid(mSubscriberClient1));
-        assertEquals(UNKNOWN_PACKAGE, mClientManager.getPackageName(mSubscriberClient2));
-        assertEquals(-1, mClientManager.getSubscriberUid(mSubscriberClient2));
-    }
-
-    @Test
-    public void testAddSubscriber_NotForegroundUser() {
-        mCallingAppUid = UserHandle.getUid(USER_ID_U11, 0);
-
-        try {
-            mClientManager.addSubscriber(mSubscriberClient1);
-            fail("Expected client to be rejected");
-        } catch (SecurityException expected) {
-            // expected
-        }
-        assertEquals(UNKNOWN_PACKAGE, mClientManager.getPackageName(mSubscriberClient1));
-        assertEquals(-1, mClientManager.getSubscriberUid(mSubscriberClient1));
-    }
-
-    @Test
-    public void testAddSubscriber_MultipleCalls() {
-        mClientManager.addSubscriber(mSubscriberClient1);
-        mClientManager.addSubscriber(mSubscriberClient1);
-        verify(mPackageManager, atMost(1)).getNameForUid(anyInt());
-        assertEquals(TEST_PACKAGE, mClientManager.getPackageName(mSubscriberClient1));
-        assertEquals(mCallingAppUid, mClientManager.getSubscriberUid(mSubscriberClient1));
-        assertEquals(UNKNOWN_PACKAGE, mClientManager.getPackageName(mSubscriberClient2));
-        assertEquals(-1, mClientManager.getSubscriberUid(mSubscriberClient2));
-    }
-
-    @Test
-    public void testAddSubscriber_MultipleClients_SamePackage() {
-        mClientManager.addSubscriber(mSubscriberClient1);
-        mClientManager.addSubscriber(mSubscriberClient2);
-        verify(mPackageManager, atMost(2)).getNameForUid(anyInt());
-        assertEquals(TEST_PACKAGE, mClientManager.getPackageName(mSubscriberClient1));
-        assertEquals(mCallingAppUid, mClientManager.getSubscriberUid(mSubscriberClient1));
-        assertEquals(TEST_PACKAGE, mClientManager.getPackageName(mSubscriberClient2));
-        assertEquals(mCallingAppUid, mClientManager.getSubscriberUid(mSubscriberClient2));
-    }
-
-    @Test
-    public void testAddSubscriber_MultipleClients_ForegroundAndSystemUsers_SamePackage() {
-        int clientUid1 = mCallingAppUid;
-        mClientManager.addSubscriber(mSubscriberClient1);
-
-        mCallingAppUid = UserHandle.getUid(UserHandle.USER_SYSTEM, 0);
-        when(mPackageManager.getNameForUid(mCallingAppUid)).thenReturn(TEST_PACKAGE);
-        mClientManager.addSubscriber(mSubscriberClient2);
-
-        verify(mPackageManager, atMost(2)).getNameForUid(anyInt());
-        assertEquals(TEST_PACKAGE, mClientManager.getPackageName(mSubscriberClient1));
-        assertEquals(clientUid1, mClientManager.getSubscriberUid(mSubscriberClient1));
-        assertEquals(TEST_PACKAGE, mClientManager.getPackageName(mSubscriberClient2));
-        assertEquals(mCallingAppUid, mClientManager.getSubscriberUid(mSubscriberClient2));
-    }
-
-
-    @Test
-    public void testAddSubscriber_MultipleClients_MultiplePackages() {
-        int clientUid1 = mCallingAppUid;
-        mClientManager.addSubscriber(mSubscriberClient1);
-
-        mCallingAppUid = UserHandle.getUid(mForegroundUserId, 1);
-        when(mPackageManager.getNameForUid(mCallingAppUid)).thenReturn("test.package2");
-        mClientManager.addSubscriber(mSubscriberClient2);
-
-        verify(mPackageManager, times(2)).getNameForUid(anyInt());
-        assertEquals(TEST_PACKAGE, mClientManager.getPackageName(mSubscriberClient1));
-        assertEquals(clientUid1, mClientManager.getSubscriberUid(mSubscriberClient1));
-        assertEquals("test.package2", mClientManager.getPackageName(mSubscriberClient2));
-        assertEquals(mCallingAppUid, mClientManager.getSubscriberUid(mSubscriberClient2));
-    }
-
-    @Test
-    public void testRemoveSubscriber() {
-        mClientManager.addSubscriber(mSubscriberClient1);
-        mClientManager.removeSubscriber(mSubscriberClient1);
-        verify(mBrokerService).removeDeadSubscriber(mSubscriberClient1);
-        assertEquals(UNKNOWN_PACKAGE, mClientManager.getPackageName(mSubscriberClient1));
-        assertEquals(-1, mClientManager.getSubscriberUid(mSubscriberClient1));
-    }
-
-    @Test
-    public void testRemoveSubscriber_NotRegistered() {
-        mClientManager.removeSubscriber(mSubscriberClient1);
-        assertEquals(UNKNOWN_PACKAGE, mClientManager.getPackageName(mSubscriberClient1));
-        assertEquals(-1, mClientManager.getSubscriberUid(mSubscriberClient1));
-    }
-
-    @Test
-    public void testRemoveSubscriber_OnDeath() {
-        mClientManager.addSubscriber(mSubscriberClient1);
-
-        verify(mSubscriberBinder1).linkToDeath(mDeathRecipient.capture(), eq(0));
-        mDeathRecipient.getValue().binderDied();
-
-        verify(mBrokerService).removeDeadSubscriber(mSubscriberClient1);
-        assertEquals(UNKNOWN_PACKAGE, mClientManager.getPackageName(mSubscriberClient1));
-        assertEquals(-1, mClientManager.getSubscriberUid(mSubscriberClient1));
-    }
-
-    @Test
-    public void testOnUserSwitch_RemoveSubscriber() {
-        mClientManager.addSubscriber(mSubscriberClient1);
-
-        notifyUserSwitched(USER_ID_U11, false);
-        verify(mBrokerService).removeDeadSubscriber(mSubscriberClient1);
-        assertEquals(UNKNOWN_PACKAGE, mClientManager.getPackageName(mSubscriberClient1));
-        assertEquals(-1, mClientManager.getSubscriberUid(mSubscriberClient1));
-        assertTrue(mClientManager.getAllSubscribers().isEmpty());
-    }
-
-    @Test
-    public void testOnUserSwitch_RemoveSubscriber_AddNewSubscriber() {
-        mClientManager.addSubscriber(mSubscriberClient1);
-
-        notifyUserSwitched(USER_ID_U11, false);
-        verify(mBrokerService).removeDeadSubscriber(mSubscriberClient1);
-
-        mCallingAppUid = UserHandle.getUid(USER_ID_U11, 0);
-        when(mPackageManager.getNameForUid(mCallingAppUid)).thenReturn(TEST_PACKAGE);
-        mClientManager.addSubscriber(mSubscriberClient2);
-
-        assertEquals(UNKNOWN_PACKAGE, mClientManager.getPackageName(mSubscriberClient1));
-        assertEquals(-1, mClientManager.getSubscriberUid(mSubscriberClient1));
-        assertEquals(TEST_PACKAGE, mClientManager.getPackageName(mSubscriberClient2));
-        assertEquals(mCallingAppUid, mClientManager.getSubscriberUid(mSubscriberClient2));
-        assertFalse(mClientManager.getAllSubscribers().contains(mSubscriberClient1));
-        assertTrue(mClientManager.getAllSubscribers().contains(mSubscriberClient2));
-    }
-
-    @Test
-    public void testOnUserSwitch_RemoveSubscriber_RetainSystemClient() {
-        mClientManager.addSubscriber(mSubscriberClient1);
-
-        mCallingAppUid = UserHandle.getUid(UserHandle.USER_SYSTEM, 0);
-        when(mPackageManager.getNameForUid(mCallingAppUid)).thenReturn(TEST_PACKAGE);
-
-        mClientManager.addSubscriber(mSubscriberClient2);
-
-        notifyUserSwitched(USER_ID_U11, false);
-
-        verify(mBrokerService).removeDeadSubscriber(mSubscriberClient1);
-        verify(mBrokerService, never()).removeDeadSubscriber(mSubscriberClient2);
-        assertEquals(UNKNOWN_PACKAGE, mClientManager.getPackageName(mSubscriberClient1));
-        assertEquals(TEST_PACKAGE, mClientManager.getPackageName(mSubscriberClient2));
-    }
-
-    @Test
-    public void testOnUserSwitch_RemoveSubscriber_RetainHalClient() {
-        IVmsPublisherClient publisherClient = createPublisherClient();
-        IVmsSubscriberClient subscriberClient = createSubscriberClient();
-        mClientManager.onHalConnected(publisherClient, subscriberClient);
-        verify(mHalClientLog).logConnectionState(ConnectionState.CONNECTED);
-        reset(mPublisherService);
-
-        notifyUserSwitched(USER_ID_U11, false);
-
-        verify(mBrokerService, never()).removeDeadSubscriber(subscriberClient);
-        assertEquals(HAL_CLIENT_NAME, mClientManager.getPackageName(subscriberClient));
-    }
-
-    @Test
-    public void testHalClientConnected() {
-        IVmsPublisherClient publisherClient = createPublisherClient();
-        IVmsSubscriberClient subscriberClient = createSubscriberClient();
-        mClientManager.onHalConnected(publisherClient, subscriberClient);
-        verify(mPublisherService).onClientConnected(eq(HAL_CLIENT_NAME), same(publisherClient));
-        verify(mHalClientLog).logConnectionState(ConnectionState.CONNECTED);
-        assertTrue(mClientManager.getAllSubscribers().contains(subscriberClient));
-        assertEquals(HAL_CLIENT_NAME, mClientManager.getPackageName(subscriberClient));
-    }
-
-    @Test
-    public void testHalClientConnected_AfterAddSubscriber() {
-        IVmsPublisherClient publisherClient = createPublisherClient();
-        IVmsSubscriberClient subscriberClient = createSubscriberClient();
-        mClientManager.addSubscriber(subscriberClient);
-
-        mClientManager.onHalConnected(publisherClient, subscriberClient);
-        verify(mPublisherService).onClientConnected(eq(HAL_CLIENT_NAME), same(publisherClient));
-        verify(mHalClientLog).logConnectionState(ConnectionState.CONNECTED);
-        assertTrue(mClientManager.getAllSubscribers().contains(subscriberClient));
-        assertEquals(HAL_CLIENT_NAME, mClientManager.getPackageName(subscriberClient));
-    }
-
-    @Test
-    public void testOnHalClientDisconnected() {
-        IVmsPublisherClient publisherClient = createPublisherClient();
-        IVmsSubscriberClient subscriberClient = createSubscriberClient();
-        mClientManager.onHalConnected(publisherClient, subscriberClient);
-        verify(mHalClientLog).logConnectionState(ConnectionState.CONNECTED);
-        reset(mPublisherService);
-
-        mClientManager.onHalDisconnected();
-        verify(mPublisherService).onClientDisconnected(eq(HAL_CLIENT_NAME));
-        verify(mBrokerService).removeDeadSubscriber(eq(subscriberClient));
-        verify(mHalClientLog).logConnectionState(ConnectionState.DISCONNECTED);
-        assertFalse(mClientManager.getAllSubscribers().contains(subscriberClient));
-        assertEquals(UNKNOWN_PACKAGE, mClientManager.getPackageName(subscriberClient));
-    }
-
-    @Test
-    public void testOnHalClientDisconnected_NotConnected() {
-        mClientManager.onHalDisconnected();
-        verify(mPublisherService, never()).onClientDisconnected(eq(HAL_CLIENT_NAME));
-        assertTrue(mClientManager.getAllSubscribers().isEmpty());
-    }
-
-    private void resetContext() {
-        reset(mContext, mSystemClientLog, mUserClientLog);
-        when(mContext.getPackageManager()).thenReturn(mPackageManager);
-        when(mContext.bindServiceAsUser(any(), any(), anyInt(), any(), any())).thenReturn(true);
-        when(mContext.getResources()).thenReturn(mResources);
-    }
-
-    private void notifySystemUserUnlocked() {
-        mClientManager.mSystemUserUnlockedListener.run();
-    }
-
-    private void notifyUserSwitched(int foregroundUserId, boolean isForegroundUserUnlocked) {
-        when(mUserManager.isUserUnlockingOrUnlocked(foregroundUserId))
-                .thenReturn(isForegroundUserUnlocked);
-        mForegroundUserId = foregroundUserId; // Member variable used by verifyUserBind()
-        mClientManager.mUserCallback.onSwitchUser(foregroundUserId);
-    }
-
-    private void notifyUserUnlocked(int foregroundUserId, boolean isForegroundUserUnlocked) {
-        when(mUserManager.isUserUnlockingOrUnlocked(foregroundUserId))
-                .thenReturn(isForegroundUserUnlocked);
-        mClientManager.mUserCallback.onUserLockChanged(foregroundUserId, isForegroundUserUnlocked);
-    }
-
-    private void verifySystemBind(int times) {
-        verify(mSystemClientLog, times(times)).logConnectionState(ConnectionState.CONNECTING);
-        verifyBind(times, SYSTEM_CLIENT_COMPONENT, UserHandle.SYSTEM);
-    }
-
-    private void verifyUserBind(int times) {
-        if (mForegroundUserId == USER_ID) {
-            verify(mUserClientLog, times(times)).logConnectionState(ConnectionState.CONNECTING);
-        } else if (mForegroundUserId == USER_ID_U11) {
-            verify(mUserClientLog2, times(times)).logConnectionState(ConnectionState.CONNECTING);
-        }
-        verifyBind(times, USER_CLIENT_COMPONENT, UserHandle.of(mForegroundUserId));
-    }
-
-    private void verifyBind(int times, ComponentName componentName, UserHandle user) {
-        Intent expectedService = new Intent();
-        expectedService.setComponent(componentName);
-        verify(mContext, times(times)).bindServiceAsUser(
-                argThat((service) -> service.filterEquals(expectedService)),
-                mConnectionCaptor.capture(),
-                eq(Context.BIND_AUTO_CREATE), any(Handler.class), eq(user));
-    }
-
-    private void verifyAndRunRebindTask() {
-        verify(mHandler).postDelayed(mRebindCaptor.capture(), eq(MILLIS_BEFORE_REBIND));
-        mRebindCaptor.getValue().run();
-    }
-
-    private void verifyOnClientConnected(String publisherName, IBinder binder) {
-        ArgumentCaptor<IVmsPublisherClient> clientCaptor =
-                ArgumentCaptor.forClass(IVmsPublisherClient.class);
-        verify(mPublisherService).onClientConnected(eq(publisherName), clientCaptor.capture());
-        assertSame(binder, clientCaptor.getValue().asBinder());
-    }
-
-    private IBinder createPublisherBinder() {
-        return createPublisherClient().asBinder();
-    }
-
-    private IVmsPublisherClient createPublisherClient() {
-        return new IVmsPublisherClient.Stub() {
-            @Override
-            public void setVmsPublisherService(IBinder token, IVmsPublisherService service) {
-                throw new RuntimeException("Unexpected call");
-            }
-
-            @Override
-            public void onVmsSubscriptionChange(VmsSubscriptionState subscriptionState) {
-                throw new RuntimeException("Unexpected call");
-            }
-        };
-    }
-
-    private IVmsSubscriberClient createSubscriberClient() {
-        return new IVmsSubscriberClient.Stub() {
-            @Override
-            public void onVmsMessageReceived(VmsLayer layer, byte[] payload) {
-                throw new RuntimeException("Unexpected call");
-            }
-
-            @Override
-            public void onLayersAvailabilityChanged(VmsAvailableLayers availableLayers) {
-                throw new RuntimeException("Unexpected call");
-            }
-        };
-    }
-}
diff --git a/tests/robotests/Android.mk b/tests/robotests/Android.mk
deleted file mode 100644
index 4ad36fa..0000000
--- a/tests/robotests/Android.mk
+++ /dev/null
@@ -1,43 +0,0 @@
-# Copyright (C) 2018 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-LOCAL_PATH:= $(call my-dir)
-
-include $(CLEAR_VARS)
-
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-
-LOCAL_PACKAGE_NAME := CarServiceRoboTests
-LOCAL_PRIVATE_PLATFORM_APIS := true
-
-LOCAL_CERTIFICATE := platform
-
-LOCAL_MODULE_TAGS := tests
-
-# When built explicitly put it in the data partition
-LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
-
-LOCAL_PROGUARD_ENABLED := disabled
-
-LOCAL_INSTRUMENTATION_FOR := CarService
-
-LOCAL_JAVA_LIBRARIES := \
-    android.car \
-    android.car.userlib \
-    robolectric_android-all-stub \
-    Robolectric_all-target \
-    mockito-robolectric-prebuilt \
-    truth-prebuilt
-
-include $(BUILD_PACKAGE)
\ No newline at end of file
diff --git a/tests/robotests/AndroidManifest.xml b/tests/robotests/AndroidManifest.xml
deleted file mode 100644
index d160175..0000000
--- a/tests/robotests/AndroidManifest.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  Copyright (C) 2018 The Android Open Source Project
-
-  Licensed under the Apache License, Version 2.0 (the "License");
-  you may not use this file except in compliance with the License.
-  You may obtain a copy of the License at
-
-       http://www.apache.org/licenses/LICENSE-2.0
-
-  Unless required by applicable law or agreed to in writing, software
-  distributed under the License is distributed on an "AS IS" BASIS,
-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  See the License for the specific language governing permissions and
-  limitations under the License.
-  -->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    coreApp="true"
-    package="com.android.car.robotests">
-
-  <application/>
-
-</manifest>
\ No newline at end of file
diff --git a/tests/robotests/config/robolectric.properties b/tests/robotests/config/robolectric.properties
deleted file mode 100644
index b4e3b78..0000000
--- a/tests/robotests/config/robolectric.properties
+++ /dev/null
@@ -1,17 +0,0 @@
-#
-# Copyright (C) 2018 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-manifest=packages/services/Car/tests/robotests/AndroidManifest.xml
-sdk=NEWEST_SDK
\ No newline at end of file
diff --git a/tests/robotests/src/com/android/car/CarServiceRobolectricTestRunner.java b/tests/robotests/src/com/android/car/CarServiceRobolectricTestRunner.java
deleted file mode 100644
index 92b3cd8..0000000
--- a/tests/robotests/src/com/android/car/CarServiceRobolectricTestRunner.java
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.car;
-
-import android.annotation.NonNull;
-
-import org.junit.runners.model.InitializationError;
-import org.robolectric.RobolectricTestRunner;
-import org.robolectric.annotation.Config;
-import org.robolectric.manifest.AndroidManifest;
-import org.robolectric.res.Fs;
-import org.robolectric.res.ResourcePath;
-
-import java.net.MalformedURLException;
-import java.net.URL;
-import java.util.List;
-
-public class CarServiceRobolectricTestRunner extends RobolectricTestRunner {
-    public CarServiceRobolectricTestRunner(Class<?> testClass) throws InitializationError {
-        super(testClass);
-    }
-
-    /**
-     * We are going to create our own custom manifest so we can add multiple resource paths to it.
-     */
-    @Override
-    protected AndroidManifest getAppManifest(Config config) {
-        try {
-            // Using the manifest file's relative path, we can figure out the application directory.
-            final URL appRoot =
-                    new URL("file:packages/services/Car/tests/robotests");
-            final URL manifestPath = new URL(appRoot, "AndroidManifest.xml");
-            final URL resDir = new URL(appRoot, "res");
-            final URL assetsDir = new URL(appRoot, "assets");
-
-            return new AndroidManifest(Fs.fromURL(manifestPath), Fs.fromURL(resDir),
-                Fs.fromURL(assetsDir), "com.android.car") {
-                @Override
-                public List<ResourcePath> getIncludedResourcePaths() {
-                    final List<ResourcePath> paths = super.getIncludedResourcePaths();
-                    return paths;
-                }
-            };
-        } catch (MalformedURLException e) {
-            throw new RuntimeException("CarServiceRobolectricTestRunner failure", e);
-        }
-    }
-
-    private static ResourcePath resourcePath(@NonNull String spec) {
-        try {
-            return new ResourcePath(null, Fs.fromURL(new URL(spec)), null);
-        } catch (MalformedURLException e) {
-            throw new RuntimeException("CarServiceRobolectricTestRunner failure", e);
-        }
-    }
-}
diff --git a/tests/robotests/src/com/android/car/testutils/shadow/ShadowActivityManager.java b/tests/robotests/src/com/android/car/testutils/shadow/ShadowActivityManager.java
deleted file mode 100644
index e213c4e..0000000
--- a/tests/robotests/src/com/android/car/testutils/shadow/ShadowActivityManager.java
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.car.testutils.shadow;
-
-import android.app.ActivityManager;
-
-import org.robolectric.RuntimeEnvironment;
-import org.robolectric.annotation.Implementation;
-import org.robolectric.annotation.Implements;
-import org.robolectric.annotation.Resetter;
-import org.robolectric.shadow.api.Shadow;
-
-@Implements(ActivityManager.class)
-public class ShadowActivityManager {
-    private static int sCurrentUserId = 0;
-    private int mUserSwitchedTo = -1;
-
-    @Resetter
-    public void reset() {
-        sCurrentUserId = 0;
-        mUserSwitchedTo = 0;
-    }
-
-    @Implementation
-    public static int getCurrentUser() {
-        return sCurrentUserId;
-    }
-
-    @Implementation
-    public boolean switchUser(int userId) {
-        mUserSwitchedTo = userId;
-        return true;
-    }
-
-    public boolean getSwitchUserCalled() {
-        return mUserSwitchedTo != -1;
-    }
-
-    public int getUserSwitchedTo() {
-        return mUserSwitchedTo;
-    }
-
-    public static void setCurrentUser(int userId) {
-        sCurrentUserId = userId;
-    }
-
-    public static ShadowActivityManager getShadow() {
-        return (ShadowActivityManager) Shadow.extract(
-            RuntimeEnvironment.application.getSystemService(ActivityManager.class));
-    }
-}
diff --git a/tests/robotests/src/com/android/car/testutils/shadow/ShadowUserHandle.java b/tests/robotests/src/com/android/car/testutils/shadow/ShadowUserHandle.java
deleted file mode 100644
index 656fa97..0000000
--- a/tests/robotests/src/com/android/car/testutils/shadow/ShadowUserHandle.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.car.testutils.shadow;
-
-import android.os.UserHandle;
-
-import org.robolectric.annotation.Implementation;
-import org.robolectric.annotation.Implements;
-
-@Implements(UserHandle.class)
-public class ShadowUserHandle {
-    @Implementation
-    public static int myUserId() {
-        try {
-            return ShadowUserManager.getShadow().getCurrentUser();
-        } catch (Throwable t) {
-            // This method may be called before ShadowUserManager is properly initialized. Just
-            // return the default 0 in those cases.
-            return 0;
-        }
-    }
-}
diff --git a/tests/robotests/src/com/android/car/testutils/shadow/ShadowUserManager.java b/tests/robotests/src/com/android/car/testutils/shadow/ShadowUserManager.java
deleted file mode 100644
index bf9cf62..0000000
--- a/tests/robotests/src/com/android/car/testutils/shadow/ShadowUserManager.java
+++ /dev/null
@@ -1,147 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.car.testutils.shadow;
-
-import android.content.Context;
-import android.content.pm.UserInfo;
-import android.os.UserHandle;
-import android.os.UserManager;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-import org.robolectric.RuntimeEnvironment;
-import org.robolectric.annotation.HiddenApi;
-import org.robolectric.annotation.Implementation;
-import org.robolectric.annotation.Implements;
-import org.robolectric.shadow.api.Shadow;
-
-@Implements(UserManager.class)
-public class ShadowUserManager extends org.robolectric.shadows.ShadowUserManager {
-    private final Map<Integer, UserInfo> mUserInfos = new HashMap<>();
-    private int mCurrentUser = UserHandle.USER_SYSTEM;
-    public boolean isSystemUser = true;
-    public boolean isPrimaryUser = true;
-    public boolean isLinkedUser = false;
-    public boolean isDemoUser = false;
-    public boolean isManagedProfile = false;
-    public boolean isGuestUser = false;
-    public boolean canSwitchUser = true;
-
-    {
-        addUserInfo(new UserInfo(UserHandle.USER_SYSTEM, "system_user", 0));
-    }
-
-    /**
-     * Used by BaseActivity when creating intents.
-     */
-    @Implementation
-    @HiddenApi
-    public List<UserInfo> getUsers() {
-        return new ArrayList<>(mUserInfos.values());
-    }
-
-    @Implementation
-    public boolean isSystemUser() {
-        return isSystemUser;
-    }
-
-    @Implementation
-    public boolean isPrimaryUser() {
-        return isPrimaryUser;
-    }
-
-    @Implementation
-    public boolean isLinkedUser() {
-        return isLinkedUser;
-    }
-
-    @Implementation
-    public boolean isDemoUser() {
-        return isDemoUser;
-    }
-
-    @Implementation
-    public boolean isGuestUser() {
-        return isGuestUser;
-    }
-
-    @Implementation
-    public boolean isManagedProfile(int userId) {
-        return isManagedProfile;
-    }
-
-    @Implementation
-    public static boolean isSplitSystemUser() {
-        return false;
-    }
-
-    @Implementation
-    public UserInfo getUserInfo(int id) {
-        return mUserInfos.get(id);
-    }
-
-    @Implementation
-    public boolean isUserUnlockingOrUnlocked(int userId) {
-        return isUserUnlocked();
-    }
-
-    @Implementation
-    public boolean canSwitchUsers() {
-        return canSwitchUser;
-    }
-
-    @Implementation
-    public boolean removeUser(int userId) {
-        mUserInfos.remove(userId);
-        return true;
-    }
-
-    @Implementation
-    public UserInfo createGuest(Context context, String name) {
-        UserInfo guest = new UserInfo(12, name, UserInfo.FLAG_GUEST);
-
-        addUserInfo(guest);
-        return guest;
-    }
-
-    public void switchUser(int userId) {
-        if (!mUserInfos.containsKey(userId)) {
-            throw new UnsupportedOperationException("Must add user before switching to it");
-        }
-        mCurrentUser = userId;
-    }
-
-    public int getCurrentUser() {
-        return mCurrentUser;
-    }
-
-    public void setCurrentUser(int userId) {
-        mCurrentUser = userId;
-    }
-
-    public void addUserInfo(UserInfo userInfo) {
-        mUserInfos.put(userInfo.id, userInfo);
-    }
-
-    public static ShadowUserManager getShadow() {
-        return (ShadowUserManager) Shadow.extract(
-                RuntimeEnvironment.application.getSystemService(UserManager.class));
-    }
-}
diff --git a/tests/robotests/src/com/android/car/users/CarUserManagerHelperRoboTest.java b/tests/robotests/src/com/android/car/users/CarUserManagerHelperRoboTest.java
deleted file mode 100644
index 6a90cdf..0000000
--- a/tests/robotests/src/com/android/car/users/CarUserManagerHelperRoboTest.java
+++ /dev/null
@@ -1,210 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.car.users;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.Mockito.when;
-
-import android.app.ActivityManager;
-import android.car.userlib.CarUserManagerHelper;
-import android.content.Context;
-import android.content.pm.UserInfo;
-import android.os.UserManager;
-
-import com.android.car.CarServiceRobolectricTestRunner;
-import com.android.car.testutils.shadow.ShadowActivityManager;
-import com.android.car.testutils.shadow.ShadowUserHandle;
-import com.android.car.testutils.shadow.ShadowUserManager;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-import org.robolectric.RuntimeEnvironment;
-import org.robolectric.annotation.Config;
-
-@RunWith(CarServiceRobolectricTestRunner.class)
-@Config(shadows = { ShadowActivityManager.class,
-        ShadowUserHandle.class, ShadowUserManager.class})
-public class CarUserManagerHelperRoboTest {
-    @Mock
-    private Context mContext;
-
-    private CarUserManagerHelper mHelper;
-
-    @Before
-    public void setUp() {
-        MockitoAnnotations.initMocks(this);
-        when(mContext.getSystemService(Context.USER_SERVICE)).thenReturn(
-                RuntimeEnvironment.application.getSystemService(UserManager.class));
-        when(mContext.getSystemService(Context.ACTIVITY_SERVICE)).thenReturn(
-                RuntimeEnvironment.application.getSystemService(ActivityManager.class));
-        when(mContext.getApplicationContext()).thenReturn(mContext);
-        mHelper = new CarUserManagerHelper(mContext);
-    }
-
-    @After
-    public void tearDown() {
-        ShadowActivityManager.getShadow().reset();
-    }
-
-    @Test
-    public void testGetForegroundUserId() {
-        ShadowActivityManager.setCurrentUser(15);
-        assertThat(mHelper.getCurrentForegroundUserId()).isEqualTo(15);
-    }
-
-    @Test
-    public void testGetForegroundUserInfo() {
-        int currentForegroundUserId = 17;
-        ShadowActivityManager.setCurrentUser(currentForegroundUserId);
-
-        assertThat(mHelper.getCurrentForegroundUserInfo().id).isEqualTo(currentForegroundUserId);
-    }
-
-    @Test
-    public void testGetCurrentProcessUserId() {
-        int currentProcessUserId = 11;
-        ShadowUserManager.getShadow().setCurrentUser(currentProcessUserId);
-
-        assertThat(mHelper.getCurrentProcessUserId()).isEqualTo(currentProcessUserId);
-    }
-
-    @Test
-    public void testGetCurrentProcessUserInfo() {
-        int currentProcessUserId = 12;
-        ShadowUserManager.getShadow().setCurrentUser(currentProcessUserId);
-        assertThat(mHelper.getCurrentProcessUserInfo().id).isEqualTo(currentProcessUserId);
-    }
-
-    @Test
-    public void testGetAllUsers() {
-        int currentProcessUserId = 12;
-        ShadowUserManager userManager = ShadowUserManager.getShadow();
-        userManager.setCurrentUser(currentProcessUserId);
-
-        UserInfo currentProcessUser = createUserInfoForId(currentProcessUserId);
-        UserInfo systemUser = createUserInfoForId(0);
-
-        UserInfo otherUser1 = createUserInfoForId(13);
-        UserInfo otherUser2 = createUserInfoForId(14);
-
-        userManager.addUserInfo(systemUser);
-        userManager.addUserInfo(currentProcessUser);
-        userManager.addUserInfo(otherUser1);
-        userManager.addUserInfo(otherUser2);
-
-        if (mHelper.isHeadlessSystemUser()) {
-            // Should return 3 users that don't have system user id.
-            assertThat(mHelper.getAllUsers())
-                .containsExactly(currentProcessUser, otherUser1, otherUser2);
-        } else {
-            assertThat(mHelper.getAllUsers())
-                .containsExactly(systemUser, currentProcessUser, otherUser1, otherUser2);
-        }
-    }
-
-    @Test
-    public void testGetAllUsersExceptForegroundUser() {
-        ShadowActivityManager.setCurrentUser(11);
-        ShadowUserManager userManager = ShadowUserManager.getShadow();
-
-        UserInfo foregroundUser = createUserInfoForId(11);
-        UserInfo otherUser1 = createUserInfoForId(12);
-        UserInfo otherUser2 = createUserInfoForId(13);
-        UserInfo otherUser3 = createUserInfoForId(14);
-
-        userManager.addUserInfo(foregroundUser);
-        userManager.addUserInfo(otherUser1);
-        userManager.addUserInfo(otherUser2);
-        userManager.addUserInfo(otherUser3);
-
-        // Should return 3 users that don't have foregroundUser id.
-        assertThat(mHelper.getAllSwitchableUsers()).hasSize(3);
-        assertThat(mHelper.getAllSwitchableUsers())
-            .containsExactly(otherUser1, otherUser2, otherUser3);
-    }
-
-    @Test
-    public void testCheckForegroundUser() {
-        ShadowActivityManager.setCurrentUser(10);
-        assertThat(mHelper.isForegroundUser(createUserInfoForId(10))).isTrue();
-        assertThat(mHelper.isForegroundUser(createUserInfoForId(11))).isFalse();
-
-        ShadowActivityManager.setCurrentUser(11);
-        assertThat(mHelper.isForegroundUser(createUserInfoForId(11))).isTrue();
-    }
-
-    @Test
-    public void testIsUserRunningCurrentProcess() {
-        ShadowUserManager shadowUserManager = ShadowUserManager.getShadow();
-        UserInfo user1 = createUserInfoForId(10);
-        UserInfo user2 = createUserInfoForId(11);
-        shadowUserManager.addUserInfo(user1);
-        shadowUserManager.addUserInfo(user2);
-        shadowUserManager.setCurrentUser(10);
-
-        assertThat(mHelper.isCurrentProcessUser(user1)).isTrue();
-        assertThat(mHelper.isCurrentProcessUser(user2)).isFalse();
-
-        shadowUserManager.setCurrentUser(11);
-        assertThat(mHelper.isCurrentProcessUser(user2)).isTrue();
-        assertThat(mHelper.isCurrentProcessUser(user1)).isFalse();
-    }
-
-    @Test
-    public void testRemoveCurrentProcessUserSwitchesToGuestUser() {
-        // Set currentProcess user to be user 10.
-        ShadowUserManager shadowUserManager = ShadowUserManager.getShadow();
-        UserInfo user1 = createUserInfoForId(10);
-        UserInfo user2 = createUserInfoForId(11);
-        shadowUserManager.addUserInfo(user1);
-        shadowUserManager.addUserInfo(user2);
-        shadowUserManager.setCurrentUser(10);
-
-        // Removing a currentProcess user, calls "switch" to guest user
-        mHelper.removeUser(user1, "testGuest");
-        assertThat(ShadowActivityManager.getShadow().getSwitchUserCalled()).isTrue();
-        assertThat(ShadowActivityManager.getShadow().getUserSwitchedTo()).isEqualTo(0);
-
-        assertThat(shadowUserManager.removeUser(10)).isTrue();
-    }
-
-    @Test
-    public void testSwitchToUser() {
-        ShadowActivityManager.setCurrentUser(20);
-
-        // Switching to foreground user doesn't do anything.
-        mHelper.switchToUser(createUserInfoForId(20));
-        assertThat(ShadowActivityManager.getShadow().getSwitchUserCalled()).isFalse();
-
-        // Switching to non-current, non-guest user, simply calls switchUser.
-        UserInfo userToSwitchTo = new UserInfo(22, "Test User", 0);
-        mHelper.switchToUser(userToSwitchTo);
-        assertThat(ShadowActivityManager.getShadow().getSwitchUserCalled()).isTrue();
-        assertThat(ShadowActivityManager.getShadow().getUserSwitchedTo()).isEqualTo(22);
-    }
-
-    private UserInfo createUserInfoForId(int id) {
-        UserInfo userInfo = new UserInfo();
-        userInfo.id = id;
-        return userInfo;
-    }
-}
diff --git a/tests/vehiclehal_test/assets/car_hvac_set_test.json b/tests/vehiclehal_test/assets/car_hvac_set_test.json
new file mode 100644
index 0000000..e42875f
--- /dev/null
+++ b/tests/vehiclehal_test/assets/car_hvac_set_test.json
@@ -0,0 +1,26 @@
+[
+  {
+    "timestamp": 1526063903356950016,
+    "areaId": 117,
+    "value": 0,
+    "prop": 354419984
+  },
+  {
+    "timestamp": 1526063903357100032,
+    "areaId": 117,
+    "value": 1,
+    "prop": 354419984
+  },
+  {
+    "timestamp": 1526063903358950016,
+    "areaId": 117,
+    "value": 0,
+    "prop": 354419984
+  },
+  {
+    "timestamp": 1526063903359100032,
+    "areaId": 117,
+    "value": 1,
+    "prop": 354419984
+  }
+]
\ No newline at end of file
diff --git a/tests/vehiclehal_test/assets/car_hvac_test.json b/tests/vehiclehal_test/assets/car_hvac_test.json
index 7f6c0bb..bf51b8d 100644
--- a/tests/vehiclehal_test/assets/car_hvac_test.json
+++ b/tests/vehiclehal_test/assets/car_hvac_test.json
@@ -1,518 +1,506 @@
 [
     {
-        "timestamp": 1526063903356950016, 
-        "areaId": 1, 
-        "value": 1, 
-        "prop": 354419984
-    }, 
-    {
-        "timestamp": 1526063903357100032, 
-        "areaId": 4, 
-        "value": 1, 
-        "prop": 354419984
-    }, 
-    {
-        "timestamp": 1526063903757636096, 
-        "areaId": 0, 
-        "value": 1, 
+        "timestamp": 1526063903757636096,
+        "areaId": 117,
+        "value": 1,
         "prop": 356517121
-    }, 
+    },
     {
-        "timestamp": 1526063904959113984, 
-        "areaId": 0, 
-        "value": 28, 
+        "timestamp": 1526063904959113984,
+        "areaId": 49,
+        "value": 28,
         "prop": 358614275
-    }, 
+    },
     {
-        "timestamp": 1526063905159528960, 
-        "areaId": 0, 
-        "value": 27, 
+        "timestamp": 1526063905159528960,
+        "areaId": 49,
+        "value": 27,
         "prop": 358614275
-    }, 
+    },
     {
-        "timestamp": 1526063905359936000, 
-        "areaId": 0, 
-        "value": 26, 
+        "timestamp": 1526063905359936000,
+        "areaId": 49,
+        "value": 26,
         "prop": 358614275
-    }, 
+    },
     {
-        "timestamp": 1526063905560376832, 
-        "areaId": 0, 
-        "value": 25, 
+        "timestamp": 1526063905560376832,
+        "areaId": 49,
+        "value": 25,
         "prop": 358614275
-    }, 
+    },
     {
-        "timestamp": 1526063905760837120, 
-        "areaId": 0, 
-        "value": 24, 
+        "timestamp": 1526063905760837120,
+        "areaId": 49,
+        "value": 24,
         "prop": 358614275
-    }, 
+    },
     {
-        "timestamp": 1526063905961300992, 
-        "areaId": 0, 
-        "value": 23, 
+        "timestamp": 1526063905961300992,
+        "areaId": 49,
+        "value": 23,
         "prop": 358614275
-    }, 
+    },
     {
-        "timestamp": 1526063906362006016, 
-        "areaId": 0, 
-        "value": 1, 
+        "timestamp": 1526063906362006016,
+        "areaId": 117,
+        "value": 1,
         "prop": 356517120
-    }, 
+    },
     {
-        "timestamp": 1526063906562436096, 
-        "areaId": 0, 
-        "value": 2, 
+        "timestamp": 1526063906562436096,
+        "areaId": 117,
+        "value": 2,
         "prop": 356517120
-    }, 
+    },
     {
-        "timestamp": 1526063906762857984, 
-        "areaId": 0, 
-        "value": 3, 
+        "timestamp": 1526063906762857984,
+        "areaId": 117,
+        "value": 3,
         "prop": 356517120
-    }, 
+    },
     {
-        "timestamp": 1526063906963272960, 
-        "areaId": 0, 
-        "value": 4, 
+        "timestamp": 1526063906963272960,
+        "areaId": 117,
+        "value": 4,
         "prop": 356517120
-    }, 
+    },
     {
-        "timestamp": 1526063908364721920, 
-        "areaId": 0, 
-        "value": 1, 
+        "timestamp": 1526063908364721920,
+        "areaId": 117,
+        "value": 1,
         "prop": 356517121
-    }, 
+    },
     {
-        "timestamp": 1526063910066729984, 
-        "areaId": 0, 
-        "value": 4, 
+        "timestamp": 1526063910066729984,
+        "areaId": 117,
+        "value": 4,
         "prop": 356517121
-    }, 
+    },
     {
-        "timestamp": 1526063911268203008, 
-        "areaId": 0, 
-        "value": 22, 
+        "timestamp": 1526063911268203008,
+        "areaId": 49,
+        "value": 22,
         "prop": 358614275
-    }, 
+    },
     {
-        "timestamp": 1526063911468478976, 
-        "areaId": 0, 
-        "value": 23, 
+        "timestamp": 1526063911468478976,
+        "areaId": 49,
+        "value": 23,
         "prop": 358614275
-    }, 
+    },
     {
-        "timestamp": 1526063911668872192, 
-        "areaId": 0, 
-        "value": 24, 
+        "timestamp": 1526063911668872192,
+        "areaId": 49,
+        "value": 24,
         "prop": 358614275
-    }, 
+    },
     {
-        "timestamp": 1526063911869281024, 
-        "areaId": 0, 
-        "value": 25, 
+        "timestamp": 1526063911869281024,
+        "areaId": 49,
+        "value": 25,
         "prop": 358614275
-    }, 
+    },
     {
-        "timestamp": 1526063912069678080, 
-        "areaId": 0, 
-        "value": 26, 
+        "timestamp": 1526063912069678080,
+        "areaId": 49,
+        "value": 26,
         "prop": 358614275
-    }, 
+    },
     {
-        "timestamp": 1526063912270088960, 
-        "areaId": 0, 
-        "value": 27, 
+        "timestamp": 1526063912270088960,
+        "areaId": 49,
+        "value": 27,
         "prop": 358614275
-    }, 
+    },
     {
-        "timestamp": 1526063912670825984, 
-        "areaId": 0, 
-        "value": 5, 
+        "timestamp": 1526063912670825984,
+        "areaId": 117,
+        "value": 5,
         "prop": 356517120
-    }, 
+    },
     {
-        "timestamp": 1526063912871236096, 
-        "areaId": 0, 
-        "value": 4, 
+        "timestamp": 1526063912871236096,
+        "areaId": 117,
+        "value": 4,
         "prop": 356517120
-    }, 
+    },
     {
-        "timestamp": 1526063913071654912, 
-        "areaId": 0, 
-        "value": 3, 
+        "timestamp": 1526063913071654912,
+        "areaId": 117,
+        "value": 3,
         "prop": 356517120
-    }, 
+    },
     {
-        "timestamp": 1526063913272064000, 
-        "areaId": 0, 
-        "value": 2, 
+        "timestamp": 1526063913272064000,
+        "areaId": 117,
+        "value": 2,
         "prop": 356517120
-    }, 
+    },
     {
-        "timestamp": 1526063914373497856, 
-        "areaId": 0, 
-        "value": 1, 
+        "timestamp": 1526063914373497856,
+        "areaId": 117,
+        "value": 1,
         "prop": 356517121
-    }, 
+    },
     {
-        "timestamp": 1526063915574942976, 
-        "areaId": 0, 
-        "value": 28, 
+        "timestamp": 1526063915574942976,
+        "areaId": 49,
+        "value": 28,
         "prop": 358614275
-    }, 
+    },
     {
-        "timestamp": 1526063915775356928, 
-        "areaId": 0, 
-        "value": 27, 
+        "timestamp": 1526063915775356928,
+        "areaId": 49,
+        "value": 27,
         "prop": 358614275
-    }, 
+    },
     {
-        "timestamp": 1526063915975784960, 
-        "areaId": 0, 
-        "value": 26, 
+        "timestamp": 1526063915975784960,
+        "areaId": 49,
+        "value": 26,
         "prop": 358614275
-    }, 
+    },
     {
-        "timestamp": 1526063916176208128, 
-        "areaId": 0, 
-        "value": 25, 
+        "timestamp": 1526063916176208128,
+        "areaId": 49,
+        "value": 25,
         "prop": 358614275
-    }, 
+    },
     {
-        "timestamp": 1526063916376483840, 
-        "areaId": 0, 
-        "value": 24, 
+        "timestamp": 1526063916376483840,
+        "areaId": 49,
+        "value": 24,
         "prop": 358614275
-    }, 
+    },
     {
-        "timestamp": 1526063916576890880, 
-        "areaId": 0, 
-        "value": 23, 
+        "timestamp": 1526063916576890880,
+        "areaId": 49,
+        "value": 23,
         "prop": 358614275
-    }, 
+    },
     {
-        "timestamp": 1526063916977551872, 
-        "areaId": 0, 
-        "value": 1, 
+        "timestamp": 1526063916977551872,
+        "areaId": 117,
+        "value": 1,
         "prop": 356517120
-    }, 
+    },
     {
-        "timestamp": 1526063917177978112, 
-        "areaId": 0, 
-        "value": 2, 
+        "timestamp": 1526063917177978112,
+        "areaId": 117,
+        "value": 2,
         "prop": 356517120
-    }, 
+    },
     {
-        "timestamp": 1526063917378403072, 
-        "areaId": 0, 
-        "value": 3, 
+        "timestamp": 1526063917378403072,
+        "areaId": 117,
+        "value": 3,
         "prop": 356517120
-    }, 
+    },
     {
-        "timestamp": 1526063917578809856, 
-        "areaId": 0, 
-        "value": 4, 
+        "timestamp": 1526063917578809856,
+        "areaId": 117,
+        "value": 4,
         "prop": 356517120
-    }, 
+    },
     {
-        "timestamp": 1526063918980086016, 
-        "areaId": 0, 
-        "value": 1, 
+        "timestamp": 1526063918980086016,
+        "areaId": 117,
+        "value": 1,
         "prop": 356517121
-    }, 
+    },
     {
-        "timestamp": 1526063920681338112, 
-        "areaId": 0, 
-        "value": 4, 
+        "timestamp": 1526063920681338112,
+        "areaId": 117,
+        "value": 4,
         "prop": 356517121
-    }, 
+    },
     {
-        "timestamp": 1526063921882802944, 
-        "areaId": 0, 
-        "value": 22, 
+        "timestamp": 1526063921882802944,
+        "areaId": 49,
+        "value": 22,
         "prop": 358614275
-    }, 
+    },
     {
-        "timestamp": 1526063922083273984, 
-        "areaId": 0, 
-        "value": 23, 
+        "timestamp": 1526063922083273984,
+        "areaId": 49,
+        "value": 23,
         "prop": 358614275
-    }, 
+    },
     {
-        "timestamp": 1526063922283792896, 
-        "areaId": 0, 
-        "value": 24, 
+        "timestamp": 1526063922283792896,
+        "areaId": 49,
+        "value": 24,
         "prop": 358614275
-    }, 
+    },
     {
-        "timestamp": 1526063922484265984, 
-        "areaId": 0, 
-        "value": 25, 
+        "timestamp": 1526063922484265984,
+        "areaId": 49,
+        "value": 25,
         "prop": 358614275
-    }, 
+    },
     {
-        "timestamp": 1526063922684783872, 
-        "areaId": 0, 
-        "value": 26, 
+        "timestamp": 1526063922684783872,
+        "areaId": 49,
+        "value": 26,
         "prop": 358614275
-    }, 
+    },
     {
-        "timestamp": 1526063922885256960, 
-        "areaId": 0, 
-        "value": 27, 
+        "timestamp": 1526063922885256960,
+        "areaId": 49,
+        "value": 27,
         "prop": 358614275
-    }, 
+    },
     {
-        "timestamp": 1526063923285954048, 
-        "areaId": 0, 
-        "value": 5, 
+        "timestamp": 1526063923285954048,
+        "areaId": 117,
+        "value": 5,
         "prop": 356517120
-    }, 
+    },
     {
-        "timestamp": 1526063923486427136, 
-        "areaId": 0, 
-        "value": 4, 
+        "timestamp": 1526063923486427136,
+        "areaId": 117,
+        "value": 4,
         "prop": 356517120
-    }, 
+    },
     {
-        "timestamp": 1526063923686938880, 
-        "areaId": 0, 
-        "value": 3, 
+        "timestamp": 1526063923686938880,
+        "areaId": 117,
+        "value": 3,
         "prop": 356517120
-    }, 
+    },
     {
-        "timestamp": 1526063923887389952, 
-        "areaId": 0, 
-        "value": 2, 
+        "timestamp": 1526063923887389952,
+        "areaId": 117,
+        "value": 2,
         "prop": 356517120
-    }, 
+    },
     {
-        "timestamp": 1526063924988778240, 
-        "areaId": 0, 
-        "value": 1, 
+        "timestamp": 1526063924988778240,
+        "areaId": 117,
+        "value": 1,
         "prop": 356517121
-    }, 
+    },
     {
-        "timestamp": 1526063926190287104, 
-        "areaId": 0, 
-        "value": 28, 
+        "timestamp": 1526063926190287104,
+        "areaId": 49,
+        "value": 28,
         "prop": 358614275
-    }, 
+    },
     {
-        "timestamp": 1526063926390775040, 
-        "areaId": 0, 
-        "value": 27, 
+        "timestamp": 1526063926390775040,
+        "areaId": 49,
+        "value": 27,
         "prop": 358614275
-    }, 
+    },
     {
-        "timestamp": 1526063926591278080, 
-        "areaId": 0, 
-        "value": 26, 
+        "timestamp": 1526063926591278080,
+        "areaId": 49,
+        "value": 26,
         "prop": 358614275
-    }, 
+    },
     {
-        "timestamp": 1526063926791796992, 
-        "areaId": 0, 
-        "value": 25, 
+        "timestamp": 1526063926791796992,
+        "areaId": 49,
+        "value": 25,
         "prop": 358614275
-    }, 
+    },
     {
-        "timestamp": 1526063926992291840, 
-        "areaId": 0, 
-        "value": 24, 
+        "timestamp": 1526063926992291840,
+        "areaId": 49,
+        "value": 24,
         "prop": 358614275
-    }, 
+    },
     {
-        "timestamp": 1526063927192840960, 
-        "areaId": 0, 
-        "value": 23, 
+        "timestamp": 1526063927192840960,
+        "areaId": 49,
+        "value": 23,
         "prop": 358614275
-    }, 
+    },
     {
-        "timestamp": 1526063927593507072, 
-        "areaId": 0, 
-        "value": 1, 
+        "timestamp": 1526063927593507072,
+        "areaId": 117,
+        "value": 1,
         "prop": 356517120
-    }, 
+    },
     {
-        "timestamp": 1526063927793946112, 
-        "areaId": 0, 
-        "value": 2, 
+        "timestamp": 1526063927793946112,
+        "areaId": 117,
+        "value": 2,
         "prop": 356517120
-    }, 
+    },
     {
-        "timestamp": 1526063927994479872, 
-        "areaId": 0, 
-        "value": 3, 
+        "timestamp": 1526063927994479872,
+        "areaId": 117,
+        "value": 3,
         "prop": 356517120
-    }, 
+    },
     {
-        "timestamp": 1526063928194946048, 
-        "areaId": 0, 
-        "value": 4, 
+        "timestamp": 1526063928194946048,
+        "areaId": 117,
+        "value": 4,
         "prop": 356517120
-    }, 
+    },
     {
-        "timestamp": 1526063929596730112, 
-        "areaId": 0, 
-        "value": 1, 
+        "timestamp": 1526063929596730112,
+        "areaId": 117,
+        "value": 1,
         "prop": 356517121
-    }, 
+    },
     {
-        "timestamp": 1526063931298659072, 
-        "areaId": 0, 
-        "value": 4, 
+        "timestamp": 1526063931298659072,
+        "areaId": 117,
+        "value": 4,
         "prop": 356517121
-    }, 
+    },
     {
-        "timestamp": 1526063932500180992, 
-        "areaId": 0, 
-        "value": 22, 
+        "timestamp": 1526063932500180992,
+        "areaId": 49,
+        "value": 22,
         "prop": 358614275
-    }, 
+    },
     {
-        "timestamp": 1526063932700491008, 
-        "areaId": 0, 
-        "value": 23, 
+        "timestamp": 1526063932700491008,
+        "areaId": 49,
+        "value": 23,
         "prop": 358614275
-    }, 
+    },
     {
-        "timestamp": 1526063932900928000, 
-        "areaId": 0, 
-        "value": 24, 
+        "timestamp": 1526063932900928000,
+        "areaId": 49,
+        "value": 24,
         "prop": 358614275
-    }, 
+    },
     {
-        "timestamp": 1526063933101340928, 
-        "areaId": 0, 
-        "value": 25, 
+        "timestamp": 1526063933101340928,
+        "areaId": 49,
+        "value": 25,
         "prop": 358614275
-    }, 
+    },
     {
-        "timestamp": 1526063933301820160, 
-        "areaId": 0, 
-        "value": 26, 
+        "timestamp": 1526063933301820160,
+        "areaId": 49,
+        "value": 26,
         "prop": 358614275
-    }, 
+    },
     {
-        "timestamp": 1526063933502290944, 
-        "areaId": 0, 
-        "value": 27, 
+        "timestamp": 1526063933502290944,
+        "areaId": 49,
+        "value": 27,
         "prop": 358614275
-    }, 
+    },
     {
-        "timestamp": 1526063933903042048, 
-        "areaId": 0, 
-        "value": 5, 
+        "timestamp": 1526063933903042048,
+        "areaId": 117,
+        "value": 5,
         "prop": 356517120
-    }, 
+    },
     {
-        "timestamp": 1526063934103492864, 
-        "areaId": 0, 
-        "value": 4, 
+        "timestamp": 1526063934103492864,
+        "areaId": 117,
+        "value": 4,
         "prop": 356517120
-    }, 
+    },
     {
-        "timestamp": 1526063934303913984, 
-        "areaId": 0, 
-        "value": 3, 
+        "timestamp": 1526063934303913984,
+        "areaId": 117,
+        "value": 3,
         "prop": 356517120
-    }, 
+    },
     {
-        "timestamp": 1526063934504412928, 
-        "areaId": 0, 
-        "value": 2, 
+        "timestamp": 1526063934504412928,
+        "areaId": 117,
+        "value": 2,
         "prop": 356517120
-    }, 
+    },
     {
-        "timestamp": 1526063935606041856, 
-        "areaId": 0, 
-        "value": 1, 
+        "timestamp": 1526063935606041856,
+        "areaId": 117,
+        "value": 1,
         "prop": 356517121
-    }, 
+    },
     {
-        "timestamp": 1526063936807610880, 
-        "areaId": 0, 
-        "value": 28, 
+        "timestamp": 1526063936807610880,
+        "areaId": 49,
+        "value": 28,
         "prop": 358614275
-    }, 
+    },
     {
-        "timestamp": 1526063937008130048, 
-        "areaId": 0, 
-        "value": 27, 
+        "timestamp": 1526063937008130048,
+        "areaId": 49,
+        "value": 27,
         "prop": 358614275
-    }, 
+    },
     {
-        "timestamp": 1526063937208636160, 
-        "areaId": 0, 
-        "value": 26, 
+        "timestamp": 1526063937208636160,
+        "areaId": 49,
+        "value": 26,
         "prop": 358614275
-    }, 
+    },
     {
-        "timestamp": 1526063937409096960, 
-        "areaId": 0, 
-        "value": 25, 
+        "timestamp": 1526063937409096960,
+        "areaId": 49,
+        "value": 25,
         "prop": 358614275
-    }, 
+    },
     {
-        "timestamp": 1526063937609554176, 
-        "areaId": 0, 
-        "value": 24, 
+        "timestamp": 1526063937609554176,
+        "areaId": 49,
+        "value": 24,
         "prop": 358614275
-    }, 
+    },
     {
-        "timestamp": 1526063937810017024, 
-        "areaId": 0, 
-        "value": 23, 
+        "timestamp": 1526063937810017024,
+        "areaId": 49,
+        "value": 23,
         "prop": 358614275
-    }, 
+    },
     {
-        "timestamp": 1526063938210696960, 
-        "areaId": 0, 
-        "value": 1, 
+        "timestamp": 1526063938210696960,
+        "areaId": 117,
+        "value": 1,
         "prop": 356517120
-    }, 
+    },
     {
-        "timestamp": 1526063938411200000, 
-        "areaId": 0, 
-        "value": 2, 
+        "timestamp": 1526063938411200000,
+        "areaId": 117,
+        "value": 2,
         "prop": 356517120
-    }, 
+    },
     {
-        "timestamp": 1526063938611734016, 
-        "areaId": 0, 
-        "value": 3, 
+        "timestamp": 1526063938611734016,
+        "areaId": 117,
+        "value": 3,
         "prop": 356517120
-    }, 
+    },
     {
-        "timestamp": 1526063938812249856, 
-        "areaId": 0, 
-        "value": 4, 
+        "timestamp": 1526063938812249856,
+        "areaId": 117,
+        "value": 4,
         "prop": 356517120
-    }, 
+    },
     {
-        "timestamp": 1526063940214057984, 
-        "areaId": 0, 
-        "value": 1, 
+        "timestamp": 1526063940214057984,
+        "areaId": 117,
+        "value": 1,
         "prop": 356517121
-    }, 
+    },
     {
-        "timestamp": 1526063941916071936, 
-        "areaId": 0, 
-        "value": 4, 
+        "timestamp": 1526063941916071936,
+        "areaId": 117,
+        "value": 4,
         "prop": 356517121
-    }, 
+    },
     {
-        "timestamp": 1526063943123698944, 
-        "areaId": 0, 
-        "value": 22, 
+        "timestamp": 1526063943123698944,
+        "areaId": 49,
+        "value": 22,
         "prop": 358614275
-    }, 
+    },
     {
-        "timestamp": 1526063943323981056, 
-        "areaId": 0, 
-        "value": 23, 
+        "timestamp": 1526063943323981056,
+        "areaId": 49,
+        "value": 23,
         "prop": 358614275
     }
 ]
\ No newline at end of file
diff --git a/tests/vehiclehal_test/assets/car_info_test.json b/tests/vehiclehal_test/assets/car_info_test.json
index 90c9bdc..66aa87b 100644
--- a/tests/vehiclehal_test/assets/car_info_test.json
+++ b/tests/vehiclehal_test/assets/car_info_test.json
@@ -14,7 +14,7 @@
     {
         "timestamp": 1526063903360950016,
         "areaId": 0,
-        "value": "Test Car",
+        "value": "Toy Vehicle",
         "prop": 286261505
     }
 ]
diff --git a/tests/vehiclehal_test/assets/car_property_test.json b/tests/vehiclehal_test/assets/car_property_test.json
new file mode 100644
index 0000000..881ddf4
--- /dev/null
+++ b/tests/vehiclehal_test/assets/car_property_test.json
@@ -0,0 +1,26 @@
+[
+  {
+    "timestamp": 151111100000000,
+    "areaId": 0,
+    "value": 8,
+    "prop": 289408000
+  },
+  {
+    "timestamp": 101111100000000,
+    "areaId": 0,
+    "value": 4,
+    "prop": 289408000
+  },
+  {
+    "timestamp": 181111100000000,
+    "areaId": 0,
+    "value": 16,
+    "prop": 289408000
+  },
+  {
+    "timestamp": 101111100000000,
+    "areaId": 0,
+    "value": 4,
+    "prop": 289408000
+  }
+]
\ No newline at end of file
diff --git a/tests/vehiclehal_test/src/com/android/car/vehiclehal/test/CarPropertyTest.java b/tests/vehiclehal_test/src/com/android/car/vehiclehal/test/CarPropertyTest.java
index f9d1fd8..8e41c19 100644
--- a/tests/vehiclehal_test/src/com/android/car/vehiclehal/test/CarPropertyTest.java
+++ b/tests/vehiclehal_test/src/com/android/car/vehiclehal/test/CarPropertyTest.java
@@ -15,14 +15,22 @@
  */
 package com.android.car.vehiclehal.test;
 
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 
+import static java.lang.Integer.toHexString;
+
 import android.car.Car;
 import android.car.hardware.CarPropertyConfig;
 import android.car.hardware.CarPropertyValue;
 import android.car.hardware.property.CarPropertyManager;
 import android.car.hardware.property.CarPropertyManager.CarPropertyEventCallback;
+import android.car.hardware.property.VehicleVendorPermission;
+import android.hardware.automotive.vehicle.V2_0.VehiclePropertyGroup;
+import android.os.SystemClock;
 import android.util.ArraySet;
 import android.util.Log;
 
@@ -33,11 +41,11 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import static java.lang.Integer.toHexString;
-
-import java.io.File;
 import java.time.Duration;
+import java.util.Arrays;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
 
 /**
  * The test suite will execute end-to-end Car Property API test by generating VHAL property data
@@ -51,88 +59,145 @@
     private static final String TAG = Utils.concatTag(CarPropertyTest.class);
 
     // Test should be completed within 10 minutes as it only covers a finite set of properties
-    private static final Duration TEST_TIME_OUT = Duration.ofMinutes(10);
+    private static final Duration TEST_TIME_OUT = Duration.ofMinutes(3);
 
     private static final String CAR_HVAC_TEST_JSON = "car_hvac_test.json";
+    private static final String CAR_HVAC_TEST_SET_JSON = "car_hvac_test.json";
     private static final String CAR_INFO_TEST_JSON = "car_info_test.json";
+    // kMixedTypePropertyForTest property ID
+    private static final int MIXED_TYPE_PROPERTY = 0x21e01111;
+    // kSetPropertyFromVehicleForTest
+    private static final int SET_INT_FROM_VEHICLE = 0x21e01112;
+    private static final int SET_FLOAT_FROM_VEHICLE = 0x21e01113;
+    private static final int SET_BOOLEAN_FROM_VEHICLE = 0x21e01114;
+    private static final int WAIT_FOR_CALLBACK = 200;
 
+    // kMixedTypePropertyForTest default value
+    private static final Object[] DEFAULT_VALUE = {"MIXED property", true, 2, 3, 4.5f};
+    private static final String CAR_PROPERTY_TEST_JSON = "car_property_test.json";
+    private static final int GEAR_PROPERTY_ID = 289408000;
+
+    private static final Set<String> VENDOR_PERMISSIONS = new HashSet<>(Arrays.asList(
+            Car.PERMISSION_VENDOR_EXTENSION,
+            VehicleVendorPermission.PERMISSION_SET_CAR_VENDOR_CATEGORY_WINDOW,
+            VehicleVendorPermission.PERMISSION_GET_CAR_VENDOR_CATEGORY_WINDOW,
+            // permissions for the property related with door
+            VehicleVendorPermission.PERMISSION_SET_CAR_VENDOR_CATEGORY_DOOR,
+            VehicleVendorPermission.PERMISSION_GET_CAR_VENDOR_CATEGORY_DOOR,
+            // permissions for the property related with seat
+            VehicleVendorPermission.PERMISSION_SET_CAR_VENDOR_CATEGORY_SEAT,
+            VehicleVendorPermission.PERMISSION_GET_CAR_VENDOR_CATEGORY_SEAT,
+            // permissions for the property related with mirror
+            VehicleVendorPermission.PERMISSION_SET_CAR_VENDOR_CATEGORY_MIRROR,
+            VehicleVendorPermission.PERMISSION_GET_CAR_VENDOR_CATEGORY_MIRROR,
+
+            // permissions for the property related with car's information
+            VehicleVendorPermission.PERMISSION_SET_CAR_VENDOR_CATEGORY_INFO,
+            VehicleVendorPermission.PERMISSION_GET_CAR_VENDOR_CATEGORY_INFO,
+            // permissions for the property related with car's engine
+            VehicleVendorPermission.PERMISSION_SET_CAR_VENDOR_CATEGORY_ENGINE,
+            VehicleVendorPermission.PERMISSION_GET_CAR_VENDOR_CATEGORY_ENGINE,
+            // permissions for the property related with car's HVAC
+            VehicleVendorPermission.PERMISSION_SET_CAR_VENDOR_CATEGORY_HVAC,
+            VehicleVendorPermission.PERMISSION_GET_CAR_VENDOR_CATEGORY_HVAC,
+            // permissions for the property related with car's light
+            VehicleVendorPermission.PERMISSION_SET_CAR_VENDOR_CATEGORY_LIGHT,
+            VehicleVendorPermission.PERMISSION_GET_CAR_VENDOR_CATEGORY_LIGHT,
+
+            // permissions reserved for other vendor permission
+            VehicleVendorPermission.PERMISSION_SET_CAR_VENDOR_CATEGORY_1,
+            VehicleVendorPermission.PERMISSION_GET_CAR_VENDOR_CATEGORY_1,
+            VehicleVendorPermission.PERMISSION_SET_CAR_VENDOR_CATEGORY_2,
+            VehicleVendorPermission.PERMISSION_GET_CAR_VENDOR_CATEGORY_2,
+            VehicleVendorPermission.PERMISSION_SET_CAR_VENDOR_CATEGORY_3,
+            VehicleVendorPermission.PERMISSION_GET_CAR_VENDOR_CATEGORY_3,
+            VehicleVendorPermission.PERMISSION_SET_CAR_VENDOR_CATEGORY_4,
+            VehicleVendorPermission.PERMISSION_GET_CAR_VENDOR_CATEGORY_4,
+            VehicleVendorPermission.PERMISSION_SET_CAR_VENDOR_CATEGORY_5,
+            VehicleVendorPermission.PERMISSION_GET_CAR_VENDOR_CATEGORY_5,
+            VehicleVendorPermission.PERMISSION_SET_CAR_VENDOR_CATEGORY_6,
+            VehicleVendorPermission.PERMISSION_GET_CAR_VENDOR_CATEGORY_6,
+            VehicleVendorPermission.PERMISSION_SET_CAR_VENDOR_CATEGORY_7,
+            VehicleVendorPermission.PERMISSION_GET_CAR_VENDOR_CATEGORY_7,
+            VehicleVendorPermission.PERMISSION_SET_CAR_VENDOR_CATEGORY_8,
+            VehicleVendorPermission.PERMISSION_GET_CAR_VENDOR_CATEGORY_8,
+            VehicleVendorPermission.PERMISSION_SET_CAR_VENDOR_CATEGORY_9,
+            VehicleVendorPermission.PERMISSION_GET_CAR_VENDOR_CATEGORY_9,
+            VehicleVendorPermission.PERMISSION_SET_CAR_VENDOR_CATEGORY_10,
+            VehicleVendorPermission.PERMISSION_GET_CAR_VENDOR_CATEGORY_10
+    ));
     private class CarPropertyEventReceiver implements CarPropertyEventCallback {
 
         private VhalEventVerifier mVerifier;
-        private Integer mNumOfEventToSkip;
+        private boolean mStartVerify = false;
 
-        CarPropertyEventReceiver(VhalEventVerifier verifier, int numOfEventToSkip) {
+        CarPropertyEventReceiver(VhalEventVerifier verifier) {
             mVerifier = verifier;
-            mNumOfEventToSkip = numOfEventToSkip;
         }
 
         @Override
         public void onChangeEvent(CarPropertyValue carPropertyValue) {
-            Log.d(TAG, "Received event: " + carPropertyValue);
-            synchronized (mNumOfEventToSkip) {
-                if (mNumOfEventToSkip > 0) {
-                    mNumOfEventToSkip--;
-                    return;
-                }
+            if (mStartVerify) {
+                mVerifier.verify(carPropertyValue);
             }
-            mVerifier.verify(carPropertyValue);
         }
 
         @Override
         public void onErrorEvent(final int propertyId, final int zone) {
             Assert.fail("Error: propertyId=" + toHexString(propertyId) + " zone=" + zone);
         }
-    }
 
-    private int countNumPropEventsToSkip(CarPropertyManager propMgr, ArraySet<Integer> props) {
-        int numToSkip = 0;
-        for (CarPropertyConfig c : propMgr.getPropertyList(props)) {
-            numToSkip += c.getAreaCount();
+        // Start verifying events
+        public void startVerifying() {
+            mStartVerify = true;
         }
-        return numToSkip;
     }
 
     /**
-     * This test will let Default VHAL to generate HVAC data and verify on-the-fly in the test. It
-     * is simulating the HVAC actions coming from hard buttons in a car.
+     * This test will use {@link CarPropertyManager#setProperty(Class, int, int, Object)} to turn
+     * on the HVAC_PROWER and then let Default VHAL to generate HVAC data and verify on-the-fly
+     * in the test. It is simulating the HVAC actions coming from hard buttons in a car.
      * @throws Exception
      */
     @Test
     public void testHvacHardButtonOperations() throws Exception {
+
         Log.d(TAG, "Prepare HVAC test data");
         List<CarPropertyValue> expectedEvents = getExpectedEvents(CAR_HVAC_TEST_JSON);
-        VhalEventVerifier verifier = new VhalEventVerifier(expectedEvents);
+        List<CarPropertyValue> expectedSetEvents = getExpectedEvents(CAR_HVAC_TEST_SET_JSON);
 
         CarPropertyManager propMgr = (CarPropertyManager) mCar.getCarManager(Car.PROPERTY_SERVICE);
         assertNotNull("CarPropertyManager is null", propMgr);
 
-        ArraySet<Integer> props = new ArraySet<>();
-        for (CarPropertyValue event : expectedEvents) {
-            if (!props.contains(event.getPropertyId())) {
-                props.add(event.getPropertyId());
-            }
+        // test set method from android side
+        for (CarPropertyValue expectedEvent : expectedSetEvents) {
+            Class valueClass = expectedEvent.getValue().getClass();
+            propMgr.setProperty(valueClass,
+                    expectedEvent.getPropertyId(),
+                    expectedEvent.getAreaId(),
+                    expectedEvent.getValue());
+            Thread.sleep(WAIT_FOR_CALLBACK);
+            CarPropertyValue receivedEvent = propMgr.getProperty(valueClass,
+                    expectedEvent.getPropertyId(), expectedEvent.getAreaId());
+            assertTrue("Mismatched events, expected: " + expectedEvent + ", received: "
+                    + receivedEvent, Utils.areCarPropertyValuesEqual(expectedEvent, receivedEvent));
         }
 
-        int numToSkip = countNumPropEventsToSkip(propMgr, props);
-        Log.d(TAG, String.format("Start listening to the HAL."
-                                 + " Skipping %d events for listener registration", numToSkip));
-        CarPropertyEventCallback receiver =
-                new CarPropertyEventReceiver(verifier, numToSkip);
+        // test that set from vehicle side will trigger callback to android
+        VhalEventVerifier verifier = new VhalEventVerifier(expectedEvents);
+        ArraySet<Integer> props = new ArraySet<>();
+        for (CarPropertyValue event : expectedEvents) {
+                props.add(event.getPropertyId());
+        }
+        CarPropertyEventReceiver receiver =
+                new CarPropertyEventReceiver(verifier);
         for (Integer prop : props) {
             propMgr.registerCallback(receiver, prop, 0);
         }
-
-        File sharedJson = makeShareable(CAR_HVAC_TEST_JSON);
-        Log.d(TAG, "Send command to VHAL to start generation");
-        VhalEventGenerator hvacGenerator =
-                new JsonVhalEventGenerator(mVehicle).setJsonFile(sharedJson);
-        hvacGenerator.start();
-
-        Log.d(TAG, "Receiving and verifying VHAL events");
+        Thread.sleep(WAIT_FOR_CALLBACK);
+        receiver.startVerifying();
+        injectEventFromVehicleSide(expectedEvents, propMgr);
         verifier.waitForEnd(TEST_TIME_OUT.toMillis());
-
-        Log.d(TAG, "Send command to VHAL to stop generation");
-        hvacGenerator.stop();
         propMgr.unregisterCallback(receiver);
 
         assertTrue("Detected mismatched events: " + verifier.getResultString(),
@@ -140,38 +205,7 @@
     }
 
     /**
-     * This test will exercise on "set" calls to inject HVAC data in order to test the Car Property
-     * API end-to-end functionality.
-     * @throws Exception
-     */
-    @Test
-    @SuppressWarnings("unchecked")
-    public void testHvacSetGetOperations() throws Exception {
-        Log.d(TAG, "Prepare HVAC test data");
-        List<CarPropertyValue> expectedEvents = getExpectedEvents(CAR_HVAC_TEST_JSON);
-
-        CarPropertyManager propMgr = (CarPropertyManager) mCar.getCarManager(Car.PROPERTY_SERVICE);
-        assertNotNull("CarPropertyManager is null", propMgr);
-
-        final long waitForSetMillisecond = 2;
-        for (CarPropertyValue expectedEvent : expectedEvents) {
-            Class valueClass = expectedEvent.getValue().getClass();
-            propMgr.setProperty(valueClass,
-                                expectedEvent.getPropertyId(),
-                                expectedEvent.getAreaId(),
-                                expectedEvent.getValue());
-
-            Thread.sleep(waitForSetMillisecond);
-            CarPropertyValue receivedEvent = propMgr.getProperty(valueClass,
-                    expectedEvent.getPropertyId(), expectedEvent.getAreaId());
-            assertTrue("Mismatched events, expected: " + expectedEvent + ", received: "
-                    + receivedEvent, Utils.areCarPropertyValuesEqual(expectedEvent, receivedEvent));
-        }
-    }
-
-    /**
-     * This test will load static vehicle information from test data file and verify them through
-     * get calls.
+     * Static properties' value should never be changed.
      * @throws Exception
      */
     @Test
@@ -181,20 +215,6 @@
         List<CarPropertyValue> expectedEvents = getExpectedEvents(CAR_INFO_TEST_JSON);
         CarPropertyManager propMgr = (CarPropertyManager) mCar.getCarManager(Car.PROPERTY_SERVICE);
         assertNotNull("CarPropertyManager is null", propMgr);
-
-        File sharedJson = makeShareable(CAR_INFO_TEST_JSON);
-        Log.d(TAG, "Send command to VHAL to start generation");
-        VhalEventGenerator infoGenerator =
-                new JsonVhalEventGenerator(mVehicle).setJsonFile(sharedJson);
-        infoGenerator.start();
-
-        // Wait for some time to ensure information is all loaded
-        // It is assuming the test data is not very large
-        Thread.sleep(2000);
-
-        Log.d(TAG, "Send command to VHAL to stop generation");
-        infoGenerator.stop();
-
         for (CarPropertyValue expectedEvent : expectedEvents) {
             CarPropertyValue actualEvent = propMgr.getProperty(
                     expectedEvent.getPropertyId(), expectedEvent.getAreaId());
@@ -204,4 +224,138 @@
                     Utils.areCarPropertyValuesEqual(actualEvent, expectedEvent));
         }
     }
+
+    /**
+     * This test will test set/get on MIX type properties. It needs a vendor property in Google
+     * Vehicle HAL. See kMixedTypePropertyForTest in google defaultConfig.h for details.
+     * @throws Exception
+     */
+    @Test
+    public void testMixedTypeProperty() throws Exception {
+        CarPropertyManager propertyManager =
+                (CarPropertyManager) mCar.getCarManager(Car.PROPERTY_SERVICE);
+        ArraySet<Integer> propConfigSet = new ArraySet<>();
+        propConfigSet.add(MIXED_TYPE_PROPERTY);
+
+        List<CarPropertyConfig> configs = propertyManager.getPropertyList(propConfigSet);
+
+        // use google HAL in the test
+        assertNotEquals("Can not find MIXED type properties in HAL",
+                0, configs.size());
+
+        // test CarPropertyConfig
+        CarPropertyConfig<?> cfg = configs.get(0);
+        List<Integer> configArrayExpected = Arrays.asList(1, 1, 0, 2, 0, 0, 1, 0, 0);
+        assertArrayEquals(configArrayExpected.toArray(), cfg.getConfigArray().toArray());
+
+        // test SET/GET methods
+        CarPropertyValue<Object[]> propertyValue = propertyManager.getProperty(Object[].class,
+                MIXED_TYPE_PROPERTY, 0);
+        assertArrayEquals(DEFAULT_VALUE, propertyValue.getValue());
+
+        Object[] expectedValue = {"MIXED property", false, 5, 4, 3.2f};
+        propertyManager.setProperty(Object[].class, MIXED_TYPE_PROPERTY, 0, expectedValue);
+        // Wait for VHAL
+        Thread.sleep(WAIT_FOR_CALLBACK);
+        CarPropertyValue<Object[]> result = propertyManager.getProperty(Object[].class,
+                MIXED_TYPE_PROPERTY, 0);
+        assertArrayEquals(expectedValue, result.getValue());
+    }
+
+    /**
+     * This test will test the case: vehicle events comes to android out of order.
+     * See the events in car_property_test.json.
+     * @throws Exception
+     */
+    @Test
+    public void testPropertyEventOutOfOrder() throws Exception {
+        CarPropertyManager propMgr = (CarPropertyManager) mCar.getCarManager(Car.PROPERTY_SERVICE);
+        assertNotNull("CarPropertyManager is null", propMgr);
+        List<CarPropertyValue> expectedEvents = getExpectedEvents(CAR_PROPERTY_TEST_JSON);
+
+        GearEventTestCallback cb = new GearEventTestCallback();
+        propMgr.registerCallback(cb, GEAR_PROPERTY_ID, CarPropertyManager.SENSOR_RATE_ONCHANGE);
+        injectEventFromVehicleSide(expectedEvents, propMgr);
+        // check VHAL ignored the last event in car_property_test, because it is out of order.
+        int currentGear = propMgr.getIntProperty(GEAR_PROPERTY_ID, 0);
+        assertEquals(16, currentGear);
+    }
+
+    /**
+     * Check only vendor properties have vendor permissions.
+     */
+    @Test
+    public void checkPropertyPermission() {
+        CarPropertyManager propMgr = (CarPropertyManager) mCar.getCarManager(Car.PROPERTY_SERVICE);
+        List<CarPropertyConfig> configs = propMgr.getPropertyList();
+        for (CarPropertyConfig cfg : configs) {
+            String readPermission = propMgr.getReadPermission(cfg.getPropertyId());
+            String writePermission = propMgr.getWritePermission(cfg.getPropertyId());
+            if ((cfg.getPropertyId() & VehiclePropertyGroup.MASK) == VehiclePropertyGroup.VENDOR) {
+                Assert.assertTrue(readPermission == null
+                        || VENDOR_PERMISSIONS.contains(readPermission));
+                Assert.assertTrue(writePermission == null
+                        || VENDOR_PERMISSIONS.contains(writePermission));
+            } else {
+                Assert.assertTrue(readPermission == null
+                        || !VENDOR_PERMISSIONS.contains(readPermission));
+                Assert.assertTrue(writePermission == null
+                        || !VENDOR_PERMISSIONS.contains(writePermission));
+            }
+        }
+    }
+
+    private class GearEventTestCallback implements CarPropertyEventCallback {
+        private long mTimestamp = 0L;
+
+        @Override
+        public void onChangeEvent(CarPropertyValue carPropertyValue) {
+            if (carPropertyValue.getPropertyId() != GEAR_PROPERTY_ID) {
+                return;
+            }
+            if (carPropertyValue.getStatus() == CarPropertyValue.STATUS_AVAILABLE) {
+                Assert.assertTrue("Received events out of oder",
+                        mTimestamp <= carPropertyValue.getTimestamp());
+                mTimestamp = carPropertyValue.getTimestamp();
+            }
+        }
+
+        @Override
+        public void onErrorEvent(final int propertyId, final int zone) {
+            Assert.fail("Error: propertyId: x0" + toHexString(propertyId) + " areaId: " + zone);
+        }
+    }
+
+    /**
+     * Inject events from vehicle side. It change the value of property even the property is a
+     * read_only property such as GEAR_SELECTION. It only works with Google VHAL.
+     */
+    private void injectEventFromVehicleSide(List<CarPropertyValue> expectedEvents,
+            CarPropertyManager propMgr) {
+        for (CarPropertyValue propertyValue : expectedEvents) {
+            Object[] values = new Object[3];
+            int propId;
+            // The order of values is matter
+            if (propertyValue.getValue() instanceof Integer) {
+                propId = SET_INT_FROM_VEHICLE;
+                values[0] = propertyValue.getPropertyId();
+                values[1] = propertyValue.getValue();
+                values[2] = propertyValue.getTimestamp() + SystemClock.elapsedRealtimeNanos();
+            } else if (propertyValue.getValue() instanceof Float) {
+                values[0] = propertyValue.getPropertyId();
+                values[1] = propertyValue.getTimestamp() + SystemClock.elapsedRealtimeNanos();
+                values[2] = propertyValue.getValue();
+                propId = SET_FLOAT_FROM_VEHICLE;
+            } else if (propertyValue.getValue() instanceof Boolean) {
+                propId = SET_BOOLEAN_FROM_VEHICLE;
+                values[1] = propertyValue.getPropertyId();
+                values[0] = propertyValue.getValue();
+                values[2] = propertyValue.getTimestamp() + SystemClock.elapsedRealtimeNanos();
+            } else {
+                throw new IllegalArgumentException(
+                        "Unexpected property type for property " + propertyValue.getPropertyId());
+            }
+            propMgr.setProperty(Object[].class, propId, propertyValue.getAreaId(), values);
+        }
+    }
 }
diff --git a/tools/emulator/VehicleHalProto_pb2.py b/tools/emulator/VehicleHalProto_pb2.py
index 3278ed7..6e964f6 100644
--- a/tools/emulator/VehicleHalProto_pb2.py
+++ b/tools/emulator/VehicleHalProto_pb2.py
@@ -1,153 +1,164 @@
+# -*- coding: utf-8 -*-
 # Generated by the protocol buffer compiler.  DO NOT EDIT!
 # source: VehicleHalProto.proto
 
+import sys
+_b=sys.version_info[0]<3 and (lambda x:x) or (lambda x:x.encode('latin1'))
 from google.protobuf.internal import enum_type_wrapper
 from google.protobuf import descriptor as _descriptor
 from google.protobuf import message as _message
 from google.protobuf import reflection as _reflection
-from google.protobuf import descriptor_pb2
+from google.protobuf import symbol_database as _symbol_database
 # @@protoc_insertion_point(imports)
 
+_sym_db = _symbol_database.Default()
+
 
 
 
 DESCRIPTOR = _descriptor.FileDescriptor(
   name='VehicleHalProto.proto',
-  package='emulator',
-  serialized_pb='\n\x15VehicleHalProto.proto\x12\x08\x65mulator\"\xba\x01\n\x11VehicleAreaConfig\x12\x0f\n\x07\x61rea_id\x18\x01 \x02(\x05\x12\x17\n\x0fmin_int32_value\x18\x02 \x01(\x11\x12\x17\n\x0fmax_int32_value\x18\x03 \x01(\x11\x12\x17\n\x0fmin_int64_value\x18\x04 \x01(\x12\x12\x17\n\x0fmax_int64_value\x18\x05 \x01(\x12\x12\x17\n\x0fmin_float_value\x18\x06 \x01(\x02\x12\x17\n\x0fmax_float_value\x18\x07 \x01(\x02\"\x9b\x02\n\x11VehiclePropConfig\x12\x0c\n\x04prop\x18\x01 \x02(\x05\x12\x0e\n\x06\x61\x63\x63\x65ss\x18\x02 \x01(\x05\x12\x13\n\x0b\x63hange_mode\x18\x03 \x01(\x05\x12\x12\n\nvalue_type\x18\x04 \x01(\x05\x12\x17\n\x0fsupported_areas\x18\x05 \x01(\x05\x12\x31\n\x0c\x61rea_configs\x18\x06 \x03(\x0b\x32\x1b.emulator.VehicleAreaConfig\x12\x14\n\x0c\x63onfig_flags\x18\x07 \x01(\x05\x12\x14\n\x0c\x63onfig_array\x18\x08 \x03(\x05\x12\x15\n\rconfig_string\x18\t \x01(\t\x12\x17\n\x0fmin_sample_rate\x18\n \x01(\x02\x12\x17\n\x0fmax_sample_rate\x18\x0b \x01(\x02\"\xf2\x01\n\x10VehiclePropValue\x12\x0c\n\x04prop\x18\x01 \x02(\x05\x12\x12\n\nvalue_type\x18\x02 \x01(\x05\x12\x11\n\ttimestamp\x18\x03 \x01(\x03\x12+\n\x06status\x18\n \x01(\x0e\x32\x1b.emulator.VehiclePropStatus\x12\x0f\n\x07\x61rea_id\x18\x04 \x01(\x05\x12\x14\n\x0cint32_values\x18\x05 \x03(\x11\x12\x14\n\x0cint64_values\x18\x06 \x03(\x12\x12\x14\n\x0c\x66loat_values\x18\x07 \x03(\x02\x12\x14\n\x0cstring_value\x18\x08 \x01(\t\x12\x13\n\x0b\x62ytes_value\x18\t \x01(\x0c\"/\n\x0eVehiclePropGet\x12\x0c\n\x04prop\x18\x01 \x02(\x05\x12\x0f\n\x07\x61rea_id\x18\x02 \x01(\x05\"\xd8\x01\n\x0f\x45mulatorMessage\x12#\n\x08msg_type\x18\x01 \x02(\x0e\x32\x11.emulator.MsgType\x12 \n\x06status\x18\x02 \x01(\x0e\x32\x10.emulator.Status\x12&\n\x04prop\x18\x03 \x03(\x0b\x32\x18.emulator.VehiclePropGet\x12+\n\x06\x63onfig\x18\x04 \x03(\x0b\x32\x1b.emulator.VehiclePropConfig\x12)\n\x05value\x18\x05 \x03(\x0b\x32\x1a.emulator.VehiclePropValue*\x8a\x02\n\x07MsgType\x12\x12\n\x0eGET_CONFIG_CMD\x10\x00\x12\x13\n\x0fGET_CONFIG_RESP\x10\x01\x12\x16\n\x12GET_CONFIG_ALL_CMD\x10\x02\x12\x17\n\x13GET_CONFIG_ALL_RESP\x10\x03\x12\x14\n\x10GET_PROPERTY_CMD\x10\x04\x12\x15\n\x11GET_PROPERTY_RESP\x10\x05\x12\x18\n\x14GET_PROPERTY_ALL_CMD\x10\x06\x12\x19\n\x15GET_PROPERTY_ALL_RESP\x10\x07\x12\x14\n\x10SET_PROPERTY_CMD\x10\x08\x12\x15\n\x11SET_PROPERTY_RESP\x10\t\x12\x16\n\x12SET_PROPERTY_ASYNC\x10\n*\xfb\x01\n\x06Status\x12\r\n\tRESULT_OK\x10\x00\x12\x11\n\rERROR_UNKNOWN\x10\x01\x12\x1b\n\x17\x45RROR_UNIMPLEMENTED_CMD\x10\x02\x12\x1a\n\x16\x45RROR_INVALID_PROPERTY\x10\x03\x12\x19\n\x15\x45RROR_INVALID_AREA_ID\x10\x04\x12 \n\x1c\x45RROR_PROPERTY_UNINITIALIZED\x10\x05\x12\x1d\n\x19\x45RROR_WRITE_ONLY_PROPERTY\x10\x06\x12\x1d\n\x19\x45RROR_MEMORY_ALLOC_FAILED\x10\x07\x12\x1b\n\x17\x45RROR_INVALID_OPERATION\x10\x08*>\n\x11VehiclePropStatus\x12\r\n\tAVAILABLE\x10\x00\x12\x0f\n\x0bUNAVAILABLE\x10\x01\x12\t\n\x05\x45RROR\x10\x02\x42\x02H\x03')
+  package='vhal_proto',
+  syntax='proto2',
+  serialized_options=None,
+  serialized_pb=_b('\n\x15VehicleHalProto.proto\x12\nvhal_proto\"\xba\x01\n\x11VehicleAreaConfig\x12\x0f\n\x07\x61rea_id\x18\x01 \x02(\x05\x12\x17\n\x0fmin_int32_value\x18\x02 \x01(\x11\x12\x17\n\x0fmax_int32_value\x18\x03 \x01(\x11\x12\x17\n\x0fmin_int64_value\x18\x04 \x01(\x12\x12\x17\n\x0fmax_int64_value\x18\x05 \x01(\x12\x12\x17\n\x0fmin_float_value\x18\x06 \x01(\x02\x12\x17\n\x0fmax_float_value\x18\x07 \x01(\x02\"\x9d\x02\n\x11VehiclePropConfig\x12\x0c\n\x04prop\x18\x01 \x02(\x05\x12\x0e\n\x06\x61\x63\x63\x65ss\x18\x02 \x01(\x05\x12\x13\n\x0b\x63hange_mode\x18\x03 \x01(\x05\x12\x12\n\nvalue_type\x18\x04 \x01(\x05\x12\x17\n\x0fsupported_areas\x18\x05 \x01(\x05\x12\x33\n\x0c\x61rea_configs\x18\x06 \x03(\x0b\x32\x1d.vhal_proto.VehicleAreaConfig\x12\x14\n\x0c\x63onfig_flags\x18\x07 \x01(\x05\x12\x14\n\x0c\x63onfig_array\x18\x08 \x03(\x05\x12\x15\n\rconfig_string\x18\t \x01(\t\x12\x17\n\x0fmin_sample_rate\x18\n \x01(\x02\x12\x17\n\x0fmax_sample_rate\x18\x0b \x01(\x02\"\xf4\x01\n\x10VehiclePropValue\x12\x0c\n\x04prop\x18\x01 \x02(\x05\x12\x12\n\nvalue_type\x18\x02 \x01(\x05\x12\x11\n\ttimestamp\x18\x03 \x01(\x03\x12-\n\x06status\x18\n \x01(\x0e\x32\x1d.vhal_proto.VehiclePropStatus\x12\x0f\n\x07\x61rea_id\x18\x04 \x01(\x05\x12\x14\n\x0cint32_values\x18\x05 \x03(\x11\x12\x14\n\x0cint64_values\x18\x06 \x03(\x12\x12\x14\n\x0c\x66loat_values\x18\x07 \x03(\x02\x12\x14\n\x0cstring_value\x18\x08 \x01(\t\x12\x13\n\x0b\x62ytes_value\x18\t \x01(\x0c\"/\n\x0eVehiclePropGet\x12\x0c\n\x04prop\x18\x01 \x02(\x05\x12\x0f\n\x07\x61rea_id\x18\x02 \x01(\x05\"\xe2\x01\n\x0f\x45mulatorMessage\x12%\n\x08msg_type\x18\x01 \x02(\x0e\x32\x13.vhal_proto.MsgType\x12\"\n\x06status\x18\x02 \x01(\x0e\x32\x12.vhal_proto.Status\x12(\n\x04prop\x18\x03 \x03(\x0b\x32\x1a.vhal_proto.VehiclePropGet\x12-\n\x06\x63onfig\x18\x04 \x03(\x0b\x32\x1d.vhal_proto.VehiclePropConfig\x12+\n\x05value\x18\x05 \x03(\x0b\x32\x1c.vhal_proto.VehiclePropValue*\x8a\x02\n\x07MsgType\x12\x12\n\x0eGET_CONFIG_CMD\x10\x00\x12\x13\n\x0fGET_CONFIG_RESP\x10\x01\x12\x16\n\x12GET_CONFIG_ALL_CMD\x10\x02\x12\x17\n\x13GET_CONFIG_ALL_RESP\x10\x03\x12\x14\n\x10GET_PROPERTY_CMD\x10\x04\x12\x15\n\x11GET_PROPERTY_RESP\x10\x05\x12\x18\n\x14GET_PROPERTY_ALL_CMD\x10\x06\x12\x19\n\x15GET_PROPERTY_ALL_RESP\x10\x07\x12\x14\n\x10SET_PROPERTY_CMD\x10\x08\x12\x15\n\x11SET_PROPERTY_RESP\x10\t\x12\x16\n\x12SET_PROPERTY_ASYNC\x10\n*\xfb\x01\n\x06Status\x12\r\n\tRESULT_OK\x10\x00\x12\x11\n\rERROR_UNKNOWN\x10\x01\x12\x1b\n\x17\x45RROR_UNIMPLEMENTED_CMD\x10\x02\x12\x1a\n\x16\x45RROR_INVALID_PROPERTY\x10\x03\x12\x19\n\x15\x45RROR_INVALID_AREA_ID\x10\x04\x12 \n\x1c\x45RROR_PROPERTY_UNINITIALIZED\x10\x05\x12\x1d\n\x19\x45RROR_WRITE_ONLY_PROPERTY\x10\x06\x12\x1d\n\x19\x45RROR_MEMORY_ALLOC_FAILED\x10\x07\x12\x1b\n\x17\x45RROR_INVALID_OPERATION\x10\x08*>\n\x11VehiclePropStatus\x12\r\n\tAVAILABLE\x10\x00\x12\x0f\n\x0bUNAVAILABLE\x10\x01\x12\t\n\x05\x45RROR\x10\x02')
+)
 
 _MSGTYPE = _descriptor.EnumDescriptor(
   name='MsgType',
-  full_name='emulator.MsgType',
+  full_name='vhal_proto.MsgType',
   filename=None,
   file=DESCRIPTOR,
   values=[
     _descriptor.EnumValueDescriptor(
       name='GET_CONFIG_CMD', index=0, number=0,
-      options=None,
+      serialized_options=None,
       type=None),
     _descriptor.EnumValueDescriptor(
       name='GET_CONFIG_RESP', index=1, number=1,
-      options=None,
+      serialized_options=None,
       type=None),
     _descriptor.EnumValueDescriptor(
       name='GET_CONFIG_ALL_CMD', index=2, number=2,
-      options=None,
+      serialized_options=None,
       type=None),
     _descriptor.EnumValueDescriptor(
       name='GET_CONFIG_ALL_RESP', index=3, number=3,
-      options=None,
+      serialized_options=None,
       type=None),
     _descriptor.EnumValueDescriptor(
       name='GET_PROPERTY_CMD', index=4, number=4,
-      options=None,
+      serialized_options=None,
       type=None),
     _descriptor.EnumValueDescriptor(
       name='GET_PROPERTY_RESP', index=5, number=5,
-      options=None,
+      serialized_options=None,
       type=None),
     _descriptor.EnumValueDescriptor(
       name='GET_PROPERTY_ALL_CMD', index=6, number=6,
-      options=None,
+      serialized_options=None,
       type=None),
     _descriptor.EnumValueDescriptor(
       name='GET_PROPERTY_ALL_RESP', index=7, number=7,
-      options=None,
+      serialized_options=None,
       type=None),
     _descriptor.EnumValueDescriptor(
       name='SET_PROPERTY_CMD', index=8, number=8,
-      options=None,
+      serialized_options=None,
       type=None),
     _descriptor.EnumValueDescriptor(
       name='SET_PROPERTY_RESP', index=9, number=9,
-      options=None,
+      serialized_options=None,
       type=None),
     _descriptor.EnumValueDescriptor(
       name='SET_PROPERTY_ASYNC', index=10, number=10,
-      options=None,
+      serialized_options=None,
       type=None),
   ],
   containing_type=None,
-  options=None,
-  serialized_start=1024,
-  serialized_end=1290,
+  serialized_options=None,
+  serialized_start=1040,
+  serialized_end=1306,
 )
+_sym_db.RegisterEnumDescriptor(_MSGTYPE)
 
 MsgType = enum_type_wrapper.EnumTypeWrapper(_MSGTYPE)
 _STATUS = _descriptor.EnumDescriptor(
   name='Status',
-  full_name='emulator.Status',
+  full_name='vhal_proto.Status',
   filename=None,
   file=DESCRIPTOR,
   values=[
     _descriptor.EnumValueDescriptor(
       name='RESULT_OK', index=0, number=0,
-      options=None,
+      serialized_options=None,
       type=None),
     _descriptor.EnumValueDescriptor(
       name='ERROR_UNKNOWN', index=1, number=1,
-      options=None,
+      serialized_options=None,
       type=None),
     _descriptor.EnumValueDescriptor(
       name='ERROR_UNIMPLEMENTED_CMD', index=2, number=2,
-      options=None,
+      serialized_options=None,
       type=None),
     _descriptor.EnumValueDescriptor(
       name='ERROR_INVALID_PROPERTY', index=3, number=3,
-      options=None,
+      serialized_options=None,
       type=None),
     _descriptor.EnumValueDescriptor(
       name='ERROR_INVALID_AREA_ID', index=4, number=4,
-      options=None,
+      serialized_options=None,
       type=None),
     _descriptor.EnumValueDescriptor(
       name='ERROR_PROPERTY_UNINITIALIZED', index=5, number=5,
-      options=None,
+      serialized_options=None,
       type=None),
     _descriptor.EnumValueDescriptor(
       name='ERROR_WRITE_ONLY_PROPERTY', index=6, number=6,
-      options=None,
+      serialized_options=None,
       type=None),
     _descriptor.EnumValueDescriptor(
       name='ERROR_MEMORY_ALLOC_FAILED', index=7, number=7,
-      options=None,
+      serialized_options=None,
       type=None),
     _descriptor.EnumValueDescriptor(
       name='ERROR_INVALID_OPERATION', index=8, number=8,
-      options=None,
+      serialized_options=None,
       type=None),
   ],
   containing_type=None,
-  options=None,
-  serialized_start=1293,
-  serialized_end=1544,
+  serialized_options=None,
+  serialized_start=1309,
+  serialized_end=1560,
 )
+_sym_db.RegisterEnumDescriptor(_STATUS)
 
 Status = enum_type_wrapper.EnumTypeWrapper(_STATUS)
 _VEHICLEPROPSTATUS = _descriptor.EnumDescriptor(
   name='VehiclePropStatus',
-  full_name='emulator.VehiclePropStatus',
+  full_name='vhal_proto.VehiclePropStatus',
   filename=None,
   file=DESCRIPTOR,
   values=[
     _descriptor.EnumValueDescriptor(
       name='AVAILABLE', index=0, number=0,
-      options=None,
+      serialized_options=None,
       type=None),
     _descriptor.EnumValueDescriptor(
       name='UNAVAILABLE', index=1, number=1,
-      options=None,
+      serialized_options=None,
       type=None),
     _descriptor.EnumValueDescriptor(
       name='ERROR', index=2, number=2,
-      options=None,
+      serialized_options=None,
       type=None),
   ],
   containing_type=None,
-  options=None,
-  serialized_start=1546,
-  serialized_end=1608,
+  serialized_options=None,
+  serialized_start=1562,
+  serialized_end=1624,
 )
+_sym_db.RegisterEnumDescriptor(_VEHICLEPROPSTATUS)
 
 VehiclePropStatus = enum_type_wrapper.EnumTypeWrapper(_VEHICLEPROPSTATUS)
 GET_CONFIG_CMD = 0
@@ -178,351 +189,366 @@
 
 _VEHICLEAREACONFIG = _descriptor.Descriptor(
   name='VehicleAreaConfig',
-  full_name='emulator.VehicleAreaConfig',
+  full_name='vhal_proto.VehicleAreaConfig',
   filename=None,
   file=DESCRIPTOR,
   containing_type=None,
   fields=[
     _descriptor.FieldDescriptor(
-      name='area_id', full_name='emulator.VehicleAreaConfig.area_id', index=0,
+      name='area_id', full_name='vhal_proto.VehicleAreaConfig.area_id', index=0,
       number=1, type=5, cpp_type=1, label=2,
       has_default_value=False, default_value=0,
       message_type=None, enum_type=None, containing_type=None,
       is_extension=False, extension_scope=None,
-      options=None),
+      serialized_options=None, file=DESCRIPTOR),
     _descriptor.FieldDescriptor(
-      name='min_int32_value', full_name='emulator.VehicleAreaConfig.min_int32_value', index=1,
+      name='min_int32_value', full_name='vhal_proto.VehicleAreaConfig.min_int32_value', index=1,
       number=2, type=17, cpp_type=1, label=1,
       has_default_value=False, default_value=0,
       message_type=None, enum_type=None, containing_type=None,
       is_extension=False, extension_scope=None,
-      options=None),
+      serialized_options=None, file=DESCRIPTOR),
     _descriptor.FieldDescriptor(
-      name='max_int32_value', full_name='emulator.VehicleAreaConfig.max_int32_value', index=2,
+      name='max_int32_value', full_name='vhal_proto.VehicleAreaConfig.max_int32_value', index=2,
       number=3, type=17, cpp_type=1, label=1,
       has_default_value=False, default_value=0,
       message_type=None, enum_type=None, containing_type=None,
       is_extension=False, extension_scope=None,
-      options=None),
+      serialized_options=None, file=DESCRIPTOR),
     _descriptor.FieldDescriptor(
-      name='min_int64_value', full_name='emulator.VehicleAreaConfig.min_int64_value', index=3,
+      name='min_int64_value', full_name='vhal_proto.VehicleAreaConfig.min_int64_value', index=3,
       number=4, type=18, cpp_type=2, label=1,
       has_default_value=False, default_value=0,
       message_type=None, enum_type=None, containing_type=None,
       is_extension=False, extension_scope=None,
-      options=None),
+      serialized_options=None, file=DESCRIPTOR),
     _descriptor.FieldDescriptor(
-      name='max_int64_value', full_name='emulator.VehicleAreaConfig.max_int64_value', index=4,
+      name='max_int64_value', full_name='vhal_proto.VehicleAreaConfig.max_int64_value', index=4,
       number=5, type=18, cpp_type=2, label=1,
       has_default_value=False, default_value=0,
       message_type=None, enum_type=None, containing_type=None,
       is_extension=False, extension_scope=None,
-      options=None),
+      serialized_options=None, file=DESCRIPTOR),
     _descriptor.FieldDescriptor(
-      name='min_float_value', full_name='emulator.VehicleAreaConfig.min_float_value', index=5,
+      name='min_float_value', full_name='vhal_proto.VehicleAreaConfig.min_float_value', index=5,
       number=6, type=2, cpp_type=6, label=1,
-      has_default_value=False, default_value=0,
+      has_default_value=False, default_value=float(0),
       message_type=None, enum_type=None, containing_type=None,
       is_extension=False, extension_scope=None,
-      options=None),
+      serialized_options=None, file=DESCRIPTOR),
     _descriptor.FieldDescriptor(
-      name='max_float_value', full_name='emulator.VehicleAreaConfig.max_float_value', index=6,
+      name='max_float_value', full_name='vhal_proto.VehicleAreaConfig.max_float_value', index=6,
       number=7, type=2, cpp_type=6, label=1,
-      has_default_value=False, default_value=0,
+      has_default_value=False, default_value=float(0),
       message_type=None, enum_type=None, containing_type=None,
       is_extension=False, extension_scope=None,
-      options=None),
+      serialized_options=None, file=DESCRIPTOR),
   ],
   extensions=[
   ],
   nested_types=[],
   enum_types=[
   ],
-  options=None,
+  serialized_options=None,
   is_extendable=False,
+  syntax='proto2',
   extension_ranges=[],
-  serialized_start=36,
-  serialized_end=222,
+  oneofs=[
+  ],
+  serialized_start=38,
+  serialized_end=224,
 )
 
 
 _VEHICLEPROPCONFIG = _descriptor.Descriptor(
   name='VehiclePropConfig',
-  full_name='emulator.VehiclePropConfig',
+  full_name='vhal_proto.VehiclePropConfig',
   filename=None,
   file=DESCRIPTOR,
   containing_type=None,
   fields=[
     _descriptor.FieldDescriptor(
-      name='prop', full_name='emulator.VehiclePropConfig.prop', index=0,
+      name='prop', full_name='vhal_proto.VehiclePropConfig.prop', index=0,
       number=1, type=5, cpp_type=1, label=2,
       has_default_value=False, default_value=0,
       message_type=None, enum_type=None, containing_type=None,
       is_extension=False, extension_scope=None,
-      options=None),
+      serialized_options=None, file=DESCRIPTOR),
     _descriptor.FieldDescriptor(
-      name='access', full_name='emulator.VehiclePropConfig.access', index=1,
+      name='access', full_name='vhal_proto.VehiclePropConfig.access', index=1,
       number=2, type=5, cpp_type=1, label=1,
       has_default_value=False, default_value=0,
       message_type=None, enum_type=None, containing_type=None,
       is_extension=False, extension_scope=None,
-      options=None),
+      serialized_options=None, file=DESCRIPTOR),
     _descriptor.FieldDescriptor(
-      name='change_mode', full_name='emulator.VehiclePropConfig.change_mode', index=2,
+      name='change_mode', full_name='vhal_proto.VehiclePropConfig.change_mode', index=2,
       number=3, type=5, cpp_type=1, label=1,
       has_default_value=False, default_value=0,
       message_type=None, enum_type=None, containing_type=None,
       is_extension=False, extension_scope=None,
-      options=None),
+      serialized_options=None, file=DESCRIPTOR),
     _descriptor.FieldDescriptor(
-      name='value_type', full_name='emulator.VehiclePropConfig.value_type', index=3,
+      name='value_type', full_name='vhal_proto.VehiclePropConfig.value_type', index=3,
       number=4, type=5, cpp_type=1, label=1,
       has_default_value=False, default_value=0,
       message_type=None, enum_type=None, containing_type=None,
       is_extension=False, extension_scope=None,
-      options=None),
+      serialized_options=None, file=DESCRIPTOR),
     _descriptor.FieldDescriptor(
-      name='supported_areas', full_name='emulator.VehiclePropConfig.supported_areas', index=4,
+      name='supported_areas', full_name='vhal_proto.VehiclePropConfig.supported_areas', index=4,
       number=5, type=5, cpp_type=1, label=1,
       has_default_value=False, default_value=0,
       message_type=None, enum_type=None, containing_type=None,
       is_extension=False, extension_scope=None,
-      options=None),
+      serialized_options=None, file=DESCRIPTOR),
     _descriptor.FieldDescriptor(
-      name='area_configs', full_name='emulator.VehiclePropConfig.area_configs', index=5,
+      name='area_configs', full_name='vhal_proto.VehiclePropConfig.area_configs', index=5,
       number=6, type=11, cpp_type=10, label=3,
       has_default_value=False, default_value=[],
       message_type=None, enum_type=None, containing_type=None,
       is_extension=False, extension_scope=None,
-      options=None),
+      serialized_options=None, file=DESCRIPTOR),
     _descriptor.FieldDescriptor(
-      name='config_flags', full_name='emulator.VehiclePropConfig.config_flags', index=6,
+      name='config_flags', full_name='vhal_proto.VehiclePropConfig.config_flags', index=6,
       number=7, type=5, cpp_type=1, label=1,
       has_default_value=False, default_value=0,
       message_type=None, enum_type=None, containing_type=None,
       is_extension=False, extension_scope=None,
-      options=None),
+      serialized_options=None, file=DESCRIPTOR),
     _descriptor.FieldDescriptor(
-      name='config_array', full_name='emulator.VehiclePropConfig.config_array', index=7,
+      name='config_array', full_name='vhal_proto.VehiclePropConfig.config_array', index=7,
       number=8, type=5, cpp_type=1, label=3,
       has_default_value=False, default_value=[],
       message_type=None, enum_type=None, containing_type=None,
       is_extension=False, extension_scope=None,
-      options=None),
+      serialized_options=None, file=DESCRIPTOR),
     _descriptor.FieldDescriptor(
-      name='config_string', full_name='emulator.VehiclePropConfig.config_string', index=8,
+      name='config_string', full_name='vhal_proto.VehiclePropConfig.config_string', index=8,
       number=9, type=9, cpp_type=9, label=1,
-      has_default_value=False, default_value=unicode("", "utf-8"),
+      has_default_value=False, default_value=_b("").decode('utf-8'),
       message_type=None, enum_type=None, containing_type=None,
       is_extension=False, extension_scope=None,
-      options=None),
+      serialized_options=None, file=DESCRIPTOR),
     _descriptor.FieldDescriptor(
-      name='min_sample_rate', full_name='emulator.VehiclePropConfig.min_sample_rate', index=9,
+      name='min_sample_rate', full_name='vhal_proto.VehiclePropConfig.min_sample_rate', index=9,
       number=10, type=2, cpp_type=6, label=1,
-      has_default_value=False, default_value=0,
+      has_default_value=False, default_value=float(0),
       message_type=None, enum_type=None, containing_type=None,
       is_extension=False, extension_scope=None,
-      options=None),
+      serialized_options=None, file=DESCRIPTOR),
     _descriptor.FieldDescriptor(
-      name='max_sample_rate', full_name='emulator.VehiclePropConfig.max_sample_rate', index=10,
+      name='max_sample_rate', full_name='vhal_proto.VehiclePropConfig.max_sample_rate', index=10,
       number=11, type=2, cpp_type=6, label=1,
-      has_default_value=False, default_value=0,
+      has_default_value=False, default_value=float(0),
       message_type=None, enum_type=None, containing_type=None,
       is_extension=False, extension_scope=None,
-      options=None),
+      serialized_options=None, file=DESCRIPTOR),
   ],
   extensions=[
   ],
   nested_types=[],
   enum_types=[
   ],
-  options=None,
+  serialized_options=None,
   is_extendable=False,
+  syntax='proto2',
   extension_ranges=[],
-  serialized_start=225,
-  serialized_end=508,
+  oneofs=[
+  ],
+  serialized_start=227,
+  serialized_end=512,
 )
 
 
 _VEHICLEPROPVALUE = _descriptor.Descriptor(
   name='VehiclePropValue',
-  full_name='emulator.VehiclePropValue',
+  full_name='vhal_proto.VehiclePropValue',
   filename=None,
   file=DESCRIPTOR,
   containing_type=None,
   fields=[
     _descriptor.FieldDescriptor(
-      name='prop', full_name='emulator.VehiclePropValue.prop', index=0,
+      name='prop', full_name='vhal_proto.VehiclePropValue.prop', index=0,
       number=1, type=5, cpp_type=1, label=2,
       has_default_value=False, default_value=0,
       message_type=None, enum_type=None, containing_type=None,
       is_extension=False, extension_scope=None,
-      options=None),
+      serialized_options=None, file=DESCRIPTOR),
     _descriptor.FieldDescriptor(
-      name='value_type', full_name='emulator.VehiclePropValue.value_type', index=1,
+      name='value_type', full_name='vhal_proto.VehiclePropValue.value_type', index=1,
       number=2, type=5, cpp_type=1, label=1,
       has_default_value=False, default_value=0,
       message_type=None, enum_type=None, containing_type=None,
       is_extension=False, extension_scope=None,
-      options=None),
+      serialized_options=None, file=DESCRIPTOR),
     _descriptor.FieldDescriptor(
-      name='timestamp', full_name='emulator.VehiclePropValue.timestamp', index=2,
+      name='timestamp', full_name='vhal_proto.VehiclePropValue.timestamp', index=2,
       number=3, type=3, cpp_type=2, label=1,
       has_default_value=False, default_value=0,
       message_type=None, enum_type=None, containing_type=None,
       is_extension=False, extension_scope=None,
-      options=None),
+      serialized_options=None, file=DESCRIPTOR),
     _descriptor.FieldDescriptor(
-      name='status', full_name='emulator.VehiclePropValue.status', index=3,
+      name='status', full_name='vhal_proto.VehiclePropValue.status', index=3,
       number=10, type=14, cpp_type=8, label=1,
       has_default_value=False, default_value=0,
       message_type=None, enum_type=None, containing_type=None,
       is_extension=False, extension_scope=None,
-      options=None),
+      serialized_options=None, file=DESCRIPTOR),
     _descriptor.FieldDescriptor(
-      name='area_id', full_name='emulator.VehiclePropValue.area_id', index=4,
+      name='area_id', full_name='vhal_proto.VehiclePropValue.area_id', index=4,
       number=4, type=5, cpp_type=1, label=1,
       has_default_value=False, default_value=0,
       message_type=None, enum_type=None, containing_type=None,
       is_extension=False, extension_scope=None,
-      options=None),
+      serialized_options=None, file=DESCRIPTOR),
     _descriptor.FieldDescriptor(
-      name='int32_values', full_name='emulator.VehiclePropValue.int32_values', index=5,
+      name='int32_values', full_name='vhal_proto.VehiclePropValue.int32_values', index=5,
       number=5, type=17, cpp_type=1, label=3,
       has_default_value=False, default_value=[],
       message_type=None, enum_type=None, containing_type=None,
       is_extension=False, extension_scope=None,
-      options=None),
+      serialized_options=None, file=DESCRIPTOR),
     _descriptor.FieldDescriptor(
-      name='int64_values', full_name='emulator.VehiclePropValue.int64_values', index=6,
+      name='int64_values', full_name='vhal_proto.VehiclePropValue.int64_values', index=6,
       number=6, type=18, cpp_type=2, label=3,
       has_default_value=False, default_value=[],
       message_type=None, enum_type=None, containing_type=None,
       is_extension=False, extension_scope=None,
-      options=None),
+      serialized_options=None, file=DESCRIPTOR),
     _descriptor.FieldDescriptor(
-      name='float_values', full_name='emulator.VehiclePropValue.float_values', index=7,
+      name='float_values', full_name='vhal_proto.VehiclePropValue.float_values', index=7,
       number=7, type=2, cpp_type=6, label=3,
       has_default_value=False, default_value=[],
       message_type=None, enum_type=None, containing_type=None,
       is_extension=False, extension_scope=None,
-      options=None),
+      serialized_options=None, file=DESCRIPTOR),
     _descriptor.FieldDescriptor(
-      name='string_value', full_name='emulator.VehiclePropValue.string_value', index=8,
+      name='string_value', full_name='vhal_proto.VehiclePropValue.string_value', index=8,
       number=8, type=9, cpp_type=9, label=1,
-      has_default_value=False, default_value=unicode("", "utf-8"),
+      has_default_value=False, default_value=_b("").decode('utf-8'),
       message_type=None, enum_type=None, containing_type=None,
       is_extension=False, extension_scope=None,
-      options=None),
+      serialized_options=None, file=DESCRIPTOR),
     _descriptor.FieldDescriptor(
-      name='bytes_value', full_name='emulator.VehiclePropValue.bytes_value', index=9,
+      name='bytes_value', full_name='vhal_proto.VehiclePropValue.bytes_value', index=9,
       number=9, type=12, cpp_type=9, label=1,
-      has_default_value=False, default_value="",
+      has_default_value=False, default_value=_b(""),
       message_type=None, enum_type=None, containing_type=None,
       is_extension=False, extension_scope=None,
-      options=None),
+      serialized_options=None, file=DESCRIPTOR),
   ],
   extensions=[
   ],
   nested_types=[],
   enum_types=[
   ],
-  options=None,
+  serialized_options=None,
   is_extendable=False,
+  syntax='proto2',
   extension_ranges=[],
-  serialized_start=511,
-  serialized_end=753,
+  oneofs=[
+  ],
+  serialized_start=515,
+  serialized_end=759,
 )
 
 
 _VEHICLEPROPGET = _descriptor.Descriptor(
   name='VehiclePropGet',
-  full_name='emulator.VehiclePropGet',
+  full_name='vhal_proto.VehiclePropGet',
   filename=None,
   file=DESCRIPTOR,
   containing_type=None,
   fields=[
     _descriptor.FieldDescriptor(
-      name='prop', full_name='emulator.VehiclePropGet.prop', index=0,
+      name='prop', full_name='vhal_proto.VehiclePropGet.prop', index=0,
       number=1, type=5, cpp_type=1, label=2,
       has_default_value=False, default_value=0,
       message_type=None, enum_type=None, containing_type=None,
       is_extension=False, extension_scope=None,
-      options=None),
+      serialized_options=None, file=DESCRIPTOR),
     _descriptor.FieldDescriptor(
-      name='area_id', full_name='emulator.VehiclePropGet.area_id', index=1,
+      name='area_id', full_name='vhal_proto.VehiclePropGet.area_id', index=1,
       number=2, type=5, cpp_type=1, label=1,
       has_default_value=False, default_value=0,
       message_type=None, enum_type=None, containing_type=None,
       is_extension=False, extension_scope=None,
-      options=None),
+      serialized_options=None, file=DESCRIPTOR),
   ],
   extensions=[
   ],
   nested_types=[],
   enum_types=[
   ],
-  options=None,
+  serialized_options=None,
   is_extendable=False,
+  syntax='proto2',
   extension_ranges=[],
-  serialized_start=755,
-  serialized_end=802,
+  oneofs=[
+  ],
+  serialized_start=761,
+  serialized_end=808,
 )
 
 
 _EMULATORMESSAGE = _descriptor.Descriptor(
   name='EmulatorMessage',
-  full_name='emulator.EmulatorMessage',
+  full_name='vhal_proto.EmulatorMessage',
   filename=None,
   file=DESCRIPTOR,
   containing_type=None,
   fields=[
     _descriptor.FieldDescriptor(
-      name='msg_type', full_name='emulator.EmulatorMessage.msg_type', index=0,
+      name='msg_type', full_name='vhal_proto.EmulatorMessage.msg_type', index=0,
       number=1, type=14, cpp_type=8, label=2,
       has_default_value=False, default_value=0,
       message_type=None, enum_type=None, containing_type=None,
       is_extension=False, extension_scope=None,
-      options=None),
+      serialized_options=None, file=DESCRIPTOR),
     _descriptor.FieldDescriptor(
-      name='status', full_name='emulator.EmulatorMessage.status', index=1,
+      name='status', full_name='vhal_proto.EmulatorMessage.status', index=1,
       number=2, type=14, cpp_type=8, label=1,
       has_default_value=False, default_value=0,
       message_type=None, enum_type=None, containing_type=None,
       is_extension=False, extension_scope=None,
-      options=None),
+      serialized_options=None, file=DESCRIPTOR),
     _descriptor.FieldDescriptor(
-      name='prop', full_name='emulator.EmulatorMessage.prop', index=2,
+      name='prop', full_name='vhal_proto.EmulatorMessage.prop', index=2,
       number=3, type=11, cpp_type=10, label=3,
       has_default_value=False, default_value=[],
       message_type=None, enum_type=None, containing_type=None,
       is_extension=False, extension_scope=None,
-      options=None),
+      serialized_options=None, file=DESCRIPTOR),
     _descriptor.FieldDescriptor(
-      name='config', full_name='emulator.EmulatorMessage.config', index=3,
+      name='config', full_name='vhal_proto.EmulatorMessage.config', index=3,
       number=4, type=11, cpp_type=10, label=3,
       has_default_value=False, default_value=[],
       message_type=None, enum_type=None, containing_type=None,
       is_extension=False, extension_scope=None,
-      options=None),
+      serialized_options=None, file=DESCRIPTOR),
     _descriptor.FieldDescriptor(
-      name='value', full_name='emulator.EmulatorMessage.value', index=4,
+      name='value', full_name='vhal_proto.EmulatorMessage.value', index=4,
       number=5, type=11, cpp_type=10, label=3,
       has_default_value=False, default_value=[],
       message_type=None, enum_type=None, containing_type=None,
       is_extension=False, extension_scope=None,
-      options=None),
+      serialized_options=None, file=DESCRIPTOR),
   ],
   extensions=[
   ],
   nested_types=[],
   enum_types=[
   ],
-  options=None,
+  serialized_options=None,
   is_extendable=False,
+  syntax='proto2',
   extension_ranges=[],
-  serialized_start=805,
-  serialized_end=1021,
+  oneofs=[
+  ],
+  serialized_start=811,
+  serialized_end=1037,
 )
 
 _VEHICLEPROPCONFIG.fields_by_name['area_configs'].message_type = _VEHICLEAREACONFIG
@@ -537,38 +563,45 @@
 DESCRIPTOR.message_types_by_name['VehiclePropValue'] = _VEHICLEPROPVALUE
 DESCRIPTOR.message_types_by_name['VehiclePropGet'] = _VEHICLEPROPGET
 DESCRIPTOR.message_types_by_name['EmulatorMessage'] = _EMULATORMESSAGE
+DESCRIPTOR.enum_types_by_name['MsgType'] = _MSGTYPE
+DESCRIPTOR.enum_types_by_name['Status'] = _STATUS
+DESCRIPTOR.enum_types_by_name['VehiclePropStatus'] = _VEHICLEPROPSTATUS
+_sym_db.RegisterFileDescriptor(DESCRIPTOR)
 
-class VehicleAreaConfig(_message.Message):
-  __metaclass__ = _reflection.GeneratedProtocolMessageType
-  DESCRIPTOR = _VEHICLEAREACONFIG
+VehicleAreaConfig = _reflection.GeneratedProtocolMessageType('VehicleAreaConfig', (_message.Message,), {
+  'DESCRIPTOR' : _VEHICLEAREACONFIG,
+  '__module__' : 'VehicleHalProto_pb2'
+  # @@protoc_insertion_point(class_scope:vhal_proto.VehicleAreaConfig)
+  })
+_sym_db.RegisterMessage(VehicleAreaConfig)
 
-  # @@protoc_insertion_point(class_scope:emulator.VehicleAreaConfig)
+VehiclePropConfig = _reflection.GeneratedProtocolMessageType('VehiclePropConfig', (_message.Message,), {
+  'DESCRIPTOR' : _VEHICLEPROPCONFIG,
+  '__module__' : 'VehicleHalProto_pb2'
+  # @@protoc_insertion_point(class_scope:vhal_proto.VehiclePropConfig)
+  })
+_sym_db.RegisterMessage(VehiclePropConfig)
 
-class VehiclePropConfig(_message.Message):
-  __metaclass__ = _reflection.GeneratedProtocolMessageType
-  DESCRIPTOR = _VEHICLEPROPCONFIG
+VehiclePropValue = _reflection.GeneratedProtocolMessageType('VehiclePropValue', (_message.Message,), {
+  'DESCRIPTOR' : _VEHICLEPROPVALUE,
+  '__module__' : 'VehicleHalProto_pb2'
+  # @@protoc_insertion_point(class_scope:vhal_proto.VehiclePropValue)
+  })
+_sym_db.RegisterMessage(VehiclePropValue)
 
-  # @@protoc_insertion_point(class_scope:emulator.VehiclePropConfig)
+VehiclePropGet = _reflection.GeneratedProtocolMessageType('VehiclePropGet', (_message.Message,), {
+  'DESCRIPTOR' : _VEHICLEPROPGET,
+  '__module__' : 'VehicleHalProto_pb2'
+  # @@protoc_insertion_point(class_scope:vhal_proto.VehiclePropGet)
+  })
+_sym_db.RegisterMessage(VehiclePropGet)
 
-class VehiclePropValue(_message.Message):
-  __metaclass__ = _reflection.GeneratedProtocolMessageType
-  DESCRIPTOR = _VEHICLEPROPVALUE
-
-  # @@protoc_insertion_point(class_scope:emulator.VehiclePropValue)
-
-class VehiclePropGet(_message.Message):
-  __metaclass__ = _reflection.GeneratedProtocolMessageType
-  DESCRIPTOR = _VEHICLEPROPGET
-
-  # @@protoc_insertion_point(class_scope:emulator.VehiclePropGet)
-
-class EmulatorMessage(_message.Message):
-  __metaclass__ = _reflection.GeneratedProtocolMessageType
-  DESCRIPTOR = _EMULATORMESSAGE
-
-  # @@protoc_insertion_point(class_scope:emulator.EmulatorMessage)
+EmulatorMessage = _reflection.GeneratedProtocolMessageType('EmulatorMessage', (_message.Message,), {
+  'DESCRIPTOR' : _EMULATORMESSAGE,
+  '__module__' : 'VehicleHalProto_pb2'
+  # @@protoc_insertion_point(class_scope:vhal_proto.EmulatorMessage)
+  })
+_sym_db.RegisterMessage(EmulatorMessage)
 
 
-DESCRIPTOR.has_options = True
-DESCRIPTOR._options = _descriptor._ParseOptions(descriptor_pb2.FileOptions(), 'H\003')
 # @@protoc_insertion_point(module_scope)
diff --git a/tools/emulator/vhal_const_generate.py b/tools/emulator/vhal_const_generate.py
index 77bb74b..cc9a118 100755
--- a/tools/emulator/vhal_const_generate.py
+++ b/tools/emulator/vhal_const_generate.py
@@ -120,6 +120,7 @@
     print("    TYPE_INT64   = [VEHICLEPROPERTYTYPE_INT64]", file=vhal_20_file)
     print("    TYPE_FLOAT   = [VEHICLEPROPERTYTYPE_FLOAT]", file=vhal_20_file)
     print("    TYPE_INT32S  = [VEHICLEPROPERTYTYPE_INT32_VEC]", file=vhal_20_file)
+    print("    TYPE_INT64S  = [VEHICLEPROPERTYTYPE_INT64_VEC]", file=vhal_20_file)
     print("    TYPE_FLOATS  = [VEHICLEPROPERTYTYPE_FLOAT_VEC]", file=vhal_20_file)
     print("    TYPE_MIXED   = [VEHICLEPROPERTYTYPE_MIXED]", file=vhal_20_file)
 
diff --git a/tools/emulator/vhal_consts_2_0.py b/tools/emulator/vhal_consts_2_0.py
index 648de6d..d4e3b61 100644
--- a/tools/emulator/vhal_consts_2_0.py
+++ b/tools/emulator/vhal_consts_2_0.py
@@ -15,6 +15,283 @@
 # DO NOT EDIT MANUALLY
 # This file was autogenerated by vhal_const_generate.py
 
+# VehicleUnit
+VEHICLEUNIT_SHOULD_NOT_USE = 0x0
+VEHICLEUNIT_METER_PER_SEC = 0x1
+VEHICLEUNIT_RPM = 0x2
+VEHICLEUNIT_HERTZ = 0x3
+VEHICLEUNIT_PERCENTILE = 0x10
+VEHICLEUNIT_MILLIMETER = 0x20
+VEHICLEUNIT_METER = 0x21
+VEHICLEUNIT_KILOMETER = 0x23
+VEHICLEUNIT_MILE = 0x24
+VEHICLEUNIT_CELSIUS = 0x30
+VEHICLEUNIT_FAHRENHEIT = 0x31
+VEHICLEUNIT_KELVIN = 0x32
+VEHICLEUNIT_MILLILITER = 0x40
+VEHICLEUNIT_LITER = 0x41
+VEHICLEUNIT_GALLON = 0x42
+VEHICLEUNIT_US_GALLON = 0x42
+VEHICLEUNIT_IMPERIAL_GALLON = 0x43
+VEHICLEUNIT_NANO_SECS = 0x50
+VEHICLEUNIT_SECS = 0x53
+VEHICLEUNIT_YEAR = 0x59
+VEHICLEUNIT_WATT_HOUR = 0x60
+VEHICLEUNIT_MILLIAMPERE = 0x61
+VEHICLEUNIT_MILLIVOLT = 0x62
+VEHICLEUNIT_MILLIWATTS = 0x63
+VEHICLEUNIT_AMPERE_HOURS = 0x64
+VEHICLEUNIT_KILOWATT_HOUR = 0x65
+VEHICLEUNIT_KILOPASCAL = 0x70
+VEHICLEUNIT_PSI = 0x71
+VEHICLEUNIT_BAR = 0x72
+VEHICLEUNIT_DEGREES = 0x80
+VEHICLEUNIT_MILES_PER_HOUR = 0x90
+VEHICLEUNIT_KILOMETERS_PER_HOUR = 0x91
+
+# VehicleLightSwitch
+VEHICLELIGHTSWITCH_OFF = 0x0
+VEHICLELIGHTSWITCH_ON = 0x1
+VEHICLELIGHTSWITCH_DAYTIME_RUNNING = 0x2
+VEHICLELIGHTSWITCH_AUTOMATIC = 0x100
+
+# VmsMessageWithLayerIntegerValuesIndex
+VMSMESSAGEWITHLAYERINTEGERVALUESINDEX_LAYER_TYPE = 0x1
+VMSMESSAGEWITHLAYERINTEGERVALUESINDEX_LAYER_SUBTYPE = 0x2
+VMSMESSAGEWITHLAYERINTEGERVALUESINDEX_LAYER_VERSION = 0x3
+
+# VehiclePropertyGroup
+VEHICLEPROPERTYGROUP_SYSTEM = 0x10000000
+VEHICLEPROPERTYGROUP_VENDOR = 0x20000000
+VEHICLEPROPERTYGROUP_MASK = 0xf0000000
+
+# VehicleApPowerStateShutdownParam
+VEHICLEAPPOWERSTATESHUTDOWNPARAM_SHUTDOWN_IMMEDIATELY = 0x1
+VEHICLEAPPOWERSTATESHUTDOWNPARAM_CAN_SLEEP = 0x2
+VEHICLEAPPOWERSTATESHUTDOWNPARAM_SHUTDOWN_ONLY = 0x3
+
+# Obd2CommonIgnitionMonitors
+OBD2COMMONIGNITIONMONITORS_COMPONENTS_AVAILABLE = 0x1
+OBD2COMMONIGNITIONMONITORS_COMPONENTS_INCOMPLETE = 0x2
+OBD2COMMONIGNITIONMONITORS_FUEL_SYSTEM_AVAILABLE = 0x4
+OBD2COMMONIGNITIONMONITORS_FUEL_SYSTEM_INCOMPLETE = 0x8
+OBD2COMMONIGNITIONMONITORS_MISFIRE_AVAILABLE = 0x10
+OBD2COMMONIGNITIONMONITORS_MISFIRE_INCOMPLETE = 0x20
+
+# PortLocationType
+PORTLOCATIONTYPE_UNKNOWN = 0x0
+PORTLOCATIONTYPE_FRONT_LEFT = 0x1
+PORTLOCATIONTYPE_FRONT_RIGHT = 0x2
+PORTLOCATIONTYPE_REAR_RIGHT = 0x3
+PORTLOCATIONTYPE_REAR_LEFT = 0x4
+PORTLOCATIONTYPE_FRONT = 0x5
+PORTLOCATIONTYPE_REAR = 0x6
+
+# Obd2SparkIgnitionMonitors
+OBD2SPARKIGNITIONMONITORS_EGR_AVAILABLE = 0x40
+OBD2SPARKIGNITIONMONITORS_EGR_INCOMPLETE = 0x80
+OBD2SPARKIGNITIONMONITORS_OXYGEN_SENSOR_HEATER_AVAILABLE = 0x100
+OBD2SPARKIGNITIONMONITORS_OXYGEN_SENSOR_HEATER_INCOMPLETE = 0x200
+OBD2SPARKIGNITIONMONITORS_OXYGEN_SENSOR_AVAILABLE = 0x400
+OBD2SPARKIGNITIONMONITORS_OXYGEN_SENSOR_INCOMPLETE = 0x800
+OBD2SPARKIGNITIONMONITORS_AC_REFRIGERANT_AVAILABLE = 0x1000
+OBD2SPARKIGNITIONMONITORS_AC_REFRIGERANT_INCOMPLETE = 0x2000
+OBD2SPARKIGNITIONMONITORS_SECONDARY_AIR_SYSTEM_AVAILABLE = 0x4000
+OBD2SPARKIGNITIONMONITORS_SECONDARY_AIR_SYSTEM_INCOMPLETE = 0x8000
+OBD2SPARKIGNITIONMONITORS_EVAPORATIVE_SYSTEM_AVAILABLE = 0x10000
+OBD2SPARKIGNITIONMONITORS_EVAPORATIVE_SYSTEM_INCOMPLETE = 0x20000
+OBD2SPARKIGNITIONMONITORS_HEATED_CATALYST_AVAILABLE = 0x40000
+OBD2SPARKIGNITIONMONITORS_HEATED_CATALYST_INCOMPLETE = 0x80000
+OBD2SPARKIGNITIONMONITORS_CATALYST_AVAILABLE = 0x100000
+OBD2SPARKIGNITIONMONITORS_CATALYST_INCOMPLETE = 0x200000
+
+# VmsSubscriptionsStateIntegerValuesIndex
+VMSSUBSCRIPTIONSSTATEINTEGERVALUESINDEX_SEQUENCE_NUMBER = 0x1
+VMSSUBSCRIPTIONSSTATEINTEGERVALUESINDEX_NUMBER_OF_LAYERS = 0x2
+VMSSUBSCRIPTIONSSTATEINTEGERVALUESINDEX_NUMBER_OF_ASSOCIATED_LAYERS = 0x3
+VMSSUBSCRIPTIONSSTATEINTEGERVALUESINDEX_SUBSCRIPTIONS_START = 0x4
+
+# VehicleApPowerStateReq
+VEHICLEAPPOWERSTATEREQ_ON = 0x0
+VEHICLEAPPOWERSTATEREQ_SHUTDOWN_PREPARE = 0x1
+VEHICLEAPPOWERSTATEREQ_CANCEL_SHUTDOWN = 0x2
+VEHICLEAPPOWERSTATEREQ_FINISHED = 0x3
+
+# VehicleApPowerStateReqIndex
+VEHICLEAPPOWERSTATEREQINDEX_STATE = 0x0
+VEHICLEAPPOWERSTATEREQINDEX_ADDITIONAL = 0x1
+
+# Obd2IgnitionMonitorKind
+OBD2IGNITIONMONITORKIND_SPARK = 0x0
+OBD2IGNITIONMONITORKIND_COMPRESSION = 0x1
+
+# VehiclePropertyAccess
+VEHICLEPROPERTYACCESS_NONE = 0x0
+VEHICLEPROPERTYACCESS_READ = 0x1
+VEHICLEPROPERTYACCESS_WRITE = 0x2
+VEHICLEPROPERTYACCESS_READ_WRITE = 0x3
+
+# VehicleOilLevel
+VEHICLEOILLEVEL_CRITICALLY_LOW = 0x0
+VEHICLEOILLEVEL_LOW = 0x1
+VEHICLEOILLEVEL_NORMAL = 0x2
+VEHICLEOILLEVEL_HIGH = 0x3
+VEHICLEOILLEVEL_ERROR = 0x4
+
+# VmsBaseMessageIntegerValuesIndex
+VMSBASEMESSAGEINTEGERVALUESINDEX_MESSAGE_TYPE = 0x0
+
+# StatusCode
+STATUSCODE_OK = 0x0
+STATUSCODE_TRY_AGAIN = 0x1
+STATUSCODE_INVALID_ARG = 0x2
+STATUSCODE_NOT_AVAILABLE = 0x3
+STATUSCODE_ACCESS_DENIED = 0x4
+STATUSCODE_INTERNAL_ERROR = 0x5
+
+# VehicleLightState
+VEHICLELIGHTSTATE_OFF = 0x0
+VEHICLELIGHTSTATE_ON = 0x1
+VEHICLELIGHTSTATE_DAYTIME_RUNNING = 0x2
+
+# VmsStartSessionMessageIntegerValuesIndex
+VMSSTARTSESSIONMESSAGEINTEGERVALUESINDEX_SERVICE_ID = 0x1
+VMSSTARTSESSIONMESSAGEINTEGERVALUESINDEX_CLIENT_ID = 0x2
+
+# VmsPublisherInformationIntegerValuesIndex
+VMSPUBLISHERINFORMATIONINTEGERVALUESINDEX_PUBLISHER_ID = 0x1
+
+# VehiclePropertyChangeMode
+VEHICLEPROPERTYCHANGEMODE_STATIC = 0x0
+VEHICLEPROPERTYCHANGEMODE_ON_CHANGE = 0x1
+VEHICLEPROPERTYCHANGEMODE_CONTINUOUS = 0x2
+
+# VmsMessageType
+VMSMESSAGETYPE_SUBSCRIBE = 0x1
+VMSMESSAGETYPE_SUBSCRIBE_TO_PUBLISHER = 0x2
+VMSMESSAGETYPE_UNSUBSCRIBE = 0x3
+VMSMESSAGETYPE_UNSUBSCRIBE_TO_PUBLISHER = 0x4
+VMSMESSAGETYPE_OFFERING = 0x5
+VMSMESSAGETYPE_AVAILABILITY_REQUEST = 0x6
+VMSMESSAGETYPE_SUBSCRIPTIONS_REQUEST = 0x7
+VMSMESSAGETYPE_AVAILABILITY_RESPONSE = 0x8
+VMSMESSAGETYPE_AVAILABILITY_CHANGE = 0x9
+VMSMESSAGETYPE_SUBSCRIPTIONS_RESPONSE = 0xa
+VMSMESSAGETYPE_SUBSCRIPTIONS_CHANGE = 0xb
+VMSMESSAGETYPE_DATA = 0xc
+VMSMESSAGETYPE_PUBLISHER_ID_REQUEST = 0xd
+VMSMESSAGETYPE_PUBLISHER_ID_RESPONSE = 0xe
+VMSMESSAGETYPE_PUBLISHER_INFORMATION_REQUEST = 0xf
+VMSMESSAGETYPE_PUBLISHER_INFORMATION_RESPONSE = 0x10
+VMSMESSAGETYPE_START_SESSION = 0x11
+VMSMESSAGETYPE_LAST_VMS_MESSAGE_TYPE = 0x11
+
+# DiagnosticIntegerSensorIndex
+DIAGNOSTICINTEGERSENSORINDEX_FUEL_SYSTEM_STATUS = 0x0
+DIAGNOSTICINTEGERSENSORINDEX_MALFUNCTION_INDICATOR_LIGHT_ON = 0x1
+DIAGNOSTICINTEGERSENSORINDEX_IGNITION_MONITORS_SUPPORTED = 0x2
+DIAGNOSTICINTEGERSENSORINDEX_IGNITION_SPECIFIC_MONITORS = 0x3
+DIAGNOSTICINTEGERSENSORINDEX_INTAKE_AIR_TEMPERATURE = 0x4
+DIAGNOSTICINTEGERSENSORINDEX_COMMANDED_SECONDARY_AIR_STATUS = 0x5
+DIAGNOSTICINTEGERSENSORINDEX_NUM_OXYGEN_SENSORS_PRESENT = 0x6
+DIAGNOSTICINTEGERSENSORINDEX_RUNTIME_SINCE_ENGINE_START = 0x7
+DIAGNOSTICINTEGERSENSORINDEX_DISTANCE_TRAVELED_WITH_MALFUNCTION_INDICATOR_LIGHT_ON = 0x8
+DIAGNOSTICINTEGERSENSORINDEX_WARMUPS_SINCE_CODES_CLEARED = 0x9
+DIAGNOSTICINTEGERSENSORINDEX_DISTANCE_TRAVELED_SINCE_CODES_CLEARED = 0xa
+DIAGNOSTICINTEGERSENSORINDEX_ABSOLUTE_BAROMETRIC_PRESSURE = 0xb
+DIAGNOSTICINTEGERSENSORINDEX_CONTROL_MODULE_VOLTAGE = 0xc
+DIAGNOSTICINTEGERSENSORINDEX_AMBIENT_AIR_TEMPERATURE = 0xd
+DIAGNOSTICINTEGERSENSORINDEX_TIME_WITH_MALFUNCTION_LIGHT_ON = 0xe
+DIAGNOSTICINTEGERSENSORINDEX_TIME_SINCE_TROUBLE_CODES_CLEARED = 0xf
+DIAGNOSTICINTEGERSENSORINDEX_MAX_FUEL_AIR_EQUIVALENCE_RATIO = 0x10
+DIAGNOSTICINTEGERSENSORINDEX_MAX_OXYGEN_SENSOR_VOLTAGE = 0x11
+DIAGNOSTICINTEGERSENSORINDEX_MAX_OXYGEN_SENSOR_CURRENT = 0x12
+DIAGNOSTICINTEGERSENSORINDEX_MAX_INTAKE_MANIFOLD_ABSOLUTE_PRESSURE = 0x13
+DIAGNOSTICINTEGERSENSORINDEX_MAX_AIR_FLOW_RATE_FROM_MASS_AIR_FLOW_SENSOR = 0x14
+DIAGNOSTICINTEGERSENSORINDEX_FUEL_TYPE = 0x15
+DIAGNOSTICINTEGERSENSORINDEX_FUEL_RAIL_ABSOLUTE_PRESSURE = 0x16
+DIAGNOSTICINTEGERSENSORINDEX_ENGINE_OIL_TEMPERATURE = 0x17
+DIAGNOSTICINTEGERSENSORINDEX_DRIVER_DEMAND_PERCENT_TORQUE = 0x18
+DIAGNOSTICINTEGERSENSORINDEX_ENGINE_ACTUAL_PERCENT_TORQUE = 0x19
+DIAGNOSTICINTEGERSENSORINDEX_ENGINE_REFERENCE_PERCENT_TORQUE = 0x1a
+DIAGNOSTICINTEGERSENSORINDEX_ENGINE_PERCENT_TORQUE_DATA_IDLE = 0x1b
+DIAGNOSTICINTEGERSENSORINDEX_ENGINE_PERCENT_TORQUE_DATA_POINT1 = 0x1c
+DIAGNOSTICINTEGERSENSORINDEX_ENGINE_PERCENT_TORQUE_DATA_POINT2 = 0x1d
+DIAGNOSTICINTEGERSENSORINDEX_ENGINE_PERCENT_TORQUE_DATA_POINT3 = 0x1e
+DIAGNOSTICINTEGERSENSORINDEX_ENGINE_PERCENT_TORQUE_DATA_POINT4 = 0x1f
+DIAGNOSTICINTEGERSENSORINDEX_LAST_SYSTEM_INDEX = 0x1f
+
+# VmsMessageWithLayerAndPublisherIdIntegerValuesIndex
+VMSMESSAGEWITHLAYERANDPUBLISHERIDINTEGERVALUESINDEX_PUBLISHER_ID = 0x4
+
+# VmsOfferingMessageIntegerValuesIndex
+VMSOFFERINGMESSAGEINTEGERVALUESINDEX_PUBLISHER_ID = 0x1
+VMSOFFERINGMESSAGEINTEGERVALUESINDEX_NUMBER_OF_OFFERS = 0x2
+VMSOFFERINGMESSAGEINTEGERVALUESINDEX_OFFERING_START = 0x3
+
+# VehicleApPowerStateConfigFlag
+VEHICLEAPPOWERSTATECONFIGFLAG_ENABLE_DEEP_SLEEP_FLAG = 0x1
+VEHICLEAPPOWERSTATECONFIGFLAG_CONFIG_SUPPORT_TIMER_POWER_ON_FLAG = 0x2
+
+# FuelType
+FUELTYPE_FUEL_TYPE_UNKNOWN = 0x0
+FUELTYPE_FUEL_TYPE_UNLEADED = 0x1
+FUELTYPE_FUEL_TYPE_LEADED = 0x2
+FUELTYPE_FUEL_TYPE_DIESEL_1 = 0x3
+FUELTYPE_FUEL_TYPE_DIESEL_2 = 0x4
+FUELTYPE_FUEL_TYPE_BIODIESEL = 0x5
+FUELTYPE_FUEL_TYPE_E85 = 0x6
+FUELTYPE_FUEL_TYPE_LPG = 0x7
+FUELTYPE_FUEL_TYPE_CNG = 0x8
+FUELTYPE_FUEL_TYPE_LNG = 0x9
+FUELTYPE_FUEL_TYPE_ELECTRIC = 0xa
+FUELTYPE_FUEL_TYPE_HYDROGEN = 0xb
+FUELTYPE_FUEL_TYPE_OTHER = 0xc
+
+# VehicleSeatOccupancyState
+VEHICLESEATOCCUPANCYSTATE_UNKNOWN = 0x0
+VEHICLESEATOCCUPANCYSTATE_VACANT = 0x1
+VEHICLESEATOCCUPANCYSTATE_OCCUPIED = 0x2
+
+# VehicleIgnitionState
+VEHICLEIGNITIONSTATE_UNDEFINED = 0x0
+VEHICLEIGNITIONSTATE_LOCK = 0x1
+VEHICLEIGNITIONSTATE_OFF = 0x2
+VEHICLEIGNITIONSTATE_ACC = 0x3
+VEHICLEIGNITIONSTATE_ON = 0x4
+VEHICLEIGNITIONSTATE_START = 0x5
+
+# VehicleAreaSeat
+VEHICLEAREASEAT_ROW_1_LEFT = 0x1
+VEHICLEAREASEAT_ROW_1_CENTER = 0x2
+VEHICLEAREASEAT_ROW_1_RIGHT = 0x4
+VEHICLEAREASEAT_ROW_2_LEFT = 0x10
+VEHICLEAREASEAT_ROW_2_CENTER = 0x20
+VEHICLEAREASEAT_ROW_2_RIGHT = 0x40
+VEHICLEAREASEAT_ROW_3_LEFT = 0x100
+VEHICLEAREASEAT_ROW_3_CENTER = 0x200
+VEHICLEAREASEAT_ROW_3_RIGHT = 0x400
+
+# VehicleTurnSignal
+VEHICLETURNSIGNAL_NONE = 0x0
+VEHICLETURNSIGNAL_RIGHT = 0x1
+VEHICLETURNSIGNAL_LEFT = 0x2
+
+# EvConnectorType
+EVCONNECTORTYPE_UNKNOWN = 0x0
+EVCONNECTORTYPE_IEC_TYPE_1_AC = 0x1
+EVCONNECTORTYPE_IEC_TYPE_2_AC = 0x2
+EVCONNECTORTYPE_IEC_TYPE_3_AC = 0x3
+EVCONNECTORTYPE_IEC_TYPE_4_DC = 0x4
+EVCONNECTORTYPE_IEC_TYPE_1_CCS_DC = 0x5
+EVCONNECTORTYPE_IEC_TYPE_2_CCS_DC = 0x6
+EVCONNECTORTYPE_TESLA_ROADSTER = 0x7
+EVCONNECTORTYPE_TESLA_HPWC = 0x8
+EVCONNECTORTYPE_TESLA_SUPERCHARGER = 0x9
+EVCONNECTORTYPE_GBT_AC = 0xa
+EVCONNECTORTYPE_GBT_DC = 0xb
+EVCONNECTORTYPE_OTHER = 0x65
+
 # VehiclePropertyType
 VEHICLEPROPERTYTYPE_STRING = 0x100000
 VEHICLEPROPERTYTYPE_BOOLEAN = 0x200000
@@ -28,19 +305,102 @@
 VEHICLEPROPERTYTYPE_MIXED = 0xe00000
 VEHICLEPROPERTYTYPE_MASK = 0xff0000
 
-# VehicleArea
-VEHICLEAREA_GLOBAL = 0x1000000
-VEHICLEAREA_WINDOW = 0x3000000
-VEHICLEAREA_MIRROR = 0x4000000
-VEHICLEAREA_SEAT = 0x5000000
-VEHICLEAREA_DOOR = 0x6000000
-VEHICLEAREA_WHEEL = 0x7000000
-VEHICLEAREA_MASK = 0xf000000
+# VehicleAreaMirror
+VEHICLEAREAMIRROR_DRIVER_LEFT = 0x1
+VEHICLEAREAMIRROR_DRIVER_RIGHT = 0x2
+VEHICLEAREAMIRROR_DRIVER_CENTER = 0x4
 
-# VehiclePropertyGroup
-VEHICLEPROPERTYGROUP_SYSTEM = 0x10000000
-VEHICLEPROPERTYGROUP_VENDOR = 0x20000000
-VEHICLEPROPERTYGROUP_MASK = 0xf0000000
+# Obd2FuelSystemStatus
+OBD2FUELSYSTEMSTATUS_OPEN_INSUFFICIENT_ENGINE_TEMPERATURE = 0x1
+OBD2FUELSYSTEMSTATUS_CLOSED_LOOP = 0x2
+OBD2FUELSYSTEMSTATUS_OPEN_ENGINE_LOAD_OR_DECELERATION = 0x4
+OBD2FUELSYSTEMSTATUS_OPEN_SYSTEM_FAILURE = 0x8
+OBD2FUELSYSTEMSTATUS_CLOSED_LOOP_BUT_FEEDBACK_FAULT = 0x10
+
+# Obd2SecondaryAirStatus
+OBD2SECONDARYAIRSTATUS_UPSTREAM = 0x1
+OBD2SECONDARYAIRSTATUS_DOWNSTREAM_OF_CATALYCIC_CONVERTER = 0x2
+OBD2SECONDARYAIRSTATUS_FROM_OUTSIDE_OR_OFF = 0x4
+OBD2SECONDARYAIRSTATUS_PUMP_ON_FOR_DIAGNOSTICS = 0x8
+
+# VehicleAreaWheel
+VEHICLEAREAWHEEL_UNKNOWN = 0x0
+VEHICLEAREAWHEEL_LEFT_FRONT = 0x1
+VEHICLEAREAWHEEL_RIGHT_FRONT = 0x2
+VEHICLEAREAWHEEL_LEFT_REAR = 0x4
+VEHICLEAREAWHEEL_RIGHT_REAR = 0x8
+
+# VehicleGear
+VEHICLEGEAR_GEAR_NEUTRAL = 0x1
+VEHICLEGEAR_GEAR_REVERSE = 0x2
+VEHICLEGEAR_GEAR_PARK = 0x4
+VEHICLEGEAR_GEAR_DRIVE = 0x8
+VEHICLEGEAR_GEAR_1 = 0x10
+VEHICLEGEAR_GEAR_2 = 0x20
+VEHICLEGEAR_GEAR_3 = 0x40
+VEHICLEGEAR_GEAR_4 = 0x80
+VEHICLEGEAR_GEAR_5 = 0x100
+VEHICLEGEAR_GEAR_6 = 0x200
+VEHICLEGEAR_GEAR_7 = 0x400
+VEHICLEGEAR_GEAR_8 = 0x800
+VEHICLEGEAR_GEAR_9 = 0x1000
+
+# VmsAvailabilityStateIntegerValuesIndex
+VMSAVAILABILITYSTATEINTEGERVALUESINDEX_SEQUENCE_NUMBER = 0x1
+VMSAVAILABILITYSTATEINTEGERVALUESINDEX_NUMBER_OF_ASSOCIATED_LAYERS = 0x2
+VMSAVAILABILITYSTATEINTEGERVALUESINDEX_LAYERS_START = 0x3
+
+# VehicleHwKeyInputAction
+VEHICLEHWKEYINPUTACTION_ACTION_DOWN = 0x0
+VEHICLEHWKEYINPUTACTION_ACTION_UP = 0x1
+
+# VehicleApPowerStateReport
+VEHICLEAPPOWERSTATEREPORT_WAIT_FOR_VHAL = 0x1
+VEHICLEAPPOWERSTATEREPORT_DEEP_SLEEP_ENTRY = 0x2
+VEHICLEAPPOWERSTATEREPORT_DEEP_SLEEP_EXIT = 0x3
+VEHICLEAPPOWERSTATEREPORT_SHUTDOWN_POSTPONE = 0x4
+VEHICLEAPPOWERSTATEREPORT_SHUTDOWN_START = 0x5
+VEHICLEAPPOWERSTATEREPORT_ON = 0x6
+VEHICLEAPPOWERSTATEREPORT_SHUTDOWN_PREPARE = 0x7
+VEHICLEAPPOWERSTATEREPORT_SHUTDOWN_CANCELLED = 0x8
+
+# VehicleDisplay
+VEHICLEDISPLAY_MAIN = 0x0
+VEHICLEDISPLAY_INSTRUMENT_CLUSTER = 0x1
+
+# Obd2CompressionIgnitionMonitors
+OBD2COMPRESSIONIGNITIONMONITORS_EGR_OR_VVT_AVAILABLE = 0x40
+OBD2COMPRESSIONIGNITIONMONITORS_EGR_OR_VVT_INCOMPLETE = 0x80
+OBD2COMPRESSIONIGNITIONMONITORS_PM_FILTER_AVAILABLE = 0x100
+OBD2COMPRESSIONIGNITIONMONITORS_PM_FILTER_INCOMPLETE = 0x200
+OBD2COMPRESSIONIGNITIONMONITORS_EXHAUST_GAS_SENSOR_AVAILABLE = 0x400
+OBD2COMPRESSIONIGNITIONMONITORS_EXHAUST_GAS_SENSOR_INCOMPLETE = 0x800
+OBD2COMPRESSIONIGNITIONMONITORS_BOOST_PRESSURE_AVAILABLE = 0x1000
+OBD2COMPRESSIONIGNITIONMONITORS_BOOST_PRESSURE_INCOMPLETE = 0x2000
+OBD2COMPRESSIONIGNITIONMONITORS_NOx_SCR_AVAILABLE = 0x4000
+OBD2COMPRESSIONIGNITIONMONITORS_NOx_SCR_INCOMPLETE = 0x8000
+OBD2COMPRESSIONIGNITIONMONITORS_NMHC_CATALYST_AVAILABLE = 0x10000
+OBD2COMPRESSIONIGNITIONMONITORS_NMHC_CATALYST_INCOMPLETE = 0x20000
+
+# VehicleHvacFanDirection
+VEHICLEHVACFANDIRECTION_FACE = 0x1
+VEHICLEHVACFANDIRECTION_FLOOR = 0x2
+VEHICLEHVACFANDIRECTION_DEFROST = 0x4
+
+# VehicleAreaDoor
+VEHICLEAREADOOR_ROW_1_LEFT = 0x1
+VEHICLEAREADOOR_ROW_1_RIGHT = 0x4
+VEHICLEAREADOOR_ROW_2_LEFT = 0x10
+VEHICLEAREADOOR_ROW_2_RIGHT = 0x40
+VEHICLEAREADOOR_ROW_3_LEFT = 0x100
+VEHICLEAREADOOR_ROW_3_RIGHT = 0x400
+VEHICLEAREADOOR_HOOD = 0x10000000
+VEHICLEAREADOOR_REAR = 0x20000000
+
+# SubscribeFlags
+SUBSCRIBEFLAGS_UNDEFINED = 0x0
+SUBSCRIBEFLAGS_EVENTS_FROM_CAR = 0x1
+SUBSCRIBEFLAGS_EVENTS_FROM_ANDROID = 0x2
 
 # VehicleProperty
 VEHICLEPROPERTY_INVALID = 0x0
@@ -172,303 +532,6 @@
 VEHICLEPROPERTY_READING_LIGHTS_STATE = 0x15400f03
 VEHICLEPROPERTY_READING_LIGHTS_SWITCH = 0x15400f04
 
-# VehicleSeatOccupancyState
-VEHICLESEATOCCUPANCYSTATE_UNKNOWN = 0x0
-VEHICLESEATOCCUPANCYSTATE_VACANT = 0x1
-VEHICLESEATOCCUPANCYSTATE_OCCUPIED = 0x2
-
-# VehicleLightState
-VEHICLELIGHTSTATE_OFF = 0x0
-VEHICLELIGHTSTATE_ON = 0x1
-VEHICLELIGHTSTATE_DAYTIME_RUNNING = 0x2
-
-# VehicleLightSwitch
-VEHICLELIGHTSWITCH_OFF = 0x0
-VEHICLELIGHTSWITCH_ON = 0x1
-VEHICLELIGHTSWITCH_DAYTIME_RUNNING = 0x2
-VEHICLELIGHTSWITCH_AUTOMATIC = 0x100
-
-# EvConnectorType
-EVCONNECTORTYPE_UNKNOWN = 0x0
-EVCONNECTORTYPE_IEC_TYPE_1_AC = 0x1
-EVCONNECTORTYPE_IEC_TYPE_2_AC = 0x2
-EVCONNECTORTYPE_IEC_TYPE_3_AC = 0x3
-EVCONNECTORTYPE_IEC_TYPE_4_DC = 0x4
-EVCONNECTORTYPE_IEC_TYPE_1_CCS_DC = 0x5
-EVCONNECTORTYPE_IEC_TYPE_2_CCS_DC = 0x6
-EVCONNECTORTYPE_TESLA_ROADSTER = 0x7
-EVCONNECTORTYPE_TESLA_HPWC = 0x8
-EVCONNECTORTYPE_TESLA_SUPERCHARGER = 0x9
-EVCONNECTORTYPE_GBT_AC = 0xa
-EVCONNECTORTYPE_GBT_DC = 0xb
-EVCONNECTORTYPE_OTHER = 0x65
-
-# PortLocationType
-PORTLOCATIONTYPE_UNKNOWN = 0x0
-PORTLOCATIONTYPE_FRONT_LEFT = 0x1
-PORTLOCATIONTYPE_FRONT_RIGHT = 0x2
-PORTLOCATIONTYPE_REAR_RIGHT = 0x3
-PORTLOCATIONTYPE_REAR_LEFT = 0x4
-PORTLOCATIONTYPE_FRONT = 0x5
-PORTLOCATIONTYPE_REAR = 0x6
-
-# FuelType
-FUELTYPE_FUEL_TYPE_UNKNOWN = 0x0
-FUELTYPE_FUEL_TYPE_UNLEADED = 0x1
-FUELTYPE_FUEL_TYPE_LEADED = 0x2
-FUELTYPE_FUEL_TYPE_DIESEL_1 = 0x3
-FUELTYPE_FUEL_TYPE_DIESEL_2 = 0x4
-FUELTYPE_FUEL_TYPE_BIODIESEL = 0x5
-FUELTYPE_FUEL_TYPE_E85 = 0x6
-FUELTYPE_FUEL_TYPE_LPG = 0x7
-FUELTYPE_FUEL_TYPE_CNG = 0x8
-FUELTYPE_FUEL_TYPE_LNG = 0x9
-FUELTYPE_FUEL_TYPE_ELECTRIC = 0xa
-FUELTYPE_FUEL_TYPE_HYDROGEN = 0xb
-FUELTYPE_FUEL_TYPE_OTHER = 0xc
-
-# VehicleHvacFanDirection
-VEHICLEHVACFANDIRECTION_FACE = 0x1
-VEHICLEHVACFANDIRECTION_FLOOR = 0x2
-VEHICLEHVACFANDIRECTION_DEFROST = 0x4
-
-# VehicleOilLevel
-VEHICLEOILLEVEL_CRITICALLY_LOW = 0x0
-VEHICLEOILLEVEL_LOW = 0x1
-VEHICLEOILLEVEL_NORMAL = 0x2
-VEHICLEOILLEVEL_HIGH = 0x3
-VEHICLEOILLEVEL_ERROR = 0x4
-
-# VehicleApPowerStateConfigFlag
-VEHICLEAPPOWERSTATECONFIGFLAG_ENABLE_DEEP_SLEEP_FLAG = 0x1
-VEHICLEAPPOWERSTATECONFIGFLAG_CONFIG_SUPPORT_TIMER_POWER_ON_FLAG = 0x2
-
-# VehicleApPowerStateReq
-VEHICLEAPPOWERSTATEREQ_ON = 0x0
-VEHICLEAPPOWERSTATEREQ_SHUTDOWN_PREPARE = 0x1
-VEHICLEAPPOWERSTATEREQ_CANCEL_SHUTDOWN = 0x2
-VEHICLEAPPOWERSTATEREQ_FINISHED = 0x3
-
-# VehicleApPowerStateReqIndex
-VEHICLEAPPOWERSTATEREQINDEX_STATE = 0x0
-VEHICLEAPPOWERSTATEREQINDEX_ADDITIONAL = 0x1
-
-# VehicleApPowerStateShutdownParam
-VEHICLEAPPOWERSTATESHUTDOWNPARAM_SHUTDOWN_IMMEDIATELY = 0x1
-VEHICLEAPPOWERSTATESHUTDOWNPARAM_CAN_SLEEP = 0x2
-VEHICLEAPPOWERSTATESHUTDOWNPARAM_SHUTDOWN_ONLY = 0x3
-
-# VehicleApPowerStateReport
-VEHICLEAPPOWERSTATEREPORT_WAIT_FOR_VHAL = 0x1
-VEHICLEAPPOWERSTATEREPORT_DEEP_SLEEP_ENTRY = 0x2
-VEHICLEAPPOWERSTATEREPORT_DEEP_SLEEP_EXIT = 0x3
-VEHICLEAPPOWERSTATEREPORT_SHUTDOWN_POSTPONE = 0x4
-VEHICLEAPPOWERSTATEREPORT_SHUTDOWN_START = 0x5
-VEHICLEAPPOWERSTATEREPORT_ON = 0x6
-VEHICLEAPPOWERSTATEREPORT_SHUTDOWN_PREPARE = 0x7
-VEHICLEAPPOWERSTATEREPORT_SHUTDOWN_CANCELLED = 0x8
-
-# VehicleHwKeyInputAction
-VEHICLEHWKEYINPUTACTION_ACTION_DOWN = 0x0
-VEHICLEHWKEYINPUTACTION_ACTION_UP = 0x1
-
-# VehicleDisplay
-VEHICLEDISPLAY_MAIN = 0x0
-VEHICLEDISPLAY_INSTRUMENT_CLUSTER = 0x1
-
-# VehicleUnit
-VEHICLEUNIT_SHOULD_NOT_USE = 0x0
-VEHICLEUNIT_METER_PER_SEC = 0x1
-VEHICLEUNIT_RPM = 0x2
-VEHICLEUNIT_HERTZ = 0x3
-VEHICLEUNIT_PERCENTILE = 0x10
-VEHICLEUNIT_MILLIMETER = 0x20
-VEHICLEUNIT_METER = 0x21
-VEHICLEUNIT_KILOMETER = 0x23
-VEHICLEUNIT_MILE = 0x24
-VEHICLEUNIT_CELSIUS = 0x30
-VEHICLEUNIT_FAHRENHEIT = 0x31
-VEHICLEUNIT_KELVIN = 0x32
-VEHICLEUNIT_MILLILITER = 0x40
-VEHICLEUNIT_LITER = 0x41
-VEHICLEUNIT_GALLON = 0x42
-VEHICLEUNIT_US_GALLON = 0x42
-VEHICLEUNIT_IMPERIAL_GALLON = 0x43
-VEHICLEUNIT_NANO_SECS = 0x50
-VEHICLEUNIT_SECS = 0x53
-VEHICLEUNIT_YEAR = 0x59
-VEHICLEUNIT_WATT_HOUR = 0x60
-VEHICLEUNIT_MILLIAMPERE = 0x61
-VEHICLEUNIT_MILLIVOLT = 0x62
-VEHICLEUNIT_MILLIWATTS = 0x63
-VEHICLEUNIT_AMPERE_HOURS = 0x64
-VEHICLEUNIT_KILOWATT_HOUR = 0x65
-VEHICLEUNIT_KILOPASCAL = 0x70
-VEHICLEUNIT_PSI = 0x71
-VEHICLEUNIT_BAR = 0x72
-VEHICLEUNIT_DEGREES = 0x80
-VEHICLEUNIT_MILES_PER_HOUR = 0x90
-VEHICLEUNIT_KILOMETERS_PER_HOUR = 0x91
-
-# VehiclePropertyChangeMode
-VEHICLEPROPERTYCHANGEMODE_STATIC = 0x0
-VEHICLEPROPERTYCHANGEMODE_ON_CHANGE = 0x1
-VEHICLEPROPERTYCHANGEMODE_CONTINUOUS = 0x2
-
-# VehiclePropertyAccess
-VEHICLEPROPERTYACCESS_NONE = 0x0
-VEHICLEPROPERTYACCESS_READ = 0x1
-VEHICLEPROPERTYACCESS_WRITE = 0x2
-VEHICLEPROPERTYACCESS_READ_WRITE = 0x3
-
-# VehiclePropertyStatus
-VEHICLEPROPERTYSTATUS_AVAILABLE = 0x0
-VEHICLEPROPERTYSTATUS_UNAVAILABLE = 0x1
-VEHICLEPROPERTYSTATUS_ERROR = 0x2
-
-# VehicleGear
-VEHICLEGEAR_GEAR_NEUTRAL = 0x1
-VEHICLEGEAR_GEAR_REVERSE = 0x2
-VEHICLEGEAR_GEAR_PARK = 0x4
-VEHICLEGEAR_GEAR_DRIVE = 0x8
-VEHICLEGEAR_GEAR_1 = 0x10
-VEHICLEGEAR_GEAR_2 = 0x20
-VEHICLEGEAR_GEAR_3 = 0x40
-VEHICLEGEAR_GEAR_4 = 0x80
-VEHICLEGEAR_GEAR_5 = 0x100
-VEHICLEGEAR_GEAR_6 = 0x200
-VEHICLEGEAR_GEAR_7 = 0x400
-VEHICLEGEAR_GEAR_8 = 0x800
-VEHICLEGEAR_GEAR_9 = 0x1000
-
-# VehicleAreaSeat
-VEHICLEAREASEAT_ROW_1_LEFT = 0x1
-VEHICLEAREASEAT_ROW_1_CENTER = 0x2
-VEHICLEAREASEAT_ROW_1_RIGHT = 0x4
-VEHICLEAREASEAT_ROW_2_LEFT = 0x10
-VEHICLEAREASEAT_ROW_2_CENTER = 0x20
-VEHICLEAREASEAT_ROW_2_RIGHT = 0x40
-VEHICLEAREASEAT_ROW_3_LEFT = 0x100
-VEHICLEAREASEAT_ROW_3_CENTER = 0x200
-VEHICLEAREASEAT_ROW_3_RIGHT = 0x400
-
-# VehicleAreaWindow
-VEHICLEAREAWINDOW_FRONT_WINDSHIELD = 0x1
-VEHICLEAREAWINDOW_REAR_WINDSHIELD = 0x2
-VEHICLEAREAWINDOW_ROW_1_LEFT = 0x10
-VEHICLEAREAWINDOW_ROW_1_RIGHT = 0x40
-VEHICLEAREAWINDOW_ROW_2_LEFT = 0x100
-VEHICLEAREAWINDOW_ROW_2_RIGHT = 0x400
-VEHICLEAREAWINDOW_ROW_3_LEFT = 0x1000
-VEHICLEAREAWINDOW_ROW_3_RIGHT = 0x4000
-VEHICLEAREAWINDOW_ROOF_TOP_1 = 0x10000
-VEHICLEAREAWINDOW_ROOF_TOP_2 = 0x20000
-
-# VehicleAreaDoor
-VEHICLEAREADOOR_ROW_1_LEFT = 0x1
-VEHICLEAREADOOR_ROW_1_RIGHT = 0x4
-VEHICLEAREADOOR_ROW_2_LEFT = 0x10
-VEHICLEAREADOOR_ROW_2_RIGHT = 0x40
-VEHICLEAREADOOR_ROW_3_LEFT = 0x100
-VEHICLEAREADOOR_ROW_3_RIGHT = 0x400
-VEHICLEAREADOOR_HOOD = 0x10000000
-VEHICLEAREADOOR_REAR = 0x20000000
-
-# VehicleAreaMirror
-VEHICLEAREAMIRROR_DRIVER_LEFT = 0x1
-VEHICLEAREAMIRROR_DRIVER_RIGHT = 0x2
-VEHICLEAREAMIRROR_DRIVER_CENTER = 0x4
-
-# VehicleTurnSignal
-VEHICLETURNSIGNAL_NONE = 0x0
-VEHICLETURNSIGNAL_RIGHT = 0x1
-VEHICLETURNSIGNAL_LEFT = 0x2
-
-# VehicleIgnitionState
-VEHICLEIGNITIONSTATE_UNDEFINED = 0x0
-VEHICLEIGNITIONSTATE_LOCK = 0x1
-VEHICLEIGNITIONSTATE_OFF = 0x2
-VEHICLEIGNITIONSTATE_ACC = 0x3
-VEHICLEIGNITIONSTATE_ON = 0x4
-VEHICLEIGNITIONSTATE_START = 0x5
-
-# SubscribeFlags
-SUBSCRIBEFLAGS_UNDEFINED = 0x0
-SUBSCRIBEFLAGS_EVENTS_FROM_CAR = 0x1
-SUBSCRIBEFLAGS_EVENTS_FROM_ANDROID = 0x2
-
-# StatusCode
-STATUSCODE_OK = 0x0
-STATUSCODE_TRY_AGAIN = 0x1
-STATUSCODE_INVALID_ARG = 0x2
-STATUSCODE_NOT_AVAILABLE = 0x3
-STATUSCODE_ACCESS_DENIED = 0x4
-STATUSCODE_INTERNAL_ERROR = 0x5
-
-# VehicleAreaWheel
-VEHICLEAREAWHEEL_UNKNOWN = 0x0
-VEHICLEAREAWHEEL_LEFT_FRONT = 0x1
-VEHICLEAREAWHEEL_RIGHT_FRONT = 0x2
-VEHICLEAREAWHEEL_LEFT_REAR = 0x4
-VEHICLEAREAWHEEL_RIGHT_REAR = 0x8
-
-# Obd2FuelSystemStatus
-OBD2FUELSYSTEMSTATUS_OPEN_INSUFFICIENT_ENGINE_TEMPERATURE = 0x1
-OBD2FUELSYSTEMSTATUS_CLOSED_LOOP = 0x2
-OBD2FUELSYSTEMSTATUS_OPEN_ENGINE_LOAD_OR_DECELERATION = 0x4
-OBD2FUELSYSTEMSTATUS_OPEN_SYSTEM_FAILURE = 0x8
-OBD2FUELSYSTEMSTATUS_CLOSED_LOOP_BUT_FEEDBACK_FAULT = 0x10
-
-# Obd2IgnitionMonitorKind
-OBD2IGNITIONMONITORKIND_SPARK = 0x0
-OBD2IGNITIONMONITORKIND_COMPRESSION = 0x1
-
-# Obd2CommonIgnitionMonitors
-OBD2COMMONIGNITIONMONITORS_COMPONENTS_AVAILABLE = 0x1
-OBD2COMMONIGNITIONMONITORS_COMPONENTS_INCOMPLETE = 0x2
-OBD2COMMONIGNITIONMONITORS_FUEL_SYSTEM_AVAILABLE = 0x4
-OBD2COMMONIGNITIONMONITORS_FUEL_SYSTEM_INCOMPLETE = 0x8
-OBD2COMMONIGNITIONMONITORS_MISFIRE_AVAILABLE = 0x10
-OBD2COMMONIGNITIONMONITORS_MISFIRE_INCOMPLETE = 0x20
-
-# Obd2SparkIgnitionMonitors
-OBD2SPARKIGNITIONMONITORS_EGR_AVAILABLE = 0x40
-OBD2SPARKIGNITIONMONITORS_EGR_INCOMPLETE = 0x80
-OBD2SPARKIGNITIONMONITORS_OXYGEN_SENSOR_HEATER_AVAILABLE = 0x100
-OBD2SPARKIGNITIONMONITORS_OXYGEN_SENSOR_HEATER_INCOMPLETE = 0x200
-OBD2SPARKIGNITIONMONITORS_OXYGEN_SENSOR_AVAILABLE = 0x400
-OBD2SPARKIGNITIONMONITORS_OXYGEN_SENSOR_INCOMPLETE = 0x800
-OBD2SPARKIGNITIONMONITORS_AC_REFRIGERANT_AVAILABLE = 0x1000
-OBD2SPARKIGNITIONMONITORS_AC_REFRIGERANT_INCOMPLETE = 0x2000
-OBD2SPARKIGNITIONMONITORS_SECONDARY_AIR_SYSTEM_AVAILABLE = 0x4000
-OBD2SPARKIGNITIONMONITORS_SECONDARY_AIR_SYSTEM_INCOMPLETE = 0x8000
-OBD2SPARKIGNITIONMONITORS_EVAPORATIVE_SYSTEM_AVAILABLE = 0x10000
-OBD2SPARKIGNITIONMONITORS_EVAPORATIVE_SYSTEM_INCOMPLETE = 0x20000
-OBD2SPARKIGNITIONMONITORS_HEATED_CATALYST_AVAILABLE = 0x40000
-OBD2SPARKIGNITIONMONITORS_HEATED_CATALYST_INCOMPLETE = 0x80000
-OBD2SPARKIGNITIONMONITORS_CATALYST_AVAILABLE = 0x100000
-OBD2SPARKIGNITIONMONITORS_CATALYST_INCOMPLETE = 0x200000
-
-# Obd2CompressionIgnitionMonitors
-OBD2COMPRESSIONIGNITIONMONITORS_EGR_OR_VVT_AVAILABLE = 0x40
-OBD2COMPRESSIONIGNITIONMONITORS_EGR_OR_VVT_INCOMPLETE = 0x80
-OBD2COMPRESSIONIGNITIONMONITORS_PM_FILTER_AVAILABLE = 0x100
-OBD2COMPRESSIONIGNITIONMONITORS_PM_FILTER_INCOMPLETE = 0x200
-OBD2COMPRESSIONIGNITIONMONITORS_EXHAUST_GAS_SENSOR_AVAILABLE = 0x400
-OBD2COMPRESSIONIGNITIONMONITORS_EXHAUST_GAS_SENSOR_INCOMPLETE = 0x800
-OBD2COMPRESSIONIGNITIONMONITORS_BOOST_PRESSURE_AVAILABLE = 0x1000
-OBD2COMPRESSIONIGNITIONMONITORS_BOOST_PRESSURE_INCOMPLETE = 0x2000
-OBD2COMPRESSIONIGNITIONMONITORS_NOx_SCR_AVAILABLE = 0x4000
-OBD2COMPRESSIONIGNITIONMONITORS_NOx_SCR_INCOMPLETE = 0x8000
-OBD2COMPRESSIONIGNITIONMONITORS_NMHC_CATALYST_AVAILABLE = 0x10000
-OBD2COMPRESSIONIGNITIONMONITORS_NMHC_CATALYST_INCOMPLETE = 0x20000
-
-# Obd2SecondaryAirStatus
-OBD2SECONDARYAIRSTATUS_UPSTREAM = 0x1
-OBD2SECONDARYAIRSTATUS_DOWNSTREAM_OF_CATALYCIC_CONVERTER = 0x2
-OBD2SECONDARYAIRSTATUS_FROM_OUTSIDE_OR_OFF = 0x4
-OBD2SECONDARYAIRSTATUS_PUMP_ON_FOR_DIAGNOSTICS = 0x8
-
 # Obd2FuelType
 OBD2FUELTYPE_NOT_AVAILABLE = 0x0
 OBD2FUELTYPE_GASOLINE = 0x1
@@ -495,40 +558,17 @@
 OBD2FUELTYPE_HYBRID_REGENERATIVE = 0x16
 OBD2FUELTYPE_BIFUEL_RUNNING_DIESEL = 0x17
 
-# DiagnosticIntegerSensorIndex
-DIAGNOSTICINTEGERSENSORINDEX_FUEL_SYSTEM_STATUS = 0x0
-DIAGNOSTICINTEGERSENSORINDEX_MALFUNCTION_INDICATOR_LIGHT_ON = 0x1
-DIAGNOSTICINTEGERSENSORINDEX_IGNITION_MONITORS_SUPPORTED = 0x2
-DIAGNOSTICINTEGERSENSORINDEX_IGNITION_SPECIFIC_MONITORS = 0x3
-DIAGNOSTICINTEGERSENSORINDEX_INTAKE_AIR_TEMPERATURE = 0x4
-DIAGNOSTICINTEGERSENSORINDEX_COMMANDED_SECONDARY_AIR_STATUS = 0x5
-DIAGNOSTICINTEGERSENSORINDEX_NUM_OXYGEN_SENSORS_PRESENT = 0x6
-DIAGNOSTICINTEGERSENSORINDEX_RUNTIME_SINCE_ENGINE_START = 0x7
-DIAGNOSTICINTEGERSENSORINDEX_DISTANCE_TRAVELED_WITH_MALFUNCTION_INDICATOR_LIGHT_ON = 0x8
-DIAGNOSTICINTEGERSENSORINDEX_WARMUPS_SINCE_CODES_CLEARED = 0x9
-DIAGNOSTICINTEGERSENSORINDEX_DISTANCE_TRAVELED_SINCE_CODES_CLEARED = 0xa
-DIAGNOSTICINTEGERSENSORINDEX_ABSOLUTE_BAROMETRIC_PRESSURE = 0xb
-DIAGNOSTICINTEGERSENSORINDEX_CONTROL_MODULE_VOLTAGE = 0xc
-DIAGNOSTICINTEGERSENSORINDEX_AMBIENT_AIR_TEMPERATURE = 0xd
-DIAGNOSTICINTEGERSENSORINDEX_TIME_WITH_MALFUNCTION_LIGHT_ON = 0xe
-DIAGNOSTICINTEGERSENSORINDEX_TIME_SINCE_TROUBLE_CODES_CLEARED = 0xf
-DIAGNOSTICINTEGERSENSORINDEX_MAX_FUEL_AIR_EQUIVALENCE_RATIO = 0x10
-DIAGNOSTICINTEGERSENSORINDEX_MAX_OXYGEN_SENSOR_VOLTAGE = 0x11
-DIAGNOSTICINTEGERSENSORINDEX_MAX_OXYGEN_SENSOR_CURRENT = 0x12
-DIAGNOSTICINTEGERSENSORINDEX_MAX_INTAKE_MANIFOLD_ABSOLUTE_PRESSURE = 0x13
-DIAGNOSTICINTEGERSENSORINDEX_MAX_AIR_FLOW_RATE_FROM_MASS_AIR_FLOW_SENSOR = 0x14
-DIAGNOSTICINTEGERSENSORINDEX_FUEL_TYPE = 0x15
-DIAGNOSTICINTEGERSENSORINDEX_FUEL_RAIL_ABSOLUTE_PRESSURE = 0x16
-DIAGNOSTICINTEGERSENSORINDEX_ENGINE_OIL_TEMPERATURE = 0x17
-DIAGNOSTICINTEGERSENSORINDEX_DRIVER_DEMAND_PERCENT_TORQUE = 0x18
-DIAGNOSTICINTEGERSENSORINDEX_ENGINE_ACTUAL_PERCENT_TORQUE = 0x19
-DIAGNOSTICINTEGERSENSORINDEX_ENGINE_REFERENCE_PERCENT_TORQUE = 0x1a
-DIAGNOSTICINTEGERSENSORINDEX_ENGINE_PERCENT_TORQUE_DATA_IDLE = 0x1b
-DIAGNOSTICINTEGERSENSORINDEX_ENGINE_PERCENT_TORQUE_DATA_POINT1 = 0x1c
-DIAGNOSTICINTEGERSENSORINDEX_ENGINE_PERCENT_TORQUE_DATA_POINT2 = 0x1d
-DIAGNOSTICINTEGERSENSORINDEX_ENGINE_PERCENT_TORQUE_DATA_POINT3 = 0x1e
-DIAGNOSTICINTEGERSENSORINDEX_ENGINE_PERCENT_TORQUE_DATA_POINT4 = 0x1f
-DIAGNOSTICINTEGERSENSORINDEX_LAST_SYSTEM_INDEX = 0x1f
+# VehicleAreaWindow
+VEHICLEAREAWINDOW_FRONT_WINDSHIELD = 0x1
+VEHICLEAREAWINDOW_REAR_WINDSHIELD = 0x2
+VEHICLEAREAWINDOW_ROW_1_LEFT = 0x10
+VEHICLEAREAWINDOW_ROW_1_RIGHT = 0x40
+VEHICLEAREAWINDOW_ROW_2_LEFT = 0x100
+VEHICLEAREAWINDOW_ROW_2_RIGHT = 0x400
+VEHICLEAREAWINDOW_ROW_3_LEFT = 0x1000
+VEHICLEAREAWINDOW_ROW_3_RIGHT = 0x4000
+VEHICLEAREAWINDOW_ROOF_TOP_1 = 0x10000
+VEHICLEAREAWINDOW_ROOF_TOP_2 = 0x20000
 
 # DiagnosticFloatSensorIndex
 DIAGNOSTICFLOATSENSORINDEX_CALCULATED_ENGINE_LOAD = 0x0
@@ -604,59 +644,19 @@
 DIAGNOSTICFLOATSENSORINDEX_ENGINE_FUEL_RATE = 0x46
 DIAGNOSTICFLOATSENSORINDEX_LAST_SYSTEM_INDEX = 0x46
 
-# VmsMessageType
-VMSMESSAGETYPE_START_SESSION = 0x11
-VMSMESSAGETYPE_SUBSCRIBE = 0x1
-VMSMESSAGETYPE_SUBSCRIBE_TO_PUBLISHER = 0x2
-VMSMESSAGETYPE_UNSUBSCRIBE = 0x3
-VMSMESSAGETYPE_UNSUBSCRIBE_TO_PUBLISHER = 0x4
-VMSMESSAGETYPE_OFFERING = 0x5
-VMSMESSAGETYPE_AVAILABILITY_REQUEST = 0x6
-VMSMESSAGETYPE_SUBSCRIPTIONS_REQUEST = 0x7
-VMSMESSAGETYPE_AVAILABILITY_RESPONSE = 0x8
-VMSMESSAGETYPE_AVAILABILITY_CHANGE = 0x9
-VMSMESSAGETYPE_SUBSCRIPTIONS_RESPONSE = 0xa
-VMSMESSAGETYPE_SUBSCRIPTIONS_CHANGE = 0xb
-VMSMESSAGETYPE_DATA = 0xc
-VMSMESSAGETYPE_PUBLISHER_ID_REQUEST = 0xd
-VMSMESSAGETYPE_PUBLISHER_ID_RESPONSE = 0xe
-VMSMESSAGETYPE_PUBLISHER_INFORMATION_REQUEST = 0xf
-VMSMESSAGETYPE_PUBLISHER_INFORMATION_RESPONSE = 0x10
-VMSMESSAGETYPE_LAST_VMS_MESSAGE_TYPE = 0x11
+# VehicleArea
+VEHICLEAREA_GLOBAL = 0x1000000
+VEHICLEAREA_WINDOW = 0x3000000
+VEHICLEAREA_MIRROR = 0x4000000
+VEHICLEAREA_SEAT = 0x5000000
+VEHICLEAREA_DOOR = 0x6000000
+VEHICLEAREA_WHEEL = 0x7000000
+VEHICLEAREA_MASK = 0xf000000
 
-# VmsBaseMessageIntegerValuesIndex
-VMSBASEMESSAGEINTEGERVALUESINDEX_MESSAGE_TYPE = 0x0
-
-# VmsStartSessionMessageIntegerValuesIndex
-VMSSTARTSESSIONMESSAGEINTEGERVALUESINDEX_SERVICE_ID = 0x1
-VMSSTARTSESSIONMESSAGEINTEGERVALUESINDEX_CLIENT_ID = 0x2
-
-# VmsMessageWithLayerIntegerValuesIndex
-VMSMESSAGEWITHLAYERINTEGERVALUESINDEX_LAYER_TYPE = 0x1
-VMSMESSAGEWITHLAYERINTEGERVALUESINDEX_LAYER_SUBTYPE = 0x2
-VMSMESSAGEWITHLAYERINTEGERVALUESINDEX_LAYER_VERSION = 0x3
-
-# VmsMessageWithLayerAndPublisherIdIntegerValuesIndex
-VMSMESSAGEWITHLAYERANDPUBLISHERIDINTEGERVALUESINDEX_PUBLISHER_ID = 0x4
-
-# VmsOfferingMessageIntegerValuesIndex
-VMSOFFERINGMESSAGEINTEGERVALUESINDEX_PUBLISHER_ID = 0x1
-VMSOFFERINGMESSAGEINTEGERVALUESINDEX_NUMBER_OF_OFFERS = 0x2
-VMSOFFERINGMESSAGEINTEGERVALUESINDEX_OFFERING_START = 0x3
-
-# VmsSubscriptionsStateIntegerValuesIndex
-VMSSUBSCRIPTIONSSTATEINTEGERVALUESINDEX_SEQUENCE_NUMBER = 0x1
-VMSSUBSCRIPTIONSSTATEINTEGERVALUESINDEX_NUMBER_OF_LAYERS = 0x2
-VMSSUBSCRIPTIONSSTATEINTEGERVALUESINDEX_NUMBER_OF_ASSOCIATED_LAYERS = 0x3
-VMSSUBSCRIPTIONSSTATEINTEGERVALUESINDEX_SUBSCRIPTIONS_START = 0x4
-
-# VmsAvailabilityStateIntegerValuesIndex
-VMSAVAILABILITYSTATEINTEGERVALUESINDEX_SEQUENCE_NUMBER = 0x1
-VMSAVAILABILITYSTATEINTEGERVALUESINDEX_NUMBER_OF_ASSOCIATED_LAYERS = 0x2
-VMSAVAILABILITYSTATEINTEGERVALUESINDEX_LAYERS_START = 0x3
-
-# VmsPublisherInformationIntegerValuesIndex
-VMSPUBLISHERINFORMATIONINTEGERVALUESINDEX_PUBLISHER_ID = 0x1
+# VehiclePropertyStatus
+VEHICLEPROPERTYSTATUS_AVAILABLE = 0x0
+VEHICLEPROPERTYSTATUS_UNAVAILABLE = 0x1
+VEHICLEPROPERTYSTATUS_ERROR = 0x2
 
 # Create a container of value_type constants to be used by vhal_emulator
 class vhal_types_2_0:
@@ -667,5 +667,6 @@
     TYPE_INT64   = [VEHICLEPROPERTYTYPE_INT64]
     TYPE_FLOAT   = [VEHICLEPROPERTYTYPE_FLOAT]
     TYPE_INT32S  = [VEHICLEPROPERTYTYPE_INT32_VEC]
+    TYPE_INT64S  = [VEHICLEPROPERTYTYPE_INT64_VEC]
     TYPE_FLOATS  = [VEHICLEPROPERTYTYPE_FLOAT_VEC]
     TYPE_MIXED   = [VEHICLEPROPERTYTYPE_MIXED]
diff --git a/tools/keventreader/server/Android.mk b/tools/keventreader/server/Android.mk
index 3dd6bba..301f557 100644
--- a/tools/keventreader/server/Android.mk
+++ b/tools/keventreader/server/Android.mk
@@ -28,9 +28,6 @@
     ../common/com/android/car/keventreader/IEventCallback.aidl \
     ../common/com/android/car/keventreader/IEventProvider.aidl \
 
-LOCAL_C_INCLUDES += \
-    frameworks/base/include
-
 LOCAL_SHARED_LIBRARIES := \
     libbinder \
     liblog \
diff --git a/tools/keventreader/server/keymap.cpp b/tools/keventreader/server/keymap.cpp
index 2e7c06d..5a86ff1 100644
--- a/tools/keventreader/server/keymap.cpp
+++ b/tools/keventreader/server/keymap.cpp
@@ -28,7 +28,7 @@
     fillMap();
 }
 
-std::string_view Keymap::getDisplayName(int keycode) {
+std::string Keymap::getDisplayName(int keycode) {
     auto iter = mKeyMap.find(keycode), end = mKeyMap.end();
     if (iter == end) {
         std::stringstream ss;
diff --git a/tools/keventreader/server/keymap.h b/tools/keventreader/server/keymap.h
index 28f68d6..86ec4d5 100644
--- a/tools/keventreader/server/keymap.h
+++ b/tools/keventreader/server/keymap.h
@@ -25,7 +25,7 @@
     public:
         static Keymap& get();
 
-        std::string_view getDisplayName(int keycode);
+        std::string getDisplayName(int keycode);
 
     private:
         std::map<int, const char*> mKeyMap;
diff --git a/user/OWNERS b/user/OWNERS
new file mode 100644
index 0000000..4e8ed97
--- /dev/null
+++ b/user/OWNERS
@@ -0,0 +1,4 @@
+# Library owners
+ahugh@google.com
+jovanak@google.com
+yizheng@google.com
diff --git a/user/car-user-lib/src/android/car/userlib/CarUserManagerHelper.java b/user/car-user-lib/src/android/car/userlib/CarUserManagerHelper.java
index 2a992cf..eab85e9 100644
--- a/user/car-user-lib/src/android/car/userlib/CarUserManagerHelper.java
+++ b/user/car-user-lib/src/android/car/userlib/CarUserManagerHelper.java
@@ -21,25 +21,16 @@
 import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
 import android.app.ActivityManager;
-import android.content.BroadcastReceiver;
 import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
 import android.content.pm.UserInfo;
 import android.graphics.Bitmap;
-import android.graphics.drawable.BitmapDrawable;
-import android.graphics.drawable.Drawable;
-import android.os.Bundle;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.provider.Settings;
 import android.sysprop.CarProperties;
-import android.telephony.TelephonyManager;
-import android.text.TextUtils;
 import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.os.RoSystemProperties;
 import com.android.internal.util.UserIcons;
 
 import com.google.android.collect.Sets;
@@ -59,7 +50,9 @@
  * and switching users. Methods related to get users will exclude system user by default.
  *
  * @hide
+ * @deprecated In the process of being removed.  Use {@link UserManager} APIs directly instead.
  */
+@Deprecated
 public final class CarUserManagerHelper {
     private static final String TAG = "CarUserManagerHelper";
 
@@ -84,38 +77,10 @@
             UserManager.DISALLOW_UNINSTALL_APPS
     );
 
-    /**
-     * Default set of restrictions for Guest users.
-     */
-    private static final Set<String> DEFAULT_GUEST_RESTRICTIONS = Sets.newArraySet(
-            UserManager.DISALLOW_FACTORY_RESET,
-            UserManager.DISALLOW_REMOVE_USER,
-            UserManager.DISALLOW_MODIFY_ACCOUNTS,
-            UserManager.DISALLOW_INSTALL_APPS,
-            UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES,
-            UserManager.DISALLOW_UNINSTALL_APPS
-    );
-
     private final Context mContext;
     private final UserManager mUserManager;
     private final ActivityManager mActivityManager;
     private final TestableFrameworkWrapper mTestableFrameworkWrapper;
-    private String mDefaultAdminName;
-    private Bitmap mDefaultGuestUserIcon;
-    private ArrayList<OnUsersUpdateListener> mUpdateListeners;
-    private final BroadcastReceiver mUserChangeReceiver = new BroadcastReceiver() {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            ArrayList<OnUsersUpdateListener> copyOfUpdateListeners;
-            synchronized (mUpdateListeners) {
-                copyOfUpdateListeners = new ArrayList(mUpdateListeners);
-            }
-
-            for (OnUsersUpdateListener listener : copyOfUpdateListeners) {
-                listener.onUsersUpdate();
-            }
-        }
-    };
 
     /**
      * Initializes with a default name for admin users.
@@ -128,7 +93,6 @@
 
     @VisibleForTesting
     CarUserManagerHelper(Context context, TestableFrameworkWrapper testableFrameworkWrapper) {
-        mUpdateListeners = new ArrayList<>();
         mContext = context.getApplicationContext();
         mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
         mActivityManager = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
@@ -136,47 +100,6 @@
     }
 
     /**
-     * Registers a listener for updates to all users - removing, adding users or changing user info.
-     *
-     * @param listener Instance of {@link OnUsersUpdateListener}.
-     */
-    public void registerOnUsersUpdateListener(OnUsersUpdateListener listener) {
-        if (listener == null) {
-            return;
-        }
-
-        synchronized (mUpdateListeners) {
-            if (mUpdateListeners.isEmpty()) {
-                // First listener being added, register receiver.
-                registerReceiver();
-            }
-
-            if (!mUpdateListeners.contains(listener)) {
-                mUpdateListeners.add(listener);
-            }
-        }
-    }
-
-    /**
-     * Unregisters on user update listener.
-     * Unregisters {@code BroadcastReceiver} if no listeners remain.
-     *
-     * @param listener Instance of {@link OnUsersUpdateListener} to unregister.
-     */
-    public void unregisterOnUsersUpdateListener(OnUsersUpdateListener listener) {
-        synchronized (mUpdateListeners) {
-            if (mUpdateListeners.contains(listener)) {
-                mUpdateListeners.remove(listener);
-
-                if (mUpdateListeners.isEmpty()) {
-                    // No more listeners, unregister broadcast receiver.
-                    unregisterReceiver();
-                }
-            }
-        }
-    }
-
-    /**
      * Set last active user.
      *
      * @param userId last active user id.
@@ -187,21 +110,6 @@
     }
 
     /**
-     * Set last active user.
-     *
-     * @param userId last active user id.
-     * @param skipGlobalSetting whether to skip set the global settings value.
-     * @deprecated Use {@link #setLastActiveUser(int)} instead.
-     */
-    @Deprecated
-    public void setLastActiveUser(int userId, boolean skipGlobalSetting) {
-        if (!skipGlobalSetting) {
-            Settings.Global.putInt(
-                    mContext.getContentResolver(), Settings.Global.LAST_ACTIVE_USER_ID, userId);
-        }
-    }
-
-    /**
      * Get user id for the last active user.
      *
      * @return user id of the last active user.
@@ -268,100 +176,12 @@
     }
 
     /**
-     * Sets default guest restrictions that will be applied every time a Guest user is created.
-     *
-     * <p> Restrictions are written to disk and persistent across boots.
-     */
-    public void initDefaultGuestRestrictions() {
-        Bundle defaultGuestRestrictions = new Bundle();
-        for (String restriction : DEFAULT_GUEST_RESTRICTIONS) {
-            defaultGuestRestrictions.putBoolean(restriction, true);
-        }
-        mUserManager.setDefaultGuestRestrictions(defaultGuestRestrictions);
-    }
-
-    /**
-     * Returns {@code true} if the system is in the headless user 0 model.
-     *
-     * @return {@boolean true} if headless system user.
-     */
-    public boolean isHeadlessSystemUser() {
-        return RoSystemProperties.MULTIUSER_HEADLESS_SYSTEM_USER;
-    }
-
-    /**
-     * Gets UserInfo for the system user.
-     *
-     * @return {@link UserInfo} for the system user.
-     */
-    public UserInfo getSystemUserInfo() {
-        return mUserManager.getUserInfo(UserHandle.USER_SYSTEM);
-    }
-
-    /**
-     * Gets UserInfo for the current foreground user.
-     *
-     * Concept of foreground user is relevant for the multi-user deployment. Foreground user
-     * corresponds to the currently "logged in" user.
-     *
-     * @return {@link UserInfo} for the foreground user.
-     */
-    public UserInfo getCurrentForegroundUserInfo() {
-        return mUserManager.getUserInfo(getCurrentForegroundUserId());
-    }
-
-    /**
-     * @return Id of the current foreground user.
-     */
-    public int getCurrentForegroundUserId() {
-        return mActivityManager.getCurrentUser();
-    }
-
-    /**
-     * Gets UserInfo for the user running the caller process.
-     *
-     * <p>Differentiation between foreground user and current process user is relevant for
-     * multi-user deployments.
-     *
-     * <p>Some multi-user aware components (like SystemUI) needs to run a singleton component
-     * in system user. Current process user is always the same for that component, even when
-     * the foreground user changes.
-     *
-     * @return {@link UserInfo} for the user running the current process.
-     */
-    public UserInfo getCurrentProcessUserInfo() {
-        return mUserManager.getUserInfo(getCurrentProcessUserId());
-    }
-
-    /**
-     * @return Id for the user running the current process.
-     */
-    public int getCurrentProcessUserId() {
-        return UserHandle.myUserId();
-    }
-
-    /**
-     * Gets all the existing users on the system that are not currently running as
-     * the foreground user.
-     * These are all the users that can be switched to from the foreground user.
-     *
-     * @return List of {@code UserInfo} for each user that is not the foreground user.
-     */
-    public List<UserInfo> getAllSwitchableUsers() {
-        if (isHeadlessSystemUser()) {
-            return getAllUsersExceptSystemUserAndSpecifiedUser(getCurrentForegroundUserId());
-        } else {
-            return getAllUsersExceptSpecifiedUser(getCurrentForegroundUserId());
-        }
-    }
-
-    /**
      * Gets all the users that can be brought to the foreground on the system.
      *
      * @return List of {@code UserInfo} for users that associated with a real person.
      */
-    public List<UserInfo> getAllUsers() {
-        if (isHeadlessSystemUser()) {
+    private List<UserInfo> getAllUsers() {
+        if (UserManager.isHeadlessSystemUserMode()) {
             return getAllUsersExceptSystemUserAndSpecifiedUser(UserHandle.USER_SYSTEM);
         } else {
             return mUserManager.getUsers(/* excludeDying= */ true);
@@ -373,7 +193,7 @@
      *
      * @return List of {@code UserInfo} for non-ephemeral users that associated with a real person.
      */
-    public List<UserInfo> getAllPersistentUsers() {
+    private List<UserInfo> getAllPersistentUsers() {
         List<UserInfo> users = getAllUsers();
         for (Iterator<UserInfo> iterator = users.iterator(); iterator.hasNext(); ) {
             UserInfo userInfo = iterator.next();
@@ -386,61 +206,6 @@
     }
 
     /**
-     * Gets all the users that can be brought to the foreground on the system that have admin roles.
-     *
-     * @return List of {@code UserInfo} for admin users that associated with a real person.
-     */
-    public List<UserInfo> getAllAdminUsers() {
-        List<UserInfo> users = getAllUsers();
-
-        for (Iterator<UserInfo> iterator = users.iterator(); iterator.hasNext(); ) {
-            UserInfo userInfo = iterator.next();
-            if (!userInfo.isAdmin()) {
-                // Remove user that is not admin.
-                iterator.remove();
-            }
-        }
-        return users;
-    }
-
-    /**
-     * Gets all users that are not guests.
-     *
-     * @return List of {@code UserInfo} for all users who are not guest users.
-     */
-    public List<UserInfo> getAllUsersExceptGuests() {
-        List<UserInfo> users = getAllUsers();
-
-        for (Iterator<UserInfo> iterator = users.iterator(); iterator.hasNext(); ) {
-            UserInfo userInfo = iterator.next();
-            if (userInfo.isGuest()) {
-                // Remove guests.
-                iterator.remove();
-            }
-        }
-        return users;
-    }
-
-    /**
-     * Get all the users except the one with userId passed in.
-     *
-     * @param userId of the user not to be returned.
-     * @return All users other than user with userId.
-     */
-    private List<UserInfo> getAllUsersExceptSpecifiedUser(int userId) {
-        List<UserInfo> users = mUserManager.getUsers(/* excludeDying= */true);
-
-        for (Iterator<UserInfo> iterator = users.iterator(); iterator.hasNext(); ) {
-            UserInfo userInfo = iterator.next();
-            if (userInfo.id == userId) {
-                // Remove user with userId from the list.
-                iterator.remove();
-            }
-        }
-        return users;
-    }
-
-    /**
      * Get all the users except system user and the one with userId passed in.
      *
      * @param userId of the user not to be returned.
@@ -459,288 +224,9 @@
         return users;
     }
 
-    /**
-     * Maximum number of users allowed on the device. This includes real users, managed profiles
-     * and restricted users, but excludes guests.
-     *
-     * <p> It excludes system user in headless system user model.
-     *
-     * @return Maximum number of users that can be present on the device.
-     */
-    public int getMaxSupportedUsers() {
-        if (isHeadlessSystemUser()) {
-            return mTestableFrameworkWrapper.userManagerGetMaxSupportedUsers() - 1;
-        }
-        return mTestableFrameworkWrapper.userManagerGetMaxSupportedUsers();
-    }
-
-    /**
-     * Get the maximum number of real (non-guest, non-managed profile) users that can be created on
-     * the device. This is a dynamic value and it decreases with the increase of the number of
-     * managed profiles on the device.
-     *
-     * <p> It excludes system user in headless system user model.
-     *
-     * @return Maximum number of real users that can be created.
-     */
-    public int getMaxSupportedRealUsers() {
-        return getMaxSupportedUsers() - getManagedProfilesCount();
-    }
-
-    /**
-     * Returns true if the maximum number of users on the device has been reached, false otherwise.
-     */
-    public boolean isUserLimitReached() {
-        int countNonGuestUsers = getAllUsersExceptGuests().size();
-        int maxSupportedUsers = getMaxSupportedUsers();
-
-        if (countNonGuestUsers > maxSupportedUsers) {
-            Log.e(TAG, "There are more users on the device than allowed.");
-            return true;
-        }
-
-        return getAllUsersExceptGuests().size() == maxSupportedUsers;
-    }
-
-    private int getManagedProfilesCount() {
-        List<UserInfo> users = getAllUsers();
-
-        // Count all users that are managed profiles of another user.
-        int managedProfilesCount = 0;
-        for (UserInfo user : users) {
-            if (user.isManagedProfile()) {
-                managedProfilesCount++;
-            }
-        }
-        return managedProfilesCount;
-    }
-
-    // User information accessors
-
-    /**
-     * Checks whether the user is system user.
-     *
-     * @param userInfo User to check against system user.
-     * @return {@code true} if system user, {@code false} otherwise.
-     */
-    public boolean isSystemUser(UserInfo userInfo) {
-        return userInfo.id == UserHandle.USER_SYSTEM;
-    }
-
-    /**
-     * Checks whether the user is last active user.
-     *
-     * @param userInfo User to check against last active user.
-     * @return {@code true} if is last active user, {@code false} otherwise.
-     */
-    public boolean isLastActiveUser(UserInfo userInfo) {
-        return userInfo.id == getLastActiveUser();
-    }
-
-    /**
-     * Checks whether passed in user is the foreground user.
-     *
-     * @param userInfo User to check.
-     * @return {@code true} if foreground user, {@code false} otherwise.
-     */
-    public boolean isForegroundUser(UserInfo userInfo) {
-        return getCurrentForegroundUserId() == userInfo.id;
-    }
-
-    /**
-     * Checks whether passed in user is the user that's running the current process.
-     *
-     * @param userInfo User to check.
-     * @return {@code true} if user running the process, {@code false} otherwise.
-     */
-    public boolean isCurrentProcessUser(UserInfo userInfo) {
-        return getCurrentProcessUserId() == userInfo.id;
-    }
-
-    // Foreground user information accessors.
-
-    /**
-     * Checks if the foreground user is a guest user.
-     */
-    public boolean isForegroundUserGuest() {
-        return getCurrentForegroundUserInfo().isGuest();
-    }
-
-    /**
-     * Checks if the foreground user is a demo user.
-     */
-    public boolean isForegroundUserDemo() {
-        return getCurrentForegroundUserInfo().isDemo();
-    }
-
-    /**
-     * Checks if the foreground user is ephemeral.
-     */
-    public boolean isForegroundUserEphemeral() {
-        return getCurrentForegroundUserInfo().isEphemeral();
-    }
-
-    /**
-     * Checks if the given user is non-ephemeral.
-     *
-     * @param userId User to check
-     * @return {@code true} if given user is persistent user.
-     */
-    public boolean isPersistentUser(int userId) {
-        UserInfo user = mUserManager.getUserInfo(userId);
-        return !user.isEphemeral();
-    }
-
-    /**
-     * Returns whether this user can be removed from the system.
-     *
-     * @param userInfo User to be removed
-     * @return {@code true} if they can be removed, {@code false} otherwise.
-     */
-    public boolean canUserBeRemoved(UserInfo userInfo) {
-        return !isSystemUser(userInfo);
-    }
-
-    /**
-     * Returns whether a user has a restriction.
-     *
-     * @param restriction Restriction to check. Should be a UserManager.* restriction.
-     * @param userInfo the user whose restriction is to be checked
-     */
-    public boolean hasUserRestriction(String restriction, UserInfo userInfo) {
-        return mUserManager.hasUserRestriction(restriction, userInfo.getUserHandle());
-    }
-
-    /**
-     * Return whether the foreground user has a restriction.
-     *
-     * @param restriction Restriction to check. Should be a UserManager.* restriction.
-     * @return Whether that restriction exists for the foreground user.
-     */
-    public boolean foregroundUserHasUserRestriction(String restriction) {
-        return hasUserRestriction(restriction, getCurrentForegroundUserInfo());
-    }
-
-    /**
-     * Checks if the foreground user can add new users.
-     */
-    public boolean canForegroundUserAddUsers() {
-        return !foregroundUserHasUserRestriction(UserManager.DISALLOW_ADD_USER);
-    }
-
-    /**
-     * Checks if the current process user can modify accounts. Demo and Guest users cannot modify
-     * accounts even if the DISALLOW_MODIFY_ACCOUNTS restriction is not applied.
-     */
-    public boolean canForegroundUserModifyAccounts() {
-        return !foregroundUserHasUserRestriction(UserManager.DISALLOW_MODIFY_ACCOUNTS)
-            && !isForegroundUserDemo()
-            && !isForegroundUserGuest();
-    }
-
-    /**
-     * Returns whether the foreground user can switch to other users.
-     *
-     * <p>For instance switching users is not allowed if the current user is in a phone call,
-     * or {@link #{UserManager.DISALLOW_USER_SWITCH} is set.
-     */
-    public boolean canForegroundUserSwitchUsers() {
-        boolean inIdleCallState = TelephonyManager.getDefault().getCallState()
-                == TelephonyManager.CALL_STATE_IDLE;
-        boolean disallowUserSwitching =
-                foregroundUserHasUserRestriction(UserManager.DISALLOW_USER_SWITCH);
-        return (inIdleCallState && !disallowUserSwitching);
-    }
-
-    // Current process user information accessors
-
-    /**
-     * Checks whether this process is running under the system user.
-     */
-    public boolean isCurrentProcessSystemUser() {
-        return mUserManager.isSystemUser();
-    }
-
-    /**
-     * Checks if the calling app is running in a demo user.
-     */
-    public boolean isCurrentProcessDemoUser() {
-        return mUserManager.isDemoUser();
-    }
-
-    /**
-     * Checks if the calling app is running as an admin user.
-     */
-    public boolean isCurrentProcessAdminUser() {
-        return mUserManager.isAdminUser();
-    }
-
-    /**
-     * Checks if the calling app is running as a guest user.
-     */
-    public boolean isCurrentProcessGuestUser() {
-        return mUserManager.isGuestUser();
-    }
-
-    /**
-     * Check is the calling app is running as a restricted profile user (ie. a LinkedUser).
-     * Restricted profiles are only available when {@link #isHeadlessSystemUser()} is false.
-     */
-    public boolean isCurrentProcessRestrictedProfileUser() {
-        return mUserManager.isRestrictedProfile();
-    }
-
     // Current process user restriction accessors
 
     /**
-     * Return whether the user running the current process has a restriction.
-     *
-     * @param restriction Restriction to check. Should be a UserManager.* restriction.
-     * @return Whether that restriction exists for the user running the process.
-     */
-    public boolean isCurrentProcessUserHasRestriction(String restriction) {
-        return mUserManager.hasUserRestriction(restriction);
-    }
-
-    /**
-     * Checks if the current process user can modify accounts. Demo and Guest users cannot modify
-     * accounts even if the DISALLOW_MODIFY_ACCOUNTS restriction is not applied.
-     */
-    public boolean canCurrentProcessModifyAccounts() {
-        return !isCurrentProcessUserHasRestriction(UserManager.DISALLOW_MODIFY_ACCOUNTS)
-            && !isCurrentProcessDemoUser()
-            && !isCurrentProcessGuestUser();
-    }
-
-    /**
-     * Checks if the user running the current process can add new users.
-     */
-    public boolean canCurrentProcessAddUsers() {
-        return !isCurrentProcessUserHasRestriction(UserManager.DISALLOW_ADD_USER);
-    }
-
-    /**
-     * Checks if the user running the current process can remove users.
-     */
-    public boolean canCurrentProcessRemoveUsers() {
-        return !isCurrentProcessUserHasRestriction(UserManager.DISALLOW_REMOVE_USER);
-    }
-
-    /**
-     * Returns whether the current process user can switch to other users.
-     *
-     * <p>For instance switching users is not allowed if the user is in a phone call,
-     * or {@link #{UserManager.DISALLOW_USER_SWITCH} is set.
-     */
-    public boolean canCurrentProcessSwitchUsers() {
-        boolean inIdleCallState = TelephonyManager.getDefault().getCallState()
-                == TelephonyManager.CALL_STATE_IDLE;
-        boolean disallowUserSwitching =
-                isCurrentProcessUserHasRestriction(UserManager.DISALLOW_USER_SWITCH);
-        return (inIdleCallState && !disallowUserSwitching);
-    }
-
-    /**
      * Grants admin permissions to the user.
      *
      * @param user User to be upgraded to Admin status.
@@ -750,7 +236,7 @@
             Manifest.permission.MANAGE_USERS
     })
     public void grantAdminPermissions(UserInfo user) {
-        if (!isCurrentProcessAdminUser()) {
+        if (!mUserManager.isAdminUser()) {
             Log.w(TAG, "Only admin users can assign admin permissions.");
             return;
         }
@@ -763,44 +249,6 @@
     }
 
     /**
-     * Creates a new user on the system with a default user name. This user name is set during
-     * constrution. The created user would be granted admin role. Only admins can create other
-     * admins.
-     *
-     * @return Newly created admin user, null if failed to create a user.
-     */
-    @Nullable
-    public UserInfo createNewAdminUser() {
-        return createNewAdminUser(getDefaultAdminName());
-    }
-
-    /**
-     * Creates a new user on the system, the created user would be granted admin role.
-     * Only admins can create other admins.
-     *
-     * @param userName Name to give to the newly created user.
-     * @return Newly created admin user, null if failed to create a user.
-     */
-    @Nullable
-    public UserInfo createNewAdminUser(String userName) {
-        if (!(isCurrentProcessAdminUser() || isCurrentProcessSystemUser())) {
-            // Only Admins or System user can create other privileged users.
-            Log.e(TAG, "Only admin users and system user can create other admins.");
-            return null;
-        }
-
-        UserInfo user = mUserManager.createUser(userName, UserInfo.FLAG_ADMIN);
-        if (user == null) {
-            // Couldn't create user, most likely because there are too many.
-            Log.w(TAG, "can't create admin user.");
-            return null;
-        }
-        assignDefaultIcon(user);
-
-        return user;
-    }
-
-    /**
      * Creates a new non-admin user on the system.
      *
      * @param userName Name to give to the newly created user.
@@ -816,11 +264,6 @@
         }
         setDefaultNonAdminRestrictions(user, /* enable= */ true);
 
-        // Each non-admin has sms and outgoing call restrictions applied by the UserManager on
-        // creation. We want to enable these permissions by default in the car.
-        setUserRestriction(user, UserManager.DISALLOW_SMS, /* enable= */ false);
-        setUserRestriction(user, UserManager.DISALLOW_OUTGOING_CALLS, /* enable= */ false);
-
         assignDefaultIcon(user);
         return user;
     }
@@ -831,9 +274,9 @@
      * @param userInfo User to set restrictions on.
      * @param enable If true, restriction is ON, If false, restriction is OFF.
      */
-    private void setDefaultNonAdminRestrictions(UserInfo userInfo, boolean enable) {
+    public void setDefaultNonAdminRestrictions(UserInfo userInfo, boolean enable) {
         for (String restriction : DEFAULT_NON_ADMIN_RESTRICTIONS) {
-            setUserRestriction(userInfo, restriction, enable);
+            mUserManager.setUserRestriction(restriction, enable, userInfo.getUserHandle());
         }
     }
 
@@ -845,94 +288,25 @@
      */
     private void setOptionalNonAdminRestrictions(UserInfo userInfo, boolean enable) {
         for (String restriction : OPTIONAL_NON_ADMIN_RESTRICTIONS) {
-            setUserRestriction(userInfo, restriction, enable);
+            mUserManager.setUserRestriction(restriction, enable, userInfo.getUserHandle());
         }
     }
 
     /**
-     * Sets the value of the specified restriction for the specified user.
-     *
-     * @param userInfo the user whose restriction is to be changed
-     * @param restriction the key of the restriction
-     * @param enable the value for the restriction. if true, turns the restriction ON, if false,
-     *               turns the restriction OFF.
-     */
-    public void setUserRestriction(UserInfo userInfo, String restriction, boolean enable) {
-        UserHandle userHandle = UserHandle.of(userInfo.id);
-        mUserManager.setUserRestriction(restriction, enable, userHandle);
-    }
-
-    /**
-     * Tries to remove the user that's passed in. System user cannot be removed.
-     * If the user to be removed is user currently running the process,
-     * it switches to the guest user first, and then removes the user.
-     * If the user being removed is the last admin user, this will create a new admin user.
-     *
-     * @param userInfo User to be removed
-     * @param guestUserName User name to use for the guest user if we need to switch to it
-     * @return {@code true} if user is successfully removed, {@code false} otherwise.
-     */
-    public boolean removeUser(UserInfo userInfo, String guestUserName) {
-        if (isSystemUser(userInfo)) {
-            Log.w(TAG, "User " + userInfo.id + " is system user, could not be removed.");
-            return false;
-        }
-
-        // Try to create a new admin before deleting the current one.
-        if (userInfo.isAdmin() && getAllAdminUsers().size() <= 1) {
-            return removeLastAdmin(userInfo);
-        }
-
-        if (!isCurrentProcessAdminUser() && !isCurrentProcessUser(userInfo)) {
-            // If the caller is non-admin, they can only delete themselves.
-            Log.e(TAG, "Non-admins cannot remove other users.");
-            return false;
-        }
-
-        if (userInfo.id == getCurrentForegroundUserId()) {
-            if (!canCurrentProcessSwitchUsers()) {
-                // If we can't switch to a different user, we can't exit this one and therefore
-                // can't delete it.
-                Log.w(TAG, "User switching is not allowed. Current user cannot be deleted");
-                return false;
-            }
-            startGuestSession(guestUserName);
-        }
-
-        return mUserManager.removeUser(userInfo.id);
-    }
-
-    private boolean removeLastAdmin(UserInfo userInfo) {
-        if (Log.isLoggable(TAG, Log.INFO)) {
-            Log.i(TAG, "User " + userInfo.id
-                    + " is the last admin user on device. Creating a new admin.");
-        }
-
-        UserInfo newAdmin = createNewAdminUser(getDefaultAdminName());
-        if (newAdmin == null) {
-            Log.w(TAG, "Couldn't create another admin, cannot delete current user.");
-            return false;
-        }
-
-        switchToUser(newAdmin);
-        return mUserManager.removeUser(userInfo.id);
-    }
-
-    /**
      * Switches (logs in) to another user given user id.
      *
      * @param id User id to switch to.
      * @return {@code true} if user switching succeed.
      */
     public boolean switchToUserId(int id) {
-        if (id == UserHandle.USER_SYSTEM && isHeadlessSystemUser()) {
+        if (id == UserHandle.USER_SYSTEM && UserManager.isHeadlessSystemUserMode()) {
             // System User doesn't associate with real person, can not be switched to.
             return false;
         }
-        if (!canCurrentProcessSwitchUsers()) {
+        if (mUserManager.getUserSwitchability() != UserManager.SWITCHABILITY_STATUS_OK) {
             return false;
         }
-        if (id == getCurrentForegroundUserId()) {
+        if (id == ActivityManager.getCurrentUser()) {
             return false;
         }
         return mActivityManager.switchUser(id);
@@ -949,162 +323,16 @@
     }
 
     /**
-     * Creates a new guest or finds the existing one, and switches into it.
+     * Assigns a default icon to a user according to the user's id.
      *
-     * @param guestName Username for the guest user.
-     * @return {@code true} if switch to guest user succeed.
+     * @param userInfo User whose avatar is set to default icon.
+     * @return Bitmap of the user icon.
      */
-    public boolean startGuestSession(String guestName) {
-        UserInfo guest = createNewOrFindExistingGuest(guestName);
-        if (guest == null) {
-            return false;
-        }
-        return switchToUserId(guest.id);
-    }
-
-    /**
-     * Creates and returns a new guest user or returns the existing one.
-     * Returns null if it fails to create a new guest.
-     *
-     * @param guestName Username for guest if new guest is being created.
-     */
-    @Nullable
-    public UserInfo createNewOrFindExistingGuest(String guestName) {
-        // CreateGuest will return null if a guest already exists.
-        UserInfo newGuest = mUserManager.createGuest(mContext, guestName);
-        if (newGuest != null) {
-            assignDefaultIcon(newGuest);
-            return newGuest;
-        }
-
-        UserInfo existingGuest = findExistingGuestUser();
-        if (existingGuest == null) {
-            // Most likely a guest got removed just before we tried to look for it.
-            Log.w(TAG, "Couldn't create a new guest and couldn't find an existing one.");
-        }
-
-        return existingGuest;
-    }
-
-    /**
-     * Returns UserInfo for the existing guest user, or null if there are no guests on the device.
-     */
-    @Nullable
-    private UserInfo findExistingGuestUser() {
-        for (UserInfo userInfo : getAllUsers()) {
-            if (userInfo.isGuest() && !userInfo.guestToRemove) {
-                return userInfo;
-            }
-        }
-        return null;
-    }
-
-    /**
-     * Gets a bitmap representing the user's default avatar.
-     *
-     * @param userInfo User whose avatar should be returned.
-     * @return Default user icon
-     */
-    public Bitmap getUserDefaultIcon(UserInfo userInfo) {
-        return UserIcons.convertToBitmap(
-            UserIcons.getDefaultUserIcon(mContext.getResources(), userInfo.id, false));
-    }
-
-    /**
-     * Gets a bitmap representing the default icon for a Guest user.
-     *
-     * @return Default guest user icon
-     */
-    public Bitmap getGuestDefaultIcon() {
-        if (mDefaultGuestUserIcon == null) {
-            mDefaultGuestUserIcon = UserIcons.convertToBitmap(UserIcons.getDefaultUserIcon(
-                mContext.getResources(), UserHandle.USER_NULL, false));
-        }
-        return mDefaultGuestUserIcon;
-    }
-
-    /**
-     * Gets an icon for the user.
-     *
-     * @param userInfo User for which we want to get the icon.
-     * @return a Bitmap for the icon
-     */
-    public Bitmap getUserIcon(UserInfo userInfo) {
-        Bitmap picture = mUserManager.getUserIcon(userInfo.id);
-
-        if (picture == null) {
-            return assignDefaultIcon(userInfo);
-        }
-
-        return picture;
-    }
-
-    /**
-     * Method for scaling a Bitmap icon to a desirable size.
-     *
-     * @param icon Bitmap to scale.
-     * @param desiredSize Wanted size for the icon.
-     * @return Drawable for the icon, scaled to the new size.
-     */
-    public Drawable scaleUserIcon(Bitmap icon, int desiredSize) {
-        Bitmap scaledIcon = Bitmap.createScaledBitmap(
-                icon, desiredSize, desiredSize, true /* filter */);
-        return new BitmapDrawable(mContext.getResources(), scaledIcon);
-    }
-
-    /**
-     * Sets new Username for the user.
-     *
-     * @param user User whose name should be changed.
-     * @param name New username.
-     */
-    public void setUserName(UserInfo user, String name) {
-        mUserManager.setUserName(user.id, name);
-    }
-
-    private void registerReceiver() {
-        IntentFilter filter = new IntentFilter();
-        filter.addAction(Intent.ACTION_USER_REMOVED);
-        filter.addAction(Intent.ACTION_USER_ADDED);
-        filter.addAction(Intent.ACTION_USER_INFO_CHANGED);
-        filter.addAction(Intent.ACTION_USER_SWITCHED);
-        filter.addAction(Intent.ACTION_USER_STOPPED);
-        filter.addAction(Intent.ACTION_USER_UNLOCKED);
-        mContext.registerReceiverAsUser(mUserChangeReceiver, UserHandle.ALL, filter, null, null);
-    }
-
-    // Assigns a default icon to a user according to the user's id.
     private Bitmap assignDefaultIcon(UserInfo userInfo) {
-        Bitmap bitmap = userInfo.isGuest()
-                ? getGuestDefaultIcon() : getUserDefaultIcon(userInfo);
+        int idForIcon = userInfo.isGuest() ? UserHandle.USER_NULL : userInfo.id;
+        Bitmap bitmap = UserIcons.convertToBitmap(
+                UserIcons.getDefaultUserIcon(mContext.getResources(), idForIcon, false));
         mUserManager.setUserIcon(userInfo.id, bitmap);
         return bitmap;
     }
-
-    private void unregisterReceiver() {
-        mContext.unregisterReceiver(mUserChangeReceiver);
-    }
-
-    private String getDefaultAdminName() {
-        if (TextUtils.isEmpty(mDefaultAdminName)) {
-            mDefaultAdminName = mContext.getString(com.android.internal.R.string.owner_name);
-        }
-        return mDefaultAdminName;
-    }
-
-    @VisibleForTesting
-    void setDefaultAdminName(String defaultAdminName) {
-        mDefaultAdminName = defaultAdminName;
-    }
-
-    /**
-     * Interface for listeners that want to register for receiving updates to changes to the users
-     * on the system including removing and adding users, and changing user info.
-     */
-    public interface OnUsersUpdateListener {
-        /**
-         * Method that will get called when users list has been changed.
-         */
-        void onUsersUpdate();
-    }
 }
diff --git a/vehicle-hal-support-lib/Android.bp b/vehicle-hal-support-lib/Android.bp
new file mode 100644
index 0000000..ca685a4
--- /dev/null
+++ b/vehicle-hal-support-lib/Android.bp
@@ -0,0 +1,40 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+java_library {
+
+    name: "vehicle-hal-support-lib",
+
+    srcs: ["src/**/*.java"],
+
+    static_libs: [
+        "android.hidl.base-V1.0-java",
+        "android.hardware.automotive.vehicle-V2.0-java"
+    ],
+
+}
+
+java_library {
+
+    name: "vehicle-hal-support-lib-for-test",
+
+    srcs: ["test/**/*.java"],
+
+    static_libs: [
+        "vehicle-hal-support-lib",
+        "junit",
+    ],
+
+}
diff --git a/vehicle-hal-support-lib/Android.mk b/vehicle-hal-support-lib/Android.mk
deleted file mode 100644
index 22e094c..0000000
--- a/vehicle-hal-support-lib/Android.mk
+++ /dev/null
@@ -1,31 +0,0 @@
-# Copyright (C) 2015 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-LOCAL_PATH:= $(call my-dir)
-
-include $(CLEAR_VARS)
-
-LOCAL_MODULE := vehicle-hal-support-lib
-
-LOCAL_MODULE_TAGS := optional
-
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-
-LOCAL_STATIC_JAVA_LIBRARIES := \
-    android.hidl.base-V1.0-java \
-    android.hardware.automotive.vehicle-V2.0-java \
-    junit
-
-include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/vehicle-hal-support-lib/src/com/android/car/vehiclehal/test/VehiclePropConfigBuilder.java b/vehicle-hal-support-lib/src/com/android/car/vehiclehal/test/VehiclePropConfigBuilder.java
index 59868d5..c66ba14 100644
--- a/vehicle-hal-support-lib/src/com/android/car/vehiclehal/test/VehiclePropConfigBuilder.java
+++ b/vehicle-hal-support-lib/src/com/android/car/vehiclehal/test/VehiclePropConfigBuilder.java
@@ -95,6 +95,14 @@
     }
 
     @CheckResult
+    public  VehiclePropConfigBuilder addAreaConfig(int areaId) {
+        VehicleAreaConfig area = new VehicleAreaConfig();
+        area.areaId = areaId;
+        mConfig.areaConfigs.add(area);
+        return this;
+    }
+
+    @CheckResult
     public VehiclePropConfigBuilder addAreaConfig(int areaId, int minValue, int maxValue) {
         VehicleAreaConfig area = new VehicleAreaConfig();
         area.areaId = areaId;
diff --git a/vehicle-hal-support-lib/src/com/android/car/vehiclehal/test/MockedVehicleHal.java b/vehicle-hal-support-lib/test/com/android/car/vehiclehal/test/MockedVehicleHal.java
similarity index 100%
rename from vehicle-hal-support-lib/src/com/android/car/vehiclehal/test/MockedVehicleHal.java
rename to vehicle-hal-support-lib/test/com/android/car/vehiclehal/test/MockedVehicleHal.java
diff --git a/watchdog/Android.mk b/watchdog/Android.mk
new file mode 100644
index 0000000..f0724fc
--- /dev/null
+++ b/watchdog/Android.mk
@@ -0,0 +1,16 @@
+# Copyright (C) 2019 The Android Open Source Project
+
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH := $(call my-dir)
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/watchdog/aidl/Android.bp b/watchdog/aidl/Android.bp
new file mode 100644
index 0000000..5e80398
--- /dev/null
+++ b/watchdog/aidl/Android.bp
@@ -0,0 +1,27 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+aidl_interface {
+    name: "carwatchdog_aidl_interface",
+    srcs: [
+        "android/automotive/watchdog/*.aidl",
+    ],
+    stability: "vintf",
+    backend: {
+        java: {
+            platform_apis: true,
+            enabled: true,
+        },
+    },
+}
diff --git a/watchdog/aidl/android/automotive/watchdog/ICarWatchdog.aidl b/watchdog/aidl/android/automotive/watchdog/ICarWatchdog.aidl
new file mode 100644
index 0000000..3d696d5
--- /dev/null
+++ b/watchdog/aidl/android/automotive/watchdog/ICarWatchdog.aidl
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.automotive.watchdog;
+
+import android.automotive.watchdog.ICarWatchdogClient;
+import android.automotive.watchdog.ICarWatchdogMonitor;
+import android.automotive.watchdog.PowerCycle;
+import android.automotive.watchdog.TimeoutLength;
+import android.automotive.watchdog.UserState;
+
+/**
+ * ICarWatchdog is an interface implemented by watchdog server.
+ * For health check, 4 components are involved: watchdog server, watchdog mediator, watchdog
+ * client, and watchdog monitor.
+ *   - watchdog server: checks clients' health status by pinging and waiting for the response.
+ *   - watchdog mediator: is a watchdog client by reporting its health status to the server, and
+ *                        at the same time plays a role of watchdog server by checking its clients'
+ *                        health status.
+ *   - watchdog client: reports its health status by responding to server's ping within timeout.
+ *   - watchdog monitor: captures and reports the process state of watchdog clients.
+ */
+
+@VintfStability
+interface ICarWatchdog {
+  /**
+   * Register the client to the watchdog server.
+   *
+   * @param client              Watchdog client to register.
+   * @param timeout             Timeout length specified through enum.
+   */
+  void registerClient(in ICarWatchdogClient client, in TimeoutLength timeout);
+
+  /**
+   * Unregister the client from the watchdog server.
+   *
+   * @param client              Watchdog client to unregister.
+   */
+  void unregisterClient(in ICarWatchdogClient client);
+
+  /**
+   * Register the mediator to the watchdog server.
+   * Note that watchdog mediator is also a watchdog client.
+   * The caller should have system UID.
+   *
+   * @param mediator            Watchdog mediator to register.
+   */
+  void registerMediator(in ICarWatchdogClient mediator);
+
+  /**
+   * Unregister the mediator from the watchdog server.
+   * Note that watchdog mediator is also a watchdog client.
+   * The caller should have system UID.
+   *
+   * @param mediator            Watchdog mediator to unregister.
+   */
+  void unregisterMediator(in ICarWatchdogClient mediator);
+
+  /**
+   * Register the monitor to the watchdog server.
+   * The caller should have system UID.
+   *
+   * @param monitor             Watchdog monitor to register.
+   */
+  void registerMonitor(in ICarWatchdogMonitor monitor);
+
+  /**
+   * Unregister the monitor from the watchdog server.
+   * The caller should have system UID.
+   *
+   * @param monitor             Watchdog monitor to unregister.
+   */
+  void unregisterMonitor(in ICarWatchdogMonitor monitor);
+
+  /**
+   * Tell watchdog server that the client is alive.
+   *
+   * @param client              Watchdog client that is responding.
+   * @param sessionId           Session id given by watchdog server.
+   */
+  void tellClientAlive(in ICarWatchdogClient client, in int sessionId);
+
+  /**
+   * Tell watchdog server that the mediator is alive together with the status of clients under
+   * the mediator.
+   * The caller should have system UID.
+   *
+   * @param mediator             Watchdog mediator that is responding.
+   * @param clientsNotResponding Array of process id of clients which haven't responded to the
+   *                             mediator.
+   * @param sessionId            Session id given by watchdog server.
+   */
+  void tellMediatorAlive(
+          in ICarWatchdogClient mediator, in int[] clientsNotResponding, in int sessionId);
+
+  /**
+   * Tell watchdog server that the monitor has finished dumping process information.
+   * The caller should have system UID.
+   *
+   * @param monitor              Watchdog monitor that is registered to watchdog server.
+   * @param pid                  Process id that has been dumped.
+   */
+  void tellDumpFinished(in ICarWatchdogMonitor monitor, in int pid);
+
+  /**
+   * Notify watchdog server that the device goes into a new power cycle.
+   * The caller should have system UID.
+   *
+   * @param cycle                Power cycle of the device.
+   */
+  void notifyPowerCycleChange(in PowerCycle cycle);
+
+  /**
+   * Notify watchdog server that Android user is started or stopped.
+   * The caller should have system UID.
+   *
+   * @param userId               Android user id.
+   * @param state                User state.
+   */
+  void notifyUserStateChange(in int userId, in UserState state);
+}
diff --git a/watchdog/aidl/android/automotive/watchdog/ICarWatchdogClient.aidl b/watchdog/aidl/android/automotive/watchdog/ICarWatchdogClient.aidl
new file mode 100644
index 0000000..1d33957
--- /dev/null
+++ b/watchdog/aidl/android/automotive/watchdog/ICarWatchdogClient.aidl
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.automotive.watchdog;
+
+import android.automotive.watchdog.TimeoutLength;
+
+@VintfStability
+oneway interface ICarWatchdogClient {
+  /**
+   * Check if the client is alive.
+   * Watchdog server or mediator calls this method, expecting the clients will respond within
+   * timeout. The final timeout is decided by the server, considering the requested timeout on
+   * client registration. If no response from the clients, watchdog server will dump process
+   * information and kill them.
+   *
+   * @param sessionId           Unique id to identify each health check session.
+   * @param timeout             Final timeout given by the server based on client request.
+   */
+  void checkIfAlive(in int sessionId, in TimeoutLength timeout);
+}
diff --git a/watchdog/aidl/android/automotive/watchdog/ICarWatchdogMonitor.aidl b/watchdog/aidl/android/automotive/watchdog/ICarWatchdogMonitor.aidl
new file mode 100644
index 0000000..d55434c
--- /dev/null
+++ b/watchdog/aidl/android/automotive/watchdog/ICarWatchdogMonitor.aidl
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.automotive.watchdog;
+
+import android.automotive.watchdog.ICarWatchdogClient;
+
+@VintfStability
+oneway interface ICarWatchdogMonitor {
+  /**
+   * Called when the client has not responded within the given timeout.
+   * Watchdog server calls this method, requesting the monitor to dump process information of the
+   * client.
+   *
+   * @param client              Binder object of the client.
+   * @param pid                 Process id of the client.
+   */
+  void onClientNotResponding(in ICarWatchdogClient client, in int pid);
+}
diff --git a/watchdog/aidl/android/automotive/watchdog/PowerCycle.aidl b/watchdog/aidl/android/automotive/watchdog/PowerCycle.aidl
new file mode 100644
index 0000000..d0e82bd
--- /dev/null
+++ b/watchdog/aidl/android/automotive/watchdog/PowerCycle.aidl
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.automotive.watchdog;
+
+/**
+ * Used by ICarWatchdog to describe the device power status.
+ */
+
+@VintfStability
+@Backing(type="int")
+enum PowerCycle {
+  /**
+   * The system is about to shut down.
+   */
+  POWER_CYCLE_SHUTDOWN,
+
+  /**
+   * The system is being suspended.
+   */
+  POWER_CYCLE_SUSPEND,
+
+  /**
+   * The system resumes working.
+   */
+  POWER_CYCLE_RESUME,
+}
diff --git a/watchdog/aidl/android/automotive/watchdog/TimeoutLength.aidl b/watchdog/aidl/android/automotive/watchdog/TimeoutLength.aidl
new file mode 100644
index 0000000..20fa7d7
--- /dev/null
+++ b/watchdog/aidl/android/automotive/watchdog/TimeoutLength.aidl
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.automotive.watchdog;
+
+/**
+ * Used by ICarWatchdog and ICarWatchdogMediator to determine if the clients are in bad state.
+ * Watchdog server will decide that the clients are in bad state when they don't respond within
+ * the timeout. Different timeouts are used by different clients based on how responsive they
+ * should be.
+ */
+
+@VintfStability
+@Backing(type="int")
+enum TimeoutLength {
+  /**
+   * Timeout is 3 seconds.
+   * This is for services which should be responsive.
+   */
+  TIMEOUT_CRITICAL,
+
+  /**
+   * Timeout is 5 seconds.
+   * This is for services which are relatively responsive.
+   */
+  TIMEOUT_MODERATE,
+
+  /**
+   * Timeout is 10 seconds.
+   * This is for all other services.
+   */
+  TIMEOUT_NORMAL,
+}
diff --git a/watchdog/aidl/android/automotive/watchdog/UserState.aidl b/watchdog/aidl/android/automotive/watchdog/UserState.aidl
new file mode 100644
index 0000000..d55ea11
--- /dev/null
+++ b/watchdog/aidl/android/automotive/watchdog/UserState.aidl
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.automotive.watchdog;
+
+/**
+ * Used by ICarWatchdog to describe whether user is started or stopped.
+ */
+
+@VintfStability
+@Backing(type="int")
+enum UserState {
+  /**
+   * The user is started.
+   */
+  USER_STATE_STARTED,
+
+  /**
+   * The user is stopped.
+   */
+  USER_STATE_STOPPED,
+}
diff --git a/watchdog/product/carwatchdog.mk b/watchdog/product/carwatchdog.mk
new file mode 100644
index 0000000..381b29b
--- /dev/null
+++ b/watchdog/product/carwatchdog.mk
@@ -0,0 +1,22 @@
+# Copyright (C) 2020 The Android Open Source Project
+
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Add carwatchdogd to product package
+PRODUCT_PACKAGES += carwatchdogd
+
+# SELinux public policies for car watchdog services
+BOARD_PLAT_PUBLIC_SEPOLICY_DIR += packages/services/Car/watchdog/sepolicy/public
+
+# SELinux private policies for car watchdog services
+BOARD_PLAT_PRIVATE_SEPOLICY_DIR += packages/services/Car/watchdog/sepolicy/private
diff --git a/watchdog/sepolicy/private/carwatchdlogclientdomain.te b/watchdog/sepolicy/private/carwatchdlogclientdomain.te
new file mode 100644
index 0000000..10c4e52
--- /dev/null
+++ b/watchdog/sepolicy/private/carwatchdlogclientdomain.te
@@ -0,0 +1,4 @@
+# Car watchdog client domain
+binder_use(carwatchdogclient_domain)
+
+allow carwatchdogclient_domain carwatchdogd_service:service_manager find;
diff --git a/watchdog/sepolicy/private/carwatchdog.te b/watchdog/sepolicy/private/carwatchdog.te
new file mode 100644
index 0000000..c3c45f8
--- /dev/null
+++ b/watchdog/sepolicy/private/carwatchdog.te
@@ -0,0 +1,9 @@
+# Car watchdog server
+typeattribute carwatchdogd coredomain;
+
+type carwatchdogd_exec, exec_type, file_type, system_file_type;
+
+init_daemon_domain(carwatchdogd)
+add_service(carwatchdogd, carwatchdogd_service)
+binder_use(carwatchdogd)
+binder_service(carwatchdogd)
diff --git a/watchdog/sepolicy/private/file_contexts b/watchdog/sepolicy/private/file_contexts
new file mode 100644
index 0000000..fe0bcb1
--- /dev/null
+++ b/watchdog/sepolicy/private/file_contexts
@@ -0,0 +1,2 @@
+# Car watchdog server
+/system/bin/carwatchdogd  u:object_r:carwatchdogd_exec:s0
diff --git a/watchdog/sepolicy/private/service_contexts b/watchdog/sepolicy/private/service_contexts
new file mode 100644
index 0000000..ea1ba15
--- /dev/null
+++ b/watchdog/sepolicy/private/service_contexts
@@ -0,0 +1,2 @@
+# Car watchdog server
+android.automotive.watchdog.ICarWatchdog/default  u:object_r:carwatchdogd_service:s0
diff --git a/watchdog/sepolicy/public/attributes b/watchdog/sepolicy/public/attributes
new file mode 100644
index 0000000..d4ae54f
--- /dev/null
+++ b/watchdog/sepolicy/public/attributes
@@ -0,0 +1,3 @@
+# All car watchdog clients
+attribute carwatchdogclient_domain;
+expandattribute carwatchdogclient_domain true;
diff --git a/watchdog/sepolicy/public/carwatchdog.te b/watchdog/sepolicy/public/carwatchdog.te
new file mode 100644
index 0000000..fedf3b1
--- /dev/null
+++ b/watchdog/sepolicy/public/carwatchdog.te
@@ -0,0 +1,5 @@
+# Car watchdog server
+type carwatchdogd, domain;
+
+binder_call(carwatchdogd, carwatchdogclient_domain)
+binder_call(carwatchdogclient_domain, carwatchdogd)
diff --git a/watchdog/sepolicy/public/service.te b/watchdog/sepolicy/public/service.te
new file mode 100644
index 0000000..eb41df4
--- /dev/null
+++ b/watchdog/sepolicy/public/service.te
@@ -0,0 +1,2 @@
+# Car watchdog server
+type carwatchdogd_service, service_manager_type;
diff --git a/watchdog/sepolicy/public/te_macros b/watchdog/sepolicy/public/te_macros
new file mode 100644
index 0000000..abda525
--- /dev/null
+++ b/watchdog/sepolicy/public/te_macros
@@ -0,0 +1,11 @@
+#####################################
+# carwatchdog_client_domain(domain)
+# Allow a base set of permissions required for a domain to be a
+# client of a carwatchdog.
+#
+# For example, make some_domain a car watchd client:
+#   carwatchdog_client_domain(some_domain)
+#
+define(`carwatchdog_client_domain', `
+typeattribute $1 carwatchdogclient_domain;
+')
diff --git a/watchdog/server/Android.bp b/watchdog/server/Android.bp
new file mode 100644
index 0000000..0cc5e57
--- /dev/null
+++ b/watchdog/server/Android.bp
@@ -0,0 +1,116 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+cc_defaults {
+    name: "carwatchdogd_defaults",
+    cflags: [
+        "-Wall",
+        "-Wno-missing-field-initializers",
+        "-Werror",
+        "-Wno-unused-variable",
+        "-Wunused-parameter",
+    ],
+    include_dirs: [
+        "system/core/base/include",
+    ],
+}
+
+cc_defaults {
+    name: "libwatchdog_ioperfcollection_defaults",
+    shared_libs: [
+        "libbinder",
+        "libbase",
+        "libcutils",
+        "liblog",
+        "libutils",
+    ],
+}
+
+cc_library {
+    name: "libwatchdog_ioperfcollection",
+    defaults: [
+        "carwatchdogd_defaults",
+        "libwatchdog_ioperfcollection_defaults",
+    ],
+    srcs: [
+        "src/IoPerfCollection.cpp",
+        "src/ProcPidStat.cpp",
+        "src/ProcStat.cpp",
+        "src/UidIoStats.cpp",
+    ],
+    static_libs: [
+        "libgtest_prod",
+    ],
+    export_include_dirs: [
+        "src",
+    ],
+}
+
+cc_test {
+    name: "libwatchdog_ioperfcollection_test",
+    defaults: [
+        "carwatchdogd_defaults",
+        "libwatchdog_ioperfcollection_defaults",
+    ],
+    test_suites: ["general-tests"],
+    srcs: [
+        "tests/IoPerfCollectionTest.cpp",
+        "tests/UidIoStatsTest.cpp",
+        "tests/ProcPidStatTest.cpp",
+        "tests/ProcStatTest.cpp",
+    ],
+    static_libs: [
+        "libgmock",
+        "libgtest",
+        "libwatchdog_ioperfcollection",
+    ],
+}
+
+cc_defaults {
+    name: "libwatchdog_process_service_defaults",
+    shared_libs: [
+        "carwatchdog_aidl_interface-cpp",
+        "libbase",
+        "libbinder",
+        "liblog",
+        "libutils",
+    ],
+}
+
+cc_library {
+    name: "libwatchdog_process_service",
+    defaults: [
+        "carwatchdogd_defaults",
+        "libwatchdog_process_service_defaults"
+    ],
+    srcs: [
+        "src/WatchdogProcessService.cpp",
+    ],
+}
+
+cc_binary {
+    name: "carwatchdogd",
+    defaults: [
+        "carwatchdogd_defaults",
+        "libwatchdog_process_service_defaults",
+    ],
+    srcs: [
+        "src/main.cpp",
+        "src/ServiceManager.cpp",
+    ],
+    init_rc: ["carwatchdogd.rc"],
+    shared_libs: [
+    	"libwatchdog_process_service",
+    ],
+}
diff --git a/watchdog/server/carwatchdogd.rc b/watchdog/server/carwatchdogd.rc
new file mode 100644
index 0000000..3220b90
--- /dev/null
+++ b/watchdog/server/carwatchdogd.rc
@@ -0,0 +1,18 @@
+# Copyright (C) 2020 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+service carwatchdogd /system/bin/carwatchdogd
+    class core
+    user system
+    group system
diff --git a/watchdog/server/src/IoPerfCollection.cpp b/watchdog/server/src/IoPerfCollection.cpp
new file mode 100644
index 0000000..20fd91d
--- /dev/null
+++ b/watchdog/server/src/IoPerfCollection.cpp
@@ -0,0 +1,373 @@
+/**
+ * Copyright (c) 2020, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "carwatchdogd"
+
+#include "IoPerfCollection.h"
+
+#include <android-base/stringprintf.h>
+#include <binder/IServiceManager.h>
+#include <cutils/android_filesystem_config.h>
+#include <inttypes.h>
+#include <log/log.h>
+#include <pwd.h>
+
+#include <string>
+#include <unordered_map>
+#include <unordered_set>
+#include <vector>
+
+namespace android {
+namespace automotive {
+namespace watchdog {
+
+using android::defaultServiceManager;
+using android::IBinder;
+using android::IServiceManager;
+using android::sp;
+using android::String16;
+using android::base::Error;
+using android::base::Result;
+using android::base::StringAppendF;
+using android::content::pm::IPackageManagerNative;
+
+namespace {
+
+double percentage(uint64_t numer, uint64_t denom) {
+    return denom == 0 ? 0.0 : (static_cast<double>(numer) / static_cast<double>(denom)) * 100.0;
+}
+
+}  // namespace
+
+std::string toString(const UidIoPerfData& data) {
+    std::string buffer;
+    StringAppendF(&buffer, "Top N Reads:\n");
+    StringAppendF(&buffer,
+                  "Android User ID, Package Name, Foreground Bytes, Foreground Bytes %%, "
+                  "Foreground Fsync, Foreground Fsync %%, Background Bytes, Background Bytes %%, "
+                  "Background Fsync, Background Fsync %%\n");
+    for (const auto& stat : data.topNReads) {
+        StringAppendF(&buffer, "%" PRIu32 ", %s", stat.userId, stat.packageName.c_str());
+        for (int i = 0; i < UID_STATES; ++i) {
+            StringAppendF(&buffer, ", %" PRIu64 ", %.2f%%, %" PRIu64 ", %.2f%%", stat.bytes[i],
+                          stat.bytesPercent[i], stat.fsync[i], stat.fsyncPercent[i]);
+        }
+        StringAppendF(&buffer, "\n");
+    }
+    StringAppendF(&buffer, "Top N Writes:\n");
+    StringAppendF(&buffer,
+                  "Android User ID, Package Name, Foreground Bytes, Foreground Bytes %%, "
+                  "Foreground Fsync, Foreground Fsync %%, Background Bytes, Background Bytes %%, "
+                  "Background Fsync, Background Fsync %%\n");
+    for (const auto& stat : data.topNWrites) {
+        StringAppendF(&buffer, "%" PRIu32 ", %s", stat.userId, stat.packageName.c_str());
+        for (int i = 0; i < UID_STATES; ++i) {
+            StringAppendF(&buffer, ", %" PRIu64 ", %.2f%%, %" PRIu64 ", %.2f%%", stat.bytes[i],
+                          stat.bytesPercent[i], stat.fsync[i], stat.fsyncPercent[i]);
+        }
+        StringAppendF(&buffer, "\n");
+    }
+    return buffer;
+}
+
+std::string toString(const SystemIoPerfData& data) {
+    std::string buffer;
+    StringAppendF(&buffer, "CPU I/O wait time/percent: %" PRIu64 " / %.2f%%\n", data.cpuIoWaitTime,
+                  data.cpuIoWaitPercent);
+    StringAppendF(&buffer, "Number of I/O blocked processes/percent: %" PRIu32 " / %.2f%%\n",
+                  data.ioBlockedProcessesCnt, data.ioBlockedProcessesPercent);
+    return buffer;
+}
+
+Result<void> IoPerfCollection::start() {
+    Mutex::Autolock lock(mMutex);
+    if (mCurrCollectionEvent != CollectionEvent::NONE) {
+        return Error() << "Cannot start I/O performance collection more than once";
+    }
+    mCurrCollectionEvent = CollectionEvent::BOOT_TIME;
+
+    // TODO(b/148486340): Implement this method.
+    return Error() << "Unimplemented method";
+}
+
+Result<void> IoPerfCollection::onBootFinished() {
+    Mutex::Autolock lock(mMutex);
+    if (mCurrCollectionEvent != CollectionEvent::BOOT_TIME) {
+        return Error() << "Current collection event " << toEventString(mCurrCollectionEvent)
+                       << " != " << toEventString(CollectionEvent::BOOT_TIME)
+                       << " collection event";
+    }
+
+    // TODO(b/148486340): Implement this method.
+    return Error() << "Unimplemented method";
+}
+
+status_t IoPerfCollection::dump(int /*fd*/) {
+    Mutex::Autolock lock(mMutex);
+
+    // TODO(b/148486340): Implement this method.
+
+    // TODO: Report when any of the proc collectors' enabled() method returns false.
+
+    return INVALID_OPERATION;
+}
+
+status_t IoPerfCollection::startCustomCollection(std::chrono::seconds /*interval*/,
+                                                 std::chrono::seconds /*maxDuration*/) {
+    Mutex::Autolock lock(mMutex);
+    if (mCurrCollectionEvent != CollectionEvent::PERIODIC) {
+        ALOGE(
+            "Cannot start a custom collection when "
+            "the current collection event %s != %s collection event",
+            toEventString(mCurrCollectionEvent).c_str(),
+            toEventString(CollectionEvent::PERIODIC).c_str());
+        return INVALID_OPERATION;
+    }
+
+    // TODO(b/148486340): Implement this method.
+    return INVALID_OPERATION;
+}
+
+status_t IoPerfCollection::endCustomCollection(int /*fd*/) {
+    Mutex::Autolock lock(mMutex);
+    if (mCurrCollectionEvent != CollectionEvent::CUSTOM) {
+        ALOGE("No custom collection is running");
+        return INVALID_OPERATION;
+    }
+
+    // TODO(b/148486340): Implement this method.
+
+    // TODO: Report when any of the proc collectors' enabled() method returns false.
+
+    return INVALID_OPERATION;
+}
+
+Result<void> IoPerfCollection::collect() {
+    Mutex::Autolock lock(mMutex);
+
+    // TODO(b/148486340): Implement this method.
+    return Error() << "Unimplemented method";
+}
+
+Result<void> IoPerfCollection::collectUidIoPerfDataLocked(UidIoPerfData* uidIoPerfData) {
+    if (!mUidIoStats.enabled()) {
+        // Don't return an error to avoid log spamming on every collection. Instead, report this
+        // once in the generated dump.
+        return {};
+    }
+
+    const Result<std::unordered_map<uint32_t, UidIoUsage>>& usage = mUidIoStats.collect();
+    if (!usage) {
+        return Error() << "Failed to collect uid I/O usage: " << usage.error();
+    }
+
+    // Fetch only the top N reads and writes from the usage records.
+    UidIoUsage tempUsage = {};
+    std::vector<const UidIoUsage*> topNReads(mTopNStatsPerCategory, &tempUsage);
+    std::vector<const UidIoUsage*> topNWrites(mTopNStatsPerCategory, &tempUsage);
+    uint64_t total[METRIC_TYPES][UID_STATES] = {{0}};
+    std::unordered_set<uint32_t> unmappedUids;
+
+    for (const auto& uIt : *usage) {
+        const UidIoUsage& curUsage = uIt.second;
+        if (curUsage.ios.isZero()) {
+            continue;
+        }
+        if (mUidToPackageNameMapping.find(curUsage.uid) == mUidToPackageNameMapping.end()) {
+            unmappedUids.insert(curUsage.uid);
+        }
+        total[READ_BYTES][FOREGROUND] += curUsage.ios.metrics[READ_BYTES][FOREGROUND];
+        total[READ_BYTES][BACKGROUND] += curUsage.ios.metrics[READ_BYTES][BACKGROUND];
+        total[WRITE_BYTES][FOREGROUND] += curUsage.ios.metrics[WRITE_BYTES][FOREGROUND];
+        total[WRITE_BYTES][BACKGROUND] += curUsage.ios.metrics[WRITE_BYTES][BACKGROUND];
+        total[FSYNC_COUNT][FOREGROUND] += curUsage.ios.metrics[FSYNC_COUNT][FOREGROUND];
+        total[FSYNC_COUNT][BACKGROUND] += curUsage.ios.metrics[FSYNC_COUNT][BACKGROUND];
+
+        for (auto it = topNReads.begin(); it != topNReads.end(); ++it) {
+            const UidIoUsage* curRead = *it;
+            if (curRead->ios.sumReadBytes() > curUsage.ios.sumReadBytes()) {
+                continue;
+            }
+            topNReads.erase(topNReads.end() - 1);
+            topNReads.emplace(it, &curUsage);
+            break;
+        }
+        for (auto it = topNWrites.begin(); it != topNWrites.end(); ++it) {
+            const UidIoUsage* curWrite = *it;
+            if (curWrite->ios.sumWriteBytes() > curUsage.ios.sumWriteBytes()) {
+                continue;
+            }
+            topNWrites.erase(topNWrites.end() - 1);
+            topNWrites.emplace(it, &curUsage);
+            break;
+        }
+    }
+
+    const auto& ret = updateUidToPackageNameMapping(unmappedUids);
+    if (!ret) {
+        ALOGW("%s", ret.error().message().c_str());
+    }
+
+    // Convert the top N I/O usage to UidIoPerfData.
+    for (const auto& usage : topNReads) {
+        if (usage->ios.isZero()) {
+            // End of non-zero usage records. This case occurs when the number of UIDs with active
+            // I/O operations is < |kTopNStatsPerCategory|.
+            break;
+        }
+        struct UidIoPerfData::Stats stats = {
+            .userId = multiuser_get_user_id(usage->uid),
+            .packageName = std::to_string(usage->uid),
+            .bytes = {usage->ios.metrics[READ_BYTES][FOREGROUND],
+                      usage->ios.metrics[READ_BYTES][BACKGROUND]},
+            .bytesPercent = {percentage(usage->ios.metrics[READ_BYTES][FOREGROUND],
+                                        total[READ_BYTES][FOREGROUND]),
+                             percentage(usage->ios.metrics[READ_BYTES][BACKGROUND],
+                                        total[READ_BYTES][BACKGROUND])},
+            .fsync = {usage->ios.metrics[FSYNC_COUNT][FOREGROUND],
+                      usage->ios.metrics[FSYNC_COUNT][BACKGROUND]},
+            .fsyncPercent = {percentage(usage->ios.metrics[FSYNC_COUNT][FOREGROUND],
+                                        total[FSYNC_COUNT][FOREGROUND]),
+                             percentage(usage->ios.metrics[FSYNC_COUNT][BACKGROUND],
+                                        total[FSYNC_COUNT][BACKGROUND])},
+        };
+
+        if (mUidToPackageNameMapping.find(usage->uid) != mUidToPackageNameMapping.end()) {
+            stats.packageName = mUidToPackageNameMapping[usage->uid];
+        }
+
+        uidIoPerfData->topNReads.emplace_back(stats);
+    }
+
+    for (const auto& usage : topNWrites) {
+        if (usage->ios.isZero()) {
+            // End of non-zero usage records. This case occurs when the number of UIDs with active
+            // I/O operations is < |kTopNStatsPerCategory|.
+            break;
+        }
+        struct UidIoPerfData::Stats stats = {
+            .userId = multiuser_get_user_id(usage->uid),
+            .packageName = std::to_string(usage->uid),
+            .bytes = {usage->ios.metrics[WRITE_BYTES][FOREGROUND],
+                      usage->ios.metrics[WRITE_BYTES][BACKGROUND]},
+            .bytesPercent = {percentage(usage->ios.metrics[WRITE_BYTES][FOREGROUND],
+                                        total[WRITE_BYTES][FOREGROUND]),
+                             percentage(usage->ios.metrics[WRITE_BYTES][BACKGROUND],
+                                        total[WRITE_BYTES][BACKGROUND])},
+            .fsync = {usage->ios.metrics[FSYNC_COUNT][FOREGROUND],
+                      usage->ios.metrics[FSYNC_COUNT][BACKGROUND]},
+            .fsyncPercent = {percentage(usage->ios.metrics[FSYNC_COUNT][FOREGROUND],
+                                        total[FSYNC_COUNT][FOREGROUND]),
+                             percentage(usage->ios.metrics[FSYNC_COUNT][BACKGROUND],
+                                        total[FSYNC_COUNT][BACKGROUND])},
+        };
+
+        if (mUidToPackageNameMapping.find(usage->uid) != mUidToPackageNameMapping.end()) {
+            stats.packageName = mUidToPackageNameMapping[usage->uid];
+        }
+
+        uidIoPerfData->topNWrites.emplace_back(stats);
+    }
+    return {};
+}
+
+Result<void> IoPerfCollection::collectSystemIoPerfDataLocked(SystemIoPerfData* systemIoPerfData) {
+    if (!mProcStat.enabled()) {
+        // Don't return an error to avoid log spamming on every collection. Instead, report this
+        // once in the generated dump.
+        return {};
+    }
+
+    const Result<ProcStatInfo>& procStatInfo = mProcStat.collect();
+    if (!procStatInfo) {
+        return Error() << "Failed to collect proc stats: " << procStatInfo.error();
+    }
+
+    systemIoPerfData->cpuIoWaitTime = procStatInfo->cpuStats.ioWaitTime;
+    systemIoPerfData->cpuIoWaitPercent =
+            percentage(procStatInfo->cpuStats.ioWaitTime, procStatInfo->totalCpuTime());
+    systemIoPerfData->ioBlockedProcessesCnt = procStatInfo->ioBlockedProcessesCnt;
+    systemIoPerfData->ioBlockedProcessesPercent =
+            percentage(procStatInfo->ioBlockedProcessesCnt, procStatInfo->totalProcessesCnt());
+    return {};
+}
+
+Result<void> IoPerfCollection::collectProcessIoPerfDataLocked(
+    ProcessIoPerfData* /*processIoPerfData*/) {
+    // TODO(b/148486340): Implement this method.
+    return Error() << "Unimplemented method";
+}
+
+Result<void> IoPerfCollection::updateUidToPackageNameMapping(
+    const std::unordered_set<uint32_t>& uids) {
+    std::vector<int32_t> appUids;
+
+    for (const auto& uid : uids) {
+        if (uid >= AID_APP_START) {
+            appUids.emplace_back(static_cast<int32_t>(uid));
+            continue;
+        }
+        // System/native UIDs.
+        passwd* usrpwd = getpwuid(uid);
+        if (!usrpwd) {
+            continue;
+        }
+        mUidToPackageNameMapping[uid] = std::string(usrpwd->pw_name);
+    }
+
+    if (appUids.empty()) {
+        return {};
+    }
+
+    if (mPackageManager == nullptr) {
+        auto ret = retrievePackageManager();
+        if (!ret) {
+            return Error() << "Failed to retrieve package manager: " << ret.error();
+        }
+    }
+
+    std::vector<std::string> packageNames;
+    const binder::Status& status = mPackageManager->getNamesForUids(appUids, &packageNames);
+    if (!status.isOk()) {
+        return Error() << "package_native::getNamesForUids failed: " << status.exceptionMessage();
+    }
+
+    for (uint32_t i = 0; i < appUids.size(); i++) {
+        if (!packageNames[i].empty()) {
+            mUidToPackageNameMapping[appUids[i]] = packageNames[i];
+        }
+    }
+
+    return {};
+}
+
+Result<void> IoPerfCollection::retrievePackageManager() {
+    const sp<IServiceManager> sm = defaultServiceManager();
+    if (sm == nullptr) {
+        return Error() << "Failed to retrieve defaultServiceManager";
+    }
+
+    sp<IBinder> binder = sm->getService(String16("package_native"));
+    if (binder == nullptr) {
+        return Error() << "Failed to get service package_native";
+    }
+    mPackageManager = interface_cast<IPackageManagerNative>(binder);
+    return {};
+}
+
+}  // namespace watchdog
+}  // namespace automotive
+}  // namespace android
diff --git a/watchdog/server/src/IoPerfCollection.h b/watchdog/server/src/IoPerfCollection.h
new file mode 100644
index 0000000..259b186
--- /dev/null
+++ b/watchdog/server/src/IoPerfCollection.h
@@ -0,0 +1,239 @@
+/**
+ * Copyright (c) 2020, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef WATCHDOG_SERVER_SRC_IOPERFCOLLECTION_H_
+#define WATCHDOG_SERVER_SRC_IOPERFCOLLECTION_H_
+
+#include <android-base/chrono_utils.h>
+#include <android-base/result.h>
+#include <android/content/pm/IPackageManagerNative.h>
+#include <cutils/multiuser.h>
+#include <gtest/gtest_prod.h>
+#include <time.h>
+#include <utils/Errors.h>
+#include <utils/Mutex.h>
+
+#include <string>
+#include <unordered_map>
+#include <unordered_set>
+#include <vector>
+
+#include "ProcStat.h"
+#include "UidIoStats.h"
+
+namespace android {
+namespace automotive {
+namespace watchdog {
+
+// TODO(b/148489461): Replace the below constants (except kCustomCollection* constants) with
+// read-only persistent properties.
+const int kTopNStatsPerCategory = 5;
+const std::chrono::seconds kBoottimeCollectionInterval = 1s;
+const std::chrono::seconds kPeriodicCollectionInterval = 10s;
+// Number of periodic collection perf data snapshots to cache in memory.
+const uint kPeriodicCollectionBufferSize = 180;
+
+// Default values for the custom collection interval and max_duration.
+const std::chrono::seconds kCustomCollectionInterval = 10s;
+const std::chrono::seconds kCustomCollectionDuration = 30min;
+
+// Performance data collected from the `/proc/uid_io/stats` file.
+struct UidIoPerfData {
+    struct Stats {
+        userid_t userId = 0;
+        std::string packageName;
+        uint64_t bytes[UID_STATES];
+        double bytesPercent[UID_STATES];
+        uint64_t fsync[UID_STATES];
+        double fsyncPercent[UID_STATES];
+    };
+    std::vector<Stats> topNReads = {};
+    std::vector<Stats> topNWrites = {};
+};
+
+std::string toString(const UidIoPerfData& perfData);
+
+// Performance data collected from the `/proc/stats` file.
+struct SystemIoPerfData {
+    uint64_t cpuIoWaitTime = 0;
+    double cpuIoWaitPercent = 0.0;
+    uint32_t ioBlockedProcessesCnt = 0;
+    double ioBlockedProcessesPercent = 0;
+};
+
+std::string toString(const SystemIoPerfData& perfData);
+
+// Performance data collected from the `/proc/[pid]/stat` and `/proc/[pid]/task/[tid]/stat` files.
+struct ProcessIoPerfData {
+    struct Stats {
+        userid_t userId = 0;
+        std::string packageName;
+        uint64_t count = 0;
+        uint64_t percent = 0;
+    };
+    std::vector<Stats> topNIoBlockedProcesses = {};
+    std::vector<Stats> topNMajorPageFaults = {};
+    uint64_t totalMajorPageFaults = 0;
+    // Percentage of increase in the major page faults since last collection.
+    double majorPageFaultsIncrease = 0.0;
+};
+
+struct IoPerfRecord {
+    int64_t time;  // Collection time.
+    UidIoPerfData uidIoPerfData;
+    SystemIoPerfData systemIoPerfData;
+    ProcessIoPerfData processIoPerfData;
+};
+
+enum CollectionEvent {
+    BOOT_TIME = 0,
+    PERIODIC,
+    CUSTOM,
+    NONE,
+};
+
+static inline std::string toEventString(CollectionEvent event) {
+    switch (event) {
+        case CollectionEvent::BOOT_TIME:
+            return "BOOT_TIME";
+        case CollectionEvent::PERIODIC:
+            return "PERIODIC";
+        case CollectionEvent::CUSTOM:
+            return "CUSTOM";
+        case CollectionEvent::NONE:
+            return "NONE";
+        default:
+            return "INVALID";
+    }
+}
+
+// IoPerfCollection implements the I/O performance data collection module of the CarWatchDog
+// service. It exposes APIs that the CarWatchDog main thread and binder service can call to start
+// a collection, update the collection type, and generate collection dumps.
+class IoPerfCollection {
+public:
+    IoPerfCollection() :
+          mTopNStatsPerCategory(kTopNStatsPerCategory),
+          mBoottimeRecords({}),
+          mPeriodicRecords({}),
+          mCustomRecords({}),
+          mCurrCollectionEvent(CollectionEvent::NONE),
+          mUidToPackageNameMapping({}),
+          mUidIoStats(),
+          mProcStat() {}
+
+    // Starts the boot-time collection on a separate thread and returns immediately. Must be called
+    // only once. Otherwise, returns an error.
+    android::base::Result<void> start();
+
+    // Stops the boot-time collection thread, caches boot-time perf records, starts the periodic
+    // collection on a separate thread, and returns immediately.
+    android::base::Result<void> onBootFinished();
+
+    // Generates a dump from the boot-time and periodic collection events.
+    // Returns any error observed during the dump generation.
+    status_t dump(int fd);
+
+    // Starts a custom collection on a separate thread, stops the periodic collection (won't discard
+    // the collected data), and returns immediately. Returns any error observed during this process.
+    // The custom collection happens once every |interval| seconds. When the |maxDuration| is
+    // reached, stops the collection, discards the collected data, and starts the periodic
+    // collection. This is needed to ensure the custom collection doesn't run forever when
+    // a subsequent |endCustomCollection| call is not received.
+    status_t startCustomCollection(std::chrono::seconds interval = kCustomCollectionInterval,
+                                   std::chrono::seconds maxDuration = kCustomCollectionDuration);
+
+    // Stops the current custom collection thread, generates a dump, starts the periodic collection
+    // on a separate thread, and returns immediately. Returns an error when there is no custom
+    // collection running or when a dump couldn't be generated from the custom collection.
+    status_t endCustomCollection(int fd);
+
+private:
+    // Only used by tests.
+    explicit IoPerfCollection(std::string uidIoStatsPath, std::string procStatPath) :
+          mTopNStatsPerCategory(kTopNStatsPerCategory),
+          mBoottimeRecords({}),
+          mPeriodicRecords({}),
+          mCustomRecords({}),
+          mCurrCollectionEvent(CollectionEvent::NONE),
+          mUidToPackageNameMapping({}),
+          mUidIoStats(uidIoStatsPath),
+          mProcStat(procStatPath) {}
+
+    // Collects/stores the performance data for the current collection event.
+    android::base::Result<void> collect();
+
+    // Collects performance data from the `/proc/uid_io/stats` file.
+    android::base::Result<void> collectUidIoPerfDataLocked(UidIoPerfData* uidIoPerfData);
+
+    // Collects performance data from the `/proc/stats` file.
+    android::base::Result<void> collectSystemIoPerfDataLocked(SystemIoPerfData* systemIoPerfData);
+
+    // Collects performance data from the `/proc/[pid]/stat` and
+    // `/proc/[pid]/task/[tid]/stat` files.
+    android::base::Result<void> collectProcessIoPerfDataLocked(
+            ProcessIoPerfData* processIoPerfData);
+
+    // Updates the |mUidToPackageNameMapping| for the given |uids|.
+    android::base::Result<void> updateUidToPackageNameMapping(
+            const std::unordered_set<uint32_t>& uids);
+
+    // Retrieves package manager from the default service manager.
+    android::base::Result<void> retrievePackageManager();
+
+    int mTopNStatsPerCategory;
+
+    // Makes sure only one collection is running at any given time.
+    Mutex mMutex;
+
+    // Cache of the performance records collected during boot-time collection.
+    std::vector<IoPerfRecord> mBoottimeRecords GUARDED_BY(mMutex);
+
+    // Cache of the performance records collected during periodic collection. Size of this cache
+    // is limited by |kPeriodicCollectionBufferSize|.
+    std::vector<IoPerfRecord> mPeriodicRecords GUARDED_BY(mMutex);
+
+    // Cache of the performance records collected during custom collection. This cache is cleared
+    // at the end of every custom collection.
+    std::vector<IoPerfRecord> mCustomRecords GUARDED_BY(mMutex);
+
+    // Tracks the current collection event. Updated on |start|, |onBootComplete|,
+    // |startCustomCollection| and |endCustomCollection|.
+    CollectionEvent mCurrCollectionEvent GUARDED_BY(mMutex);
+
+    // Cache of uid to package name mapping.
+    std::unordered_map<uint64_t, std::string> mUidToPackageNameMapping GUARDED_BY(mMutex);
+
+    // Collector/parser for `/proc/uid_io/stats`.
+    UidIoStats mUidIoStats GUARDED_BY(mMutex);
+
+    // Collector/parser for `/proc/stat`.
+    ProcStat mProcStat GUARDED_BY(mMutex);
+
+    // To get the package names from app uids.
+    android::sp<android::content::pm::IPackageManagerNative> mPackageManager GUARDED_BY(mMutex);
+
+    FRIEND_TEST(IoPerfCollectionTest, TestValidUidIoStatFile);
+    FRIEND_TEST(IoPerfCollectionTest, TestUidIOStatsLessThanTopNStatsLimit);
+    FRIEND_TEST(IoPerfCollectionTest, TestProcUidIoStatsContentsFromDevice);
+    FRIEND_TEST(IoPerfCollectionTest, TestValidProcStatFile);
+};
+
+}  // namespace watchdog
+}  // namespace automotive
+}  // namespace android
+
+#endif  //  WATCHDOG_SERVER_SRC_IOPERFCOLLECTION_H_
diff --git a/watchdog/server/src/ProcPidStat.cpp b/watchdog/server/src/ProcPidStat.cpp
new file mode 100644
index 0000000..b92c739
--- /dev/null
+++ b/watchdog/server/src/ProcPidStat.cpp
@@ -0,0 +1,315 @@
+/**
+ * Copyright (c) 2020, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "carwatchdogd"
+
+#include "ProcPidStat.h"
+
+#include <android-base/file.h>
+#include <android-base/parseint.h>
+#include <android-base/strings.h>
+#include <dirent.h>
+#include <log/log.h>
+
+#include <string>
+#include <unordered_map>
+#include <vector>
+
+namespace android {
+namespace automotive {
+namespace watchdog {
+
+using android::base::EndsWith;
+using android::base::Error;
+using android::base::ParseInt;
+using android::base::ParseUint;
+using android::base::ReadFileToString;
+using android::base::Result;
+using android::base::Split;
+
+namespace {
+
+enum ReadError {
+    ERR_INVALID_FILE = 0,
+    ERR_FILE_OPEN_READ = 1,
+    NUM_ERRORS = 2,
+};
+
+// /proc/PID/stat or /proc/PID/task/TID/stat format:
+// <pid> <comm> <state> <ppid> <pgrp ID> <session ID> <tty_nr> <tpgid> <flags> <minor faults>
+// <children minor faults> <major faults> <children major faults> <user mode time>
+// <system mode time> <children user mode time> <children kernel mode time> <priority> <nice value>
+// <num threads> <start time since boot> <virtual memory size> <resident set size> <rss soft limit>
+// <start code addr> <end code addr> <start stack addr> <ESP value> <EIP> <bitmap of pending sigs>
+// <bitmap of blocked sigs> <bitmap of ignored sigs> <waiting channel> <num pages swapped>
+// <cumulative pages swapped> <exit signal> <processor #> <real-time prio> <agg block I/O delays>
+// <guest time> <children guest time> <start data addr> <end data addr> <start break addr>
+// <cmd line args start addr> <amd line args end addr> <env start addr> <env end addr> <exit code>
+// Example line: 1 (init) S 0 0 0 0 0 0 0 0 220 0 0 0 0 0 0 0 2 0 0 ...etc...
+bool parsePidStatLine(const std::string& line, PidStat* pidStat) {
+    std::vector<std::string> fields = Split(line, " ");
+
+    // Note: Regex parsing for the below logic increased the time taken to run the
+    // ProcPidStatTest#TestProcPidStatContentsFromDevice from 151.7ms to 1.3 seconds.
+
+    // Comm string is enclosed with ( ) brackets and may contain space(s). Thus calculate the
+    // commEndOffset based on the field that contains the closing bracket.
+    size_t commEndOffset = 0;
+    for (size_t i = 1; i < fields.size(); ++i) {
+        pidStat->comm += fields[i];
+        if (EndsWith(fields[i], ")")) {
+            commEndOffset = i - 1;
+            break;
+        }
+        pidStat->comm += " ";
+    }
+
+    if (pidStat->comm.front() != '(' || pidStat->comm.back() != ')') {
+        ALOGW("Comm string `%s` not enclosed in brackets", pidStat->comm.c_str());
+        return false;
+    }
+    pidStat->comm.erase(pidStat->comm.begin());
+    pidStat->comm.erase(pidStat->comm.end() - 1);
+
+    // The required data is in the first 22 + |commEndOffset| fields so make sure there are at least
+    // these many fields in the file.
+    if (fields.size() < 22 + commEndOffset || !ParseUint(fields[0], &pidStat->pid) ||
+        !ParseUint(fields[3 + commEndOffset], &pidStat->ppid) ||
+        !ParseUint(fields[11 + commEndOffset], &pidStat->majorFaults) ||
+        !ParseUint(fields[19 + commEndOffset], &pidStat->numThreads) ||
+        !ParseUint(fields[21 + commEndOffset], &pidStat->startTime)) {
+        ALOGW("Invalid proc pid stat contents: \"%s\"", line.c_str());
+        return false;
+    }
+    pidStat->state = fields[2 + commEndOffset];
+    return true;
+}
+
+Result<void> readPidStatFile(const std::string& path, PidStat* pidStat) {
+    std::string buffer;
+    if (!ReadFileToString(path, &buffer)) {
+        return Error(ERR_FILE_OPEN_READ) << "ReadFileToString failed for " << path;
+    }
+    std::vector<std::string> lines = Split(std::move(buffer), "\n");
+    if (lines.size() != 1 && (lines.size() != 2 || !lines[1].empty())) {
+        return Error(ERR_INVALID_FILE) << path << " contains " << lines.size() << " lines != 1";
+    }
+    if (!parsePidStatLine(std::move(lines[0]), pidStat)) {
+        return Error(ERR_INVALID_FILE) << "Failed to parse the contents of " << path;
+    }
+    return {};
+}
+
+}  // namespace
+
+Result<std::vector<ProcessStats>> ProcPidStat::collect() {
+    if (!mEnabled) {
+        return Error() << "Can not access PID stat files under " << kProcDirPath;
+    }
+
+    Mutex::Autolock lock(mMutex);
+    const auto& processStats = getProcessStatsLocked();
+    if (!processStats) {
+        return Error() << processStats.error();
+    }
+
+    std::vector<ProcessStats> delta;
+    for (const auto& it : *processStats) {
+        const ProcessStats& curStats = it.second;
+        const auto& cachedIt = mLastProcessStats.find(it.first);
+        if (cachedIt == mLastProcessStats.end() ||
+            cachedIt->second.process.startTime != curStats.process.startTime) {
+            // New/reused PID so don't calculate the delta.
+            delta.emplace_back(curStats);
+            continue;
+        }
+
+        ProcessStats deltaStats = curStats;
+        const ProcessStats& cachedStats = cachedIt->second;
+        deltaStats.process.majorFaults -= cachedStats.process.majorFaults;
+        for (auto& deltaThread : deltaStats.threads) {
+            const auto& cachedThread = cachedStats.threads.find(deltaThread.first);
+            if (cachedThread == cachedStats.threads.end() ||
+                cachedThread->second.startTime != deltaThread.second.startTime) {
+                // New TID or TID reused by the same PID so don't calculate the delta.
+                continue;
+            }
+            deltaThread.second.majorFaults -= cachedThread->second.majorFaults;
+        }
+        delta.emplace_back(deltaStats);
+    }
+    mLastProcessStats = *processStats;
+    return delta;
+}
+
+Result<std::unordered_map<uint32_t, ProcessStats>> ProcPidStat::getProcessStatsLocked() const {
+    std::unordered_map<uint32_t, ProcessStats> processStats;
+    auto procDirp = std::unique_ptr<DIR, int (*)(DIR*)>(opendir(mPath.c_str()), closedir);
+    if (!procDirp) {
+        return Error() << "Failed to open " << mPath << " directory";
+    }
+    dirent* pidDir = nullptr;
+    while ((pidDir = readdir(procDirp.get())) != nullptr) {
+        // 1. Read top-level pid stats.
+        uint32_t pid = 0;
+        if (pidDir->d_type != DT_DIR || !ParseUint(pidDir->d_name, &pid)) {
+            continue;
+        }
+        ProcessStats curStats;
+        std::string path = StringPrintf((mPath + kStatFileFormat).c_str(), pid);
+        const auto& ret = readPidStatFile(path, &curStats.process);
+        if (!ret) {
+            // PID may disappear between scanning the directory and parsing the stat file.
+            // Thus treat ERR_FILE_OPEN_READ errors as soft errors.
+            if (ret.error().code() != ERR_FILE_OPEN_READ) {
+                return Error() << "Failed to read top-level per-process stat file: "
+                               << ret.error().message().c_str();
+            }
+            ALOGW("Failed to read top-level per-process stat file %s: %s", path.c_str(),
+                  ret.error().message().c_str());
+            continue;
+        }
+
+        // 2. When not found in the cache, fetch tgid/UID as soon as possible because processes
+        // may terminate during scanning.
+        const auto& it = mLastProcessStats.find(curStats.process.pid);
+        if (it == mLastProcessStats.end() ||
+            it->second.process.startTime != curStats.process.startTime || it->second.tgid == -1 ||
+            it->second.uid == -1) {
+            const auto& ret = getPidStatusLocked(&curStats);
+            if (!ret) {
+                if (ret.error().code() != ERR_FILE_OPEN_READ) {
+                    return Error() << "Failed to read pid status for pid " << curStats.process.pid
+                                   << ": " << ret.error().message().c_str();
+                }
+                ALOGW("Failed to read pid status for pid %" PRIu32 ": %s", curStats.process.pid,
+                      ret.error().message().c_str());
+                // Default tgid and uid values are -1 (aka unknown).
+            }
+        } else {
+            // Fetch from cache.
+            curStats.tgid = it->second.tgid;
+            curStats.uid = it->second.uid;
+        }
+
+        if (curStats.tgid != -1 && curStats.tgid != curStats.process.pid) {
+            ALOGW("Skipping non-process (i.e., Tgid != PID) entry for PID %" PRIu32,
+                  curStats.process.pid);
+            continue;
+        }
+
+        // 3. Fetch per-thread stats.
+        std::string taskDir = StringPrintf((mPath + kTaskDirFormat).c_str(), pid);
+        auto taskDirp = std::unique_ptr<DIR, int (*)(DIR*)>(opendir(taskDir.c_str()), closedir);
+        if (!taskDirp) {
+            // Treat this as a soft error so at least the process stats will be collected.
+            ALOGW("Failed to open %s directory", taskDir.c_str());
+        }
+        dirent* tidDir = nullptr;
+        bool didReadMainThread = false;
+        while (taskDirp != nullptr && (tidDir = readdir(taskDirp.get())) != nullptr) {
+            uint32_t tid = 0;
+            if (tidDir->d_type != DT_DIR || !ParseUint(tidDir->d_name, &tid)) {
+                continue;
+            }
+            if (processStats.find(tid) != processStats.end()) {
+                return Error() << "Process stats already exists for TID " << tid
+                               << ". Stats will be double counted";
+            }
+
+            PidStat curThreadStat = {};
+            path = StringPrintf((taskDir + kStatFileFormat).c_str(), tid);
+            const auto& ret = readPidStatFile(path, &curThreadStat);
+            if (!ret) {
+                if (ret.error().code() != ERR_FILE_OPEN_READ) {
+                    return Error() << "Failed to read per-thread stat file: "
+                                   << ret.error().message().c_str();
+                }
+                // Maybe the thread terminated before reading the file so skip this thread and
+                // continue with scanning the next thread's stat.
+                ALOGW("Failed to read per-thread stat file %s: %s", path.c_str(),
+                      ret.error().message().c_str());
+                continue;
+            }
+            if (curThreadStat.pid == curStats.process.pid) {
+                didReadMainThread = true;
+            }
+            curStats.threads[curThreadStat.pid] = curThreadStat;
+        }
+        if (!didReadMainThread) {
+            // In the event of failure to read main-thread info (mostly because the process
+            // terminated during scanning/parsing), fill out the stat that are common between main
+            // thread and the process.
+            curStats.threads[curStats.process.pid] = PidStat{
+                    .pid = curStats.process.pid,
+                    .comm = curStats.process.comm,
+                    .state = curStats.process.state,
+                    .ppid = curStats.process.ppid,
+                    .numThreads = curStats.process.numThreads,
+                    .startTime = curStats.process.startTime,
+            };
+        }
+        processStats[curStats.process.pid] = curStats;
+    }
+    return processStats;
+}
+
+Result<void> ProcPidStat::getPidStatusLocked(ProcessStats* processStats) const {
+    std::string buffer;
+    std::string path = StringPrintf((mPath + kStatusFileFormat).c_str(), processStats->process.pid);
+    if (!ReadFileToString(path, &buffer)) {
+        return Error(ERR_FILE_OPEN_READ) << "ReadFileToString failed for " << path;
+    }
+    std::vector<std::string> lines = Split(std::move(buffer), "\n");
+    bool didReadUid = false;
+    bool didReadTgid = false;
+    for (size_t i = 0; i < lines.size(); ++i) {
+        if (lines[i].empty()) {
+            continue;
+        }
+        if (!lines[i].compare(0, 4, "Uid:")) {
+            if (didReadUid) {
+                return Error(ERR_INVALID_FILE)
+                        << "Duplicate UID line: \"" << lines[i] << "\" in file " << path;
+            }
+            std::vector<std::string> fields = Split(lines[i], "\t");
+            if (fields.size() < 2 || !ParseInt(fields[1], &processStats->uid)) {
+                return Error(ERR_INVALID_FILE)
+                        << "Invalid UID line: \"" << lines[i] << "\" in file " << path;
+            }
+            didReadUid = true;
+        } else if (!lines[i].compare(0, 5, "Tgid:")) {
+            if (didReadTgid) {
+                return Error(ERR_INVALID_FILE)
+                        << "Duplicate Tgid line: \"" << lines[i] << "\" in file" << path;
+            }
+            std::vector<std::string> fields = Split(lines[i], "\t");
+            if (fields.size() != 2 || !ParseInt(fields[1], &processStats->tgid)) {
+                return Error(ERR_INVALID_FILE)
+                        << "Invalid tgid line: \"" << lines[i] << "\" in file" << path;
+            }
+            didReadTgid = true;
+        }
+    }
+    if (!didReadUid || !didReadTgid) {
+        return Error(ERR_INVALID_FILE) << "Incomplete file " << mPath + kStatusFileFormat;
+    }
+    return {};
+}
+
+}  // namespace watchdog
+}  // namespace automotive
+}  // namespace android
diff --git a/watchdog/server/src/ProcPidStat.h b/watchdog/server/src/ProcPidStat.h
new file mode 100644
index 0000000..5fa1fb7
--- /dev/null
+++ b/watchdog/server/src/ProcPidStat.h
@@ -0,0 +1,118 @@
+/**
+ * Copyright (c) 2020, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef WATCHDOG_SERVER_SRC_PROCPIDSTAT_H_
+#define WATCHDOG_SERVER_SRC_PROCPIDSTAT_H_
+
+#include <android-base/result.h>
+#include <android-base/stringprintf.h>
+#include <gtest/gtest_prod.h>
+#include <inttypes.h>
+#include <stdint.h>
+#include <utils/Mutex.h>
+
+#include <string>
+#include <unordered_map>
+#include <vector>
+
+namespace android {
+namespace automotive {
+namespace watchdog {
+
+using android::base::StringPrintf;
+
+#define PID_FOR_INIT 1
+
+constexpr const char* kProcDirPath = "/proc";
+constexpr const char* kStatFileFormat = "/%" PRIu32 "/stat";
+constexpr const char* kTaskDirFormat = "/%" PRIu32 "/task";
+constexpr const char* kStatusFileFormat = "/%" PRIu32 "/status";
+
+struct PidStat {
+    uint32_t pid = 0;
+    std::string comm = "";
+    std::string state = "";
+    uint32_t ppid = 0;
+    uint64_t majorFaults = 0;
+    uint32_t numThreads = 0;
+    uint64_t startTime = 0;  // Useful when identifying PID/TID reuse
+};
+
+struct ProcessStats {
+    int64_t tgid = -1;                              // -1 indicates a failure to read this value
+    int64_t uid = -1;                               // -1 indicates a failure to read this value
+    PidStat process = {};                           // Aggregated stats across all the threads
+    std::unordered_map<uint32_t, PidStat> threads;  // Per-thread stat including the main thread
+};
+
+// Collector/parser for `/proc/[pid]/stat`, `/proc/[pid]/task/[tid]/stat` and /proc/[pid]/status`
+// files.
+class ProcPidStat {
+public:
+    explicit ProcPidStat(const std::string& path = kProcDirPath) :
+          mLastProcessStats({}), mPath(path) {
+        std::string pidStatPath = StringPrintf((mPath + kStatFileFormat).c_str(), PID_FOR_INIT);
+        std::string tidStatPath = StringPrintf((mPath + kTaskDirFormat + kStatFileFormat).c_str(),
+                                               PID_FOR_INIT, PID_FOR_INIT);
+        std::string pidStatusPath = StringPrintf((mPath + kStatusFileFormat).c_str(), PID_FOR_INIT);
+
+        mEnabled = !access(pidStatPath.c_str(), R_OK) && !access(tidStatPath.c_str(), R_OK) &&
+                !access(pidStatusPath.c_str(), R_OK);
+    }
+
+    // Collects pid info delta since the last collection.
+    android::base::Result<std::vector<ProcessStats>> collect();
+
+    // Called by IoPerfCollection and tests.
+    bool enabled() { return mEnabled; }
+
+private:
+    // Reads the contents of the below files:
+    // 1. Pid stat file at |mPath| + |kStatFileFormat|
+    // 2. Tid stat file at |mPath| + |kTaskDirFormat| + |kStatFileFormat|
+    android::base::Result<std::unordered_map<uint32_t, ProcessStats>> getProcessStatsLocked() const;
+
+    // Reads the tgid and real UID for the given PID from |mPath| + |kStatusFileFormat|.
+    android::base::Result<void> getPidStatusLocked(ProcessStats* processStats) const;
+
+    // Makes sure only one collection is running at any given time.
+    Mutex mMutex;
+
+    // Last dump of per-process stats. Useful for calculating the delta and identifying PID/TID
+    // reuse.
+    std::unordered_map<uint32_t, ProcessStats> mLastProcessStats GUARDED_BY(mMutex);
+
+    // True if the below files are accessible:
+    // 1. Pid stat file at |mPath| + |kTaskStatFileFormat|
+    // 2. Tid stat file at |mPath| + |kTaskDirFormat| + |kStatFileFormat|
+    // 3. Pid status file at |mPath| + |kStatusFileFormat|
+    // Otherwise, set to false.
+    bool mEnabled;
+
+    // Proc directory path. Default value is |kProcDirPath|.
+    // Updated by tests to point to a different location when needed.
+    std::string mPath;
+
+    FRIEND_TEST(ProcPidStatTest, TestValidStatFiles);
+    FRIEND_TEST(ProcPidStatTest, TestHandlesProcessTerminationBetweenScanningAndParsing);
+    FRIEND_TEST(ProcPidStatTest, TestHandlesPidTidReuse);
+};
+
+}  // namespace watchdog
+}  // namespace automotive
+}  // namespace android
+
+#endif  //  WATCHDOG_SERVER_SRC_PROCPIDSTAT_H_
diff --git a/watchdog/server/src/ProcStat.cpp b/watchdog/server/src/ProcStat.cpp
new file mode 100644
index 0000000..34b847f
--- /dev/null
+++ b/watchdog/server/src/ProcStat.cpp
@@ -0,0 +1,159 @@
+/**
+ * Copyright (c) 2020, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "carwatchdogd"
+
+#include "ProcStat.h"
+
+#include <android-base/file.h>
+#include <android-base/parseint.h>
+#include <android-base/strings.h>
+#include <log/log.h>
+
+#include <string>
+#include <vector>
+
+namespace android {
+namespace automotive {
+namespace watchdog {
+
+using android::base::Error;
+using android::base::ReadFileToString;
+using android::base::Result;
+using android::base::StartsWith;
+using base::ParseUint;
+using base::Split;
+
+namespace {
+
+bool parseCpuStats(const std::string& data, CpuStats* cpuStats) {
+    std::vector<std::string> fields = Split(data, " ");
+    if (fields.size() == 12 && fields[1].empty()) {
+        // The first cpu line will have an extra space after the first word. This will generate an
+        // empty element when the line is split on " ". Erase the extra element.
+        fields.erase(fields.begin() + 1);
+    }
+    if (fields.size() != 11 || fields[0] != "cpu" || !ParseUint(fields[1], &cpuStats->userTime) ||
+        !ParseUint(fields[2], &cpuStats->niceTime) || !ParseUint(fields[3], &cpuStats->sysTime) ||
+        !ParseUint(fields[4], &cpuStats->idleTime) ||
+        !ParseUint(fields[5], &cpuStats->ioWaitTime) || !ParseUint(fields[6], &cpuStats->irqTime) ||
+        !ParseUint(fields[7], &cpuStats->softIrqTime) ||
+        !ParseUint(fields[8], &cpuStats->stealTime) ||
+        !ParseUint(fields[9], &cpuStats->guestTime) ||
+        !ParseUint(fields[10], &cpuStats->guestNiceTime)) {
+        ALOGW("Invalid cpu line: \"%s\"", data.c_str());
+        return false;
+    }
+    return true;
+}
+
+bool parseProcsCount(const std::string& data, uint32_t* out) {
+    std::vector<std::string> fields = Split(data, " ");
+    if (fields.size() != 2 || !StartsWith(fields[0], "procs_") || !ParseUint(fields[1], out)) {
+        ALOGW("Invalid procs_ line: \"%s\"", data.c_str());
+        return false;
+    }
+    return true;
+}
+
+}  // namespace
+
+Result<ProcStatInfo> ProcStat::collect() {
+    if (!kEnabled) {
+        return Error() << "Can not access " << kPath;
+    }
+
+    Mutex::Autolock lock(mMutex);
+    const auto& info = getProcStatLocked();
+    if (!info) {
+        return Error() << "Failed to get proc stat contents: " << info.error();
+    }
+
+    ProcStatInfo delta;
+
+    delta.cpuStats.userTime = info->cpuStats.userTime - mLastCpuStats.userTime;
+    delta.cpuStats.niceTime = info->cpuStats.niceTime - mLastCpuStats.niceTime;
+    delta.cpuStats.sysTime = info->cpuStats.sysTime - mLastCpuStats.sysTime;
+    delta.cpuStats.idleTime = info->cpuStats.idleTime - mLastCpuStats.idleTime;
+    delta.cpuStats.ioWaitTime = info->cpuStats.ioWaitTime - mLastCpuStats.ioWaitTime;
+    delta.cpuStats.irqTime = info->cpuStats.irqTime - mLastCpuStats.irqTime;
+    delta.cpuStats.softIrqTime = info->cpuStats.softIrqTime - mLastCpuStats.softIrqTime;
+    delta.cpuStats.stealTime = info->cpuStats.stealTime - mLastCpuStats.stealTime;
+    delta.cpuStats.guestTime = info->cpuStats.guestTime - mLastCpuStats.guestTime;
+    delta.cpuStats.guestNiceTime = info->cpuStats.guestNiceTime - mLastCpuStats.guestNiceTime;
+
+    // Process counts are real-time values. Thus they should be reported as-is and not their deltas.
+    delta.runnableProcessesCnt = info->runnableProcessesCnt;
+    delta.ioBlockedProcessesCnt = info->ioBlockedProcessesCnt;
+
+    mLastCpuStats = info->cpuStats;
+
+    return delta;
+}
+
+Result<ProcStatInfo> ProcStat::getProcStatLocked() const {
+    std::string buffer;
+    if (!ReadFileToString(kPath, &buffer)) {
+        return Error() << "ReadFileToString failed for " << kPath;
+    }
+
+    std::vector<std::string> lines = Split(std::move(buffer), "\n");
+    ProcStatInfo info;
+    bool didReadProcsRunning = false;
+    bool didReadProcsBlocked = false;
+    for (size_t i = 0; i < lines.size(); i++) {
+        if (lines[i].empty()) {
+            continue;
+        }
+        if (!lines[i].compare(0, 4, "cpu ")) {
+            if (info.totalCpuTime() != 0) {
+                return Error() << "Duplicate `cpu .*` line in " << kPath;
+            }
+            if (!parseCpuStats(std::move(lines[i]), &info.cpuStats)) {
+                return Error() << "Failed to parse `cpu .*` line in " << kPath;
+            }
+        } else if (!lines[i].compare(0, 6, "procs_")) {
+            if (!lines[i].compare(0, 13, "procs_running")) {
+                if (didReadProcsRunning) {
+                    return Error() << "Duplicate `procs_running .*` line in " << kPath;
+                }
+                if (!parseProcsCount(std::move(lines[i]), &info.runnableProcessesCnt)) {
+                    return Error() << "Failed to parse `procs_running .*` line in " << kPath;
+                }
+                didReadProcsRunning = true;
+                continue;
+            } else if (!lines[i].compare(0, 13, "procs_blocked")) {
+                if (didReadProcsBlocked) {
+                    return Error() << "Duplicate `procs_blocked .*` line in " << kPath;
+                }
+                if (!parseProcsCount(std::move(lines[i]), &info.ioBlockedProcessesCnt)) {
+                    return Error() << "Failed to parse `procs_blocked .*` line in " << kPath;
+                }
+                didReadProcsBlocked = true;
+                continue;
+            }
+            return Error() << "Unknown procs_ line `" << lines[i] << "` in " << kPath;
+        }
+    }
+    if (info.totalCpuTime() == 0 || !didReadProcsRunning || !didReadProcsBlocked) {
+        return Error() << kPath << " is incomplete";
+    }
+    return info;
+}
+
+}  // namespace watchdog
+}  // namespace automotive
+}  // namespace android
diff --git a/watchdog/server/src/ProcStat.h b/watchdog/server/src/ProcStat.h
new file mode 100644
index 0000000..b3b3eb1
--- /dev/null
+++ b/watchdog/server/src/ProcStat.h
@@ -0,0 +1,97 @@
+/**
+ * Copyright (c) 2020, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef WATCHDOG_SERVER_SRC_PROCSTAT_H_
+#define WATCHDOG_SERVER_SRC_PROCSTAT_H_
+
+#include <android-base/result.h>
+#include <stdint.h>
+#include <utils/Mutex.h>
+
+namespace android {
+namespace automotive {
+namespace watchdog {
+
+constexpr const char* kProcStatPath = "/proc/stat";
+
+struct CpuStats {
+    uint64_t userTime = 0;       // Time spent in user mode.
+    uint64_t niceTime = 0;       // Time spent in user mode with low priority (nice).
+    uint64_t sysTime = 0;        // Time spent in system mode.
+    uint64_t idleTime = 0;       // Time spent in the idle task.
+    uint64_t ioWaitTime = 0;     // Time spent on context switching/waiting due to I/O operations.
+    uint64_t irqTime = 0;        // Time servicing interrupts.
+    uint64_t softIrqTime = 0;    // Time servicing soft interrupts.
+    uint64_t stealTime = 0;      // Stolen time (Time spent in other OS in a virtualized env).
+    uint64_t guestTime = 0;      // Time spent running a virtual CPU for guest OS.
+    uint64_t guestNiceTime = 0;  // Time spent running a niced virtual CPU for guest OS.
+};
+
+class ProcStatInfo {
+public:
+    ProcStatInfo() : cpuStats({}), runnableProcessesCnt(0), ioBlockedProcessesCnt(0) {}
+    CpuStats cpuStats;
+    uint32_t runnableProcessesCnt;
+    uint32_t ioBlockedProcessesCnt;
+
+    uint64_t totalCpuTime() const {
+        return cpuStats.userTime + cpuStats.niceTime + cpuStats.sysTime + cpuStats.idleTime +
+                cpuStats.ioWaitTime + cpuStats.irqTime + cpuStats.softIrqTime + cpuStats.stealTime +
+                cpuStats.guestTime + cpuStats.guestNiceTime;
+    }
+    uint32_t totalProcessesCnt() const { return runnableProcessesCnt + ioBlockedProcessesCnt; }
+    bool operator==(const ProcStatInfo& info) const {
+        return memcmp(&cpuStats, &info.cpuStats, sizeof(cpuStats)) == 0 &&
+                runnableProcessesCnt == info.runnableProcessesCnt &&
+                ioBlockedProcessesCnt == info.ioBlockedProcessesCnt;
+    }
+};
+
+// Collector/parser for `/proc/stat` file.
+class ProcStat {
+public:
+    explicit ProcStat(const std::string& path = kProcStatPath) :
+          mLastCpuStats({}), kEnabled(!access(path.c_str(), R_OK)), kPath(path) {}
+
+    // Collects proc stat delta since the last collection.
+    android::base::Result<ProcStatInfo> collect();
+
+    // Returns true when the proc stat file is accessible. Otherwise, returns false.
+    // Called by IoPerfCollection and tests.
+    bool enabled() { return kEnabled; }
+
+private:
+    // Reads the contents of |kPath|.
+    android::base::Result<ProcStatInfo> getProcStatLocked() const;
+
+    // Makes sure only one collection is running at any given time.
+    Mutex mMutex;
+
+    // Last dump of cpu stats from the file at |kPath|.
+    CpuStats mLastCpuStats GUARDED_BY(mMutex);
+
+    // True if |kPath| is accessible.
+    const bool kEnabled;
+
+    // Path to proc stat file. Default path is |kProcStatPath|.
+    const std::string kPath;
+};
+
+}  // namespace watchdog
+}  // namespace automotive
+}  // namespace android
+
+#endif  //  WATCHDOG_SERVER_SRC_PROCSTAT_H_
diff --git a/watchdog/server/src/ServiceManager.cpp b/watchdog/server/src/ServiceManager.cpp
new file mode 100644
index 0000000..9e48636
--- /dev/null
+++ b/watchdog/server/src/ServiceManager.cpp
@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "carwatchdogd"
+
+#include "ServiceManager.h"
+#include "WatchdogProcessService.h"
+
+#include <binder/IServiceManager.h>
+
+namespace android {
+namespace automotive {
+namespace watchdog {
+
+using android::defaultServiceManager;
+using android::String16;
+using android::automotive::watchdog::WatchdogProcessService;
+using android::base::Error;
+using android::base::Result;
+
+Result<void> ServiceManager::startService(ServiceType type, const sp<Looper>& looper) {
+    switch (type) {
+        case PROCESS_ANR_MONITOR:
+            return startProcessAnrMonitor(looper);
+        case IO_PERFORMANCE_MONITOR:
+            return startIoPerfMonitor();
+        default:
+            return Error() << "Invalid service type";
+    }
+}
+
+Result<void> ServiceManager::startProcessAnrMonitor(const sp<Looper>& looper) {
+    sp<WatchdogProcessService> service = new WatchdogProcessService(looper);
+    status_t status =
+            defaultServiceManager()
+                    ->addService(String16("android.automotive.watchdog.ICarWatchdog/default"),
+                                 service);
+    if (status != OK) {
+        return Error(status) << "Failed to start carwatchdog process ANR monitor";
+    }
+    return {};
+}
+
+Result<void> ServiceManager::startIoPerfMonitor() {
+    return Error() << "Not implemented";
+}
+
+}  // namespace watchdog
+}  // namespace automotive
+}  // namespace android
diff --git a/watchdog/server/src/ServiceManager.h b/watchdog/server/src/ServiceManager.h
new file mode 100644
index 0000000..306aa08
--- /dev/null
+++ b/watchdog/server/src/ServiceManager.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef WATCHDOG_SERVER_SRC_SERVICEMANAGER_H_
+#define WATCHDOG_SERVER_SRC_SERVICEMANAGER_H_
+
+#include <android-base/result.h>
+#include <utils/Looper.h>
+
+namespace android {
+namespace automotive {
+namespace watchdog {
+
+enum ServiceType {
+    PROCESS_ANR_MONITOR,
+    IO_PERFORMANCE_MONITOR,
+};
+
+class ServiceManager {
+public:
+public:
+    static android::base::Result<void> startService(ServiceType type,
+                                                    const android::sp<Looper>& looper);
+
+private:
+    static android::base::Result<void> startProcessAnrMonitor(const android::sp<Looper>& looper);
+    static android::base::Result<void> startIoPerfMonitor();
+};
+
+}  // namespace watchdog
+}  // namespace automotive
+}  // namespace android
+
+#endif  // WATCHDOG_SERVER_SRC_SERVICEMANAGER_H_
diff --git a/watchdog/server/src/UidIoStats.cpp b/watchdog/server/src/UidIoStats.cpp
new file mode 100644
index 0000000..6b9d109
--- /dev/null
+++ b/watchdog/server/src/UidIoStats.cpp
@@ -0,0 +1,154 @@
+/**
+ * Copyright (c) 2020, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "carwatchdogd"
+#define DEBUG false
+
+#include "UidIoStats.h"
+
+#include <android-base/file.h>
+#include <android-base/parseint.h>
+#include <android-base/stringprintf.h>
+#include <android-base/strings.h>
+#include <inttypes.h>
+#include <log/log.h>
+
+#include <string>
+#include <unordered_map>
+#include <vector>
+#include <utility>
+
+namespace android {
+namespace automotive {
+namespace watchdog {
+
+using android::base::Error;
+using android::base::ReadFileToString;
+using android::base::Result;
+using android::base::StringPrintf;
+using base::ParseUint;
+using base::Split;
+
+namespace {
+
+bool parseUidIoStats(const std::string& data, UidIoStat* uidIoStat) {
+    std::vector<std::string> fields = Split(data, " ");
+    if (fields.size() < 11 || !ParseUint(fields[0], &uidIoStat->uid) ||
+        !ParseUint(fields[1], &uidIoStat->io[FOREGROUND].rchar) ||
+        !ParseUint(fields[2], &uidIoStat->io[FOREGROUND].wchar) ||
+        !ParseUint(fields[3], &uidIoStat->io[FOREGROUND].readBytes) ||
+        !ParseUint(fields[4], &uidIoStat->io[FOREGROUND].writeBytes) ||
+        !ParseUint(fields[5], &uidIoStat->io[BACKGROUND].rchar) ||
+        !ParseUint(fields[6], &uidIoStat->io[BACKGROUND].wchar) ||
+        !ParseUint(fields[7], &uidIoStat->io[BACKGROUND].readBytes) ||
+        !ParseUint(fields[8], &uidIoStat->io[BACKGROUND].writeBytes) ||
+        !ParseUint(fields[9], &uidIoStat->io[FOREGROUND].fsync) ||
+        !ParseUint(fields[10], &uidIoStat->io[BACKGROUND].fsync)) {
+        ALOGW("Invalid uid I/O stats: \"%s\"", data.c_str());
+        return false;
+    }
+    return true;
+}
+
+}  // namespace
+
+bool IoUsage::isZero() const {
+    for (int i = 0; i < METRIC_TYPES; i++) {
+        for (int j = 0; j < UID_STATES; j++) {
+            if (metrics[i][j]) {
+                return false;
+            }
+        }
+    }
+    return true;
+}
+
+std::string IoUsage::toString() const {
+    return StringPrintf("FgRdBytes:%" PRIu64 " BgRdBytes:%" PRIu64 " FgWrBytes:%" PRIu64
+                        " BgWrBytes:%" PRIu64 " FgFsync:%" PRIu64 " BgFsync:%" PRIu64,
+                        metrics[READ_BYTES][FOREGROUND], metrics[READ_BYTES][BACKGROUND],
+                        metrics[WRITE_BYTES][FOREGROUND], metrics[WRITE_BYTES][BACKGROUND],
+                        metrics[FSYNC_COUNT][FOREGROUND], metrics[FSYNC_COUNT][BACKGROUND]);
+}
+
+Result<std::unordered_map<uint32_t, UidIoUsage>> UidIoStats::collect() {
+    if (!kEnabled) {
+        return Error() << "Can not access " << kPath;
+    }
+
+    Mutex::Autolock lock(mMutex);
+    const auto& uidIoStats = getUidIoStatsLocked();
+    if (!uidIoStats.ok() || uidIoStats->empty()) {
+        return Error() << "Failed to get UID IO stats: " << uidIoStats.error();
+    }
+
+    std::unordered_map<uint32_t, UidIoUsage> usage;
+    for (const auto& it : *uidIoStats) {
+        const UidIoStat& uidIoStat = it.second;
+        usage[uidIoStat.uid] = {};
+        struct UidIoUsage& uidUsage = usage[uidIoStat.uid];
+        uidUsage.uid = uidIoStat.uid;
+
+        int64_t fgRdDelta = uidIoStat.io[FOREGROUND].readBytes -
+                            mLastUidIoStats[uidIoStat.uid].io[FOREGROUND].readBytes;
+        int64_t bgRdDelta = uidIoStat.io[BACKGROUND].readBytes -
+                            mLastUidIoStats[uidIoStat.uid].io[BACKGROUND].readBytes;
+        int64_t fgWrDelta = uidIoStat.io[FOREGROUND].writeBytes -
+                            mLastUidIoStats[uidIoStat.uid].io[FOREGROUND].writeBytes;
+        int64_t bgWrDelta = uidIoStat.io[BACKGROUND].writeBytes -
+                            mLastUidIoStats[uidIoStat.uid].io[BACKGROUND].writeBytes;
+        int64_t fgFsDelta =
+            uidIoStat.io[FOREGROUND].fsync - mLastUidIoStats[uidIoStat.uid].io[FOREGROUND].fsync;
+        int64_t bgFsDelta =
+            uidIoStat.io[BACKGROUND].fsync - mLastUidIoStats[uidIoStat.uid].io[BACKGROUND].fsync;
+
+        uidUsage.ios.metrics[READ_BYTES][FOREGROUND] += (fgRdDelta < 0) ? 0 : fgRdDelta;
+        uidUsage.ios.metrics[READ_BYTES][BACKGROUND] += (bgRdDelta < 0) ? 0 : bgRdDelta;
+        uidUsage.ios.metrics[WRITE_BYTES][FOREGROUND] += (fgWrDelta < 0) ? 0 : fgWrDelta;
+        uidUsage.ios.metrics[WRITE_BYTES][BACKGROUND] += (bgWrDelta < 0) ? 0 : bgWrDelta;
+        uidUsage.ios.metrics[FSYNC_COUNT][FOREGROUND] += (fgFsDelta < 0) ? 0 : fgFsDelta;
+        uidUsage.ios.metrics[FSYNC_COUNT][BACKGROUND] += (bgFsDelta < 0) ? 0 : bgFsDelta;
+    }
+    mLastUidIoStats = *uidIoStats;
+    return usage;
+}
+
+Result<std::unordered_map<uint32_t, UidIoStat>> UidIoStats::getUidIoStatsLocked() const {
+    std::string buffer;
+    if (!ReadFileToString(kPath, &buffer)) {
+        return Error() << "ReadFileToString failed for " << kPath;
+    }
+
+    std::vector<std::string> ioStats = Split(std::move(buffer), "\n");
+    std::unordered_map<uint32_t, UidIoStat> uidIoStats;
+    UidIoStat uidIoStat;
+    for (size_t i = 0; i < ioStats.size(); i++) {
+        if (ioStats[i].empty() || !ioStats[i].compare(0, 4, "task")) {
+            // Skip per-task stats as CONFIG_UID_SYS_STATS_DEBUG is not set in the kernel and
+            // the collected data is aggregated only per-UID.
+            continue;
+        }
+        if (!parseUidIoStats(std::move(ioStats[i]), &uidIoStat)) {
+            return Error() << "Failed to parse the contents of " << kPath;
+        }
+        uidIoStats[uidIoStat.uid] = uidIoStat;
+    }
+    return uidIoStats;
+}
+
+}  // namespace watchdog
+}  // namespace automotive
+}  // namespace android
diff --git a/watchdog/server/src/UidIoStats.h b/watchdog/server/src/UidIoStats.h
new file mode 100644
index 0000000..f344ef2
--- /dev/null
+++ b/watchdog/server/src/UidIoStats.h
@@ -0,0 +1,125 @@
+/**
+ * Copyright (c) 2020, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef WATCHDOG_SERVER_SRC_UIDIOSTATS_H_
+#define WATCHDOG_SERVER_SRC_UIDIOSTATS_H_
+
+#include <android-base/result.h>
+#include <stdint.h>
+#include <utils/Mutex.h>
+
+#include <string>
+#include <unordered_map>
+
+namespace android {
+namespace automotive {
+namespace watchdog {
+
+constexpr const char* kUidIoStatsPath = "/proc/uid_io/stats";
+
+enum UidState {
+    FOREGROUND = 0,
+    BACKGROUND,
+    UID_STATES,
+};
+
+enum MetricType {
+    READ_BYTES = 0,
+    WRITE_BYTES,
+    FSYNC_COUNT,
+    METRIC_TYPES,
+};
+
+struct IoStat {
+    uint64_t rchar = 0;       // characters read
+    uint64_t wchar = 0;       // characters written
+    uint64_t readBytes = 0;   // bytes read (from storage layer)
+    uint64_t writeBytes = 0;  // bytes written (to storage layer)
+    uint64_t fsync = 0;       // number of fsync syscalls
+};
+
+struct UidIoStat {
+    uint32_t uid = 0;  // linux user id
+    IoStat io[UID_STATES] = {{}};
+};
+
+class IoUsage {
+  public:
+    IoUsage() : metrics{{0}} {};
+    IoUsage(uint64_t fgRdBytes, uint64_t bgRdBytes, uint64_t fgWrBytes, uint64_t bgWrBytes,
+            uint64_t fgFsync, uint64_t bgFsync) {
+        metrics[READ_BYTES][FOREGROUND] = fgRdBytes;
+        metrics[READ_BYTES][BACKGROUND] = bgRdBytes;
+        metrics[WRITE_BYTES][FOREGROUND] = fgWrBytes;
+        metrics[WRITE_BYTES][BACKGROUND] = bgWrBytes;
+        metrics[FSYNC_COUNT][FOREGROUND] = fgFsync;
+        metrics[FSYNC_COUNT][BACKGROUND] = bgFsync;
+    }
+    bool operator==(const IoUsage& usage) const {
+        return memcmp(&metrics, &usage.metrics, sizeof(metrics)) == 0;
+    }
+    uint64_t sumReadBytes() const {
+        return metrics[READ_BYTES][FOREGROUND] + metrics[READ_BYTES][BACKGROUND];
+    }
+    uint64_t sumWriteBytes() const {
+        return metrics[WRITE_BYTES][FOREGROUND] + metrics[WRITE_BYTES][BACKGROUND];
+    }
+    bool isZero() const;
+    std::string toString() const;
+    uint64_t metrics[METRIC_TYPES][UID_STATES];
+};
+
+struct UidIoUsage {
+    uint32_t uid = 0;
+    IoUsage ios = {};
+};
+
+class UidIoStats {
+  public:
+    explicit UidIoStats(const std::string& path = kUidIoStatsPath)
+        : kEnabled(!access(path.c_str(), R_OK)), kPath(path) {}
+
+    // Collects the I/O usage since the last collection.
+    android::base::Result<std::unordered_map<uint32_t, UidIoUsage>> collect();
+
+    // Returns true when the uid_io stats file is accessible. Otherwise, returns false.
+    // Called by IoPerfCollection and tests.
+    bool enabled() {
+        return kEnabled;
+    }
+
+  private:
+    // Reads the contents of |kPath|.
+    android::base::Result<std::unordered_map<uint32_t, UidIoStat>> getUidIoStatsLocked() const;
+
+    // Makes sure only one collection is running at any given time.
+    Mutex mMutex;
+
+    // Last dump from the file at |kPath|.
+    std::unordered_map<uint32_t, UidIoStat> mLastUidIoStats GUARDED_BY(mMutex);
+
+    // True if kPath is accessible.
+    const bool kEnabled;
+
+    // Path to uid_io stats file. Default path is |kUidIoStatsPath|.
+    const std::string kPath;
+};
+
+}  // namespace watchdog
+}  // namespace automotive
+}  // namespace android
+
+#endif  //  WATCHDOG_SERVER_SRC_UIDIOSTATS_H_
diff --git a/watchdog/server/src/WatchdogProcessService.cpp b/watchdog/server/src/WatchdogProcessService.cpp
new file mode 100644
index 0000000..fee09a9
--- /dev/null
+++ b/watchdog/server/src/WatchdogProcessService.cpp
@@ -0,0 +1,426 @@
+/**
+ * Copyright (c) 2020, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "carwatchdogd"
+#define DEBUG false
+
+#include "WatchdogProcessService.h"
+
+#include <android-base/chrono_utils.h>
+#include <android-base/stringprintf.h>
+#include <binder/IPCThreadState.h>
+#include <private/android_filesystem_config.h>
+
+namespace android {
+namespace automotive {
+namespace watchdog {
+
+using std::literals::chrono_literals::operator""s;
+using android::base::Error;
+using android::base::Result;
+using android::base::StringPrintf;
+using android::binder::Status;
+
+static const std::vector<TimeoutLength> kTimeouts = {TimeoutLength::TIMEOUT_CRITICAL,
+                                                     TimeoutLength::TIMEOUT_MODERATE,
+                                                     TimeoutLength::TIMEOUT_NORMAL};
+
+static std::chrono::nanoseconds timeoutToDurationNs(const TimeoutLength& timeout) {
+    switch (timeout) {
+        case TimeoutLength::TIMEOUT_CRITICAL:
+            return 3s;  // 3s and no buffer time.
+        case TimeoutLength::TIMEOUT_MODERATE:
+            return 6s;  // 5s + 1s as buffer time.
+        case TimeoutLength::TIMEOUT_NORMAL:
+            return 12s;  // 10s + 2s as buffer time.
+    }
+}
+
+static Status checkSystemPermission() {
+    uid_t callingUid = IPCThreadState::self()->getCallingUid();
+    if (callingUid != AID_SYSTEM) {
+        return Status::fromExceptionCode(Status::EX_SECURITY,
+                                         "Calling process does not have proper privilege.");
+    }
+    return Status::ok();
+}
+
+WatchdogProcessService::WatchdogProcessService(const sp<Looper>& handlerLooper) :
+      mHandlerLooper(handlerLooper), mLastSessionId(0) {
+    mMessageHandler = new MessageHandlerImpl(this);
+    for (const auto& timeout : kTimeouts) {
+        mClients.insert(std::make_pair(timeout, std::vector<ClientInfo>()));
+        mPingedClients.insert(std::make_pair(timeout, PingedClientSet()));
+    }
+}
+
+Status WatchdogProcessService::registerClient(const sp<ICarWatchdogClient>& client,
+                                              TimeoutLength timeout) {
+    Mutex::Autolock lock(mMutex);
+    return registerClientLocked(client, timeout, ClientType::Regular);
+}
+
+Status WatchdogProcessService::unregisterClient(const sp<ICarWatchdogClient>& client) {
+    Mutex::Autolock lock(mMutex);
+    sp<IBinder> binder = asBinder(client);
+    // kTimeouts is declared as global static constant to cover all kinds of timeout (CRITICAL,
+    // MODERATE, NORMAL).
+    Status status = unregisterClientLocked(kTimeouts, binder);
+    if (!status.isOk()) {
+        ALOGW("Cannot unregister the client: %s", status.exceptionMessage().c_str());
+        return status;
+    }
+    return Status::ok();
+}
+
+Status WatchdogProcessService::registerMediator(const sp<ICarWatchdogClient>& mediator) {
+    Status status = checkSystemPermission();
+    if (!status.isOk()) {
+        return status;
+    }
+    Mutex::Autolock lock(mMutex);
+    // Mediator's timeout is always TIMEOUT_NORMAL.
+    return registerClientLocked(mediator, TimeoutLength::TIMEOUT_NORMAL, ClientType::Mediator);
+}
+
+Status WatchdogProcessService::unregisterMediator(const sp<ICarWatchdogClient>& mediator) {
+    Status status = checkSystemPermission();
+    if (!status.isOk()) {
+        return status;
+    }
+    std::vector<TimeoutLength> timeouts = {TimeoutLength::TIMEOUT_NORMAL};
+    sp<IBinder> binder = asBinder(mediator);
+    Mutex::Autolock lock(mMutex);
+    status = unregisterClientLocked(timeouts, binder);
+    if (!status.isOk()) {
+        ALOGW("Cannot unregister the mediator. The mediator has not been registered.");
+        return Status::fromExceptionCode(Status::EX_ILLEGAL_ARGUMENT,
+                                         "The mediator has not been registered.");
+    }
+    return Status::ok();
+}
+
+Status WatchdogProcessService::registerMonitor(const sp<ICarWatchdogMonitor>& monitor) {
+    Status status = checkSystemPermission();
+    if (!status.isOk()) {
+        return status;
+    }
+    Mutex::Autolock lock(mMutex);
+    if (mMonitor != nullptr) {
+        ALOGW("Cannot register the monitor. The other monitor is already registered.");
+        return Status::fromExceptionCode(Status::EX_ILLEGAL_ARGUMENT,
+                                         "The other monitor is already registered.");
+    }
+    sp<IBinder> binder = asBinder(monitor);
+    status_t ret = binder->linkToDeath(this);
+    if (ret != OK) {
+        ALOGW("Cannot register the monitor. The monitor is dead.");
+        return Status::fromExceptionCode(Status::EX_ILLEGAL_STATE, "The monitor is dead.");
+    }
+    mMonitor = monitor;
+    return Status::ok();
+}
+
+Status WatchdogProcessService::unregisterMonitor(const sp<ICarWatchdogMonitor>& monitor) {
+    Status status = checkSystemPermission();
+    if (!status.isOk()) {
+        return status;
+    }
+    Mutex::Autolock lock(mMutex);
+    if (mMonitor != monitor) {
+        ALOGW("Cannot unregister the monitor. The monitor has not been registered.");
+        return Status::fromExceptionCode(Status::EX_ILLEGAL_ARGUMENT,
+                                         "The monitor has not been registered.");
+    }
+    sp<IBinder> binder = asBinder(monitor);
+    binder->unlinkToDeath(this);
+    mMonitor = nullptr;
+    return Status::ok();
+}
+
+Status WatchdogProcessService::tellClientAlive(const sp<ICarWatchdogClient>& client,
+                                               int32_t sessionId) {
+    Mutex::Autolock lock(mMutex);
+    return tellClientAliveLocked(client, sessionId);
+}
+
+Status WatchdogProcessService::tellMediatorAlive(const sp<ICarWatchdogClient>& mediator,
+                                                 const std::vector<int32_t>& clientsNotResponding,
+                                                 int32_t sessionId) {
+    Status status;
+    {
+        Mutex::Autolock lock(mMutex);
+        status = tellClientAliveLocked(mediator, sessionId);
+    }
+    if (status.isOk()) {
+        dumpAndKillAllProcesses(clientsNotResponding);
+    }
+    return status;
+}
+
+Status WatchdogProcessService::tellDumpFinished(const sp<ICarWatchdogMonitor>& monitor,
+                                                int32_t pid) {
+    // TODO(b/148223510): implement this method.
+    (void)monitor;
+    (void)pid;
+    return Status::ok();
+}
+
+Status WatchdogProcessService::notifyPowerCycleChange(PowerCycle cycle) {
+    // TODO(b/148223510): implement this method.
+    (void)cycle;
+    return Status::ok();
+}
+
+Status WatchdogProcessService::notifyUserStateChange(int32_t userId, UserState state) {
+    // TODO(b/148223510): implement this method.
+    (void)userId;
+    (void)state;
+    return Status::ok();
+}
+
+status_t WatchdogProcessService::dump(int fd, const Vector<String16>&) {
+    // TODO(b/148223510): implement this method.
+    (void)fd;
+    return NO_ERROR;
+}
+
+void WatchdogProcessService::doHealthCheck(int what) {
+    mHandlerLooper->removeMessages(mMessageHandler, what);
+    const TimeoutLength timeout = static_cast<TimeoutLength>(what);
+    std::vector<ClientInfo> clientsToCheck;
+    PingedClientSet& pingedClients = mPingedClients[timeout];
+
+    dumpAndKillClientsIfNotResponding(timeout);
+
+    /* Generates a temporary/local vector containing clients.
+     * Using a local copy may send unnecessary ping messages to clients after they are unregistered.
+     * Clients should be able to handle them.
+     */
+    {
+        Mutex::Autolock lock(mMutex);
+        clientsToCheck = mClients[timeout];
+        pingedClients.clear();
+    }
+
+    for (const auto& clientInfo : clientsToCheck) {
+        int32_t sessionId = getNewSessionId();
+        PingedClient targetClient(clientInfo.client, sessionId);
+        {
+            Mutex::Autolock lock(mMutex);
+            pingedClients.insert(targetClient);
+        }
+        Status status = clientInfo.client->checkIfAlive(sessionId, timeout);
+        if (!status.isOk()) {
+            ALOGW("Sending a ping message to client(pid: %d) failed: %s", clientInfo.pid,
+                  status.exceptionMessage().c_str());
+            {
+                Mutex::Autolock lock(mMutex);
+                pingedClients.erase(targetClient);
+            }
+        }
+    }
+    // Though the size of pingedClients is a more specific measure, clientsToCheck is used as a
+    // conservative approach.
+    if (clientsToCheck.size() > 0) {
+        auto durationNs = timeoutToDurationNs(timeout);
+        mHandlerLooper->sendMessageDelayed(durationNs.count(), mMessageHandler, Message(what));
+    }
+}
+
+void WatchdogProcessService::terminate() {
+    Mutex::Autolock lock(mMutex);
+    for (const auto& timeout : kTimeouts) {
+        std::vector<ClientInfo>& clients = mClients[timeout];
+        for (auto it = clients.begin(); it != clients.end();) {
+            sp<IBinder> binder = asBinder((*it).client);
+            binder->unlinkToDeath(this);
+            it = clients.erase(it);
+        }
+    }
+}
+
+void WatchdogProcessService::binderDied(const wp<IBinder>& who) {
+    Mutex::Autolock lock(mMutex);
+    IBinder* binder = who.unsafe_get();
+    // Check if dead binder is monitor.
+    sp<IBinder> monitor = asBinder(mMonitor);
+    if (monitor == binder) {
+        mMonitor = nullptr;
+        ALOGI("The monitor has died.");
+        return;
+    }
+    findClientAndProcessLocked(kTimeouts, binder,
+                               [&](std::vector<ClientInfo>& clients,
+                                   std::vector<ClientInfo>::const_iterator it) {
+                                   clients.erase(it);
+                               });
+}
+
+bool WatchdogProcessService::isRegisteredLocked(const sp<ICarWatchdogClient>& client) {
+    sp<IBinder> binder = asBinder(client);
+    return findClientAndProcessLocked(kTimeouts, binder, nullptr);
+}
+
+Status WatchdogProcessService::registerClientLocked(const sp<ICarWatchdogClient>& client,
+                                                    TimeoutLength timeout, ClientType clientType) {
+    const char* clientName = clientType == ClientType::Regular ? "client" : "mediator";
+    if (isRegisteredLocked(client)) {
+        std::string errorStr = StringPrintf("The %s is already registered.", clientName);
+        const char* errorCause = errorStr.c_str();
+        ALOGW("Cannot register the %s. %s", clientName, errorCause);
+        return Status::fromExceptionCode(Status::EX_ILLEGAL_ARGUMENT, errorCause);
+    }
+    sp<IBinder> binder = asBinder(client);
+    status_t status = binder->linkToDeath(this);
+    if (status != OK) {
+        std::string errorStr = StringPrintf("The %s is dead.", clientName);
+        const char* errorCause = errorStr.c_str();
+        ALOGW("Cannot register the %s. %s", clientName, errorCause);
+        return Status::fromExceptionCode(Status::EX_ILLEGAL_STATE, errorCause);
+    }
+    std::vector<ClientInfo>& clients = mClients[timeout];
+    pid_t callingPid = IPCThreadState::self()->getCallingPid();
+    clients.push_back(ClientInfo(client, callingPid, clientType));
+
+    // If the client array becomes non-empty, start health checking.
+    if (clients.size() == 1) {
+        startHealthChecking(timeout);
+    }
+    return Status::ok();
+}
+
+Status WatchdogProcessService::unregisterClientLocked(const std::vector<TimeoutLength>& timeouts,
+                                                      sp<IBinder> binder) {
+    bool result = findClientAndProcessLocked(timeouts, binder,
+                                             [&](std::vector<ClientInfo>& clients,
+                                                 std::vector<ClientInfo>::const_iterator it) {
+                                                 binder->unlinkToDeath(this);
+                                                 clients.erase(it);
+                                             });
+    return result ? Status::ok()
+                  : Status::fromExceptionCode(Status::EX_ILLEGAL_ARGUMENT,
+                                              "The client has not been registered.");
+}
+
+Status WatchdogProcessService::tellClientAliveLocked(const sp<ICarWatchdogClient>& client,
+                                                     int32_t sessionId) {
+    for (const auto& timeout : kTimeouts) {
+        PingedClientSet& clients = mPingedClients[timeout];
+        PingedClient respondingClient(client, sessionId);
+        PingedClientSet::const_iterator it = clients.find(respondingClient);
+        if (it == clients.cend()) {
+            continue;
+        }
+        clients.erase(it);
+        return Status::ok();
+    }
+    return Status::fromExceptionCode(Status::EX_ILLEGAL_ARGUMENT,
+                                     "The client is not registered or the session ID is not found");
+}
+
+bool WatchdogProcessService::findClientAndProcessLocked(const std::vector<TimeoutLength> timeouts,
+                                                        const sp<IBinder> binder,
+                                                        const Processor& processor) {
+    for (const auto& timeout : timeouts) {
+        std::vector<ClientInfo>& clients = mClients[timeout];
+        for (auto it = clients.begin(); it != clients.end(); it++) {
+            if (asBinder((*it).client) != binder) {
+                continue;
+            }
+            if (processor != nullptr) {
+                processor(clients, it);
+            }
+            return true;
+        }
+    }
+    return false;
+}
+
+Result<void> WatchdogProcessService::startHealthChecking(TimeoutLength timeout) {
+    int what = static_cast<int>(timeout);
+    auto durationNs = timeoutToDurationNs(timeout);
+    mHandlerLooper->sendMessageDelayed(durationNs.count(), mMessageHandler, Message(what));
+    return {};
+}
+
+Result<void> WatchdogProcessService::dumpAndKillClientsIfNotResponding(TimeoutLength timeout) {
+    std::vector<int32_t> processIds;
+    {
+        Mutex::Autolock lock(mMutex);
+        PingedClientSet& clients = mPingedClients[timeout];
+        for (PingedClientSet::const_iterator it = clients.cbegin(); it != clients.cend(); it++) {
+            pid_t pid = -1;
+            sp<IBinder> binder = asBinder((*it).client);
+            std::vector<TimeoutLength> timeouts = {timeout};
+            findClientAndProcessLocked(timeouts, binder,
+                                       [&](std::vector<ClientInfo>& clients,
+                                           std::vector<ClientInfo>::const_iterator it) {
+                                           pid = (*it).pid;
+                                           clients.erase(it);
+                                       });
+            if (pid != -1) {
+                processIds.push_back(pid);
+            }
+        }
+    }
+    return dumpAndKillAllProcesses(processIds);
+}
+
+// TODO(ericjeong): do it quickly or do it in a separate thread.
+Result<void> WatchdogProcessService::dumpAndKillAllProcesses(
+        const std::vector<int32_t>& processesNotResponding) {
+    Mutex::Autolock lock(mMutex);
+    if (mMonitor == nullptr) {
+        std::string errorMsg = "Cannot dump and kill processes: Monitor is not set";
+        ALOGW("%s", errorMsg.c_str());
+        return Error() << errorMsg;
+    }
+    // TODO(b/149346622): Change the interface of ICarWatchdogMonitor and follow up here.
+    for (auto pid : processesNotResponding) {
+        mMonitor->onClientNotResponding(nullptr, pid);
+        ALOGD("Dumping and killing process(%d) is requested.", pid);
+    }
+    return {};
+}
+
+int32_t WatchdogProcessService::getNewSessionId() {
+    // Make sure that session id is always positive number.
+    if (++mLastSessionId <= 0) {
+        mLastSessionId = 1;
+    }
+    return mLastSessionId;
+}
+
+WatchdogProcessService::MessageHandlerImpl::MessageHandlerImpl(
+        const sp<WatchdogProcessService>& service) :
+      mService(service) {}
+
+void WatchdogProcessService::MessageHandlerImpl::handleMessage(const Message& message) {
+    switch (message.what) {
+        case static_cast<int>(TimeoutLength::TIMEOUT_CRITICAL):
+        case static_cast<int>(TimeoutLength::TIMEOUT_MODERATE):
+        case static_cast<int>(TimeoutLength::TIMEOUT_NORMAL):
+            mService->doHealthCheck(message.what);
+            break;
+        default:
+            ALOGW("Unknown message: %d", message.what);
+    }
+}
+
+}  // namespace watchdog
+}  // namespace automotive
+}  // namespace android
diff --git a/watchdog/server/src/WatchdogProcessService.h b/watchdog/server/src/WatchdogProcessService.h
new file mode 100644
index 0000000..eac28fd
--- /dev/null
+++ b/watchdog/server/src/WatchdogProcessService.h
@@ -0,0 +1,137 @@
+/**
+ * Copyright (c) 2020, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef WATCHDOG_SERVER_SRC_WATCHDOGPROCESSSERVICE_H_
+#define WATCHDOG_SERVER_SRC_WATCHDOGPROCESSSERVICE_H_
+
+#include <android-base/result.h>
+#include <android/automotive/watchdog/BnCarWatchdog.h>
+#include <utils/Looper.h>
+#include <utils/Mutex.h>
+
+#include <unordered_map>
+#include <unordered_set>
+#include <vector>
+
+namespace android {
+namespace automotive {
+namespace watchdog {
+
+class WatchdogProcessService : public BnCarWatchdog, public IBinder::DeathRecipient {
+public:
+    explicit WatchdogProcessService(const android::sp<Looper>& handlerLooper);
+
+    status_t dump(int fd, const Vector<String16>& args) override;
+
+    binder::Status registerClient(const sp<ICarWatchdogClient>& client,
+                                  TimeoutLength timeout) override;
+    binder::Status unregisterClient(const sp<ICarWatchdogClient>& client) override;
+    binder::Status registerMediator(const sp<ICarWatchdogClient>& mediator) override;
+    binder::Status unregisterMediator(const sp<ICarWatchdogClient>& mediator) override;
+    binder::Status registerMonitor(const sp<ICarWatchdogMonitor>& monitor) override;
+    binder::Status unregisterMonitor(const sp<ICarWatchdogMonitor>& monitor) override;
+    binder::Status tellClientAlive(const sp<ICarWatchdogClient>& client,
+                                   int32_t sessionId) override;
+    binder::Status tellMediatorAlive(const sp<ICarWatchdogClient>& mediator,
+                                     const std::vector<int32_t>& clientsNotResponding,
+                                     int32_t sessionId) override;
+    binder::Status tellDumpFinished(const android::sp<ICarWatchdogMonitor>& monitor,
+                                    int32_t pid) override;
+    binder::Status notifyPowerCycleChange(PowerCycle cycle) override;
+    binder::Status notifyUserStateChange(int32_t userId, UserState state) override;
+
+    void doHealthCheck(int what);
+    void terminate();
+
+private:
+    enum ClientType {
+        Regular,
+        Mediator,
+    };
+
+    struct ClientInfo {
+        ClientInfo(const android::sp<ICarWatchdogClient>& client, pid_t pid, ClientType type) :
+              client(client), pid(pid), type(type) {}
+
+        android::sp<ICarWatchdogClient> client;
+        pid_t pid;
+        ClientType type;
+    };
+
+    struct PingedClient {
+        PingedClient(const android::sp<ICarWatchdogClient>& client, int32_t sessionId) :
+              client(client), sessionId(sessionId) {}
+
+        bool operator==(const PingedClient& other) const { return sessionId == other.sessionId; }
+
+        android::sp<ICarWatchdogClient> client;
+        int32_t sessionId;
+    };
+
+    struct PingedClientHash {
+        std::size_t operator()(const PingedClient& pingedClient) const {
+            return pingedClient.sessionId;
+        }
+    };
+
+    typedef std::unordered_set<PingedClient, PingedClientHash> PingedClientSet;
+
+    class MessageHandlerImpl : public MessageHandler {
+    public:
+        explicit MessageHandlerImpl(const android::sp<WatchdogProcessService>& service);
+
+        void handleMessage(const Message& message) override;
+
+    private:
+        android::sp<WatchdogProcessService> mService;
+    };
+
+private:
+    void binderDied(const android::wp<IBinder>& who) override;
+
+    binder::Status unregisterClientLocked(const std::vector<TimeoutLength>& timeouts,
+                                          android::sp<IBinder> binder);
+    bool isRegisteredLocked(const android::sp<ICarWatchdogClient>& client);
+    binder::Status registerClientLocked(const android::sp<ICarWatchdogClient>& client,
+                                        TimeoutLength timeout, ClientType clientType);
+    binder::Status tellClientAliveLocked(const android::sp<ICarWatchdogClient>& client,
+                                         int32_t sessionId);
+    base::Result<void> startHealthChecking(TimeoutLength timeout);
+    base::Result<void> dumpAndKillClientsIfNotResponding(TimeoutLength timeout);
+    base::Result<void> dumpAndKillAllProcesses(const std::vector<int32_t>& processesNotResponding);
+    int32_t getNewSessionId();
+
+    using Processor =
+            std::function<void(std::vector<ClientInfo>&, std::vector<ClientInfo>::const_iterator)>;
+    bool findClientAndProcessLocked(const std::vector<TimeoutLength> timeouts,
+                                    const android::sp<IBinder> binder, const Processor& processor);
+
+private:
+    Mutex mMutex;
+    sp<Looper> mHandlerLooper;
+    android::sp<MessageHandlerImpl> mMessageHandler;
+    std::unordered_map<TimeoutLength, std::vector<ClientInfo>> mClients GUARDED_BY(mMutex);
+    std::unordered_map<TimeoutLength, PingedClientSet> mPingedClients GUARDED_BY(mMutex);
+    android::sp<ICarWatchdogMonitor> mMonitor GUARDED_BY(mMutex);
+    // mLastSessionId is accessed only within main thread. No need for mutual-exclusion.
+    int32_t mLastSessionId;
+};
+
+}  // namespace watchdog
+}  // namespace automotive
+}  // namespace android
+
+#endif  // WATCHDOG_SERVER_SRC_WATCHDOGPROCESSSERVICE_H_
diff --git a/watchdog/server/src/main.cpp b/watchdog/server/src/main.cpp
new file mode 100644
index 0000000..a49e071
--- /dev/null
+++ b/watchdog/server/src/main.cpp
@@ -0,0 +1,89 @@
+/**
+ * Copyright (c) 2020, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "carwatchdogd"
+
+#include "ServiceManager.h"
+
+#include <android-base/result.h>
+#include <binder/IPCThreadState.h>
+#include <binder/IServiceManager.h>
+#include <binder/ProcessState.h>
+#include <log/log.h>
+#include <signal.h>
+#include <utils/Looper.h>
+
+using android::IPCThreadState;
+using android::Looper;
+using android::ProcessState;
+using android::sp;
+using android::automotive::watchdog::ServiceManager;
+using android::automotive::watchdog::ServiceType;
+using android::base::Result;
+
+namespace {
+
+void sigHandler(int sig) {
+    IPCThreadState::self()->stopProcess();
+    // TODO(ericjeong): Give services a chance to handle SIGTERM.
+    ALOGW("car watchdog server terminated on receiving signal %d.", sig);
+    exit(1);
+}
+
+void registerSigHandler() {
+    struct sigaction sa;
+    sigemptyset(&sa.sa_mask);
+    sa.sa_flags = 0;
+    sa.sa_handler = sigHandler;
+    sigaction(SIGQUIT, &sa, nullptr);
+    sigaction(SIGTERM, &sa, nullptr);
+}
+
+}  // namespace
+
+int main(int /*argc*/, char** /*argv*/) {
+    const size_t maxBinderThreadCount = 16;
+    // Set up the looper
+    sp<Looper> looper(Looper::prepare(/*opts=*/0));
+
+    // Set up the binder
+    sp<ProcessState> ps(ProcessState::self());
+    ps->setThreadPoolMaxThreadCount(maxBinderThreadCount);
+    ps->startThreadPool();
+    ps->giveThreadPoolName();
+    IPCThreadState::self()->disableBackgroundScheduling(true);
+
+    // Start the services
+    ServiceType supportedServices[] = {ServiceType::PROCESS_ANR_MONITOR};
+    for (const auto type : supportedServices) {
+        auto result = ServiceManager::startService(type, looper);
+        if (!result.ok()) {
+            ALOGE("%s", result.error().message().c_str());
+            exit(result.error().code());
+        }
+    }
+
+    registerSigHandler();
+
+    // Loop forever -- the health check runs on this thread in a handler, and the binder calls
+    // remain responsive in their pool of threads.
+    while (true) {
+        looper->pollAll(/*timeoutMillis=*/-1);
+    }
+    ALOGW("Car watchdog server escaped from its loop.");
+
+    return 0;
+}
diff --git a/watchdog/server/tests/IoPerfCollectionTest.cpp b/watchdog/server/tests/IoPerfCollectionTest.cpp
new file mode 100644
index 0000000..a68aa5d
--- /dev/null
+++ b/watchdog/server/tests/IoPerfCollectionTest.cpp
@@ -0,0 +1,317 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "IoPerfCollection.h"
+
+#include <android-base/file.h>
+#include <cutils/android_filesystem_config.h>
+
+#include <algorithm>
+
+#include "UidIoStats.h"
+#include "gmock/gmock.h"
+
+namespace android {
+namespace automotive {
+namespace watchdog {
+
+using ::android::base::WriteStringToFile;
+
+namespace {
+bool isEqual(const UidIoPerfData& lhs, const UidIoPerfData& rhs) {
+    if (lhs.topNReads.size() != rhs.topNReads.size() ||
+        lhs.topNWrites.size() != rhs.topNWrites.size()) {
+        return false;
+    }
+    return std::equal(lhs.topNReads.begin(), lhs.topNReads.end(), rhs.topNReads.begin(),
+                      [&](const UidIoPerfData::Stats& l, const UidIoPerfData::Stats& r) -> bool {
+                          bool isEqual = l.userId == r.userId && l.packageName == r.packageName;
+                          for (int i = 0; i < UID_STATES; ++i) {
+                              isEqual &= l.bytes[i] == r.bytes[i] &&
+                                         l.bytesPercent[i] == r.bytesPercent[i] &&
+                                         l.fsync[i] == r.fsync[i] &&
+                                         l.fsyncPercent[i] == r.fsyncPercent[i];
+                          }
+                          return isEqual;
+                      });
+}
+
+bool isEqual(const SystemIoPerfData& lhs, const SystemIoPerfData& rhs) {
+    return lhs.cpuIoWaitTime == rhs.cpuIoWaitTime && lhs.cpuIoWaitPercent == rhs.cpuIoWaitPercent &&
+            lhs.ioBlockedProcessesCnt == rhs.ioBlockedProcessesCnt &&
+            lhs.ioBlockedProcessesPercent == rhs.ioBlockedProcessesPercent;
+}
+
+}  // namespace
+
+TEST(IoPerfCollectionTest, TestValidUidIoStatFile) {
+    // Format: uid fgRdChar fgWrChar fgRdBytes fgWrBytes bgRdChar bgWrChar bgRdBytes bgWrBytes
+    // fgFsync bgFsync
+    constexpr char firstSnapshot[] =
+        "1001234 5000 1000 3000 500 0 0 0 0 20 0\n"
+        "1005678 500 100 30 50 300 400 100 200 45 60\n"
+        "1009 0 0 0 0 40000 50000 20000 30000 0 300\n"
+        "1001000 4000 3000 2000 1000 400 300 200 100 50 10\n";
+
+    struct UidIoPerfData expectedUidIoPerfData = {};
+    expectedUidIoPerfData.topNReads.push_back({
+        // uid: 1009
+        .userId = 0,
+        .packageName = "mount",
+        .bytes = {0, 20000},
+        .bytesPercent = {0, (20000.0 / 20300.0) * 100},
+        .fsync = {0, 300},
+        .fsyncPercent = {0, (300.0 / 370.0) * 100},
+    });
+    expectedUidIoPerfData.topNReads.push_back({
+        // uid: 1001234
+        .userId = 10,
+        .packageName = "1001234",
+        .bytes = {3000, 0},
+        .bytesPercent = {(3000.0 / 5030.0) * 100, 0},
+        .fsync = {20, 0},
+        .fsyncPercent = {(20.0 / 115.0) * 100, 0},
+    });
+    expectedUidIoPerfData.topNWrites.push_back({
+        // uid: 1009
+        .userId = 0,
+        .packageName = "mount",
+        .bytes = {0, 20000},
+        .bytesPercent = {0, (30000.0 / 30300.0) * 100},
+        .fsync = {0, 300},
+        .fsyncPercent = {0, (300.0 / 370.0) * 100},
+    });
+    expectedUidIoPerfData.topNWrites.push_back({
+        // uid: 1001000
+        .userId = 10,
+        .packageName = "shared:android.uid.system",
+        .bytes = {1000, 100},
+        .bytesPercent = {(1000.0 / 1550.0) * 100, (100.0 / 30300.0) * 100},
+        .fsync = {50, 10},
+        .fsyncPercent = {(50.0 / 115.0) * 100, (10.0 / 370.0) * 100},
+    });
+
+    TemporaryFile tf;
+    ASSERT_NE(tf.fd, -1);
+    ASSERT_TRUE(WriteStringToFile(firstSnapshot, tf.path));
+
+    IoPerfCollection collector(tf.path, "");
+    collector.mTopNStatsPerCategory = 2;
+    ASSERT_TRUE(collector.mUidIoStats.enabled()) << "Temporary file is inaccessible";
+
+    struct UidIoPerfData actualUidIoPerfData = {};
+    auto ret = collector.collectUidIoPerfDataLocked(&actualUidIoPerfData);
+    ASSERT_RESULT_OK(ret);
+    EXPECT_TRUE(isEqual(expectedUidIoPerfData, actualUidIoPerfData))
+        << "First snapshot doesn't match.\nExpected:\n"
+        << toString(expectedUidIoPerfData) << "\nActual:\n"
+        << toString(actualUidIoPerfData);
+
+    constexpr char secondSnapshot[] =
+        "1001234 10000 2000 7000 950 0 0 0 0 45 0\n"
+        "1005678 600 100 40 50 1000 1000 1000 600 50 70\n"
+        "1003456 300 500 200 300 0 0 0 0 50 0\n"
+        "1001000 400 300 200 100 40 30 20 10 5 1\n";
+
+    expectedUidIoPerfData = {};
+    expectedUidIoPerfData.topNReads.push_back({
+        // uid: 1001234
+        .userId = 10,
+        .packageName = "1001234",
+        .bytes = {4000, 0},
+        .bytesPercent = {(4000.0 / 4210.0) * 100, 0},
+        .fsync = {25, 0},
+        .fsyncPercent = {(25.0 / 80.0) * 100, 0},
+    });
+    expectedUidIoPerfData.topNReads.push_back({
+        // uid: 1005678
+        .userId = 10,
+        .packageName = "1005678",
+        .bytes = {10, 900},
+        .bytesPercent = {(10.0 / 4210.0) * 100, (900.0 / 900.0) * 100},
+        .fsync = {5, 10},
+        .fsyncPercent = {(5.0 / 80.0) * 100, (10.0 / 10.0) * 100},
+    });
+    expectedUidIoPerfData.topNWrites.push_back({
+        // uid: 1001234
+        .userId = 0,
+        .packageName = "1001234",
+        .bytes = {450, 0},
+        .bytesPercent = {(450.0 / 750.0) * 100, 0},
+        .fsync = {25, 0},
+        .fsyncPercent = {(25.0 / 80.0) * 100, 0},
+    });
+    expectedUidIoPerfData.topNWrites.push_back({
+        // uid: 1005678
+        .userId = 10,
+        .packageName = "1005678",
+        .bytes = {0, 400},
+        .bytesPercent = {0, (400.0 / 400.0) * 100},
+        .fsync = {5, 10},
+        .fsyncPercent = {(5.0 / 80.0) * 100, (10.0 / 10.0) * 100},
+    });
+    ASSERT_TRUE(WriteStringToFile(secondSnapshot, tf.path));
+    actualUidIoPerfData = {};
+    ret = collector.collectUidIoPerfDataLocked(&actualUidIoPerfData);
+    ASSERT_RESULT_OK(ret);
+    EXPECT_TRUE(isEqual(expectedUidIoPerfData, actualUidIoPerfData))
+        << "Second snapshot doesn't match.\nExpected:\n"
+        << toString(expectedUidIoPerfData) << "\nActual:\n"
+        << toString(actualUidIoPerfData);
+}
+
+TEST(IoPerfCollectionTest, TestUidIOStatsLessThanTopNStatsLimit) {
+    // Format: uid fgRdChar fgWrChar fgRdBytes fgWrBytes bgRdChar bgWrChar bgRdBytes bgWrBytes
+    // fgFsync bgFsync
+    constexpr char contents[] = "1001234 5000 1000 3000 500 0 0 0 0 20 0\n";
+
+    struct UidIoPerfData expectedUidIoPerfData = {};
+    expectedUidIoPerfData.topNReads.push_back({
+        // uid: 1001234
+        .userId = 10,
+        .packageName = "1001234",
+        .bytes = {3000, 0},
+        .bytesPercent = {100, 0},
+        .fsync = {20, 0},
+        .fsyncPercent = {100, 0},
+    });
+    expectedUidIoPerfData.topNWrites.push_back({
+        // uid: 1001234
+        .userId = 10,
+        .packageName = "1001234",
+        .bytes = {500, 0},
+        .bytesPercent = {0, 100},
+        .fsync = {20, 0},
+        .fsyncPercent = {100, 0},
+    });
+
+    TemporaryFile tf;
+    ASSERT_NE(tf.fd, -1);
+    ASSERT_TRUE(WriteStringToFile(contents, tf.path));
+
+    IoPerfCollection collector(tf.path, "");
+    collector.mTopNStatsPerCategory = 10;
+    ASSERT_TRUE(collector.mUidIoStats.enabled()) << "Temporary file is inaccessible";
+
+    struct UidIoPerfData actualUidIoPerfData = {};
+    const auto& ret = collector.collectUidIoPerfDataLocked(&actualUidIoPerfData);
+    ASSERT_RESULT_OK(ret);
+    EXPECT_TRUE(isEqual(expectedUidIoPerfData, actualUidIoPerfData))
+        << "Collected data doesn't match.\nExpected:\n"
+        << toString(expectedUidIoPerfData) << "\nActual:\n"
+        << toString(actualUidIoPerfData);
+}
+
+TEST(IoPerfCollectionTest, TestProcUidIoStatsContentsFromDevice) {
+    // TODO(b/148486340): Enable the test after appropriate SELinux privileges are available to
+    // read the proc file.
+    /*IoPerfCollection collector;
+    ASSERT_TRUE(collector.mUidIoStats.enabled()) << "/proc/uid_io/stats file is inaccessible";
+
+    struct UidIoPerfData perfData = {};
+    const auto& ret = collector.collectUidIoPerfDataLocked(&perfData);
+    ASSERT_RESULT_OK(ret);
+    // The below check should pass because the /proc/uid_io/stats file should have at least
+    // |mTopNStatsPerCategory| entries since bootup.
+    EXPECT_EQ(perfData.topNReads.size(), collector.mTopNStatsPerCategory);
+    EXPECT_EQ(perfData.topNWrites.size(), collector.mTopNStatsPerCategory);
+
+    int numMappedAppUid = 0;
+    int numMappedSysUid = 0;
+    for (const auto& it : collector.mUidToPackageNameMapping)  {
+        if (it.first >= AID_APP_START) {
+            ++numMappedAppUid;
+        } else {
+            ++numMappedSysUid;
+        }
+    }
+    EXPECT_GT(numMappedAppUid, 0);
+    EXPECT_GT(numMappedSysUid, 0);*/
+}
+
+TEST(IoPerfCollectionTest, TestValidProcStatFile) {
+    constexpr char firstSnapshot[] =
+            "cpu  6200 5700 1700 3100 1100 5200 3900 0 0 0\n"
+            "cpu0 2400 2900 600 690 340 4300 2100 0 0 0\n"
+            "cpu1 1900 2380 510 760 51 370 1500 0 0 0\n"
+            "cpu2 900 400 400 1000 600 400 160 0 0 0\n"
+            "cpu3 1000 20 190 650 109 130 140 0 0 0\n"
+            "intr 694351583 0 0 0 297062868 0 5922464 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 "
+            "0 0\n"
+            // Skipped most of the intr line as it is not important for testing the ProcStat parsing
+            // logic.
+            "ctxt 579020168\n"
+            "btime 1579718450\n"
+            "processes 113804\n"
+            "procs_running 17\n"
+            "procs_blocked 5\n"
+            "softirq 33275060 934664 11958403 5111 516325 200333 0 341482 10651335 0 8667407\n";
+    struct SystemIoPerfData expectedSystemIoPerfData = {
+            .cpuIoWaitTime = 1100,
+            .cpuIoWaitPercent = (1100.0 / 26900.0) * 100,
+            .ioBlockedProcessesCnt = 5,
+            .ioBlockedProcessesPercent = (5.0 / 22.0) * 100,
+    };
+
+    TemporaryFile tf;
+    ASSERT_NE(tf.fd, -1);
+    ASSERT_TRUE(WriteStringToFile(firstSnapshot, tf.path));
+
+    IoPerfCollection collector("", tf.path);
+    ASSERT_TRUE(collector.mProcStat.enabled()) << "Temporary file is inaccessible";
+
+    struct SystemIoPerfData actualSystemIoPerfData = {};
+    auto ret = collector.collectSystemIoPerfDataLocked(&actualSystemIoPerfData);
+    ASSERT_RESULT_OK(ret);
+    EXPECT_TRUE(isEqual(expectedSystemIoPerfData, actualSystemIoPerfData))
+            << "First snapshot doesn't match.\nExpected:\n"
+            << toString(expectedSystemIoPerfData) << "\nActual:\n"
+            << toString(actualSystemIoPerfData);
+
+    constexpr char secondSnapshot[] =
+            "cpu  16200 8700 2000 4100 2200 6200 5900 0 0 0\n"
+            "cpu0 4400 3400 700 890 800 4500 3100 0 0 0\n"
+            "cpu1 5900 3380 610 960 100 670 2000 0 0 0\n"
+            "cpu2 2900 1000 450 1400 800 600 460 0 0 0\n"
+            "cpu3 3000 920 240 850 500 430 340 0 0 0\n"
+            "intr 694351583 0 0 0 297062868 0 5922464 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 "
+            "0 0\n"
+            "ctxt 579020168\n"
+            "btime 1579718450\n"
+            "processes 113804\n"
+            "procs_running 10\n"
+            "procs_blocked 2\n"
+            "softirq 33275060 934664 11958403 5111 516325 200333 0 341482 10651335 0 8667407\n";
+    expectedSystemIoPerfData = {
+            .cpuIoWaitTime = 1100,
+            .cpuIoWaitPercent = (1100.0 / 18400.0) * 100,
+            .ioBlockedProcessesCnt = 2,
+            .ioBlockedProcessesPercent = (2.0 / 12.0) * 100,
+    };
+
+    ASSERT_TRUE(WriteStringToFile(secondSnapshot, tf.path));
+    actualSystemIoPerfData = {};
+    ret = collector.collectSystemIoPerfDataLocked(&actualSystemIoPerfData);
+    ASSERT_RESULT_OK(ret);
+    EXPECT_TRUE(isEqual(expectedSystemIoPerfData, actualSystemIoPerfData))
+            << "Second snapshot doesn't match.\nExpected:\n"
+            << toString(expectedSystemIoPerfData) << "\nActual:\n"
+            << toString(actualSystemIoPerfData);
+}
+
+}  // namespace watchdog
+}  // namespace automotive
+}  // namespace android
diff --git a/watchdog/server/tests/ProcPidStatTest.cpp b/watchdog/server/tests/ProcPidStatTest.cpp
new file mode 100644
index 0000000..abd1c2b
--- /dev/null
+++ b/watchdog/server/tests/ProcPidStatTest.cpp
@@ -0,0 +1,671 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "ProcPidStat.h"
+
+#include <android-base/file.h>
+#include <android-base/stringprintf.h>
+#include <errno.h>
+#include <inttypes.h>
+
+#include <algorithm>
+#include <string>
+
+#include "gmock/gmock.h"
+
+namespace android {
+namespace automotive {
+namespace watchdog {
+
+using android::base::Error;
+using android::base::Result;
+using android::base::StringAppendF;
+using android::base::StringPrintf;
+using android::base::WriteStringToFile;
+
+namespace {
+
+std::string toString(const PidStat& stat) {
+    return StringPrintf("PID: %" PRIu32 ", PPID: %" PRIu32 ", Comm: %s, State: %s, "
+                        "Major page faults: %" PRIu64 ", Num threads: %" PRIu32
+                        ", Start time: %" PRIu64,
+                        stat.pid, stat.ppid, stat.comm.c_str(), stat.state.c_str(),
+                        stat.majorFaults, stat.numThreads, stat.startTime);
+}
+
+std::string toString(const ProcessStats& stats) {
+    std::string buffer;
+    StringAppendF(&buffer, "Tgid: %" PRIi64 ", UID: %" PRIi64 ", %s\n", stats.tgid, stats.uid,
+                  toString(stats.process).c_str());
+    StringAppendF(&buffer, "\tThread stats:\n");
+    for (const auto& it : stats.threads) {
+        StringAppendF(&buffer, "\t\t%s\n", toString(it.second).c_str());
+    }
+    StringAppendF(&buffer, "\n");
+    return buffer;
+}
+
+std::string toString(const std::vector<ProcessStats>& stats) {
+    std::string buffer;
+    StringAppendF(&buffer, "Number of processes: %d\n", static_cast<int>(stats.size()));
+    for (const auto& it : stats) {
+        StringAppendF(&buffer, "%s", toString(it).c_str());
+    }
+    return buffer;
+}
+
+bool isEqual(const PidStat& lhs, const PidStat& rhs) {
+    return lhs.pid == rhs.pid && lhs.comm == rhs.comm && lhs.state == rhs.state &&
+            lhs.ppid == rhs.ppid && lhs.majorFaults == rhs.majorFaults &&
+            lhs.numThreads == rhs.numThreads && lhs.startTime == rhs.startTime;
+}
+
+bool isEqual(std::vector<ProcessStats>* lhs, std::vector<ProcessStats>* rhs) {
+    if (lhs->size() != rhs->size()) {
+        return false;
+    }
+    std::sort(lhs->begin(), lhs->end(), [&](const ProcessStats& l, const ProcessStats& r) -> bool {
+        return l.process.pid < r.process.pid;
+    });
+    std::sort(rhs->begin(), rhs->end(), [&](const ProcessStats& l, const ProcessStats& r) -> bool {
+        return l.process.pid < r.process.pid;
+    });
+    return std::equal(lhs->begin(), lhs->end(), rhs->begin(),
+                      [&](const ProcessStats& l, const ProcessStats& r) -> bool {
+                          if (l.tgid != r.tgid || l.uid != r.uid ||
+                              !isEqual(l.process, r.process) ||
+                              l.threads.size() != r.threads.size()) {
+                              return false;
+                          }
+                          for (const auto& lIt : l.threads) {
+                              const auto& rIt = r.threads.find(lIt.first);
+                              if (rIt == r.threads.end()) {
+                                  return false;
+                              }
+                              if (!isEqual(lIt.second, rIt->second)) {
+                                  return false;
+                              }
+                          }
+                          return true;
+                      });
+}
+
+Result<void> makeDir(std::string path) {
+    if (mkdir(path.c_str(), 0700) && errno != EEXIST) {
+        return Error() << "Could not mkdir " << path << ": " << strerror(errno);
+    }
+    return {};
+}
+
+Result<void> populateProcPidDir(
+        const std::string& procDirPath,
+        const std::unordered_map<uint32_t, std::vector<uint32_t>>& pidToTids,
+        const std::unordered_map<uint32_t, std::string>& processStat,
+        const std::unordered_map<uint32_t, std::string>& processStatus,
+        const std::unordered_map<uint32_t, std::string>& threadStat) {
+    for (const auto& it : pidToTids) {
+        // 1. Create /proc/PID dir.
+        const auto& pidDirRes = makeDir(StringPrintf("%s/%" PRIu32, procDirPath.c_str(), it.first));
+        if (!pidDirRes) {
+            return Error() << "Failed to create top-level per-process directory: "
+                           << pidDirRes.error();
+        }
+
+        // 2. Create /proc/PID/stat file.
+        uint32_t pid = it.first;
+        if (processStat.find(pid) != processStat.end()) {
+            std::string path = StringPrintf((procDirPath + kStatFileFormat).c_str(), pid);
+            if (!WriteStringToFile(processStat.at(pid), path)) {
+                return Error() << "Failed to write pid stat file " << path;
+            }
+        }
+
+        // 3. Create /proc/PID/status file.
+        if (processStatus.find(pid) != processStatus.end()) {
+            std::string path = StringPrintf((procDirPath + kStatusFileFormat).c_str(), pid);
+            if (!WriteStringToFile(processStatus.at(pid), path)) {
+                return Error() << "Failed to write pid status file " << path;
+            }
+        }
+
+        // 4. Create /proc/PID/task dir.
+        const auto& taskDirRes = makeDir(StringPrintf((procDirPath + kTaskDirFormat).c_str(), pid));
+        if (!taskDirRes) {
+            return Error() << "Failed to create task directory: " << taskDirRes.error();
+        }
+
+        // 5. Create /proc/PID/task/TID dirs and /proc/PID/task/TID/stat files.
+        for (const auto& tid : it.second) {
+            const auto& tidDirRes = makeDir(
+                    StringPrintf((procDirPath + kTaskDirFormat + "/%" PRIu32).c_str(), pid, tid));
+            if (!tidDirRes) {
+                return Error() << "Failed to create per-thread directory: " << tidDirRes.error();
+            }
+            if (threadStat.find(tid) != threadStat.end()) {
+                std::string path =
+                        StringPrintf((procDirPath + kTaskDirFormat + kStatFileFormat).c_str(), pid,
+                                     tid);
+                if (!WriteStringToFile(threadStat.at(tid), path)) {
+                    return Error() << "Failed to write thread stat file " << path;
+                }
+            }
+        }
+    }
+
+    return {};
+}
+
+}  // namespace
+
+TEST(ProcPidStatTest, TestValidStatFiles) {
+    std::unordered_map<uint32_t, std::vector<uint32_t>> pidToTids = {
+            {1, {1, 453}},
+            {1000, {1000, 1100}},
+    };
+
+    std::unordered_map<uint32_t, std::string> perProcessStat = {
+            {1, "1 (init) S 0 0 0 0 0 0 0 0 220 0 0 0 0 0 0 0 2 0 0\n"},
+            {1000, "1000 (system_server) R 1 0 0 0 0 0 0 0 600 0 0 0 0 0 0 0 2 0 1000\n"},
+    };
+
+    std::unordered_map<uint32_t, std::string> perProcessStatus = {
+            {1, "Pid:\t1\nTgid:\t1\nUid:\t0\t0\t0\t0\n"},
+            {1000, "Pid:\t1000\nTgid:\t1000\nUid:\t10001234\t10001234\t10001234\t10001234\n"},
+    };
+
+    std::unordered_map<uint32_t, std::string> perThreadStat = {
+            {1, "1 (init) S 0 0 0 0 0 0 0 0 200 0 0 0 0 0 0 0 2 0 0\n"},
+            {453, "453 (init) S 0 0 0 0 0 0 0 0 20 0 0 0 0 0 0 0 2 0 275\n"},
+            {1000, "1000 (system_server) R 1 0 0 0 0 0 0 0 250 0 0 0 0 0 0 0 2 0 1000\n"},
+            {1100, "1100 (system_server) S 1 0 0 0 0 0 0 0 350 0 0 0 0 0 0 0 2 0 1200\n"},
+    };
+
+    std::vector<ProcessStats> expected = {
+            {
+                    .tgid = 1,
+                    .uid = 0,
+                    .process = {1, "init", "S", 0, 220, 2, 0},
+                    .threads =
+                            {
+                                    {1, {1, "init", "S", 0, 200, 2, 0}},
+                                    {453, {453, "init", "S", 0, 20, 2, 275}},
+                            },
+            },
+            {
+                    .tgid = 1000,
+                    .uid = 10001234,
+                    .process = {1000, "system_server", "R", 1, 600, 2, 1000},
+                    .threads =
+                            {
+                                    {1000, {1000, "system_server", "R", 1, 250, 2, 1000}},
+                                    {1100, {1100, "system_server", "S", 1, 350, 2, 1200}},
+                            },
+            },
+    };
+
+    TemporaryDir firstSnapshot;
+    auto ret = populateProcPidDir(firstSnapshot.path, pidToTids, perProcessStat, perProcessStatus,
+                                  perThreadStat);
+    ASSERT_TRUE(ret) << "Failed to populate proc pid dir: " << ret.error();
+
+    ProcPidStat procPidStat(firstSnapshot.path);
+    ASSERT_TRUE(procPidStat.enabled())
+            << "Files under the path `" << firstSnapshot.path << "` are inaccessible";
+
+    auto actual = procPidStat.collect();
+    ASSERT_TRUE(actual) << "Failed to collect proc pid stat: " << actual.error();
+
+    EXPECT_TRUE(isEqual(&expected, &actual.value())) << "First snapshot doesn't match.\nExpected:\n"
+                                                     << toString(expected) << "\nActual:\n"
+                                                     << toString(*actual);
+
+    pidToTids = {
+            {1, {1, 453}}, {1000, {1000, 1400}},  // TID 1100 terminated and 1400 instantiated.
+    };
+
+    perProcessStat = {
+            {1, "1 (init) S 0 0 0 0 0 0 0 0 920 0 0 0 0 0 0 0 2 0 0\n"},
+            {1000, "1000 (system_server) R 1 0 0 0 0 0 0 0 1550 0 0 0 0 0 0 0 2 0 1000\n"},
+    };
+
+    perThreadStat = {
+            {1, "1 (init) S 0 0 0 0 0 0 0 0 600 0 0 0 0 0 0 0 2 0 0\n"},
+            {453, "453 (init) S 0 0 0 0 0 0 0 0 320 0 0 0 0 0 0 0 2 0 275\n"},
+            {1000, "1000 (system_server) R 1 0 0 0 0 0 0 0 600 0 0 0 0 0 0 0 2 0 1000\n"},
+            // TID 1100 hits +400 major page faults before terminating. This is counted against
+            // PID 1000's perProcessStat.
+            {1400, "1400 (system_server) S 1 0 0 0 0 0 0 0 200 0 0 0 0 0 0 0 2 0 8977476\n"},
+    };
+
+    expected = {
+            {
+                    .tgid = 1,
+                    .uid = 0,
+                    .process = {1, "init", "S", 0, 700, 2, 0},
+                    .threads =
+                            {
+                                    {1, {1, "init", "S", 0, 400, 2, 0}},
+                                    {453, {453, "init", "S", 0, 300, 2, 275}},
+                            },
+            },
+            {
+                    .tgid = 1000,
+                    .uid = 10001234,
+                    .process = {1000, "system_server", "R", 1, 950, 2, 1000},
+                    .threads =
+                            {
+                                    {1000, {1000, "system_server", "R", 1, 350, 2, 1000}},
+                                    {1400, {1400, "system_server", "S", 1, 200, 2, 8977476}},
+                            },
+            },
+    };
+
+    TemporaryDir secondSnapshot;
+    ret = populateProcPidDir(secondSnapshot.path, pidToTids, perProcessStat, perProcessStatus,
+                             perThreadStat);
+    ASSERT_TRUE(ret) << "Failed to populate proc pid dir: " << ret.error();
+
+    procPidStat.mPath = secondSnapshot.path;
+    ASSERT_TRUE(procPidStat.enabled())
+            << "Files under the path `" << secondSnapshot.path << "` are inaccessible";
+
+    actual = procPidStat.collect();
+    ASSERT_TRUE(actual) << "Failed to collect proc pid stat: " << actual.error();
+
+    EXPECT_TRUE(isEqual(&expected, &actual.value()))
+            << "Second snapshot doesn't match.\nExpected:\n"
+            << toString(expected) << "\nActual:\n"
+            << toString(*actual);
+}
+
+TEST(ProcPidStatTest, TestHandlesProcessTerminationBetweenScanningAndParsing) {
+    std::unordered_map<uint32_t, std::vector<uint32_t>> pidToTids = {
+            {1, {1}},
+            {100, {100}},          // Process terminates after scanning PID directory.
+            {1000, {1000}},        // Process terminates after reading stat file.
+            {2000, {2000}},        // Process terminates after scanning task directory.
+            {3000, {3000, 3300}},  // TID 3300 terminates after scanning task directory.
+    };
+
+    std::unordered_map<uint32_t, std::string> perProcessStat = {
+            {1, "1 (init) S 0 0 0 0 0 0 0 0 220 0 0 0 0 0 0 0 1 0 0\n"},
+            // Process 100 terminated.
+            {1000, "1000 (system_server) R 1 0 0 0 0 0 0 0 600 0 0 0 0 0 0 0 1 0 1000\n"},
+            {2000, "2000 (logd) R 1 0 0 0 0 0 0 0 1200 0 0 0 0 0 0 0 1 0 4567\n"},
+            {3000, "3000 (disk I/O) R 1 0 0 0 0 0 0 0 10300 0 0 0 0 0 0 0 2 0 67890\n"},
+    };
+
+    std::unordered_map<uint32_t, std::string> perProcessStatus = {
+            {1, "Pid:\t1\nTgid:\t1\nUid:\t0\t0\t0\t0\n"},
+            // Process 1000 terminated.
+            {2000, "Pid:\t2000\nTgid:\t2000\nUid:\t10001234\t10001234\t10001234\t10001234\n"},
+            {3000, "Pid:\t3000\nTgid:\t3000\nUid:\t10001234\t10001234\t10001234\t10001234\n"},
+    };
+
+    std::unordered_map<uint32_t, std::string> perThreadStat = {
+            {1, "1 (init) S 0 0 0 0 0 0 0 0 200 0 0 0 0 0 0 0 1 0 0\n"},
+            // Process 2000 terminated.
+            {3000, "3000 (disk I/O) R 1 0 0 0 0 0 0 0 2400 0 0 0 0 0 0 0 2 0 67890\n"},
+            // TID 3300 terminated.
+    };
+
+    std::vector<ProcessStats> expected = {
+            {
+                    .tgid = 1,
+                    .uid = 0,
+                    .process = {1, "init", "S", 0, 220, 1, 0},
+                    .threads = {{1, {1, "init", "S", 0, 200, 1, 0}}},
+            },
+            {
+                    .tgid = -1,
+                    .uid = -1,
+                    .process = {1000, "system_server", "R", 1, 600, 1, 1000},
+                    // Stats common between process and main-thread are copied when
+                    // main-thread stats are not available.
+                    .threads = {{1000, {1000, "system_server", "R", 1, 0, 1, 1000}}},
+            },
+            {
+                    .tgid = 2000,
+                    .uid = 10001234,
+                    .process = {2000, "logd", "R", 1, 1200, 1, 4567},
+                    .threads = {{2000, {2000, "logd", "R", 1, 0, 1, 4567}}},
+            },
+            {
+                    .tgid = 3000,
+                    .uid = 10001234,
+                    .process = {3000, "disk I/O", "R", 1, 10300, 2, 67890},
+                    .threads = {{3000, {3000, "disk I/O", "R", 1, 2400, 2, 67890}}},
+            },
+    };
+
+    TemporaryDir procDir;
+    const auto& ret = populateProcPidDir(procDir.path, pidToTids, perProcessStat, perProcessStatus,
+                                         perThreadStat);
+    ASSERT_TRUE(ret) << "Failed to populate proc pid dir: " << ret.error();
+
+    ProcPidStat procPidStat(procDir.path);
+    ASSERT_TRUE(procPidStat.enabled())
+            << "Files under the path `" << procDir.path << "` are inaccessible";
+
+    auto actual = procPidStat.collect();
+    ASSERT_TRUE(actual) << "Failed to collect proc pid stat: " << actual.error();
+
+    EXPECT_TRUE(isEqual(&expected, &actual.value()))
+            << "Proc pid contents doesn't match.\nExpected:\n"
+            << toString(expected) << "\nActual:\n"
+            << toString(*actual);
+}
+
+TEST(ProcPidStatTest, TestHandlesPidTidReuse) {
+    std::unordered_map<uint32_t, std::vector<uint32_t>> pidToTids = {
+            {1, {1, 367, 453, 589}},
+            {1000, {1000}},
+            {2345, {2345}},
+    };
+
+    std::unordered_map<uint32_t, std::string> perProcessStat = {
+            {1, "1 (init) S 0 0 0 0 0 0 0 0 1200 0 0 0 0 0 0 0 4 0 0\n"},
+            {1000, "1000 (system_server) R 1 0 0 0 0 0 0 0 250 0 0 0 0 0 0 0 1 0 1000\n"},
+            {2345, "2345 (logd) R 1 0 0 0 0 0 0 0 54354 0 0 0 0 0 0 0 1 0 456\n"},
+    };
+
+    std::unordered_map<uint32_t, std::string> perProcessStatus = {
+            {1, "Pid:\t1\nTgid:\t1\nUid:\t0\t0\t0\t0\n"},
+            {1000, "Pid:\t1000\nTgid:\t1000\nUid:\t10001234\t10001234\t10001234\t10001234\n"},
+            {2345, "Pid:\t2345\nTgid:\t2345\nUid:\t10001234\t10001234\t10001234\t10001234\n"},
+    };
+
+    std::unordered_map<uint32_t, std::string> perThreadStat = {
+            {1, "1 (init) S 0 0 0 0 0 0 0 0 200 0 0 0 0 0 0 0 4 0 0\n"},
+            {367, "367 (init) S 0 0 0 0 0 0 0 0 400 0 0 0 0 0 0 0 4 0 100\n"},
+            {453, "453 (init) S 0 0 0 0 0 0 0 0 100 0 0 0 0 0 0 0 4 0 275\n"},
+            {589, "589 (init) S 0 0 0 0 0 0 0 0 500 0 0 0 0 0 0 0 4 0 600\n"},
+            {1000, "1000 (system_server) R 1 0 0 0 0 0 0 0 250 0 0 0 0 0 0 0 1 0 1000\n"},
+            {2345, "2345 (logd) R 1 0 0 0 0 0 0 0 54354 0 0 0 0 0 0 0 1 0 456\n"},
+    };
+
+    std::vector<ProcessStats> expected = {
+            {
+                    .tgid = 1,
+                    .uid = 0,
+                    .process = {1, "init", "S", 0, 1200, 4, 0},
+                    .threads =
+                            {
+                                    {1, {1, "init", "S", 0, 200, 4, 0}},
+                                    {367, {367, "init", "S", 0, 400, 4, 100}},
+                                    {453, {453, "init", "S", 0, 100, 4, 275}},
+                                    {589, {589, "init", "S", 0, 500, 4, 600}},
+                            },
+            },
+            {
+                    .tgid = 1000,
+                    .uid = 10001234,
+                    .process = {1000, "system_server", "R", 1, 250, 1, 1000},
+                    .threads = {{1000, {1000, "system_server", "R", 1, 250, 1, 1000}}},
+            },
+            {
+                    .tgid = 2345,
+                    .uid = 10001234,
+                    .process = {2345, "logd", "R", 1, 54354, 1, 456},
+                    .threads = {{2345, {2345, "logd", "R", 1, 54354, 1, 456}}},
+            },
+    };
+
+    TemporaryDir firstSnapshot;
+    auto ret = populateProcPidDir(firstSnapshot.path, pidToTids, perProcessStat, perProcessStatus,
+                                  perThreadStat);
+    ASSERT_TRUE(ret) << "Failed to populate proc pid dir: " << ret.error();
+
+    ProcPidStat procPidStat(firstSnapshot.path);
+    ASSERT_TRUE(procPidStat.enabled())
+            << "Files under the path `" << firstSnapshot.path << "` are inaccessible";
+
+    auto actual = procPidStat.collect();
+    ASSERT_TRUE(actual) << "Failed to collect proc pid stat: " << actual.error();
+
+    EXPECT_TRUE(isEqual(&expected, &actual.value())) << "First snapshot doesn't match.\nExpected:\n"
+                                                     << toString(expected) << "\nActual:\n"
+                                                     << toString(*actual);
+
+    pidToTids = {
+            {1, {1, 589}},       // TID 589 reused by the same process.
+            {367, {367, 2000}},  // TID 367 reused as a PID. PID 2000 reused as a TID.
+            // PID 1000 reused as a new PID. TID 453 reused by a different PID.
+            {1000, {1000, 453}},
+    };
+
+    perProcessStat = {
+            {1, "1 (init) S 0 0 0 0 0 0 0 0 1800 0 0 0 0 0 0 0 2 0 0\n"},
+            {367, "367 (system_server) R 1 0 0 0 0 0 0 0 100 0 0 0 0 0 0 0 2 0 3450\n"},
+            {1000, "1000 (logd) R 1 0 0 0 0 0 0 0 2000 0 0 0 0 0 0 0 2 0 4650\n"},
+    };
+
+    perProcessStatus = {
+            {1, "Pid:\t1\nTgid:\t1\nUid:\t0\t0\t0\t0\n"},
+            {367, "Pid:\t367\nTgid:\t367\nUid:\t10001234\t10001234\t10001234\t10001234\n"},
+            {1000, "Pid:\t1000\nTgid:\t1000\nUid:\t10001234\t10001234\t10001234\t10001234\n"},
+    };
+
+    perThreadStat = {
+            {1, "1 (init) S 0 0 0 0 0 0 0 0 500 0 0 0 0 0 0 0 2 0 0\n"},
+            {589, "589 (init) S 0 0 0 0 0 0 0 0 300 0 0 0 0 0 0 0 2 0 2345\n"},
+            {367, "367 (system_server) R 1 0 0 0 0 0 0 0 50 0 0 0 0 0 0 0 2 0 3450\n"},
+            {2000, "2000 (system_server) R 1 0 0 0 0 0 0 0 50 0 0 0 0 0 0 0 2 0 3670\n"},
+            {1000, "1000 (logd) R 1 0 0 0 0 0 0 0 200 0 0 0 0 0 0 0 2 0 4650\n"},
+            {453, "453 (logd) D 1 0 0 0 0 0 0 0 1800 0 0 0 0 0 0 0 2 0 4770\n"},
+    };
+
+    expected = {
+            {
+                    .tgid = 1,
+                    .uid = 0,
+                    .process = {1, "init", "S", 0, 600, 2, 0},
+                    .threads =
+                            {
+                                    {1, {1, "init", "S", 0, 300, 2, 0}},
+                                    {589, {589, "init", "S", 0, 300, 2, 2345}},
+                            },
+            },
+            {
+                    .tgid = 367,
+                    .uid = 10001234,
+                    .process = {367, "system_server", "R", 1, 100, 2, 3450},
+                    .threads =
+                            {
+                                    {367, {367, "system_server", "R", 1, 50, 2, 3450}},
+                                    {2000, {2000, "system_server", "R", 1, 50, 2, 3670}},
+                            },
+            },
+            {
+                    .tgid = 1000,
+                    .uid = 10001234,
+                    .process = {1000, "logd", "R", 1, 2000, 2, 4650},
+                    .threads =
+                            {
+                                    {1000, {1000, "logd", "R", 1, 200, 2, 4650}},
+                                    {453, {453, "logd", "D", 1, 1800, 2, 4770}},
+                            },
+            },
+    };
+
+    TemporaryDir secondSnapshot;
+    ret = populateProcPidDir(secondSnapshot.path, pidToTids, perProcessStat, perProcessStatus,
+                             perThreadStat);
+    ASSERT_TRUE(ret) << "Failed to populate proc pid dir: " << ret.error();
+
+    procPidStat.mPath = secondSnapshot.path;
+    ASSERT_TRUE(procPidStat.enabled())
+            << "Files under the path `" << secondSnapshot.path << "` are inaccessible";
+
+    actual = procPidStat.collect();
+    ASSERT_TRUE(actual) << "Failed to collect proc pid stat: " << actual.error();
+
+    EXPECT_TRUE(isEqual(&expected, &actual.value()))
+            << "Second snapshot doesn't match.\nExpected:\n"
+            << toString(expected) << "\nActual:\n"
+            << toString(*actual);
+}
+
+TEST(ProcPidStatTest, TestErrorOnCorruptedProcessStatFile) {
+    std::unordered_map<uint32_t, std::vector<uint32_t>> pidToTids = {
+            {1, {1}},
+    };
+
+    std::unordered_map<uint32_t, std::string> perProcessStat = {
+            {1, "1 (init) S 0 0 0 0 0 0 0 0 200 0 0 0 CORRUPTED DATA\n"},
+    };
+
+    std::unordered_map<uint32_t, std::string> perProcessStatus = {
+            {1, "Pid:\t1\nTgid:\t1\nUid:\t0\t0\t0\t0\n"},
+    };
+
+    std::unordered_map<uint32_t, std::string> perThreadStat = {
+            {1, "1 (init) S 0 0 0 0 0 0 0 0 200 0 0 0 0 0 0 0 1 0 0\n"},
+    };
+
+    TemporaryDir procDir;
+    const auto& ret = populateProcPidDir(procDir.path, pidToTids, perProcessStat, perProcessStatus,
+                                         perThreadStat);
+    ASSERT_TRUE(ret) << "Failed to populate proc pid dir: " << ret.error();
+
+    ProcPidStat procPidStat(procDir.path);
+    ASSERT_TRUE(procPidStat.enabled())
+            << "Files under the path `" << procDir.path << "` are inaccessible";
+
+    const auto& actual = procPidStat.collect();
+    ASSERT_FALSE(actual) << "No error returned for invalid process stat file";
+}
+
+TEST(ProcPidStatTest, TestErrorOnCorruptedProcessStatusFile) {
+    std::unordered_map<uint32_t, std::vector<uint32_t>> pidToTids = {
+            {1, {1}},
+    };
+
+    std::unordered_map<uint32_t, std::string> perProcessStat = {
+            {1, "1 (init) S 0 0 0 0 0 0 0 0 200 0 0 0 0 0 0 0 1 0 0\n"},
+    };
+
+    std::unordered_map<uint32_t, std::string> perProcessStatus = {
+            {1, "Pid:\t1\nTgid:\t1\nCORRUPTED DATA\n"},
+    };
+
+    std::unordered_map<uint32_t, std::string> perThreadStat = {
+            {1, "1 (init) S 0 0 0 0 0 0 0 0 200 0 0 0 0 0 0 0 1 0 0\n"},
+    };
+
+    TemporaryDir procDir;
+    const auto& ret = populateProcPidDir(procDir.path, pidToTids, perProcessStat, perProcessStatus,
+                                         perThreadStat);
+    ASSERT_TRUE(ret) << "Failed to populate proc pid dir: " << ret.error();
+
+    ProcPidStat procPidStat(procDir.path);
+    ASSERT_TRUE(procPidStat.enabled())
+            << "Files under the path `" << procDir.path << "` are inaccessible";
+
+    const auto& actual = procPidStat.collect();
+    ASSERT_FALSE(actual) << "No error returned for invalid process status file";
+}
+
+TEST(ProcPidStatTest, TestErrorOnCorruptedThreadStatFile) {
+    std::unordered_map<uint32_t, std::vector<uint32_t>> pidToTids = {
+            {1, {1}},
+    };
+
+    std::unordered_map<uint32_t, std::string> perProcessStat = {
+            {1, "1 (init) S 0 0 0 0 0 0 0 0 200 0 0 0 0 0 0 0 1 0 0\n"},
+    };
+
+    std::unordered_map<uint32_t, std::string> perProcessStatus = {
+            {1, "Pid:\t1\nTgid:\t1\nUid:\t0\t0\t0\t0\n"},
+    };
+
+    std::unordered_map<uint32_t, std::string> perThreadStat = {
+            {1, "1 (init) S 0 0 0 0 0 0 0 0 200 0 0 0 CORRUPTED DATA\n"},
+    };
+
+    TemporaryDir procDir;
+    const auto& ret = populateProcPidDir(procDir.path, pidToTids, perProcessStat, perProcessStatus,
+                                         perThreadStat);
+    ASSERT_TRUE(ret) << "Failed to populate proc pid dir: " << ret.error();
+
+    ProcPidStat procPidStat(procDir.path);
+    ASSERT_TRUE(procPidStat.enabled())
+            << "Files under the path `" << procDir.path << "` are inaccessible";
+
+    const auto& actual = procPidStat.collect();
+    ASSERT_FALSE(actual) << "No error returned for invalid thread stat file";
+}
+
+TEST(ProcPidStatTest, TestHandlesSpaceInCommName) {
+    std::unordered_map<uint32_t, std::vector<uint32_t>> pidToTids = {
+            {1, {1}},
+    };
+
+    std::unordered_map<uint32_t, std::string> perProcessStat = {
+            {1, "1 (random process name with space) S 0 0 0 0 0 0 0 0 200 0 0 0 0 0 0 0 1 0 0\n"},
+    };
+
+    std::unordered_map<uint32_t, std::string> perProcessStatus = {
+            {1, "Pid:\t1\nTgid:\t1\nUid:\t0\t0\t0\t0\n"},
+    };
+
+    std::unordered_map<uint32_t, std::string> perThreadStat = {
+            {1, "1 (random process name with space) S 0 0 0 0 0 0 0 0 200 0 0 0 0 0 0 0 1 0 0\n"},
+    };
+
+    std::vector<ProcessStats> expected = {
+            {
+                    .tgid = 1,
+                    .uid = 0,
+                    .process = {1, "random process name with space", "S", 0, 200, 1, 0},
+                    .threads = {{1, {1, "random process name with space", "S", 0, 200, 1, 0}}},
+            },
+    };
+
+    TemporaryDir procDir;
+    const auto& ret = populateProcPidDir(procDir.path, pidToTids, perProcessStat, perProcessStatus,
+                                         perThreadStat);
+    ASSERT_TRUE(ret) << "Failed to populate proc pid dir: " << ret.error();
+
+    ProcPidStat procPidStat(procDir.path);
+    ASSERT_TRUE(procPidStat.enabled())
+            << "Files under the path `" << procDir.path << "` are inaccessible";
+
+    auto actual = procPidStat.collect();
+    ASSERT_TRUE(actual) << "Failed to collect proc pid stat: " << actual.error();
+
+    EXPECT_TRUE(isEqual(&expected, &actual.value()))
+            << "Proc pid contents doesn't match.\nExpected:\n"
+            << toString(expected) << "\nActual:\n"
+            << toString(*actual);
+}
+
+TEST(ProcPidStatTest, TestProcPidStatContentsFromDevice) {
+    // TODO(b/148486340): Enable the test after appropriate SELinux privileges are available to
+    // read the proc file.
+    /*ProcPidStat procPidStat;
+    ASSERT_TRUE(procPidStat.enabled()) << "/proc/[pid]/.* files are inaccessible";
+
+    const auto& processStats = procPidStat.collect();
+    ASSERT_TRUE(processStats) << "Failed to collect proc pid stat: " << processStats.error();
+
+    // The below check should pass because there should be at least one process.
+    EXPECT_GT(processStats->size(), 0);*/
+}
+
+}  // namespace watchdog
+}  // namespace automotive
+}  // namespace android
diff --git a/watchdog/server/tests/ProcStatTest.cpp b/watchdog/server/tests/ProcStatTest.cpp
new file mode 100644
index 0000000..ad37e66
--- /dev/null
+++ b/watchdog/server/tests/ProcStatTest.cpp
@@ -0,0 +1,270 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "ProcStat.h"
+
+#include <android-base/file.h>
+#include <android-base/stringprintf.h>
+#include <inttypes.h>
+
+#include <string>
+
+#include "gmock/gmock.h"
+
+namespace android {
+namespace automotive {
+namespace watchdog {
+
+using android::base::StringPrintf;
+using android::base::WriteStringToFile;
+
+namespace {
+
+std::string toString(const ProcStatInfo& info) {
+    const auto& cpuStats = info.cpuStats;
+    return StringPrintf("Cpu Stats:\nUserTime: %" PRIu64 " NiceTime: %" PRIu64 " SysTime: %" PRIu64
+                        " IdleTime: %" PRIu64 " IoWaitTime: %" PRIu64 " IrqTime: %" PRIu64
+                        " SoftIrqTime: %" PRIu64 " StealTime: %" PRIu64 " GuestTime: %" PRIu64
+                        " GuestNiceTime: %" PRIu64 "\nNumber of running processes: %" PRIu32
+                        "\nNumber of blocked processes: %" PRIu32,
+                        cpuStats.userTime, cpuStats.niceTime, cpuStats.sysTime, cpuStats.idleTime,
+                        cpuStats.ioWaitTime, cpuStats.irqTime, cpuStats.softIrqTime,
+                        cpuStats.stealTime, cpuStats.guestTime, cpuStats.guestNiceTime,
+                        info.runnableProcessesCnt, info.ioBlockedProcessesCnt);
+}
+
+}  // namespace
+
+TEST(ProcStatTest, TestValidStatFile) {
+    constexpr char firstSnapshot[] =
+            "cpu  6200 5700 1700 3100 1100 5200 3900 0 0 0\n"
+            "cpu0 2400 2900 600 690 340 4300 2100 0 0 0\n"
+            "cpu1 1900 2380 510 760 51 370 1500 0 0 0\n"
+            "cpu2 900 400 400 1000 600 400 160 0 0 0\n"
+            "cpu3 1000 20 190 650 109 130 140 0 0 0\n"
+            "intr 694351583 0 0 0 297062868 0 5922464 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 "
+            "0 0\n"
+            // Skipped most of the intr line as it is not important for testing the ProcStat parsing
+            // logic.
+            "ctxt 579020168\n"
+            "btime 1579718450\n"
+            "processes 113804\n"
+            "procs_running 17\n"
+            "procs_blocked 5\n"
+            "softirq 33275060 934664 11958403 5111 516325 200333 0 341482 10651335 0 8667407\n";
+    ProcStatInfo expectedFirstDelta;
+    expectedFirstDelta.cpuStats = {
+            .userTime = 6200,
+            .niceTime = 5700,
+            .sysTime = 1700,
+            .idleTime = 3100,
+            .ioWaitTime = 1100,
+            .irqTime = 5200,
+            .softIrqTime = 3900,
+            .stealTime = 0,
+            .guestTime = 0,
+            .guestNiceTime = 0,
+    };
+    expectedFirstDelta.runnableProcessesCnt = 17;
+    expectedFirstDelta.ioBlockedProcessesCnt = 5;
+
+    TemporaryFile tf;
+    ASSERT_NE(tf.fd, -1);
+    ASSERT_TRUE(WriteStringToFile(firstSnapshot, tf.path));
+
+    ProcStat procStat(tf.path);
+    ASSERT_TRUE(procStat.enabled()) << "Temporary file is inaccessible";
+
+    const auto& actualFirstDelta = procStat.collect();
+    EXPECT_RESULT_OK(actualFirstDelta);
+    EXPECT_EQ(expectedFirstDelta, *actualFirstDelta)
+            << "First snapshot doesnt't match.\nExpected:\n"
+            << toString(expectedFirstDelta) << "\nActual:\n"
+            << toString(*actualFirstDelta);
+
+    constexpr char secondSnapshot[] =
+            "cpu  16200 8700 2000 4100 2200 6200 5900 0 0 0\n"
+            "cpu0 4400 3400 700 890 800 4500 3100 0 0 0\n"
+            "cpu1 5900 3380 610 960 100 670 2000 0 0 0\n"
+            "cpu2 2900 1000 450 1400 800 600 460 0 0 0\n"
+            "cpu3 3000 920 240 850 500 430 340 0 0 0\n"
+            "intr 694351583 0 0 0 297062868 0 5922464 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 "
+            "0 0\n"
+            "ctxt 579020168\n"
+            "btime 1579718450\n"
+            "processes 113804\n"
+            "procs_running 10\n"
+            "procs_blocked 2\n"
+            "softirq 33275060 934664 11958403 5111 516325 200333 0 341482 10651335 0 8667407\n";
+    ProcStatInfo expectedSecondDelta;
+    expectedSecondDelta.cpuStats = {
+            .userTime = 10000,
+            .niceTime = 3000,
+            .sysTime = 300,
+            .idleTime = 1000,
+            .ioWaitTime = 1100,
+            .irqTime = 1000,
+            .softIrqTime = 2000,
+            .stealTime = 0,
+            .guestTime = 0,
+            .guestNiceTime = 0,
+    };
+    expectedSecondDelta.runnableProcessesCnt = 10;
+    expectedSecondDelta.ioBlockedProcessesCnt = 2;
+
+    ASSERT_TRUE(WriteStringToFile(secondSnapshot, tf.path));
+    const auto& actualSecondDelta = procStat.collect();
+    EXPECT_RESULT_OK(actualSecondDelta);
+    EXPECT_EQ(expectedSecondDelta, *actualSecondDelta)
+            << "Second snapshot doesnt't match.\nExpected:\n"
+            << toString(expectedSecondDelta) << "\nActual:\n"
+            << toString(*actualSecondDelta);
+}
+
+TEST(ProcStatTest, TestErrorOnCorruptedStatFile) {
+    constexpr char contents[] =
+            "cpu  6200 5700 1700 3100 CORRUPTED DATA\n"
+            "cpu0 2400 2900 600 690 340 4300 2100 0 0 0\n"
+            "cpu1 1900 2380 510 760 51 370 1500 0 0 0\n"
+            "cpu2 900 400 400 1000 600 400 160 0 0 0\n"
+            "cpu3 1000 20 190 650 109 130 140 0 0 0\n"
+            "intr 694351583 0 0 0 297062868 0 5922464 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 "
+            "0 0\n"
+            "ctxt 579020168\n"
+            "btime 1579718450\n"
+            "processes 113804\n"
+            "procs_running 17\n"
+            "procs_blocked 5\n"
+            "softirq 33275060 934664 11958403 5111 516325 200333 0 341482 10651335 0 8667407\n";
+    TemporaryFile tf;
+    ASSERT_NE(tf.fd, -1);
+    ASSERT_TRUE(WriteStringToFile(contents, tf.path));
+
+    ProcStat procStat(tf.path);
+    ASSERT_TRUE(procStat.enabled()) << "Temporary file is inaccessible";
+    EXPECT_FALSE(procStat.collect().ok()) << "No error returned for corrupted file";
+}
+
+TEST(ProcStatTest, TestErrorOnMissingCpuLine) {
+    constexpr char contents[] =
+            "cpu0 2400 2900 600 690 340 4300 2100 0 0 0\n"
+            "cpu1 1900 2380 510 760 51 370 1500 0 0 0\n"
+            "cpu2 900 400 400 1000 600 400 160 0 0 0\n"
+            "cpu3 1000 20 190 650 109 130 140 0 0 0\n"
+            "intr 694351583 0 0 0 297062868 0 5922464 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 "
+            "0 0\n"
+            "ctxt 579020168\n"
+            "btime 1579718450\n"
+            "processes 113804\n"
+            "procs_running 17\n"
+            "procs_blocked 5\n"
+            "softirq 33275060 934664 11958403 5111 516325 200333 0 341482 10651335 0 8667407\n";
+    TemporaryFile tf;
+    ASSERT_NE(tf.fd, -1);
+    ASSERT_TRUE(WriteStringToFile(contents, tf.path));
+
+    ProcStat procStat(tf.path);
+    ASSERT_TRUE(procStat.enabled()) << "Temporary file is inaccessible";
+    EXPECT_FALSE(procStat.collect().ok()) << "No error returned due to missing cpu line";
+}
+
+TEST(ProcStatTest, TestErrorOnMissingProcsRunningLine) {
+    constexpr char contents[] =
+            "cpu  16200 8700 2000 4100 1250 6200 5900 0 0 0\n"
+            "cpu0 2400 2900 600 690 340 4300 2100 0 0 0\n"
+            "cpu1 1900 2380 510 760 51 370 1500 0 0 0\n"
+            "cpu2 900 400 400 1000 600 400 160 0 0 0\n"
+            "cpu3 1000 20 190 650 109 130 140 0 0 0\n"
+            "intr 694351583 0 0 0 297062868 0 5922464 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 "
+            "0 0\n"
+            "ctxt 579020168\n"
+            "btime 1579718450\n"
+            "processes 113804\n"
+            "procs_blocked 5\n"
+            "softirq 33275060 934664 11958403 5111 516325 200333 0 341482 10651335 0 8667407\n";
+    TemporaryFile tf;
+    ASSERT_NE(tf.fd, -1);
+    ASSERT_TRUE(WriteStringToFile(contents, tf.path));
+
+    ProcStat procStat(tf.path);
+    ASSERT_TRUE(procStat.enabled()) << "Temporary file is inaccessible";
+    EXPECT_FALSE(procStat.collect().ok()) << "No error returned due to missing procs_running line";
+}
+
+TEST(ProcStatTest, TestErrorOnMissingProcsBlockedLine) {
+    constexpr char contents[] =
+            "cpu  16200 8700 2000 4100 1250 6200 5900 0 0 0\n"
+            "cpu0 2400 2900 600 690 340 4300 2100 0 0 0\n"
+            "cpu1 1900 2380 510 760 51 370 1500 0 0 0\n"
+            "cpu2 900 400 400 1000 600 400 160 0 0 0\n"
+            "cpu3 1000 20 190 650 109 130 140 0 0 0\n"
+            "intr 694351583 0 0 0 297062868 0 5922464 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 "
+            "0 0\n"
+            "ctxt 579020168\n"
+            "btime 1579718450\n"
+            "processes 113804\n"
+            "procs_running 17\n"
+            "softirq 33275060 934664 11958403 5111 516325 200333 0 341482 10651335 0 8667407\n";
+    TemporaryFile tf;
+    ASSERT_NE(tf.fd, -1);
+    ASSERT_TRUE(WriteStringToFile(contents, tf.path));
+
+    ProcStat procStat(tf.path);
+    ASSERT_TRUE(procStat.enabled()) << "Temporary file is inaccessible";
+    EXPECT_FALSE(procStat.collect().ok()) << "No error returned due to missing procs_blocked line";
+}
+
+TEST(ProcStatTest, TestErrorOnUnknownProcsLine) {
+    constexpr char contents[] =
+            "cpu  16200 8700 2000 4100 1250 6200 5900 0 0 0\n"
+            "cpu0 2400 2900 600 690 340 4300 2100 0 0 0\n"
+            "cpu1 1900 2380 510 760 51 370 1500 0 0 0\n"
+            "cpu2 900 400 400 1000 600 400 160 0 0 0\n"
+            "cpu3 1000 20 190 650 109 130 140 0 0 0\n"
+            "intr 694351583 0 0 0 297062868 0 5922464 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 "
+            "0 0\n"
+            "ctxt 579020168\n"
+            "btime 1579718450\n"
+            "processes 113804\n"
+            "procs_running 17\n"
+            "procs_blocked 5\n"
+            "procs_sleeping 15\n"
+            "softirq 33275060 934664 11958403 5111 516325 200333 0 341482 10651335 0 8667407\n";
+    TemporaryFile tf;
+    ASSERT_NE(tf.fd, -1);
+    ASSERT_TRUE(WriteStringToFile(contents, tf.path));
+
+    ProcStat procStat(tf.path);
+    ASSERT_TRUE(procStat.enabled()) << "Temporary file is inaccessible";
+    EXPECT_FALSE(procStat.collect().ok()) << "No error returned due to unknown procs line";
+}
+
+TEST(ProcStatTest, TestProcStatContentsFromDevice) {
+    ProcStat procStat;
+    ASSERT_TRUE(procStat.enabled()) << kProcStatPath << " file is inaccessible";
+
+    const auto& info = procStat.collect();
+    ASSERT_RESULT_OK(info);
+
+    // The below checks should pass because the /proc/stats file should have the CPU time spent
+    // since bootup and there should be at least one running process.
+    EXPECT_GT(info->totalCpuTime(), 0);
+    EXPECT_GT(info->totalProcessesCnt(), 0);
+}
+
+}  // namespace watchdog
+}  // namespace automotive
+}  // namespace android
diff --git a/watchdog/server/tests/UidIoStatsTest.cpp b/watchdog/server/tests/UidIoStatsTest.cpp
new file mode 100644
index 0000000..f4f0dd9
--- /dev/null
+++ b/watchdog/server/tests/UidIoStatsTest.cpp
@@ -0,0 +1,121 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "UidIoStats.h"
+
+#include <android-base/file.h>
+
+#include <unordered_map>
+
+#include "gmock/gmock.h"
+
+namespace android {
+namespace automotive {
+namespace watchdog {
+
+using ::android::base::WriteStringToFile;
+
+TEST(UidIoStatsTest, TestValidStatFile) {
+    // Format: uid fgRdChar fgWrChar fgRdBytes fgWrBytes bgRdChar bgWrChar bgRdBytes bgWrBytes
+    // fgFsync bgFsync
+    constexpr char firstSnapshot[] =
+        "1001234 5000 1000 3000 500 0 0 0 0 20 0\n"
+        "1005678 500 100 30 50 300 400 100 200 45 60\n"
+        "1009 0 0 0 0 40000 50000 20000 30000 0 300\n"
+        "1001000 4000 3000 2000 1000 400 300 200 100 50 10\n";
+    std::unordered_map<uint32_t, UidIoUsage> expectedFirstUsage = {
+        {1001234,
+         {.uid = 1001234,
+          .ios = {/*fgRdBytes=*/3000, /*bgRdBytes=*/0, /*fgWrBytes=*/500,
+                  /*bgWrBytes=*/0, /*fgFsync=*/20, /*bgFsync=*/0}}},
+        {1005678, {.uid = 1005678, .ios = {30, 100, 50, 200, 45, 60}}},
+        {1009, {.uid = 1009, .ios = {0, 20000, 0, 30000, 0, 300}}},
+        {1001000, {.uid = 1001000, .ios = {2000, 200, 1000, 100, 50, 10}}},
+    };
+    TemporaryFile tf;
+    ASSERT_NE(tf.fd, -1);
+    ASSERT_TRUE(WriteStringToFile(firstSnapshot, tf.path));
+
+    UidIoStats uidIoStats(tf.path);
+    ASSERT_TRUE(uidIoStats.enabled()) << "Temporary file is inaccessible";
+
+    const auto& actualFirstUsage = uidIoStats.collect();
+    EXPECT_TRUE(actualFirstUsage) << actualFirstUsage.error();
+    EXPECT_EQ(expectedFirstUsage.size(), actualFirstUsage->size());
+    for (const auto& it : expectedFirstUsage) {
+        if (actualFirstUsage->find(it.first) == actualFirstUsage->end()) {
+            ADD_FAILURE() << "Expected uid " << it.first << " not found in the first snapshot";
+        }
+        const UidIoUsage& expected = it.second;
+        const UidIoUsage& actual = actualFirstUsage->at(it.first);
+        EXPECT_EQ(expected.uid, actual.uid);
+        EXPECT_EQ(expected.ios, actual.ios)
+            << "Unexpected I/O usage for uid " << it.first << " in first snapshot.\nExpected:\n"
+            << expected.ios.toString() << "\nActual:\n"<< actual.ios.toString();
+    }
+
+    constexpr char secondSnapshot[] =
+        "1001234 10000 2000 7000 950 0 0 0 0 45 0\n"
+        "1005678 600 100 40 50 1000 1000 1000 600 50 70\n"
+        "1003456 300 500 200 300 0 0 0 0 50 0\n"
+        "1001000 400 300 200 100 40 30 20 10 5 1\n";
+    std::unordered_map<uint32_t, UidIoUsage> expectedSecondUsage = {
+        {1001234,
+         {.uid = 1001234,
+          .ios = {/*fgRdBytes=*/4000, /*bgRdBytes=*/0,
+                  /*fgWrBytes=*/450, /*bgWrBytes=*/0, /*fgFsync=*/25,
+                  /*bgFsync=*/0}}},
+        {1005678, {.uid = 1005678, .ios = {10, 900, 0, 400, 5, 10}}},
+        {1003456, {.uid = 1003456, .ios = {200, 0, 300, 0, 50, 0}}},
+        {1001000, {.uid = 1001000, .ios = {0, 0, 0, 0, 0, 0}}},
+    };
+    ASSERT_TRUE(WriteStringToFile(secondSnapshot, tf.path));
+    const auto& actualSecondUsage = uidIoStats.collect();
+    EXPECT_TRUE(actualSecondUsage) << actualSecondUsage.error();
+    EXPECT_EQ(expectedSecondUsage.size(), actualSecondUsage->size());
+    for (const auto& it : expectedSecondUsage) {
+        if (actualSecondUsage->find(it.first) == actualSecondUsage->end()) {
+            ADD_FAILURE() << "Expected uid " << it.first << " not found in the second snapshot";
+        }
+        const UidIoUsage& expected = it.second;
+        const UidIoUsage& actual = actualSecondUsage->at(it.first);
+        EXPECT_EQ(expected.uid, actual.uid);
+        EXPECT_EQ(expected.ios, actual.ios)
+            << "Unexpected I/O usage for uid " << it.first << " in second snapshot:.\nExpected:\n"
+            << expected.ios.toString() << "\nActual:\n"<< actual.ios.toString();
+    }
+}
+
+TEST(UidIoStatsTest, TestErrorOnInvalidStatFile) {
+    // Format: uid fgRdChar fgWrChar fgRdBytes fgWrBytes bgRdChar bgWrChar bgRdBytes bgWrBytes
+    // fgFsync bgFsync
+    constexpr char contents[] =
+        "1001234 5000 1000 3000 500 0 0 0 0 20 0\n"
+        "1005678 500 100 30 50 300 400 100 200 45 60\n"
+        "1009012 0 0 0 0 40000 50000 20000 30000 0 300\n"
+        "1001000 4000 3000 2000 1000 CORRUPTED DATA\n";
+    TemporaryFile tf;
+    ASSERT_NE(tf.fd, -1);
+    ASSERT_TRUE(WriteStringToFile(contents, tf.path));
+
+    UidIoStats uidIoStats(tf.path);
+    ASSERT_TRUE(uidIoStats.enabled()) << "Temporary file is inaccessible";
+    EXPECT_FALSE(uidIoStats.collect()) << "No error returned for invalid file";
+}
+
+}  // namespace watchdog
+}  // namespace automotive
+}  // namespace android