Pass car API stubs to non-SDK enforcement
am: 43231d977b

Change-Id: I0fe7d444a91770b024c7f77f3ee43a5ea4f8099b
diff --git a/.gitignore b/.gitignore
index b292847..697ef2d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,3 +5,4 @@
 gen/
 *.pyc
 __pycache__
+.idea
\ No newline at end of file
diff --git a/TrustAgent/res/values/strings.xml b/TrustAgent/res/values/strings.xml
index d077262..192b7e4 100644
--- a/TrustAgent/res/values/strings.xml
+++ b/TrustAgent/res/values/strings.xml
@@ -36,4 +36,6 @@
     <string name="start_advertising">Start Advertising</string>
     <string name="revoke_trust">Revoke Trust</string>
 
+    <string translatable="false" name="token_handle_shared_preferences">com.android.car.trust.TOKEN_HANDLE</string>
+
 </resources>
diff --git a/TrustAgent/src/com/android/car/trust/CarBleTrustAgent.java b/TrustAgent/src/com/android/car/trust/CarBleTrustAgent.java
index 227ea89..1dfbd50 100644
--- a/TrustAgent/src/com/android/car/trust/CarBleTrustAgent.java
+++ b/TrustAgent/src/com/android/car/trust/CarBleTrustAgent.java
@@ -16,27 +16,25 @@
 
 package com.android.car.trust;
 
-import android.bluetooth.BluetoothDevice;
-import android.bluetooth.BluetoothGattServer;
-import android.bluetooth.BluetoothGattServerCallback;
-import android.bluetooth.BluetoothManager;
+import android.annotation.Nullable;
+import android.app.ActivityManager;
+import android.app.UserSwitchObserver;
+import android.bluetooth.BluetoothAdapter;
 import android.car.trust.ICarTrustAgentBleService;
 import android.car.trust.ICarTrustAgentTokenRequestDelegate;
 import android.car.trust.ICarTrustAgentUnlockCallback;
+import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
+import android.content.IntentFilter;
 import android.content.ServiceConnection;
-import android.os.Handler;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.UserHandle;
-import android.os.UserManager;
 import android.service.trust.TrustAgentService;
 import android.util.Log;
 
-import java.util.concurrent.TimeUnit;
-
 /**
  * A BluetoothLE (BLE) based {@link TrustAgentService} that uses the escrow token unlock APIs. </p>
  *
@@ -49,39 +47,44 @@
 
     private static final String TAG = CarBleTrustAgent.class.getSimpleName();
 
-    private static final long TRUST_DURATION_MS = TimeUnit.MINUTES.toMicros(5);
-    private static final long BLE_RETRY_MS = TimeUnit.SECONDS.toMillis(1);
-
-    private Handler mHandler;
-    private BluetoothManager mBluetoothManager;
-    private ICarTrustAgentBleService mCarTrustAgentBleService;
-    private boolean mCarTrustAgentBleServiceBound;
-
+    /**
+     * {@link CarTrustAgentBleService} will callback this function when it receives both
+     * handle and token.
+     */
     private final ICarTrustAgentUnlockCallback mUnlockCallback =
             new ICarTrustAgentUnlockCallback.Stub() {
         @Override
-        public void onUnlockDataReceived(byte[] token, long handle) {
-            UserManager um = (UserManager) getSystemService(Context.USER_SERVICE);
-            // TODO(b/77854782): get the actual user to unlock by token
-            UserHandle userHandle = getForegroundUserHandle();
+        public void onUnlockDataReceived(byte[] token, long handle) throws RemoteException {
+            UserHandle userHandle = getUserHandleByTokenHandle(handle);
+            if (userHandle == null) {
+                Log.e(TAG, "Unable to find user by token handle " + handle);
+                return;
+            }
 
-            Log.d(TAG, "About to unlock user. Handle: " + handle
-                    + " Time: " + System.currentTimeMillis());
-            unlockUserWithToken(handle, token, userHandle);
-
-            Log.d(TAG, "Attempted to unlock user, is user unlocked: "
-                    + um.isUserUnlocked(userHandle)
-                    + " Time: " + System.currentTimeMillis());
-            setManagingTrust(true);
-
-            if (um.isUserUnlocked(userHandle)) {
-                Log.d(TAG, getString(R.string.trust_granted_explanation));
-                grantTrust("Granting trust from escrow token",
-                        TRUST_DURATION_MS, FLAG_GRANT_TRUST_DISMISS_KEYGUARD);
+            int uid = userHandle.getIdentifier();
+            if (ActivityManager.getCurrentUser() != uid) {
+                Log.d(TAG, "Switch to user: " + uid);
+                // Try to unlock when user switch completes
+                ActivityManager.getService().registerUserSwitchObserver(
+                        getUserSwitchObserver(uid, token, handle), TAG);
+                ActivityManager am = (ActivityManager) getSystemService(ACTIVITY_SERVICE);
+                am.switchUser(uid);
+            } else {
+                unlockUserInternally(uid, token, handle);
             }
         }
     };
 
+    /**
+     * Delegates the escrow token API calls from {@link CarTrustAgentBleService} to
+     * {@link TrustAgentService}. Due to the asynchronous nature, the results will be posted to
+     * {@link CarTrustAgentBleService} by the following calls
+     * <ul>
+     *     <li>{@link #onEscrowTokenAdded(byte[], long, UserHandle)}</li>
+     *     <li>{@link #onEscrowTokenRemoved(long, boolean)}</li>
+     *     <li>{@link #onEscrowTokenStateReceived(long, int)}</li>
+     * </ul>
+     */
     private final ICarTrustAgentTokenRequestDelegate mTokenRequestDelegate =
             new ICarTrustAgentTokenRequestDelegate.Stub() {
         @Override
@@ -105,6 +108,9 @@
         }
     };
 
+    /**
+     * Service connection to {@link CarTrustAgentBleService}
+     */
     private final ServiceConnection mServiceConnection = new ServiceConnection() {
         @Override
         public void onServiceConnected(ComponentName name, IBinder service) {
@@ -114,7 +120,6 @@
             try {
                 mCarTrustAgentBleService.registerUnlockCallback(mUnlockCallback);
                 mCarTrustAgentBleService.setTokenRequestDelegate(mTokenRequestDelegate);
-                maybeStartBleUnlockService();
             } catch (RemoteException e) {
                 Log.e(TAG, "Error registerUnlockCallback", e);
             }
@@ -122,10 +127,12 @@
 
         @Override
         public void onServiceDisconnected(ComponentName name) {
+            Log.d(TAG, "CarTrustAgentBleService disconnected");
             if (mCarTrustAgentBleService != null) {
                 try {
                     mCarTrustAgentBleService.unregisterUnlockCallback(mUnlockCallback);
                     mCarTrustAgentBleService.setTokenRequestDelegate(null);
+                    mCarTrustAgentBleService.stopUnlockAdvertising();
                 } catch (RemoteException e) {
                     Log.e(TAG, "Error unregisterUnlockCallback", e);
                 }
@@ -135,45 +142,57 @@
         }
     };
 
+    /**
+     * Receives the bluetooth state change broadcasts. Bluetooth is restarted when switching user,
+     * we need to ensure calling {@link ICarTrustAgentBleService#startUnlockAdvertising} after
+     * bluetooth is started.
+     */
+    private final BroadcastReceiver mBluetoothBroadcastReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            switch (intent.getAction()) {
+                case BluetoothAdapter.ACTION_STATE_CHANGED:
+                    onBluetoothStateChanged(intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, -1));
+                    break;
+            }
+        }
+    };
+
+    private ICarTrustAgentBleService mCarTrustAgentBleService;
+    private boolean mCarTrustAgentBleServiceBound;
+
+    /**
+     * TODO: Currently it relies on {@link #onDeviceLocked()} and {@link #onDeviceUnlocked()}
+     * callback, and these callbacks won't happen if the user has unlocked once.
+     */
+    private boolean mIsOnLockScreen;
+
     @Override
     public void onCreate() {
         super.onCreate();
-
-        Log.d(TAG, "Bluetooth trust agent starting up");
-        mHandler = new Handler();
-        mBluetoothManager = (BluetoothManager) getSystemService(BLUETOOTH_SERVICE);
-
-        // If the user is already unlocked, don't bother starting the BLE service.
-        UserManager um = (UserManager) getSystemService(Context.USER_SERVICE);
-        if (!um.isUserUnlocked(getForegroundUserHandle())) {
-            Log.d(TAG, "User locked, will now bind CarTrustAgentBleService");
-            Intent intent = new Intent(this, CarTrustAgentBleService.class);
-            bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE);
-        } else {
-            setManagingTrust(true);
-        }
+        setManagingTrust(true);
+        bindService(new Intent(this, CarTrustAgentBleService.class),
+                mServiceConnection, Context.BIND_AUTO_CREATE);
+        IntentFilter intentFilter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED);
+        registerReceiver(mBluetoothBroadcastReceiver, intentFilter);
     }
 
     @Override
     public void onDestroy() {
         Log.d(TAG, "Car Trust agent shutting down");
-        mHandler.removeCallbacks(null);
-
-        // Unbind the service to avoid leaks from BLE stack.
         if (mCarTrustAgentBleServiceBound) {
             unbindService(mServiceConnection);
         }
+        unregisterReceiver(mBluetoothBroadcastReceiver);
         super.onDestroy();
     }
 
     @Override
     public void onDeviceLocked() {
         super.onDeviceLocked();
+        mIsOnLockScreen = true;
         if (mCarTrustAgentBleServiceBound) {
             try {
-                // Only one BLE advertising is allowed, ensure enrolment advertising is stopped
-                // before start unlock advertising.
-                mCarTrustAgentBleService.stopEnrolmentAdvertising();
                 mCarTrustAgentBleService.startUnlockAdvertising();
             } catch (RemoteException e) {
                 Log.e(TAG, "Error startUnlockAdvertising", e);
@@ -184,36 +203,47 @@
     @Override
     public void onDeviceUnlocked() {
         super.onDeviceUnlocked();
+        mIsOnLockScreen = false;
         if (mCarTrustAgentBleServiceBound) {
             try {
-                // Only one BLE advertising is allowed, ensure unlock advertising is stopped.
                 mCarTrustAgentBleService.stopUnlockAdvertising();
             } catch (RemoteException e) {
                 Log.e(TAG, "Error stopUnlockAdvertising", e);
             }
         }
+        // Revoke trust right after to enable keyguard when switching user
+        revokeTrust();
     }
 
-    private void maybeStartBleUnlockService() {
-        Log.d(TAG, "Trying to open a Ble GATT server");
-        BluetoothGattServer gattServer = mBluetoothManager.openGattServer(
-                this, new BluetoothGattServerCallback() {
+    private UserSwitchObserver getUserSwitchObserver(int uid,
+            byte[] token, long handle) {
+        return new UserSwitchObserver() {
             @Override
-            public void onConnectionStateChange(BluetoothDevice device, int status, int newState) {
-                super.onConnectionStateChange(device, status, newState);
+            public void onUserSwitchComplete(int newUserId) throws RemoteException {
+                if (uid != newUserId) return;
+                unlockUserInternally(uid, token, handle);
+                ActivityManager.getService().unregisterUserSwitchObserver(this);
             }
-        });
 
-        // The BLE stack is started up before the trust agent service, however Gatt capabilities
-        // might not be ready just yet. Keep trying until a GattServer can open up before proceeding
-        // to start the rest of the BLE services.
-        if (gattServer == null) {
-            Log.e(TAG, "Gatt not available, will try again...in " + BLE_RETRY_MS + "ms");
-            mHandler.postDelayed(this::maybeStartBleUnlockService, BLE_RETRY_MS);
-        } else {
-            mHandler.removeCallbacks(null);
-            gattServer.close();
-            Log.d(TAG, "GATT available, starting up UnlockService");
+            @Override
+            public void onLockedBootComplete(int newUserId) {
+                // ignored.
+            }
+        };
+    }
+
+    private void unlockUserInternally(int uid, byte[] token, long handle) {
+        Log.d(TAG, "About to unlock user: " + uid);
+        unlockUserWithToken(handle, token, UserHandle.of(uid));
+        grantTrust("Granting trust from escrow token",
+                0, FLAG_GRANT_TRUST_DISMISS_KEYGUARD);
+    }
+
+    private void onBluetoothStateChanged(int state) {
+        Log.d(TAG, "onBluetoothStateChanged: " + state);
+        if ((state == BluetoothAdapter.STATE_ON || state == BluetoothAdapter.STATE_BLE_ON)
+                && mCarTrustAgentBleServiceBound
+                && mIsOnLockScreen) {
             try {
                 mCarTrustAgentBleService.startUnlockAdvertising();
             } catch (RemoteException e) {
@@ -259,12 +289,15 @@
         }
     }
 
-    /**
-     * TODO(b/77854782): return the {@link UserHandle} of foreground user.
-     * CarBleTrustAgent itself runs as user-0
-     */
-    private UserHandle getForegroundUserHandle() {
-        Log.d(TAG, "getForegroundUserHandle for " + UserHandle.myUserId());
-        return UserHandle.of(UserHandle.myUserId());
+    private @Nullable UserHandle getUserHandleByTokenHandle(long tokenHandle) {
+        if (mCarTrustAgentBleServiceBound) {
+            try {
+                int userId = mCarTrustAgentBleService.getUserIdByEscrowTokenHandle(tokenHandle);
+                return userId < 0 ? null : UserHandle.of(userId);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Error getUserHandleByTokenHandle");
+            }
+        }
+        return null;
     }
 }
diff --git a/TrustAgent/src/com/android/car/trust/CarEnrolmentActivity.java b/TrustAgent/src/com/android/car/trust/CarEnrolmentActivity.java
index d7160e3..4b587d1 100644
--- a/TrustAgent/src/com/android/car/trust/CarEnrolmentActivity.java
+++ b/TrustAgent/src/com/android/car/trust/CarEnrolmentActivity.java
@@ -46,13 +46,16 @@
     private static final String SP_HANDLE_KEY = "sp-test";
     private static final int FINE_LOCATION_REQUEST_CODE = 42;
 
+    /**
+     * Receives escrow token callbacks, registered on {@link CarTrustAgentBleService}
+     */
     private final ICarTrustAgentTokenResponseCallback mCarTrustAgentTokenResponseCallback =
             new ICarTrustAgentTokenResponseCallback.Stub() {
         @Override
         public void onEscrowTokenAdded(byte[] token, long handle, int uid) {
             runOnUiThread(() -> {
                 mPrefs.edit().putLong(SP_HANDLE_KEY, handle).apply();
-                Log.d(TAG, "stored new handle");
+                Log.d(TAG, "stored new handle for user: " + uid);
             });
 
             if (mBluetoothDevice == null) {
@@ -81,6 +84,9 @@
         }
     };
 
+    /**
+     * Receives BLE state change callbacks, registered on {@link CarTrustAgentBleService}
+     */
     private final ICarTrustAgentBleCallback mBleConnectionCallback =
             new ICarTrustAgentBleCallback.Stub() {
         @Override
@@ -108,6 +114,12 @@
         }
     };
 
+    /**
+     * {@link CarTrustAgentBleService} will callback this when receives enrolment data.
+     *
+     * Here is the place we can prompt to the user on HU whether or not to add this
+     * {@link #mBluetoothDevice} as a trust device.
+     */
     private final ICarTrustAgentEnrolmentCallback mEnrolmentCallback =
             new ICarTrustAgentEnrolmentCallback.Stub() {
         @Override
@@ -121,6 +133,9 @@
         }
     };
 
+    /**
+     * Service connection to {@link CarTrustAgentBleService}
+     */
     private final ServiceConnection mServiceConnection = new ServiceConnection() {
         @Override
         public void onServiceConnected(ComponentName name, IBinder service) {
@@ -135,6 +150,7 @@
             } catch (RemoteException e) {
                 Log.e(TAG, "Error startEnrolmentAdvertising", e);
             }
+            checkTokenHandle();
         }
 
         @Override
@@ -169,8 +185,10 @@
         mPrefs = PreferenceManager.getDefaultSharedPreferences(this /* context */);
 
         findViewById(R.id.start_button).setOnClickListener((view) -> {
-            Intent bindIntent = new Intent(this, CarTrustAgentBleService.class);
-            bindService(bindIntent, mServiceConnection, Context.BIND_AUTO_CREATE);
+            if (!mCarTrustAgentBleServiceBound) {
+                Intent bindIntent = new Intent(this, CarTrustAgentBleService.class);
+                bindService(bindIntent, mServiceConnection, Context.BIND_AUTO_CREATE);
+            }
         });
 
         findViewById(R.id.revoke_trust_button).setOnClickListener((view) -> {
@@ -193,33 +211,17 @@
             requestPermissions(
                     new String[] { android.Manifest.permission.ACCESS_FINE_LOCATION },
                     FINE_LOCATION_REQUEST_CODE);
-        } else {
-            long tokenHandle = getTokenHandle();
-            if (tokenHandle != -1) {
-                Log.d(TAG, "onResume, checking handle active: " + tokenHandle);
-                if (mCarTrustAgentBleServiceBound) {
-                    try {
-                        // Due to the asynchronous nature of isEscrowTokenActive in
-                        // TrustAgentService, query result will be delivered via
-                        // {@link #mCarTrustAgentTokenResponseCallback}
-                        mCarTrustAgentBleService.isEscrowTokenActive(tokenHandle,
-                                UserHandle.myUserId());
-                    } catch (RemoteException e) {
-                        Log.e(TAG, "Error isEscrowTokenActive", e);
-                    }
-                }
-            } else {
-                appendOutputText("No handles found");
-            }
         }
     }
 
     @Override
-    protected void onDestroy() {
+    protected void onStop() {
+        super.onStop();
+
         if (mCarTrustAgentBleServiceBound) {
             unbindService(mServiceConnection);
+            mCarTrustAgentBleServiceBound = false;
         }
-        super.onDestroy();
     }
 
     private void appendOutputText(final String text) {
@@ -234,7 +236,23 @@
         mCarTrustAgentBleService.addEscrowToken(token, UserHandle.myUserId());
     }
 
-    private long getTokenHandle() {
-        return mPrefs.getLong(SP_HANDLE_KEY, -1);
+    private void checkTokenHandle() {
+        long tokenHandle = mPrefs.getLong(SP_HANDLE_KEY, -1);
+        if (tokenHandle != -1) {
+            Log.d(TAG, "Checking handle active: " + tokenHandle);
+            if (mCarTrustAgentBleServiceBound) {
+                try {
+                    // Due to the asynchronous nature of isEscrowTokenActive in
+                    // TrustAgentService, query result will be delivered via
+                    // {@link #mCarTrustAgentTokenResponseCallback}
+                    mCarTrustAgentBleService.isEscrowTokenActive(tokenHandle,
+                            UserHandle.myUserId());
+                } catch (RemoteException e) {
+                    Log.e(TAG, "Error isEscrowTokenActive", e);
+                }
+            }
+        } else {
+            appendOutputText("No handles found");
+        }
     }
 }
diff --git a/TrustAgent/src/com/android/car/trust/CarTrustAgentBleService.java b/TrustAgent/src/com/android/car/trust/CarTrustAgentBleService.java
index f8ba90f..2cc2080 100644
--- a/TrustAgent/src/com/android/car/trust/CarTrustAgentBleService.java
+++ b/TrustAgent/src/com/android/car/trust/CarTrustAgentBleService.java
@@ -25,6 +25,7 @@
 import android.car.trust.ICarTrustAgentTokenResponseCallback;
 import android.car.trust.ICarTrustAgentUnlockCallback;
 import android.content.Intent;
+import android.content.SharedPreferences;
 import android.os.IBinder;
 import android.os.ParcelUuid;
 import android.os.RemoteCallbackList;
@@ -62,6 +63,8 @@
     private byte[] mCurrentUnlockToken;
     private Long mCurrentUnlockHandle;
 
+    private SharedPreferences mTokenHandleSharedPreferences;
+
     @Override
     public void onCreate() {
         super.onCreate();
@@ -72,6 +75,10 @@
 
         setupEnrolmentBleServer();
         setupUnlockBleServer();
+
+        mTokenHandleSharedPreferences = getSharedPreferences(
+                getString(R.string.token_handle_shared_preferences),
+                MODE_PRIVATE);
     }
 
     @Override
@@ -260,8 +267,8 @@
 
         @Override
         public void startEnrolmentAdvertising() {
-            Log.d(TAG, "startEnrolmentAdvertising");
             stopUnlockAdvertising();
+            Log.d(TAG, "startEnrolmentAdvertising");
             startAdvertising(mEnrolmentUuid, mEnrolmentGattServer);
         }
 
@@ -290,8 +297,8 @@
 
         @Override
         public void startUnlockAdvertising() {
-            Log.d(TAG, "startUnlockAdvertising");
             stopEnrolmentAdvertising();
+            Log.d(TAG, "startUnlockAdvertising");
             startAdvertising(mUnlockUuid, mUnlockGattServer);
         }
 
@@ -353,6 +360,9 @@
         public void onEscrowTokenAdded(byte[] token, long handle, int uid)
                 throws RemoteException {
             Log.d(TAG, "onEscrowTokenAdded handle:" + handle + " uid:" + uid);
+            mTokenHandleSharedPreferences.edit()
+                    .putInt(String.valueOf(handle), uid)
+                    .apply();
             if (mTokenResponseCallback != null) {
                 mTokenResponseCallback.onEscrowTokenAdded(token, handle, uid);
             }
@@ -361,6 +371,9 @@
         @Override
         public void onEscrowTokenRemoved(long handle, boolean successful) throws RemoteException {
             Log.d(TAG, "onEscrowTokenRemoved handle:" + handle);
+            mTokenHandleSharedPreferences.edit()
+                    .remove(String.valueOf(handle))
+                    .apply();
             if (mTokenResponseCallback != null) {
                 mTokenResponseCallback.onEscrowTokenRemoved(handle, successful);
             }
@@ -373,5 +386,10 @@
                 mTokenResponseCallback.onEscrowTokenActiveStateChanged(handle, active);
             }
         }
+
+        @Override
+        public int getUserIdByEscrowTokenHandle(long tokenHandle) {
+            return mTokenHandleSharedPreferences.getInt(String.valueOf(tokenHandle), -1);
+        }
     }
 }
diff --git a/car-lib/api/current.txt b/car-lib/api/current.txt
index 9cd2e4c..57975a2 100644
--- a/car-lib/api/current.txt
+++ b/car-lib/api/current.txt
@@ -73,6 +73,126 @@
     ctor public CarNotConnectedException(java.lang.Exception);
   }
 
+  public final class VehiclePropertyIds {
+    ctor public VehiclePropertyIds();
+    method public static java.lang.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 CURRENT_GEAR = 289408001; // 0x11400401
+    field public static final int DISPLAY_BRIGHTNESS = 289409539; // 0x11400a03
+    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_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_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 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 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 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_VEHICLE_SPEED = 291504647; // 0x11600207
+    field public static final int RANGE_REMAINING = 291504904; // 0x11600308
+    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_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 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
+  }
+
 }
 
 package android.car.app.menu {
@@ -265,7 +385,6 @@
     field public static final int IGNITION_STATE_ON = 4; // 0x4
     field public static final int IGNITION_STATE_START = 5; // 0x5
     field public static final int IGNITION_STATE_UNDEFINED = 0; // 0x0
-    field public static final int INDEX_ENVIRONMENT_PRESSURE = 1; // 0x1
     field public static final int INDEX_ENVIRONMENT_TEMPERATURE = 0; // 0x0
     field public static final int INDEX_WHEEL_DISTANCE_FRONT_LEFT = 1; // 0x1
     field public static final int INDEX_WHEEL_DISTANCE_FRONT_RIGHT = 2; // 0x2
@@ -280,7 +399,6 @@
   }
 
   public static class CarSensorEvent.EnvironmentData {
-    field public float pressure;
     field public float temperature;
     field public long timestamp;
   }
@@ -300,7 +418,7 @@
     field public static final int SENSOR_RATE_UI = 5; // 0x5
     field public static final int SENSOR_TYPE_ABS_ACTIVE = 287310858; // 0x1120040a
     field public static final int SENSOR_TYPE_CAR_SPEED = 291504647; // 0x11600207
-    field public static final int SENSOR_TYPE_ENVIRONMENT = 12; // 0xc
+    field public static final int SENSOR_TYPE_ENV_OUTSIDE_TEMPERATURE = 291505923; // 0x11600703
     field public static final int SENSOR_TYPE_EV_BATTERY_CHARGE_RATE = 291504908; // 0x1160030c
     field public static final int SENSOR_TYPE_EV_BATTERY_LEVEL = 291504905; // 0x11600309
     field public static final int SENSOR_TYPE_EV_CHARGE_PORT_CONNECTED = 287310603; // 0x1120030b
diff --git a/car-lib/api/system-current.txt b/car-lib/api/system-current.txt
index d611932..725967d 100644
--- a/car-lib/api/system-current.txt
+++ b/car-lib/api/system-current.txt
@@ -112,6 +112,13 @@
     field public static final int WINDOW_ROW_3_RIGHT = 16384; // 0x4000
   }
 
+  public final class VehiclePropertyAccess {
+    field public static final int NONE = 0; // 0x0
+    field public static final int READ = 1; // 0x1
+    field public static final int READ_WRITE = 3; // 0x3
+    field public static final int WRITE = 2; // 0x2
+  }
+
 }
 
 package android.car.cluster {
diff --git a/car-lib/src/android/car/CarInfoManager.java b/car-lib/src/android/car/CarInfoManager.java
index b228ca7..d90883b 100644
--- a/car-lib/src/android/car/CarInfoManager.java
+++ b/car-lib/src/android/car/CarInfoManager.java
@@ -16,16 +16,12 @@
 
 package android.car;
 
-import static java.lang.Integer.toHexString;
-
 import android.annotation.Nullable;
 import android.car.annotation.ValueTypeDef;
 import android.car.hardware.CarPropertyValue;
-import android.car.hardware.property.ICarProperty;
+import android.car.hardware.property.CarPropertyManager;
 import android.os.Bundle;
 import android.os.IBinder;
-import android.os.RemoteException;
-import android.util.Log;
 
 
 /**
@@ -36,6 +32,7 @@
 
     private static final boolean DBG = false;
     private static final String TAG = "CarInfoManager";
+    private final CarPropertyManager mCarPropertyMgr;
     /**
      * Key for manufacturer of the car. Passed in basic info Bundle.
      * @hide
@@ -112,17 +109,15 @@
      * Passed in basic info Bundle.
      * @hide
      */
-    @ValueTypeDef(type = Integer.class)
+    @ValueTypeDef(type = Integer[].class)
     public static final int BASIC_INFO_EV_CONNECTOR_TYPES = 0x11410107;
 
-    private final ICarProperty mService;
-
     /**
      * @return Manufacturer of the car.  Null if not available.
      */
     @Nullable
     public String getManufacturer() throws CarNotConnectedException {
-        CarPropertyValue<String> carProp = getProperty(String.class,
+        CarPropertyValue<String> carProp = mCarPropertyMgr.getProperty(String.class,
                 BASIC_INFO_KEY_MANUFACTURER, 0);
         return carProp != null ? carProp.getValue() : null;
     }
@@ -134,7 +129,8 @@
      */
     @Nullable
     public String getModel() throws CarNotConnectedException {
-        CarPropertyValue<String> carProp = getProperty(String.class, BASIC_INFO_KEY_MODEL, 0);
+        CarPropertyValue<String> carProp = mCarPropertyMgr.getProperty(
+                String.class, BASIC_INFO_KEY_MODEL, 0);
         return carProp != null ? carProp.getValue() : null;
     }
 
@@ -143,7 +139,7 @@
      */
     @Nullable
     public String getModelYear() throws CarNotConnectedException {
-        CarPropertyValue<String> carProp = getProperty(String.class,
+        CarPropertyValue<String> carProp = mCarPropertyMgr.getProperty(String.class,
                 BASIC_INFO_KEY_MODEL_YEAR, 0);
         return carProp != null ? carProp.getValue() : null;
     }
@@ -163,7 +159,7 @@
      *         fuel.
      */
     public float getFuelCapacity() throws CarNotConnectedException {
-        CarPropertyValue<Float> carProp = getProperty(Float.class,
+        CarPropertyValue<Float> carProp = mCarPropertyMgr.getProperty(Float.class,
                 BASIC_INFO_FUEL_CAPACITY, 0);
         return carProp != null ? carProp.getValue() : 0f;
     }
@@ -173,8 +169,7 @@
      *         types available.
      */
     public @FuelType.Enum int[] getFuelTypes() throws CarNotConnectedException {
-        CarPropertyValue<int[]> carProp = getProperty(int[].class, BASIC_INFO_FUEL_TYPES, 0);
-        return carProp != null ? carProp.getValue() : new int[0];
+        return mCarPropertyMgr.getIntArrayProperty(BASIC_INFO_FUEL_TYPES, 0);
     }
 
     /**
@@ -182,7 +177,7 @@
      *         battery.
      */
     public float getEvBatteryCapacity() throws CarNotConnectedException {
-        CarPropertyValue<Float> carProp = getProperty(Float.class,
+        CarPropertyValue<Float> carProp = mCarPropertyMgr.getProperty(Float.class,
                 BASIC_INFO_EV_BATTERY_CAPACITY, 0);
         return carProp != null ? carProp.getValue() : 0f;
     }
@@ -192,42 +187,17 @@
      *         no connector types available.
      */
     public @EvConnectorType.Enum int[] getEvConnectorTypes() throws CarNotConnectedException {
-        CarPropertyValue<int[]> carProp = getProperty(int[].class,
-                BASIC_INFO_EV_CONNECTOR_TYPES, 0);
-        return carProp != null ? carProp.getValue() : new int[0];
+        return mCarPropertyMgr.getIntArrayProperty(BASIC_INFO_EV_CONNECTOR_TYPES, 0);
     }
 
     /** @hide */
     CarInfoManager(IBinder service) {
-        mService = ICarProperty.Stub.asInterface(service);
+        mCarPropertyMgr = new CarPropertyManager(service, null, DBG, TAG);
     }
 
     /** @hide */
     public void onCarDisconnected() {
+        mCarPropertyMgr.onCarDisconnected();
     }
 
-    private  <E> CarPropertyValue<E> getProperty(Class<E> clazz, int propId, int area)
-            throws CarNotConnectedException {
-        if (DBG) {
-            Log.d(TAG, "getProperty, propId: 0x" + toHexString(propId)
-                    + ", area: 0x" + toHexString(area) + ", class: " + clazz);
-        }
-        try {
-            CarPropertyValue<E> propVal = mService.getProperty(propId, area);
-            if (propVal != null && propVal.getValue() != null) {
-                Class<?> actualClass = propVal.getValue().getClass();
-                if (actualClass != clazz) {
-                    throw new IllegalArgumentException("Invalid property type. " + "Expected: "
-                            + clazz + ", but was: " + actualClass);
-                }
-            }
-            return propVal;
-        } catch (RemoteException e) {
-            Log.e(TAG, "getProperty failed with " + e.toString()
-                    + ", propId: 0x" + toHexString(propId) + ", area: 0x" + toHexString(area), e);
-            throw new CarNotConnectedException(e);
-        } catch (IllegalArgumentException e)  {
-            return null;
-        }
-    }
 }
diff --git a/car-lib/src/android/car/VehicleLightState.java b/car-lib/src/android/car/VehicleLightState.java
new file mode 100644
index 0000000..55f74f9
--- /dev/null
+++ b/car-lib/src/android/car/VehicleLightState.java
@@ -0,0 +1,31 @@
+/*
+ * 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;
+
+/**
+ * Used by Lights state properties to enumerate the current state of the lights.
+ * Use getProperty and setProperty in {@link android.car.hardware.property.CarPropertyManager} to
+ * set and get this VHAL property.
+ * @hide
+ */
+public final class VehicleLightState {
+    public static final int OFF = 0;
+    public static final int ON = 1;
+    public static final int DAYTIME_RUNNING = 2;
+
+    private VehicleLightState() {}
+
+}
diff --git a/car-lib/src/android/car/VehicleLightSwitch.java b/car-lib/src/android/car/VehicleLightSwitch.java
new file mode 100644
index 0000000..03780bb
--- /dev/null
+++ b/car-lib/src/android/car/VehicleLightSwitch.java
@@ -0,0 +1,31 @@
+/*
+ * 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;
+
+/**
+ * Used by lights switch properties to enumerate user selected switch setting.
+ * Use getProperty and setProperty in {@link android.car.hardware.property.CarPropertyManager} to
+ * set and get this VHAL property.
+ * @hide
+ */
+public final class VehicleLightSwitch {
+    public static final int OFF = 0;
+    public static final int ON = 1;
+    public static final int DAYTIME_RUNNING = 2;
+    public static final int AUTOMATIC = 0x100;
+
+    private VehicleLightSwitch() {}
+}
diff --git a/car-lib/src/android/car/VehiclePropertyAccess.java b/car-lib/src/android/car/VehiclePropertyAccess.java
new file mode 100644
index 0000000..22801de
--- /dev/null
+++ b/car-lib/src/android/car/VehiclePropertyAccess.java
@@ -0,0 +1,35 @@
+/*
+ * 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;
+
+import android.annotation.SystemApi;
+
+/**
+ * Define value for getAccess() in {@link android.car.hardware.CarPropertyConfig}
+ * @hide
+ */
+@SystemApi
+public final class VehiclePropertyAccess {
+    /**
+     * List of VehiclePropertyAccess from VHAL
+     */
+    public static final int NONE = 0x00;
+    public static final int READ = 0x01;
+    public static final int WRITE = 0x02;
+    public static final int READ_WRITE = 0x03;
+
+    private VehiclePropertyAccess() {}
+}
diff --git a/car-lib/src/android/car/VehiclePropertyIds.java b/car-lib/src/android/car/VehiclePropertyIds.java
new file mode 100644
index 0000000..12d2698
--- /dev/null
+++ b/car-lib/src/android/car/VehiclePropertyIds.java
@@ -0,0 +1,888 @@
+/*
+ * 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;
+
+/**
+ * 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.
+ * Use it as PorpertyId in getProperty() and setProperty() in
+ * {@link android.car.hardware.property.CarPropertyManager}
+ */
+public final class VehiclePropertyIds {
+    /**
+     * Undefined property.  */
+    public static final int INVALID = 0;
+    /**
+     * VIN of vehicle
+     */
+    public static final int INFO_VIN = 286261504;
+    /**
+     * Manufacturer of vehicle
+     */
+    public static final int INFO_MAKE = 286261505;
+    /**
+     * Model of vehicle
+     */
+    public static final int INFO_MODEL = 286261506;
+    /**
+     * Model year of vehicle.
+     */
+    public static final int INFO_MODEL_YEAR = 289407235;
+    /**
+     * Fuel capacity of the vehicle in milliliters
+     */
+    public static final int INFO_FUEL_CAPACITY = 291504388;
+    /**
+     * List of fuels the vehicle may use
+     */
+    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.
+     */
+    public static final int INFO_EV_BATTERY_CAPACITY = 291504390;
+    /**
+     * List of connectors this EV may use
+     */
+    public static final int INFO_EV_CONNECTOR_TYPE = 289472775;
+    /**
+     * Fuel door location
+     */
+    public static final int INFO_FUEL_DOOR_LOCATION = 289407240;
+    /**
+     * EV port location
+     */
+    public static final int INFO_EV_PORT_LOCATION = 289407241;
+    /**
+     * Driver's seat location
+     */
+    public static final int INFO_DRIVER_SEAT = 356516106;
+    /**
+     * Current odometer value of the vehicle
+     */
+    public static final int PERF_ODOMETER = 291504644;
+    /**
+     * Speed of the vehicle
+     */
+    public static final int PERF_VEHICLE_SPEED = 291504647;
+    /**
+     * Temperature of engine coolant
+     */
+    public static final int ENGINE_COOLANT_TEMP = 291504897;
+    /**
+     * Engine oil level
+     */
+    public static final int ENGINE_OIL_LEVEL = 289407747;
+    /**
+     * Temperature of engine oil
+     */
+    public static final int ENGINE_OIL_TEMP = 291504900;
+    /**
+     * Engine rpm
+     */
+    public static final int ENGINE_RPM = 291504901;
+    /**
+     * Reports wheel ticks
+     */
+    public static final int WHEEL_TICK = 290521862;
+    /**
+     * Fuel remaining in the the vehicle, in milliliters
+     */
+    public static final int FUEL_LEVEL = 291504903;
+    /**
+     * Fuel door open
+     */
+    public static final int FUEL_DOOR_OPEN = 287310600;
+    /**
+     * EV battery level in WH, if EV or hybrid
+     */
+    public static final int EV_BATTERY_LEVEL = 291504905;
+    /**
+     * EV charge port open
+     */
+    public static final int EV_CHARGE_PORT_OPEN = 287310602;
+    /**
+     * EV charge port connected
+     */
+    public static final int EV_CHARGE_PORT_CONNECTED = 287310603;
+    /**
+     * EV instantaneous charge rate in milliwatts
+     */
+    public static final int EV_BATTERY_INSTANTANEOUS_CHARGE_RATE = 291504908;
+    /**
+     * Range remaining
+     */
+    public static final int RANGE_REMAINING = 291504904;
+    /**
+     * Tire pressure
+     *
+     * min/max value indicates tire pressure sensor range.  Each tire will have a separate min/max
+     * value denoted by its areaConfig.areaId.
+     */
+    public static final int TIRE_PRESSURE = 392168201;
+    /**
+     * Currently selected gear
+     *
+     * This is the gear selected by the user.
+     */
+    public static final int GEAR_SELECTION = 289408000;
+    /**
+     * Current gear. In non-manual case, selected gear may not
+     * match the current gear. For example, if the selected gear is GEAR_DRIVE,
+     * the current gear will be one of GEAR_1, GEAR_2 etc, which reflects
+     * the actual gear the transmission is currently running in.
+     */
+    public static final int CURRENT_GEAR = 289408001;
+    /**
+     * Parking brake state.
+     */
+    public static final int PARKING_BRAKE_ON = 287310850;
+    /**
+     * Auto-apply parking brake.
+     */
+    public static final int PARKING_BRAKE_AUTO_APPLY = 287310851;
+    /**
+     * Warning for fuel low level.
+     */
+    public static final int FUEL_LEVEL_LOW = 287310853;
+    /**
+     * Night mode
+     */
+    public static final int NIGHT_MODE = 287310855;
+    /**
+     * State of the vehicles turn signals
+     */
+    public static final int TURN_SIGNAL_STATE = 289408008;
+    /**
+     * Represents ignition state
+     */
+    public static final int IGNITION_STATE = 289408009;
+    /**
+     * ABS is active
+     */
+    public static final int ABS_ACTIVE = 287310858;
+    /**
+     * Traction Control is active
+     */
+    public static final int TRACTION_CONTROL_ACTIVE = 287310859;
+    /**
+     * Fan speed setting
+     */
+    public static final int HVAC_FAN_SPEED = 356517120;
+    /**
+     * Fan direction setting
+     */
+    public static final int HVAC_FAN_DIRECTION = 356517121;
+    /**
+     * HVAC current temperature.
+     */
+    public static final int HVAC_TEMPERATURE_CURRENT = 358614274;
+    /**
+     * HVAC, target temperature set.
+     */
+    public static final int HVAC_TEMPERATURE_SET = 358614275;
+    /**
+     * On/off defrost for designated window
+     */
+    public static final int HVAC_DEFROSTER = 320865540;
+    /**
+     * On/off AC for designated areaId
+     */
+    public static final int HVAC_AC_ON = 354419973;
+    /**
+     * On/off max AC
+     */
+    public static final int HVAC_MAX_AC_ON = 354419974;
+    /**
+     * On/off max defrost
+     */
+    public static final int HVAC_MAX_DEFROST_ON = 354419975;
+    /**
+     * Recirculation on/off
+     */
+    public static final int HVAC_RECIRC_ON = 354419976;
+    /**
+     * Enable temperature coupling between areas.
+     */
+    public static final int HVAC_DUAL_ON = 354419977;
+    /**
+     * On/off automatic mode
+     */
+    public static final int HVAC_AUTO_ON = 354419978;
+    /**
+     * Seat heating/cooling
+     *
+     */
+    public static final int HVAC_SEAT_TEMPERATURE = 356517131;
+    /**
+     * Side Mirror Heat
+     */
+    public static final int HVAC_SIDE_MIRROR_HEAT = 339739916;
+    /**
+     * Steering Wheel Heating/Cooling
+     */
+    public static final int HVAC_STEERING_WHEEL_HEAT = 289408269;
+    /**
+     * Temperature units for display
+     */
+    public static final int HVAC_TEMPERATURE_DISPLAY_UNITS = 289408270;
+    /**
+     * Actual fan speed
+     */
+    public static final int HVAC_ACTUAL_FAN_SPEED_RPM = 356517135;
+    /**
+     * Represents global power state for HVAC. Setting this property to false
+     * MAY mark some properties that control individual HVAC features/subsystems
+     * to UNAVAILABLE state. Setting this property to true MAY mark some
+     * properties that control individual HVAC features/subsystems to AVAILABLE
+     * state (unless any/all of them are UNAVAILABLE on their own individual
+     * merits).
+     */
+    public static final int HVAC_POWER_ON = 354419984;
+    /**
+     * Fan Positions Available
+     */
+    public static final int HVAC_FAN_DIRECTION_AVAILABLE = 356582673;
+    /**
+     * Automatic recirculation on/off
+     */
+    public static final int HVAC_AUTO_RECIRC_ON = 354419986;
+    /**
+     * Seat ventilation
+     */
+    public static final int HVAC_SEAT_VENTILATION = 356517139;
+    /**
+     * Outside temperature
+     */
+    public static final int ENV_OUTSIDE_TEMPERATURE = 291505923;
+    /**
+     * Property to control power state of application processor
+     *
+     * It is assumed that AP's power state is controller by separate power
+     * controller.
+     */
+    public static final int AP_POWER_STATE_REQ = 289475072;
+    /**
+     * Property to report power state of application processor
+     *
+     * It is assumed that AP's power state is controller by separate power
+     * controller.
+     */
+    public static final int AP_POWER_STATE_REPORT = 289475073;
+    /**
+     * Property to report bootup reason for the current power on. This is a
+     * static property that will not change for the whole duration until power
+     * off. For example, even if user presses power on button after automatic
+     * power on with door unlock, bootup reason must stay with
+     * VehicleApPowerBootupReason#USER_UNLOCK.
+     */
+    public static final int AP_POWER_BOOTUP_REASON = 289409538;
+    /**
+     * Property to represent brightness of the display. Some cars have single
+     * control for the brightness of all displays and this property is to share
+     * change in that control.
+     */
+    public static final int DISPLAY_BRIGHTNESS = 289409539;
+    /**
+     * Property to feed H/W input events to android
+     */
+    public static final int HW_KEY_INPUT = 289475088;
+    /**
+     * Door position
+     *
+     * This is an integer in case a door may be set to a particular position.
+     * Max value indicates fully open, min value (0) indicates fully closed.
+     */
+    public static final int DOOR_POS = 373295872;
+    /**
+     * Door move
+     */
+    public static final int DOOR_MOVE = 373295873;
+    /**
+     * Door lock
+     */
+    public static final int DOOR_LOCK = 371198722;
+    /**
+     * Mirror Z Position
+     */
+    public static final int MIRROR_Z_POS = 339741504;
+    /**
+     * Mirror Z Move
+     */
+    public static final int MIRROR_Z_MOVE = 339741505;
+    /**
+     * Mirror Y Position
+     */
+    public static final int MIRROR_Y_POS = 339741506;
+    /**
+     * Mirror Y Move
+     */
+    public static final int MIRROR_Y_MOVE = 339741507;
+    /**
+     * Mirror Lock
+     */
+    public static final int MIRROR_LOCK = 287312708;
+    /**
+     * Mirror Fold
+     */
+    public static final int MIRROR_FOLD = 287312709;
+    /**
+     * Seat memory select
+     *
+     * This parameter selects the memory preset to use to select the seat
+     * position. The minValue is always 0, and the maxValue determines the
+     * number of seat positions available.
+     */
+    public static final int SEAT_MEMORY_SELECT = 356518784;
+    /**
+     * Seat memory set
+     *
+     * This setting allows the user to save the current seat position settings
+     * into the selected preset slot.  The maxValue for each seat position
+     * must match the maxValue for SEAT_MEMORY_SELECT.
+     */
+    public static final int SEAT_MEMORY_SET = 356518785;
+    /**
+     * Seatbelt buckled
+     *
+     * True indicates belt is buckled.
+     */
+    public static final int SEAT_BELT_BUCKLED = 354421634;
+    /**
+     * Seatbelt height position
+     */
+    public static final int SEAT_BELT_HEIGHT_POS = 356518787;
+    /**
+     * Seatbelt height move
+     */
+    public static final int SEAT_BELT_HEIGHT_MOVE = 356518788;
+    /**
+     * Seat fore/aft position
+     */
+    public static final int SEAT_FORE_AFT_POS = 356518789;
+    /**
+     * Seat fore/aft move
+     */
+    public static final int SEAT_FORE_AFT_MOVE = 356518790;
+    /**
+     * Seat backrest angle 1 position
+     */
+    public static final int SEAT_BACKREST_ANGLE_1_POS = 356518791;
+    /**
+     * Seat backrest angle 1 move
+     *
+     * Moves the backrest forward or recline.
+     */
+    public static final int SEAT_BACKREST_ANGLE_1_MOVE = 356518792;
+    /**
+     * Seat backrest angle 2 position
+     */
+    public static final int SEAT_BACKREST_ANGLE_2_POS = 356518793;
+    /**
+     * Seat backrest angle 2 move
+     */
+    public static final int SEAT_BACKREST_ANGLE_2_MOVE = 356518794;
+    /**
+     * Seat height position
+     */
+    public static final int SEAT_HEIGHT_POS = 356518795;
+    /**
+     * Seat height move
+     */
+    public static final int SEAT_HEIGHT_MOVE = 356518796;
+    /**
+     * Seat depth position
+     */
+    public static final int SEAT_DEPTH_POS = 356518797;
+    /**
+     * Seat depth move
+     */
+    public static final int SEAT_DEPTH_MOVE = 356518798;
+    /**
+     * Seat tilt position
+     */
+    public static final int SEAT_TILT_POS = 356518799;
+    /**
+     * Seat tilt move
+     */
+    public static final int SEAT_TILT_MOVE = 356518800;
+    /**
+     * Lumber fore/aft position
+     */
+    public static final int SEAT_LUMBAR_FORE_AFT_POS = 356518801;
+    /**
+     * Lumbar fore/aft move
+     */
+    public static final int SEAT_LUMBAR_FORE_AFT_MOVE = 356518802;
+    /**
+     * Lumbar side support position
+     */
+    public static final int SEAT_LUMBAR_SIDE_SUPPORT_POS = 356518803;
+    /**
+     * Lumbar side support move
+     */
+    public static final int SEAT_LUMBAR_SIDE_SUPPORT_MOVE = 356518804;
+    /**
+     * Headrest height position
+     */
+    public static final int SEAT_HEADREST_HEIGHT_POS = 289409941;
+    /**
+     * Headrest height move
+     */
+    public static final int SEAT_HEADREST_HEIGHT_MOVE = 356518806;
+    /**
+     * Headrest angle position
+     */
+    public static final int SEAT_HEADREST_ANGLE_POS = 356518807;
+    /**
+     * Headrest angle move
+     */
+    public static final int SEAT_HEADREST_ANGLE_MOVE = 356518808;
+    /**
+     * Headrest fore/aft position
+     */
+    public static final int SEAT_HEADREST_FORE_AFT_POS = 356518809;
+    /**
+     * Headrest fore/aft move
+     */
+    public static final int SEAT_HEADREST_FORE_AFT_MOVE = 356518810;
+    /**
+     * Window Position
+     */
+    public static final int WINDOW_POS = 322964416;
+    /**
+     * Window Move
+     */
+    public static final int WINDOW_MOVE = 322964417;
+    /**
+     * Window Lock
+     */
+    public static final int WINDOW_LOCK = 320867268;
+    /**
+     * Vehicle Maps Service (VMS) message
+     */
+    public static final int VEHICLE_MAP_SERVICE = 299895808;
+    /**
+     * OBD2 Live Sensor Data
+     *
+     * Reports a snapshot of the current (live) values of the OBD2 sensors available.
+     */
+    public static final int OBD2_LIVE_FRAME = 299896064;
+    /**
+     * OBD2 Freeze Frame Sensor Data
+     *
+     * Reports a snapshot of the value of the OBD2 sensors available at the time that a fault
+     * occurred and was detected.
+     */
+    public static final int OBD2_FREEZE_FRAME = 299896065;
+    /**
+     * OBD2 Freeze Frame Information
+     */
+    public static final int OBD2_FREEZE_FRAME_INFO = 299896066;
+    /**
+     * OBD2 Freeze Frame Clear
+     *
+     * This property allows deletion of any of the freeze frames stored in
+     * vehicle memory, as described by OBD2_FREEZE_FRAME_INFO.
+     */
+    public static final int OBD2_FREEZE_FRAME_CLEAR = 299896067;
+    /**
+     * Headlights State
+     */
+    public static final int HEADLIGHTS_STATE = 289410560;
+    /**
+     * High beam lights state
+     */
+    public static final int HIGH_BEAM_LIGHTS_STATE = 289410561;
+    /**
+     * Fog light state
+     */
+    public static final int FOG_LIGHTS_STATE = 289410562;
+    /**
+     * Hazard light status
+     */
+    public static final int HAZARD_LIGHTS_STATE = 289410563;
+    /**
+     * Headlight switch
+     */
+    public static final int HEADLIGHTS_SWITCH = 289410576;
+    /**
+     * High beam light switch
+     */
+    public static final int HIGH_BEAM_LIGHTS_SWITCH = 289410577;
+    /**
+     * Fog light switch
+     */
+    public static final int FOG_LIGHTS_SWITCH = 289410578;
+    /**
+     * Hazard light switch
+     */
+    public static final int HAZARD_LIGHTS_SWITCH = 289410579;
+
+    /**
+     * @param o Integer
+     * @return String
+     */
+    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 == 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 == 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 == 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";
+        }
+        return "0x" + Integer.toHexString(o);
+    }
+}
diff --git a/car-lib/src/android/car/drivingstate/CarUxRestrictions.java b/car-lib/src/android/car/drivingstate/CarUxRestrictions.java
index c6c22bc..87dfa3a 100644
--- a/car-lib/src/android/car/drivingstate/CarUxRestrictions.java
+++ b/car-lib/src/android/car/drivingstate/CarUxRestrictions.java
@@ -24,30 +24,32 @@
 import java.lang.annotation.RetentionPolicy;
 
 /**
- * Car UX Restrictions event.  This contains information on the set of UX restrictions
- * that is in place due to the car's driving state.
+ * Car UX Restrictions event.  This contains information on the set of UX restrictions that is in
+ * place due to the car's driving state.
  * <p>
  * The restriction information is organized as follows:
  * <ul>
  * <li> When there are no restrictions in place, for example when the car is parked,
  * <ul>
- * <li> {@link #mRequiresDistractionOptimization} is set to false.  Apps can display activities
+ * <li> {@link #isRequiresDistractionOptimization()} returns false.  Apps can display activities
  * that are not distraction optimized.
- * <li> {@link #mActiveRestrictions} should contain UX_RESTRICTIONS_UNRESTRICTED.  Apps don't
- * have to check for this since {@code mRequiresDistractionOptimization} is false.
+ * <li> When {@link #isRequiresDistractionOptimization()} returns false, apps don't have to call
+ * {@link #getActiveRestrictions()}, since there is no distraction optimization required.
  * </ul>
  * <li> When the driving state changes, causing the UX restrictions to come in effect,
  * <ul>
- * <li> {@code mRequiresDistractionOptimization} is set to true.  Apps can only display
- * activities that are distraction optimized.  Distraction optimized activities follow the base
- * design guidelines that provide a distraction free driving user experience.
- * <li> In addition, apps will have to check for the content of mActiveRestrictions.
- * {@code mActiveRestrictions} will have additional granular information on the set of UX
- * restrictions that are in place for the current driving state.  The content of
- * {@code mActiveRestrictions}, for the same driving state of the vehicle, could vary depending
- * on the car maker and the market.  For example, when the car is idling, the set of active
- * UX restrictions contained in the {@code mActiveRestrictions} will depend on the car maker
- * and the safety standards of the market that the vehicle is deployed in.
+ * <li> {@link #isRequiresDistractionOptimization()} returns true.  Apps can only display activities
+ * that are distraction optimized.  Distraction optimized activities must follow the base design
+ * guidelines to ensure a distraction free driving experience for the user.
+ * <li> When {@link #isRequiresDistractionOptimization()} returns true, apps must call
+ * {@link #getActiveRestrictions()}, to get the currently active UX restrictions to adhere to.
+ * {@link #getActiveRestrictions()} provides additional information on the set of UX
+ * restrictions that are in place for the current driving state.
+ * <p>
+ * The UX restrictions returned by {@link #getActiveRestrictions()}, for the same driving state of
+ * the vehicle, could vary depending on the OEM and the market.  For example, when the car is
+ * idling, the set of active UX restrictions will depend on the car maker and the safety standards
+ * of the market that the vehicle is deployed in.
  * </ul>
  * </ul>
  * <p>
diff --git a/car-lib/src/android/car/hardware/CarSensorEvent.java b/car-lib/src/android/car/hardware/CarSensorEvent.java
index 86af063..8cba0a2 100644
--- a/car-lib/src/android/car/hardware/CarSensorEvent.java
+++ b/car-lib/src/android/car/hardware/CarSensorEvent.java
@@ -92,15 +92,11 @@
     public static final int IGNITION_STATE_START = 5;
 
     /**
-     * Index for {@link CarSensorManager#SENSOR_TYPE_ENVIRONMENT} in floatValues.
+     * Index for {@link CarSensorManager#SENSOR_TYPE_ENV_OUTSIDE_TEMPERATURE} in floatValues.
      * Temperature in Celsius degrees.
      */
     public static final int INDEX_ENVIRONMENT_TEMPERATURE = 0;
-    /**
-     * Index for {@link CarSensorManager#SENSOR_TYPE_ENVIRONMENT} in floatValues.
-     * Pressure in kPa.
-     */
-    public static final int INDEX_ENVIRONMENT_PRESSURE = 1;
+
     /**
      * Index for {@link CarSensorManager#SENSOR_TYPE_WHEEL_TICK_DISTANCE} in longValues. RESET_COUNT
      * is incremented whenever the HAL detects that a sensor reset has occurred.  It represents to
@@ -208,8 +204,6 @@
         public long timestamp;
         /** If unsupported by the car, this value is NaN. */
         public float temperature;
-        /** If unsupported by the car, this value is NaN. */
-        public float pressure;
 
         /** @hide */
         private EnvironmentData() {};
@@ -217,7 +211,7 @@
 
     /**
      * Convenience method for obtaining an {@link EnvironmentData} object from a CarSensorEvent
-     * object with type {@link CarSensorManager#SENSOR_TYPE_ENVIRONMENT}.
+     * object with type {@link CarSensorManager#SENSOR_TYPE_ENV_OUTSIDE_TEMPERATURE}.
      *
      * @param data an optional output parameter which, if non-null, will be used by this method
      *     instead of a newly created object.
@@ -225,13 +219,40 @@
      * @hide
      */
     public EnvironmentData getEnvironmentData(EnvironmentData data) {
-        checkType(CarSensorManager.SENSOR_TYPE_ENVIRONMENT);
+        checkType(CarSensorManager.SENSOR_TYPE_ENV_OUTSIDE_TEMPERATURE);
         if (data == null) {
             data = new EnvironmentData();
         }
         data.timestamp = timestamp;
         data.temperature = floatValues[INDEX_ENVIRONMENT_TEMPERATURE];
-        data.pressure = floatValues[INDEX_ENVIRONMENT_PRESSURE];
+        return data;
+    }
+
+    /** @hide*/
+    public static class IgnitionStateData {
+        public long timestamp;
+        public int ignitionState;
+
+        /** @hide */
+        private IgnitionStateData() {};
+    }
+
+    /**
+     * Convenience method for obtaining a {@link IgnitionStateData} object from a CarSensorEvent
+     * object with type {@link CarSensorManager#SENSOR_TYPE_IGNITION_STATE}.
+     *
+     * @param data an optional output parameter which, if non-null, will be used by this method
+     *     instead of a newly created object.
+     * @return a IgnitionStateData object corresponding to the data contained in the CarSensorEvent.
+     * @hide
+     */
+    public IgnitionStateData getIgnitionStateData(IgnitionStateData data) {
+        checkType(CarSensorManager.SENSOR_TYPE_IGNITION_STATE);
+        if (data == null) {
+            data = new IgnitionStateData();
+        }
+        data.timestamp = timestamp;
+        data.ignitionState = intValues[0];
         return data;
     }
 
diff --git a/car-lib/src/android/car/hardware/CarSensorManager.java b/car-lib/src/android/car/hardware/CarSensorManager.java
index 2536604..338d74f 100644
--- a/car-lib/src/android/car/hardware/CarSensorManager.java
+++ b/car-lib/src/android/car/hardware/CarSensorManager.java
@@ -91,14 +91,17 @@
      * Day/night sensor. Sensor data is intValues[0].
      */
     public static final int SENSOR_TYPE_NIGHT                       = 0x11200407;
+    /**
+     * Outside Environment like temperature.
+     * This requires {@link Car#PERMISSION_EXTERIOR_ENVIRONMENT} permission.
+     */
+    public static final int SENSOR_TYPE_ENV_OUTSIDE_TEMPERATURE     = 0x11600703;
     /** @hide */
     public static final int SENSOR_TYPE_RESERVED10                  = 10;
     /** @hide */
     public static final int SENSOR_TYPE_RESERVED11                  = 11;
-    /**
-     * Environment like temperature and pressure.
-     */
-    public static final int SENSOR_TYPE_ENVIRONMENT                 = 12;
+    /** @hide */
+    public static final int SENSOR_TYPE_RESERVED12                  = 12;
     /** @hide */
     public static final int SENSOR_TYPE_RESERVED13                  = 13;
     /** @hide */
@@ -186,7 +189,7 @@
             SENSOR_TYPE_PARKING_BRAKE,
             SENSOR_TYPE_GEAR,
             SENSOR_TYPE_NIGHT,
-            SENSOR_TYPE_ENVIRONMENT,
+            SENSOR_TYPE_ENV_OUTSIDE_TEMPERATURE,
             SENSOR_TYPE_IGNITION_STATE,
             SENSOR_TYPE_WHEEL_TICK_DISTANCE,
             SENSOR_TYPE_ABS_ACTIVE,
@@ -209,7 +212,7 @@
             SENSOR_TYPE_PARKING_BRAKE,
             SENSOR_TYPE_GEAR,
             SENSOR_TYPE_NIGHT,
-            SENSOR_TYPE_ENVIRONMENT,
+            SENSOR_TYPE_ENV_OUTSIDE_TEMPERATURE,
             SENSOR_TYPE_IGNITION_STATE,
             SENSOR_TYPE_WHEEL_TICK_DISTANCE,
             SENSOR_TYPE_ABS_ACTIVE,
diff --git a/car-lib/src/android/car/hardware/property/CarPropertyManager.java b/car-lib/src/android/car/hardware/property/CarPropertyManager.java
index 5c094e7..373c23a 100644
--- a/car-lib/src/android/car/hardware/property/CarPropertyManager.java
+++ b/car-lib/src/android/car/hardware/property/CarPropertyManager.java
@@ -18,6 +18,7 @@
 
 import static java.lang.Integer.toHexString;
 
+import android.annotation.Nullable;
 import android.car.CarApiUtil;
 import android.car.CarManagerBase;
 import android.car.CarNotConnectedException;
@@ -44,6 +45,7 @@
  * @hide
  */
 public class CarPropertyManager implements CarManagerBase {
+    private final List<CarPropertyConfig> mConfigs;
     private final boolean mDbg;
     private final SingleMessageHandler<CarPropertyEvent> mHandler;
     private final ICarProperty mService;
@@ -69,10 +71,20 @@
     /**
      * Get an instance of the CarPropertyManager.
      */
-    public CarPropertyManager(IBinder service, Handler handler, boolean dbg, String tag) {
+    public CarPropertyManager(IBinder service, @Nullable Handler handler, boolean dbg, String tag) {
         mDbg = dbg;
         mTag = tag;
         mService = ICarProperty.Stub.asInterface(service);
+        try {
+            mConfigs = mService.getPropertyList();
+        } catch (Exception e) {
+            Log.e(mTag, "getPropertyList exception ", e);
+            throw new RuntimeException(e);
+        }
+        if (handler == null) {
+            mHandler = null;
+            return;
+        }
         mHandler = new SingleMessageHandler<CarPropertyEvent>(handler.getLooper(),
                 MSG_GENERIC_EVENT) {
             @Override
@@ -154,7 +166,9 @@
     }
 
     private void handleEvent(List<CarPropertyEvent> events) {
-        mHandler.sendEvents(events);
+        if (mHandler != null) {
+            mHandler.sendEvents(events);
+        }
     }
 
     /**
@@ -164,8 +178,12 @@
      */
     public void unregisterListener(CarPropertyEventListener listener) {
         synchronized (mActivePropertyListener) {
+            int [] propertyIds = new int[mActivePropertyListener.size()];
             for (int i = 0; i < mActivePropertyListener.size(); i++) {
-                doUnregisterListenerLocked(listener, mActivePropertyListener.keyAt(i));
+                propertyIds[i] = mActivePropertyListener.keyAt(i);
+            }
+            for (int prop : propertyIds) {
+                doUnregisterListenerLocked(listener, prop);
             }
         }
     }
@@ -207,41 +225,24 @@
     }
 
     /**
-     * Returns the list of properties implemented by this car.
-     *
-     * @return Caller must check the property type and typecast to the appropriate subclass
-     * (CarPropertyBooleanProperty, CarPropertyFloatProperty, CarrPropertyIntProperty)
+     * @return List of properties implemented by this car that the application may access.
      */
-    public List<CarPropertyConfig> getPropertyList() throws CarNotConnectedException {
-        try {
-            return mService.getPropertyList();
-        } catch (RemoteException e) {
-            Log.e(mTag, "getPropertyList exception ", e);
-            throw new CarNotConnectedException(e);
-        }
+    public List<CarPropertyConfig> getPropertyList() {
+        return mConfigs;
     }
 
     /**
-     * Returns the list of properties implemented by this car in given property id list.
-     *
-     * @return Caller must check the property type and typecast to the appropriate subclass
-     * (CarPropertyBooleanProperty, CarPropertyFloatProperty, CarrPropertyIntProperty)
+     * @return List of properties implemented by this car in given property ID list that application
+     *          may access.
      */
-    public List<CarPropertyConfig> getPropertyList(ArraySet<Integer> propertyIds)
-            throws CarNotConnectedException {
-        try {
-            List<CarPropertyConfig> configs = new ArrayList<>();
-            for (CarPropertyConfig c : mService.getPropertyList()) {
-                if (propertyIds.contains(c.getPropertyId())) {
-                    configs.add(c);
-                }
+    public List<CarPropertyConfig> getPropertyList(ArraySet<Integer> propertyIds) {
+        List<CarPropertyConfig> configs = new ArrayList<>();
+        for (CarPropertyConfig c : mConfigs) {
+            if (propertyIds.contains(c.getPropertyId())) {
+                configs.add(c);
             }
-            return configs;
-        } catch (RemoteException e) {
-            Log.e(mTag, "getPropertyList exception ", e);
-            throw new CarNotConnectedException(e);
         }
-
+        return configs;
     }
 
     /**
@@ -294,6 +295,26 @@
         return carProp != null ? carProp.getValue() : 0;
     }
 
+    /**
+     * Returns value of a integer array property
+     *
+     * @param prop Property ID to get
+     * @param area Zone of the property to get
+     */
+    public int[] getIntArrayProperty(int prop, int area) throws CarNotConnectedException {
+        CarPropertyValue<Integer[]> carProp = getProperty(Integer[].class, prop, area);
+        return carProp != null ? toIntArray(carProp.getValue()) : new int[0];
+    }
+
+    private static int[] toIntArray(Integer[] input) {
+        int len = input.length;
+        int[] arr = new int[len];
+        for (int i = 0; i < len; i++) {
+            arr[i] = input[i];
+        }
+        return arr;
+    }
+
     /** Return CarPropertyValue */
     @SuppressWarnings("unchecked")
     public <E> CarPropertyValue<E> getProperty(Class<E> clazz, int propId, int area)
diff --git a/car-lib/src/android/car/media/CarAudioManager.java b/car-lib/src/android/car/media/CarAudioManager.java
index c9aef74..7257255 100644
--- a/car-lib/src/android/car/media/CarAudioManager.java
+++ b/car-lib/src/android/car/media/CarAudioManager.java
@@ -47,6 +47,13 @@
         return VOLUME_SETTINGS_KEY_FOR_GROUP_PREFIX + groupId;
     }
 
+    /**
+     * Key to persist master mute state in {@link Settings.Global}
+     *
+     * @hide
+     */
+    public static final String VOLUME_SETTINGS_KEY_MASTER_MUTE = "android.car.MASTER_MUTE";
+
     private final ContentResolver mContentResolver;
     private final ICarAudio mService;
 
diff --git a/car-lib/src/android/car/settings/CarSettings.java b/car-lib/src/android/car/settings/CarSettings.java
index d7402b2..f5e1037 100644
--- a/car-lib/src/android/car/settings/CarSettings.java
+++ b/car-lib/src/android/car/settings/CarSettings.java
@@ -45,6 +45,22 @@
          */
         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";
     }
 
     /**
@@ -62,20 +78,6 @@
     public static final int DEFAULT_GARAGE_MODE_MAINTENANCE_WINDOW = 10 * 60 * 1000; // 10 mins
 
     /**
-     * Id for user that is set as default to boot into.
-     *
-     * @hide
-     */
-    public static final int DEFAULT_USER_ID_TO_BOOT_INTO = 10; // Default to first created user.
-
-    /**
-     * Id for user that is last logged in to.
-     *
-     * @hide
-     */
-    public static final int LAST_ACTIVE_USER_ID = 10; // Default to first created user.
-
-    /**
      * @hide
      */
     public static final class Secure {
diff --git a/car-lib/src/android/car/trust/ICarTrustAgentBleService.aidl b/car-lib/src/android/car/trust/ICarTrustAgentBleService.aidl
index 8b50fd3..4dec6a0 100644
--- a/car-lib/src/android/car/trust/ICarTrustAgentBleService.aidl
+++ b/car-lib/src/android/car/trust/ICarTrustAgentBleService.aidl
@@ -58,4 +58,7 @@
     void onEscrowTokenAdded(in byte[] token, long handle, int uid);
     void onEscrowTokenRemoved(long handle, boolean successful);
     void onEscrowTokenActiveStateChanged(long handle, boolean active);
+
+    /** Management */
+    int getUserIdByEscrowTokenHandle(long tokenHandle);
 }
diff --git a/car-lib/src/android/car/user/CarUserManagerHelper.java b/car-lib/src/android/car/user/CarUserManagerHelper.java
index 4a69f0b..ce50943 100644
--- a/car-lib/src/android/car/user/CarUserManagerHelper.java
+++ b/car-lib/src/android/car/user/CarUserManagerHelper.java
@@ -15,7 +15,9 @@
  */
 package android.car.user;
 
+import android.Manifest;
 import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
 import android.app.ActivityManager;
 import android.car.settings.CarSettings;
 import android.content.BroadcastReceiver;
@@ -26,15 +28,21 @@
 import android.graphics.Bitmap;
 import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.Drawable;
+import android.os.Bundle;
 import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.provider.Settings;
 import android.util.Log;
 
 import com.android.internal.util.UserIcons;
 
+import com.google.android.collect.Sets;
+
+import java.util.ArrayList;
 import java.util.Iterator;
 import java.util.List;
+import java.util.Set;
 
 /**
  * Helper class for {@link UserManager}, this is meant to be used by builds that support
@@ -49,19 +57,47 @@
 public class CarUserManagerHelper {
     private static final String TAG = "CarUserManagerHelper";
     private static final String HEADLESS_SYSTEM_USER = "android.car.systemuser.headless";
+    /**
+     * Default set of restrictions for Non-Admin users.
+     */
+    private static final Set<String> DEFAULT_NON_ADMIN_RESTRICTIONS = Sets.newArraySet(
+            UserManager.DISALLOW_FACTORY_RESET
+    );
+    /**
+     * 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_OUTGOING_CALLS,
+            UserManager.DISALLOW_SMS,
+            UserManager.DISALLOW_INSTALL_APPS,
+            UserManager.DISALLOW_UNINSTALL_APPS
+    );
+
     private final Context mContext;
     private final UserManager mUserManager;
     private final ActivityManager mActivityManager;
+    private int mLastActiveUser = UserHandle.USER_SYSTEM;
     private Bitmap mDefaultGuestUserIcon;
-    private OnUsersUpdateListener mUpdateListener;
+    private ArrayList<OnUsersUpdateListener> mUpdateListeners;
     private final BroadcastReceiver mUserChangeReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
-            mUpdateListener.onUsersUpdate();
+            ArrayList<OnUsersUpdateListener> copyOfUpdateListeners;
+            synchronized (mUpdateListeners) {
+                copyOfUpdateListeners = new ArrayList(mUpdateListeners);
+            }
+
+            for (OnUsersUpdateListener listener : copyOfUpdateListeners) {
+                listener.onUsersUpdate();
+            }
         }
     };
 
     public CarUserManagerHelper(Context context) {
+        mUpdateListeners = new ArrayList<>();
         mContext = context.getApplicationContext();
         mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
         mActivityManager = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
@@ -70,24 +106,139 @@
     /**
      * Registers a listener for updates to all users - removing, adding users or changing user info.
      *
-     * <p> Best practise is to keep one listener per helper.
-     *
      * @param listener Instance of {@link OnUsersUpdateListener}.
      */
     public void registerOnUsersUpdateListener(OnUsersUpdateListener listener) {
-        if (mUpdateListener != null) {
-            unregisterOnUsersUpdateListener();
+        if (listener == null) {
+            return;
         }
 
-        mUpdateListener = listener;
-        registerReceiver();
+        synchronized (mUpdateListeners) {
+            if (mUpdateListeners.isEmpty()) {
+                // First listener being added, register receiver.
+                registerReceiver();
+            }
+
+            if (!mUpdateListeners.contains(listener)) {
+                mUpdateListeners.add(listener);
+            }
+        }
     }
 
     /**
-     * Unregisters on user update listener by unregistering {@code BroadcastReceiver}.
+     * Unregisters on user update listener.
+     * Unregisters {@code BroadcastReceiver} if no listeners remain.
+     *
+     * @param listener Instance of {@link OnUsersUpdateListener} to unregister.
      */
-    public void unregisterOnUsersUpdateListener() {
-        unregisterReceiver();
+    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 default boot into user.
+     *
+     * @param userId default user id to boot into.
+     */
+    public void setDefaultBootUser(int userId) {
+        Settings.Global.putInt(
+                mContext.getContentResolver(),
+                CarSettings.Global.DEFAULT_USER_ID_TO_BOOT_INTO, userId);
+    }
+
+    /**
+     * Set last active user.
+     *
+     * @param userId last active user id.
+     * @param skipGlobalSetting whether to skip set the global settings value.
+     */
+    public void setLastActiveUser(int userId, boolean skipGlobalSetting) {
+        mLastActiveUser = userId;
+        if (!skipGlobalSetting) {
+            Settings.Global.putInt(
+                    mContext.getContentResolver(), CarSettings.Global.LAST_ACTIVE_USER_ID, userId);
+        }
+    }
+
+    /**
+     * Get user id for the default boot into user.
+     *
+     * @return user id of the default boot into user
+     */
+    public int getDefaultBootUser() {
+        // Make user 10 the original default boot user.
+        return Settings.Global.getInt(
+            mContext.getContentResolver(), CarSettings.Global.DEFAULT_USER_ID_TO_BOOT_INTO,
+            /* default user id= */ 10);
+    }
+
+    /**
+     * Get user id for the last active user.
+     *
+     * @return user id of the last active user.
+     */
+    public int getLastActiveUser() {
+        if (mLastActiveUser != UserHandle.USER_SYSTEM) {
+            return mLastActiveUser;
+        }
+        return Settings.Global.getInt(
+            mContext.getContentResolver(), CarSettings.Global.LAST_ACTIVE_USER_ID,
+            /* default user id= */ UserHandle.USER_SYSTEM);
+    }
+
+    /**
+     * Get user id for the initial user to boot into. This is only applicable for headless
+     * system user model.
+     *
+     * <p>If failed to retrieve the id stored in global settings or the retrieved id does not
+     * exist on device, then return the user with smallest user id.
+     *
+     * @return user id of the last active user or the smallest user id on the device.
+     */
+    public int getInitialUser() {
+        int lastActiveUserId = getLastActiveUser();
+
+        boolean isUserExist = false;
+        List<UserInfo> allUsers = getAllPersistentUsers();
+        int smallestUserId = Integer.MAX_VALUE;
+        for (UserInfo user : allUsers) {
+            if (user.id == lastActiveUserId) {
+                isUserExist = true;
+            }
+            smallestUserId = Math.min(user.id, smallestUserId);
+        }
+
+        // If the last active user is system user or the user id doesn't exist on device,
+        // return the smallest id or all users.
+        if (lastActiveUserId == UserHandle.USER_SYSTEM || !isUserExist) {
+            Log.e(TAG, "Can't get last active user id or the user no longer exist, user id: ."
+                    + lastActiveUserId);
+            lastActiveUserId = smallestUserId;
+        }
+
+        return lastActiveUserId;
+    }
+
+    /**
+     * 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);
     }
 
     /**
@@ -151,8 +302,9 @@
     }
 
     /**
-     * Gets all the existing foreground users on the system that are not currently running as
+     * 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.
      */
@@ -173,11 +325,64 @@
         if (isHeadlessSystemUser()) {
             return getAllUsersExceptSystemUserAndSpecifiedUser(UserHandle.USER_SYSTEM);
         } else {
-            return mUserManager.getUsers(/* excludeDying= */true);
+            return mUserManager.getUsers(/* excludeDying= */ true);
         }
     }
 
     /**
+     * Gets all the users that are non-ephemeral and can be brought to the foreground on the system.
+     *
+     * @return List of {@code UserInfo} for non-ephemeral users that associated with a real person.
+     */
+    public List<UserInfo> getAllPersistentUsers() {
+        List<UserInfo> users = getAllUsers();
+        for (Iterator<UserInfo> iterator = users.iterator(); iterator.hasNext(); ) {
+            UserInfo userInfo = iterator.next();
+            if (userInfo.isEphemeral()) {
+                // Remove user that is ephemeral.
+                iterator.remove();
+            }
+        }
+        return users;
+    }
+
+    /**
+     * 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.
@@ -215,6 +420,62 @@
         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 UserManager.getMaxSupportedUsers() - 1;
+        }
+        return UserManager.getMaxSupportedUsers();
+    }
+
+    /**
+     * 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
 
     /**
@@ -234,7 +495,17 @@
      * @return {@code true} if is default user, {@code false} otherwise.
      */
     public boolean isDefaultUser(UserInfo userInfo) {
-        return userInfo.id == CarSettings.DEFAULT_USER_ID_TO_BOOT_INTO;
+        return userInfo.id == getDefaultBootUser();
+    }
+
+    /**
+     * 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();
     }
 
     /**
@@ -267,6 +538,24 @@
     }
 
     /**
+     * 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
@@ -311,6 +600,13 @@
     }
 
     /**
+     * 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() {
@@ -369,17 +665,44 @@
     }
 
     /**
+     * Assigns admin privileges to the user.
+     *
+     * @param user User to be upgraded to Admin status.
+     */
+    @RequiresPermission(allOf = {
+            Manifest.permission.INTERACT_ACROSS_USERS_FULL,
+            Manifest.permission.MANAGE_USERS
+    })
+    public void assignAdminPrivileges(UserInfo user) {
+        if (!isCurrentProcessAdminUser()) {
+            Log.w(TAG, "Only admin users can assign admin privileges.");
+            return;
+        }
+
+        mUserManager.setUserAdmin(user.id);
+
+        // Remove restrictions imposed on non-admins.
+        setDefaultNonAdminRestrictions(user, /* enable= */ false);
+    }
+
+    /**
      * 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, but we haven't
-            // been able to reload the list yet.
+            // Couldn't create user, most likely because there are too many.
             Log.w(TAG, "can't create admin user.");
             return null;
         }
@@ -397,16 +720,47 @@
     public UserInfo createNewNonAdminUser(String userName) {
         UserInfo user = mUserManager.createUser(userName, 0);
         if (user == null) {
-            // Couldn't create user, most likely because there are too many, but we haven't
-            // been able to reload the list yet.
+            // Couldn't create user, most likely because there are too many.
             Log.w(TAG, "can't create non-admin user.");
             return null;
         }
+        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;
     }
 
     /**
+     * Sets the values of default Non-Admin restrictions to the passed in value.
+     *
+     * @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) {
+        for (String restriction : DEFAULT_NON_ADMIN_RESTRICTIONS) {
+            setUserRestriction(userInfo, restriction, enable);
+        }
+    }
+
+    /**
+     * 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.
@@ -420,10 +774,15 @@
             return false;
         }
 
-        // Not allow to delete the default user for now. Since default user is the one to
-        // boot into.
-        if (isHeadlessSystemUser() && isDefaultUser(userInfo)) {
-            Log.w(TAG, "User " + userInfo.id + " is the default user, could not be removed.");
+        // Not allow to delete the last admin user on the device for now.
+        if (userInfo.isAdmin() && getAllAdminUsers().size() <= 1) {
+            Log.w(TAG, "User " + userInfo.id + " is the last admin user on device.");
+            return false;
+        }
+
+        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;
         }
 
diff --git a/car-support-lib/proguard-release.flags b/car-support-lib/proguard-release.flags
index 91cab7e..4f5f917 100644
--- a/car-support-lib/proguard-release.flags
+++ b/car-support-lib/proguard-release.flags
@@ -7201,6 +7201,7 @@
     public int enabledSetting;
     public int flags;
     public int fullBackupContent;
+    public boolean hiddenUntilInstalled;
     public int installLocation;
     public int largestWidthLimitDp;
     public long longVersionCode;
@@ -7646,6 +7647,8 @@
     public abstract java.lang.String[] setPackagesSuspendedAsUser(java.lang.String[], boolean, android.os.PersistableBundle, android.os.PersistableBundle, java.lang.String, java.lang.String, int);
     public abstract void setPermissionEnforced(java.lang.String, boolean);
     public abstract boolean setRequiredForSystemUser(java.lang.String, boolean);
+    public abstract void setSystemAppHiddenUntilInstalled(java.lang.String, boolean);
+    public abstract boolean setSystemAppInstallState(java.lang.String, boolean, int);
     public abstract void setUpdateAvailable(java.lang.String, boolean);
     public abstract boolean shouldShowRequestPermissionRationale(java.lang.String, java.lang.String, int);
     public abstract void systemReady();
@@ -8589,6 +8592,7 @@
     public static int MATCH_DISABLED_UNTIL_USED_COMPONENTS;
     public static int MATCH_EXPLICITLY_VISIBLE_ONLY;
     public static int MATCH_FACTORY_ONLY;
+    public static int MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS;
     public static int MATCH_INSTANT;
     public static int MATCH_KNOWN_PACKAGES;
     public static int MATCH_STATIC_SHARED_LIBRARIES;
@@ -17003,8 +17007,8 @@
 
     public boolean equals(java.lang.Object);
     public static android.view.DisplayCutout fromBoundingRect(int, int, int, int);
-    public static android.view.DisplayCutout fromBounds(android.graphics.Path);
-    public static android.view.DisplayCutout fromResources(android.content.res.Resources, int, int);
+    public static android.view.DisplayCutout fromBounds(android.graphics.Region);
+    public static android.view.DisplayCutout fromResourcesRectApproximation(android.content.res.Resources, int, int);
     public static android.view.DisplayCutout fromSpec(java.lang.String, int, int, float);
     public java.util.List getBoundingRects();
     public android.graphics.Region getBounds();
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 8e55f8c..52fcf2b 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
@@ -24,6 +24,8 @@
     <bool name="config_enableMultiUserUI">true</bool>
     <!-- Arbitrary max 8 users. -->
     <integer name="config_multiuserMaximumUsers">8</integer>
+    <!-- If true, all guest users created on the device will be ephemeral. -->
+    <bool name="config_guestUserEphemeral">true</bool>
     <!-- Car Mode -->
     <integer name="config_defaultUiModeType">3</integer>
     <!-- Can't leave car mode -->
diff --git a/car_product/overlay/frameworks/base/packages/SystemUI/res-keyguard/layout-land/keyguard_pattern_view.xml b/car_product/overlay/frameworks/base/packages/SystemUI/res-keyguard/layout-land/keyguard_pattern_view.xml
new file mode 100644
index 0000000..bd94b20
--- /dev/null
+++ b/car_product/overlay/frameworks/base/packages/SystemUI/res-keyguard/layout-land/keyguard_pattern_view.xml
@@ -0,0 +1,85 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+**
+** Copyright 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.
+*/
+-->
+
+<!-- Car customizations
+     - Added title "Enter your Pattern" at the top
+     - Hid the emergency call at the bottom
+-->
+
+<com.android.keyguard.KeyguardPatternView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/keyguard_pattern_view"
+    android:orientation="horizontal"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:paddingHorizontal="@dimen/car_margin">
+
+    <FrameLayout
+        android:layout_height="match_parent"
+        android:layout_width="0dp"
+        android:layout_weight="1">
+
+        <com.android.internal.widget.LockPatternView
+            android:id="@+id/lockPatternView"
+            android:layout_width="@dimen/keyguard_pattern_dimension"
+            android:layout_height="@dimen/keyguard_pattern_dimension"
+            android:layout_gravity="center"/>
+    </FrameLayout>
+
+    <LinearLayout
+        android:id="@+id/container"
+        android:layout_height="match_parent"
+        android:layout_width="0dp"
+        android:layout_weight="1"
+        android:orientation="vertical"
+        android:gravity="center_vertical">
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_margin="@dimen/car_padding_2"
+            android:gravity="center"
+            android:textColor="@android:color/white"
+            android:textSize="@dimen/car_body1_size"
+            android:text="@string/keyguard_enter_your_pattern" />
+
+        <include layout="@layout/keyguard_message_area"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginBottom="@dimen/car_padding_4"/>
+
+        <Button
+            android:id="@+id/cancel_button"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center"
+            style="@style/KeyguardButton"
+            android:text="@string/cancel"/>
+
+        <include layout="@layout/keyguard_eca"
+            android:id="@+id/keyguard_selector_fade_container"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="vertical"
+            android:layout_gravity="bottom|center_horizontal"
+            android:gravity="center_horizontal"
+            android:visibility="gone" />
+    </LinearLayout>
+
+</com.android.keyguard.KeyguardPatternView>
diff --git a/car_product/overlay/frameworks/base/packages/SystemUI/res-keyguard/layout-land/keyguard_pin_view.xml b/car_product/overlay/frameworks/base/packages/SystemUI/res-keyguard/layout-land/keyguard_pin_view.xml
index 0bca46b..788ecfc 100644
--- a/car_product/overlay/frameworks/base/packages/SystemUI/res-keyguard/layout-land/keyguard_pin_view.xml
+++ b/car_product/overlay/frameworks/base/packages/SystemUI/res-keyguard/layout-land/keyguard_pin_view.xml
@@ -23,23 +23,25 @@
 -->
 
 <com.android.keyguard.KeyguardPINView
-        xmlns:android="http://schemas.android.com/apk/res/android"
-        xmlns:app="http://schemas.android.com/apk/res-auto"
-        android:id="@+id/keyguard_pin_view"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent">
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:id="@+id/keyguard_pin_view"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="horizontal"
+    android:paddingHorizontal="@dimen/car_margin">
 
     <FrameLayout
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:layout_marginLeft="@dimen/num_pad_margin_left"
-        android:layout_marginRight="@dimen/num_pad_margin_right">
+        android:layout_width="0dp"
+        android:layout_weight="1"
+        android:layout_height="match_parent">
 
         <GridLayout
             android:id="@+id/container"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
-            android:layout_gravity="center_vertical|start"
+            android:layout_gravity="center"
+            android:gravity="center"
             android:columnCount="3">
 
             <!-- Row 1 -->
@@ -106,51 +108,51 @@
                 android:background="@drawable/ripple_drawable"
                 android:contentDescription="@string/keyboardview_keycode_enter" />
         </GridLayout>
+    </FrameLayout>
 
-        <LinearLayout
+    <LinearLayout
+        android:layout_width="0dp"
+        android:layout_height="match_parent"
+        android:layout_weight="1"
+        android:gravity="center"
+        android:orientation="vertical">
+
+        <com.android.keyguard.PasswordTextView
+            android:id="@+id/pinEntry"
+            android:layout_width="@dimen/keyguard_security_width"
+            android:layout_height="@dimen/pin_entry_height"
+            android:gravity="center"
+            app:scaledTextSize="@integer/password_text_view_scale"
+            android:contentDescription="@string/keyguard_accessibility_pin_area" />
+
+        <View
+            android:id="@+id/divider"
+            android:layout_width="@dimen/keyguard_security_width"
+            android:layout_height="@dimen/divider_height"
+            android:background="@android:color/white" />
+
+        <TextView
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
-            android:layout_gravity="center_vertical|end"
+            android:layout_margin="@dimen/car_padding_2"
             android:gravity="center"
-            android:orientation="vertical">
+            android:textColor="@android:color/white"
+            android:textSize="@dimen/car_body1_size"
+            android:text="@string/keyguard_enter_your_pin" />
 
-            <com.android.keyguard.PasswordTextView
-                android:id="@+id/pinEntry"
-                android:layout_width="@dimen/keyguard_security_width"
-                android:layout_height="@dimen/pin_entry_height"
-                android:gravity="center"
-                app:scaledTextSize="@integer/password_text_view_scale"
-                android:contentDescription="@string/keyguard_accessibility_pin_area" />
+        <include layout="@layout/keyguard_message_area"
+             android:layout_width="wrap_content"
+             android:layout_height="wrap_content"
+             android:layout_marginBottom="@dimen/car_padding_4"/>
 
-            <View
-                android:id="@+id/divider"
-                android:layout_width="@dimen/keyguard_security_width"
-                android:layout_height="@dimen/divider_height"
-                android:background="@android:color/white" />
-
-            <TextView
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:layout_margin="@dimen/car_padding_2"
-                android:gravity="center"
-                android:textColor="@android:color/white"
-                android:textSize="@dimen/car_body1_size"
-                android:text="@string/keyguard_enter_your_pin" />
-
-            <include layout="@layout/keyguard_message_area"
-                 android:layout_width="wrap_content"
-                 android:layout_height="wrap_content"
-                 android:layout_marginBottom="@dimen/car_padding_4"/>
-
-            <Button
-                android:id="@+id/cancel_button"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:layout_gravity="center"
-                style="@style/KeyguardButton"
-                android:text="@string/cancel"/>
-        </LinearLayout>
-    </FrameLayout>
+        <Button
+            android:id="@+id/cancel_button"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center"
+            style="@style/KeyguardButton"
+            android:text="@string/cancel"/>
+    </LinearLayout>
 
     <!-- KeyguardPinView references these resources ids in code so removing them will cause the
          keyguard to crash. Instead put them down here where they are out of the way and set their
diff --git a/car_product/overlay/frameworks/base/packages/SystemUI/res-keyguard/values/dimens.xml b/car_product/overlay/frameworks/base/packages/SystemUI/res-keyguard/values/dimens.xml
index ea9f5bd..b7967c4 100644
--- a/car_product/overlay/frameworks/base/packages/SystemUI/res-keyguard/values/dimens.xml
+++ b/car_product/overlay/frameworks/base/packages/SystemUI/res-keyguard/values/dimens.xml
@@ -23,7 +23,7 @@
     <dimen name="pin_entry_height">@dimen/num_pad_key_height</dimen>
     <dimen name="divider_height">1dp</dimen>
     <dimen name="key_enter_margin_top">128dp</dimen>
-    <dimen name="keyguard_pattern_dimension">350dp</dimen>
+    <dimen name="keyguard_pattern_dimension">400dp</dimen>
     <dimen name="password_field_width">350dp</dimen>
     <dimen name="pin_pattern_pad_margin_vertical">0dp</dimen>
 </resources>
diff --git a/car_product/sepolicy/private/carservice_app.te b/car_product/sepolicy/private/carservice_app.te
index d02c377..fa2577f 100644
--- a/car_product/sepolicy/private/carservice_app.te
+++ b/car_product/sepolicy/private/carservice_app.te
@@ -28,12 +28,14 @@
     input_method_service
     input_service
     location_service
+    media_session_service
     network_management_service
     power_service
     procfsinspector_service
     sensorservice_service
     surfaceflinger_service
     uimode_service
+    voiceinteraction_service
 }:service_manager find;
 
 # Read and write /data/data subdirectory.
diff --git a/evs/app/RenderDirectView.cpp b/evs/app/RenderDirectView.cpp
index 45dfa78..d7267cf 100644
--- a/evs/app/RenderDirectView.cpp
+++ b/evs/app/RenderDirectView.cpp
@@ -133,6 +133,9 @@
     glDisableVertexAttribArray(1);
 
 
+    // Now that everything is submitted, release our hold on the texture resource
+    detachRenderTarget();
+
     // Wait for the rendering to finish
     glFinish();
     detachRenderTarget();
diff --git a/evs/app/RenderTopView.cpp b/evs/app/RenderTopView.cpp
index 5bc943c..f09e76f 100644
--- a/evs/app/RenderTopView.cpp
+++ b/evs/app/RenderTopView.cpp
@@ -212,6 +212,9 @@
     // 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();
diff --git a/evs/sampleDriver/GlWrapper.cpp b/evs/sampleDriver/GlWrapper.cpp
index 3055ba0..fbd36a2 100644
--- a/evs/sampleDriver/GlWrapper.cpp
+++ b/evs/sampleDriver/GlWrapper.cpp
@@ -308,6 +308,11 @@
         return false;
     }
 
+    // Turn off mip-mapping for the created texture surface
+    // (the inbound camera imagery doesn't have MIPs)
+    glBindTexture(GL_TEXTURE_2D, mTextureMap);
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+    glBindTexture(GL_TEXTURE_2D, 0);
 
     return true;
 }
diff --git a/evs/sampleDriver/ServiceNames.h b/evs/sampleDriver/ServiceNames.h
index 1178da5..6458b1b 100644
--- a/evs/sampleDriver/ServiceNames.h
+++ b/evs/sampleDriver/ServiceNames.h
@@ -13,5 +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
 
 const static char kEnumeratorServiceName[] = "EvsEnumeratorHw";
+
+#endif // ANDROID_HARDWARE_AUTOMOTIVE_EVS_V1_0_SERVICENAMES_H
diff --git a/evs/sampleDriver/VideoCapture.h b/evs/sampleDriver/VideoCapture.h
index f2d1175..63305b9 100644
--- a/evs/sampleDriver/VideoCapture.h
+++ b/evs/sampleDriver/VideoCapture.h
@@ -13,6 +13,9 @@
  * 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
+
 #include <atomic>
 #include <thread>
 #include <functional>
@@ -73,3 +76,4 @@
     };
 };
 
+#endif // ANDROID_HARDWARE_AUTOMOTIVE_EVS_V1_0_VIDEOCAPTURE_
diff --git a/service/Android.mk b/service/Android.mk
index 2ec4c4a..9ab7df3 100644
--- a/service/Android.mk
+++ b/service/Android.mk
@@ -27,6 +27,8 @@
 
 LOCAL_SRC_FILES := $(car_service_sources)
 
+LOCAL_USE_AAPT2 := true
+
 LOCAL_PACKAGE_NAME := CarService
 LOCAL_PRIVATE_PLATFORM_APIS := true
 
@@ -47,6 +49,8 @@
         car-systemtest \
         com.android.car.procfsinspector-client \
 
+include frameworks/base/packages/SettingsLib/common.mk
+
 include $(BUILD_PACKAGE)
 
 #####################################################################################
@@ -74,6 +78,8 @@
 
 LOCAL_MIN_SDK_VERSION := 25
 
+include frameworks/base/packages/SettingsLib/common.mk
+
 include $(BUILD_STATIC_JAVA_LIBRARY)
 
 include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/service/res/values/config.xml b/service/res/values/config.xml
index 45e6e0b..214cf02 100644
--- a/service/res/values/config.xml
+++ b/service/res/values/config.xml
@@ -24,6 +24,9 @@
           dynamic audio routing is disabled and audio works in legacy mode. It may be useful
           during initial development where audio hal does not support bus based addressing yet. -->
     <bool name="audioUseDynamicRouting">false</bool>
+    <!--  Configuration to persist master mute state. If this is set to true,
+          Android will restore the master mute state on boot. -->
+    <bool name="audioPersistMasterMuteState">true</bool>
     <!-- Whether to block other audio while media audio is muted with display off. When set to true,
          other sounds cannot be played either while display is off. If false, only media is muted
          and other sounds can be still played. -->
@@ -51,6 +54,9 @@
           Format of each entry is either to specify package name to whitelist the whole package
           or use format of "packagename/activity_classname" for tagging each activities.-->
     <string name="activityBlacklist"></string>
+    <!-- List of play store package names that are allowed sources of app installation-->
+    <string-array translateble="false" name="allowedAppInstallSources">
+    </string-array>
     <!-- Default home activity -->
     <string name="defaultHomeActivity"><!--com.your.package/com.your.package.Activity--></string>
     <!--  The com.android.car.VmsPublisherService will bind to this list of clients -->
diff --git a/service/src/com/android/car/CarAudioService.java b/service/src/com/android/car/CarAudioService.java
index 1b7ee67..1f9fad0 100644
--- a/service/src/com/android/car/CarAudioService.java
+++ b/service/src/com/android/car/CarAudioService.java
@@ -18,6 +18,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.car.Car;
+import android.car.media.CarAudioManager;
 import android.car.media.CarAudioPatchHandle;
 import android.car.media.ICarAudio;
 import android.car.media.ICarVolumeCallback;
@@ -45,11 +46,13 @@
 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.util.SparseIntArray;
+import android.view.KeyEvent;
 
 import com.android.internal.util.Preconditions;
 
@@ -125,6 +128,7 @@
     private final TelephonyManager mTelephonyManager;
     private final AudioManager mAudioManager;
     private final boolean mUseDynamicRouting;
+    private final boolean mPersistMasterMuteState;
     private final SparseIntArray mContextToBus = new SparseIntArray();
     private final SparseArray<CarAudioDeviceInfo> mCarAudioDeviceInfos = new SparseArray<>();
 
@@ -151,15 +155,15 @@
                     }
                     break;
                 case AudioManager.ADJUST_MUTE:
-                    mAudioManager.setMasterMute(true, flags);
+                    setMasterMute(true, flags);
                     callbackMasterMuteChange(flags);
                     break;
                 case AudioManager.ADJUST_UNMUTE:
-                    mAudioManager.setMasterMute(false, flags);
+                    setMasterMute(false, flags);
                     callbackMasterMuteChange(flags);
                     break;
                 case AudioManager.ADJUST_TOGGLE_MUTE:
-                    mAudioManager.setMasterMute(!mAudioManager.isMasterMute(), flags);
+                    setMasterMute(!mAudioManager.isMasterMute(), flags);
                     callbackMasterMuteChange(flags);
                     break;
                 case AudioManager.ADJUST_SAME:
@@ -203,6 +207,8 @@
         mTelephonyManager = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
         mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
         mUseDynamicRouting = mContext.getResources().getBoolean(R.bool.audioUseDynamicRouting);
+        mPersistMasterMuteState = mContext.getResources().getBoolean(
+                R.bool.audioPersistMasterMuteState);
     }
 
     /**
@@ -219,6 +225,13 @@
                 setupDynamicRouting();
                 setupVolumeGroups();
             }
+
+            // Restore master mute state if applicable
+            if (mPersistMasterMuteState) {
+                boolean storedMasterMute = Settings.Global.getInt(mContext.getContentResolver(),
+                        CarAudioManager.VOLUME_SETTINGS_KEY_MASTER_MUTE, 0) != 0;
+                setMasterMute(storedMasterMute, 0);
+            }
         }
     }
 
@@ -242,7 +255,8 @@
     public void dump(PrintWriter writer) {
         writer.println("*CarAudioService*");
         writer.println("\tRun in legacy mode? " + (!mUseDynamicRouting));
-        writer.println("\tMaster mute? " + mAudioManager.isMasterMute());
+        writer.println("\tPersist master mute state? " + mPersistMasterMuteState);
+        writer.println("\tMaster muted? " + mAudioManager.isMasterMute());
         // Empty line for comfortable reading
         writer.println();
         if (mUseDynamicRouting) {
@@ -283,6 +297,16 @@
         }
     }
 
+    private void setMasterMute(boolean mute, int flags) {
+        mAudioManager.setMasterMute(mute, flags);
+
+        // When the master mute is turned ON, we want the playing app to get a "pause" command.
+        // When the volume is unmuted, we want to resume playback.
+        int keycode = mute ? KeyEvent.KEYCODE_MEDIA_PAUSE : KeyEvent.KEYCODE_MEDIA_PLAY;
+        mAudioManager.dispatchMediaKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, keycode));
+        mAudioManager.dispatchMediaKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, keycode));
+    }
+
     private void callbackMasterMuteChange(int flags) {
         for (BinderInterfaceContainer.BinderInterface<ICarVolumeCallback> callback :
                 mVolumeCallbackContainer.getInterfaces()) {
@@ -292,6 +316,13 @@
                 Log.e(CarLog.TAG_AUDIO, "Failed to callback onMasterMuteChanged", e);
             }
         }
+
+        // Persists master mute state if applicable
+        if (mPersistMasterMuteState) {
+            Settings.Global.putInt(mContext.getContentResolver(),
+                    CarAudioManager.VOLUME_SETTINGS_KEY_MASTER_MUTE,
+                    mAudioManager.isMasterMute() ? 1 : 0);
+        }
     }
 
     /**
@@ -688,6 +719,11 @@
         Preconditions.checkNotNull(patch[0],
                 "createAudioPatch didn't provide expected single handle");
         Log.d(CarLog.TAG_AUDIO, "Audio patch created: " + patch[0]);
+
+        // Ensure the initial volume on output device port
+        int groupId = getVolumeGroupIdForUsage(usage);
+        setGroupVolume(groupId, getGroupVolume(groupId), 0);
+
         return new CarAudioPatchHandle(patch[0]);
     }
 
diff --git a/service/src/com/android/car/CarInputService.java b/service/src/com/android/car/CarInputService.java
index 1f67259..2b97221 100644
--- a/service/src/com/android/car/CarInputService.java
+++ b/service/src/com/android/car/CarInputService.java
@@ -16,7 +16,9 @@
 package com.android.car;
 
 import static android.hardware.input.InputManager.INJECT_INPUT_EVENT_MODE_ASYNC;
+import static android.service.voice.VoiceInteractionSession.SHOW_SOURCE_ASSIST_GESTURE;
 
+import android.app.ActivityManager;
 import android.car.input.CarInputHandlingService;
 import android.car.input.CarInputHandlingService.InputFilter;
 import android.car.input.ICarInputListener;
@@ -30,19 +32,18 @@
 import android.os.Bundle;
 import android.os.IBinder;
 import android.os.Parcel;
-import android.os.ParcelFileDescriptor;
 import android.os.RemoteException;
 import android.os.SystemClock;
 import android.os.UserHandle;
 import android.provider.CallLog.Calls;
-import android.speech.RecognizerIntent;
 import android.telecom.TelecomManager;
 import android.text.TextUtils;
 import android.util.Log;
 import android.view.KeyEvent;
 
 import com.android.car.hal.InputHalService;
-import com.android.car.hal.VehicleHal;
+import com.android.internal.app.AssistUtils;
+import com.android.internal.app.IVoiceInteractionSessionShowCallback;
 
 import java.io.PrintWriter;
 import java.util.HashMap;
@@ -83,12 +84,30 @@
         }
     }
 
+    private IVoiceInteractionSessionShowCallback mShowCallback =
+            new IVoiceInteractionSessionShowCallback.Stub() {
+        @Override
+        public void onFailed() {
+            Log.w(CarLog.TAG_INPUT, "Failed to show VoiceInteractionSession");
+        }
+
+        @Override
+        public void onShown() {
+            if (DBG) {
+                Log.d(CarLog.TAG_INPUT, "IVoiceInteractionSessionShowCallback onShown()");
+            }
+        }
+    };
+
     private static final boolean DBG = false;
+    private static final String EXTRA_CAR_PUSH_TO_TALK =
+            "com.android.car.input.EXTRA_CAR_PUSH_TO_TALK";
 
     private final Context mContext;
     private final InputHalService mInputHalService;
     private final TelecomManager mTelecomManager;
     private final InputManager mInputManager;
+    private final AssistUtils mAssistUtils;
 
     private KeyEventListener mVoiceAssistantKeyListener;
     private KeyEventListener mLongVoiceAssistantKeyListener;
@@ -153,6 +172,7 @@
         mInputHalService = inputHalService;
         mTelecomManager = context.getSystemService(TelecomManager.class);
         mInputManager = context.getSystemService(InputManager.class);
+        mAssistUtils = new AssistUtils(context);
     }
 
     private synchronized void setHandledKeys(InputFilter[] handledKeys) {
@@ -324,10 +344,18 @@
     }
 
     private void launchDefaultVoiceAssistantHandler() {
-        Log.i(CarLog.TAG_INPUT, "voice key, launch default intent");
-        Intent voiceIntent =
-                new Intent(RecognizerIntent.ACTION_VOICE_SEARCH_HANDS_FREE);
-        mContext.startActivityAsUser(voiceIntent, null, UserHandle.CURRENT_OR_SELF);
+        Log.i(CarLog.TAG_INPUT, "voice key, invoke AssistUtils");
+
+        if (mAssistUtils.getAssistComponentForUser(ActivityManager.getCurrentUser()) == null) {
+            Log.w(CarLog.TAG_INPUT, "Unable to retrieve assist component for current user");
+            return;
+        }
+
+        final Bundle args = new Bundle();
+        args.putBoolean(EXTRA_CAR_PUSH_TO_TALK, true);
+
+        mAssistUtils.showSessionForActiveService(args,
+                SHOW_SOURCE_ASSIST_GESTURE, mShowCallback, null /*activityToken*/);
     }
 
     private void handleInstrumentClusterKey(KeyEvent event) {
diff --git a/service/src/com/android/car/CarLocationService.java b/service/src/com/android/car/CarLocationService.java
index 6d93b40..a7aaf03 100644
--- a/service/src/com/android/car/CarLocationService.java
+++ b/service/src/com/android/car/CarLocationService.java
@@ -19,6 +19,7 @@
 import android.car.hardware.CarPropertyValue;
 import android.car.hardware.property.CarPropertyEvent;
 import android.car.hardware.property.ICarPropertyEventListener;
+import android.car.user.CarUserManagerHelper;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -31,6 +32,7 @@
 import android.os.HandlerThread;
 import android.os.RemoteException;
 import android.os.SystemClock;
+import android.os.UserHandle;
 import android.util.AtomicFile;
 import android.util.JsonReader;
 import android.util.JsonWriter;
@@ -60,6 +62,8 @@
     private static final long GRANULARITY_ONE_DAY_MS = 24 * 60 * 60 * 1000L;
     // The time-to-live for the cached location
     private static final long TTL_THIRTY_DAYS_MS = 30 * GRANULARITY_ONE_DAY_MS;
+    // The maximum number of times to try injecting a location
+    private static final int MAX_LOCATION_INJECTION_ATTEMPTS = 10;
 
     // Used internally for mHandlerThread synchronization
     private final Object mLock = new Object();
@@ -68,23 +72,26 @@
     private final CarPowerManagementService mCarPowerManagementService;
     private final CarPropertyService mCarPropertyService;
     private final CarPropertyEventListener mCarPropertyEventListener;
+    private final CarUserManagerHelper mCarUserManagerHelper;
     private int mTaskCount = 0;
     private HandlerThread mHandlerThread;
     private Handler mHandler;
 
     public CarLocationService(Context context, CarPowerManagementService carPowerManagementService,
-            CarPropertyService carPropertyService) {
+            CarPropertyService carPropertyService, CarUserManagerHelper carUserManagerHelper) {
         logd("constructed");
         mContext = context;
         mCarPowerManagementService = carPowerManagementService;
         mCarPropertyService = carPropertyService;
         mCarPropertyEventListener = new CarPropertyEventListener();
+        mCarUserManagerHelper = carUserManagerHelper;
     }
 
     @Override
     public void init() {
         logd("init");
         IntentFilter filter = new IntentFilter();
+        filter.addAction(Intent.ACTION_USER_SWITCHED);
         filter.addAction(Intent.ACTION_LOCKED_BOOT_COMPLETED);
         filter.addAction(LocationManager.MODE_CHANGED_ACTION);
         filter.addAction(LocationManager.GPS_ENABLED_CHANGE_ACTION);
@@ -107,17 +114,19 @@
         writer.println(TAG);
         writer.println("Context: " + mContext);
         writer.println("CarPropertyService: " + mCarPropertyService);
+        writer.println("MAX_LOCATION_INJECTION_ATTEMPTS: " + MAX_LOCATION_INJECTION_ATTEMPTS);
     }
 
     @Override
     public long onPrepareShutdown(boolean shuttingDown) {
         logd("onPrepareShutdown " + shuttingDown);
         asyncOperation(() -> storeLocation());
-        return 0;
+        return 100;
     }
 
     @Override
-    public void onPowerOn(boolean displayOn) { }
+    public void onPowerOn(boolean displayOn) {
+    }
 
     @Override
     public int getWakeupTime() {
@@ -129,27 +138,55 @@
         logd("onReceive " + intent);
         String action = intent.getAction();
         if (action == Intent.ACTION_LOCKED_BOOT_COMPLETED) {
-            asyncOperation(() -> loadLocation());
-        } else {
+            // If the system user is not headless, then we can inject location as soon as the
+            // system has completed booting.
+            if (!mCarUserManagerHelper.isHeadlessSystemUser()) {
+                logd("not headless on boot complete");
+                asyncOperation(() -> loadLocation());
+            }
+        } else if (action == Intent.ACTION_USER_SWITCHED) {
+            int userHandle = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
+            logd("USER_SWITCHED: " + userHandle);
+            if (mCarUserManagerHelper.isHeadlessSystemUser()
+                    && userHandle > UserHandle.USER_SYSTEM) {
+                asyncOperation(() -> loadLocation());
+            }
+        } else if (action == LocationManager.MODE_CHANGED_ACTION
+                && shouldCheckLocationPermissions()) {
             LocationManager locationManager =
                     (LocationManager) mContext.getSystemService(Context.LOCATION_SERVICE);
-            if (action == LocationManager.MODE_CHANGED_ACTION) {
-                boolean locationEnabled = locationManager.isLocationEnabled();
-                logd("isLocationEnabled(): " + locationEnabled);
-                if (!locationEnabled) {
-                    asyncOperation(() -> deleteCacheFile());
-                }
-            } else if (action == LocationManager.GPS_ENABLED_CHANGE_ACTION) {
-                boolean gpsEnabled =
-                        locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER);
-                logd("isProviderEnabled('gps'): " + gpsEnabled);
-                if (!gpsEnabled) {
-                    asyncOperation(() -> deleteCacheFile());
-                }
+            boolean locationEnabled = locationManager.isLocationEnabled();
+            logd("isLocationEnabled(): " + locationEnabled);
+            if (!locationEnabled) {
+                asyncOperation(() -> deleteCacheFile());
+            }
+        } else if (action == LocationManager.GPS_ENABLED_CHANGE_ACTION
+                && shouldCheckLocationPermissions()) {
+            LocationManager locationManager =
+                    (LocationManager) mContext.getSystemService(Context.LOCATION_SERVICE);
+            boolean gpsEnabled =
+                    locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER);
+            logd("isProviderEnabled('gps'): " + gpsEnabled);
+            if (!gpsEnabled) {
+                asyncOperation(() -> deleteCacheFile());
             }
         }
     }
 
+    /**
+     * Tells whether or not we should check location permissions for the sake of deleting the
+     * location cache file when permissions are lacking.  If the system user is headless but the
+     * current user is still the system user, then we should not respond to a lack of location
+     * permissions.
+     */
+    private boolean shouldCheckLocationPermissions() {
+        return !(mCarUserManagerHelper.isHeadlessSystemUser()
+                && mCarUserManagerHelper.isCurrentProcessSystemUser());
+    }
+
+    /**
+     * Gets the last known location from the LocationManager and store it in a file.
+     */
     private void storeLocation() {
         LocationManager locationManager =
                 (LocationManager) mContext.getSystemService(Context.LOCATION_SERVICE);
@@ -163,44 +200,44 @@
             FileOutputStream fos = null;
             try {
                 fos = atomicFile.startWrite();
-                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());
-                if (location.hasAltitude()) {
-                    jsonWriter.name("altitude").value(location.getAltitude());
+                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());
+                    if (location.hasAltitude()) {
+                        jsonWriter.name("altitude").value(location.getAltitude());
+                    }
+                    if (location.hasSpeed()) {
+                        jsonWriter.name("speed").value(location.getSpeed());
+                    }
+                    if (location.hasBearing()) {
+                        jsonWriter.name("bearing").value(location.getBearing());
+                    }
+                    if (location.hasAccuracy()) {
+                        jsonWriter.name("accuracy").value(location.getAccuracy());
+                    }
+                    if (location.hasVerticalAccuracy()) {
+                        jsonWriter.name("verticalAccuracy").value(
+                                location.getVerticalAccuracyMeters());
+                    }
+                    if (location.hasSpeedAccuracy()) {
+                        jsonWriter.name("speedAccuracy").value(
+                                location.getSpeedAccuracyMetersPerSecond());
+                    }
+                    if (location.hasBearingAccuracy()) {
+                        jsonWriter.name("bearingAccuracy").value(
+                                location.getBearingAccuracyDegrees());
+                    }
+                    if (location.isFromMockProvider()) {
+                        jsonWriter.name("isFromMockProvider").value(true);
+                    }
+                    long currentTime = location.getTime();
+                    // Round the time down to only be accurate within one day.
+                    jsonWriter.name("captureTime").value(
+                            currentTime - currentTime % GRANULARITY_ONE_DAY_MS);
+                    jsonWriter.endObject();
                 }
-                if (location.hasSpeed()) {
-                    jsonWriter.name("speed").value(location.getSpeed());
-                }
-                if (location.hasBearing()) {
-                    jsonWriter.name("bearing").value(location.getBearing());
-                }
-                if (location.hasAccuracy()) {
-                    jsonWriter.name("accuracy").value(location.getAccuracy());
-                }
-                if (location.hasVerticalAccuracy()) {
-                    jsonWriter.name("verticalAccuracy").value(
-                            location.getVerticalAccuracyMeters());
-                }
-                if (location.hasSpeedAccuracy()) {
-                    jsonWriter.name("speedAccuracy").value(
-                            location.getSpeedAccuracyMetersPerSecond());
-                }
-                if (location.hasBearingAccuracy()) {
-                    jsonWriter.name("bearingAccuracy").value(
-                            location.getBearingAccuracyDegrees());
-                }
-                if (location.isFromMockProvider()) {
-                    jsonWriter.name("isFromMockProvider").value(true);
-                }
-                long currentTime = location.getTime();
-                // Round the time down to only be accurate within one day.
-                jsonWriter.name("captureTime").value(
-                        currentTime - currentTime % GRANULARITY_ONE_DAY_MS);
-                jsonWriter.endObject();
-                jsonWriter.close();
                 atomicFile.finishWrite(fos);
             } catch (IOException e) {
                 Log.e(TAG, "Unable to write to disk", e);
@@ -209,6 +246,9 @@
         }
     }
 
+    /**
+     * Reads a previously stored location and attempts to inject it into the LocationManager.
+     */
     private void loadLocation() {
         Location location = readLocationFromCacheFile();
         logd("Read location from " + location.getTime());
@@ -220,10 +260,7 @@
             long elapsedTime = SystemClock.elapsedRealtimeNanos();
             location.setElapsedRealtimeNanos(elapsedTime);
             if (location.isComplete()) {
-                LocationManager locationManager =
-                        (LocationManager) mContext.getSystemService(Context.LOCATION_SERVICE);
-                boolean success = locationManager.injectLocation(location);
-                logd("Injected location " + location + " with result " + success);
+                injectLocation(location, 1);
             }
         }
     }
@@ -231,8 +268,7 @@
     private Location readLocationFromCacheFile() {
         Location location = new Location((String) null);
         AtomicFile atomicFile = new AtomicFile(mContext.getFileStreamPath(FILENAME));
-        try {
-            FileInputStream fis = atomicFile.openRead();
+        try (FileInputStream fis = atomicFile.openRead()) {
             JsonReader reader = new JsonReader(new InputStreamReader(fis, "UTF-8"));
             reader.beginObject();
             while (reader.hasNext()) {
@@ -266,7 +302,6 @@
                 }
             }
             reader.endObject();
-            fis.close();
             deleteCacheFile();
         } catch (FileNotFoundException e) {
             Log.d(TAG, "Location cache file not found.");
@@ -283,8 +318,33 @@
         mContext.deleteFile(FILENAME);
     }
 
+    /**
+     * Attempts to inject the location multiple times in case the LocationManager was not fully
+     * initialized or has not updated its handle to the current user yet.
+     */
+    private void injectLocation(Location location, int attemptCount) {
+        LocationManager locationManager =
+                (LocationManager) mContext.getSystemService(Context.LOCATION_SERVICE);
+        boolean success = locationManager.injectLocation(location);
+        logd("Injected location " + location + " with result " + success + " on attempt "
+                + attemptCount);
+        if (success) {
+            return;
+        } else if (attemptCount <= MAX_LOCATION_INJECTION_ATTEMPTS) {
+            asyncOperation(() -> {
+                injectLocation(location, attemptCount + 1);
+            }, 200 * attemptCount);
+        } else {
+            logd("No location injected.");
+        }
+    }
+
     @VisibleForTesting
     void asyncOperation(Runnable operation) {
+        asyncOperation(operation, 0);
+    }
+
+    private void asyncOperation(Runnable operation, long delayMillis) {
         synchronized (mLock) {
             // Create a new HandlerThread if this is the first task to queue.
             if (++mTaskCount == 1) {
@@ -293,7 +353,7 @@
                 mHandler = new Handler(mHandlerThread.getLooper());
             }
         }
-        mHandler.post(() -> {
+        mHandler.postDelayed(() -> {
             try {
                 operation.run();
             } finally {
@@ -306,7 +366,7 @@
                     }
                 }
             }
-        });
+        }, delayMillis);
     }
 
     private static void logd(String msg) {
diff --git a/service/src/com/android/car/CarNightService.java b/service/src/com/android/car/CarNightService.java
index e43ed3d..dcf75b5 100644
--- a/service/src/com/android/car/CarNightService.java
+++ b/service/src/com/android/car/CarNightService.java
@@ -72,23 +72,27 @@
             CarPropertyValue value = event.getCarPropertyValue();
             if (value.getPropertyId() == VehicleProperty.NIGHT_MODE) {
                 boolean nightMode = (Boolean) value.getValue();
-                if (nightMode) {
-                    mNightSetting = UiModeManager.MODE_NIGHT_YES;
-                    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 (mUiModeManager != null && (mForcedMode == FORCED_SENSOR_MODE)) {
-                    mUiModeManager.setNightMode(mNightSetting);
-                    if (DBG)  Log.d(CarLog.TAG_SENSOR, "CAR dayNight handleSensorEvent APPLIED");
-                } else {
-                    if (DBG)  Log.d(CarLog.TAG_SENSOR, "CAR dayNight handleSensorEvent IGNORED");
-                }
+                setNightMode(nightMode);
             }
         }
     }
 
+    private synchronized void setNightMode(boolean nightMode) {
+        if (nightMode) {
+            mNightSetting = UiModeManager.MODE_NIGHT_YES;
+            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 (mUiModeManager != null && (mForcedMode == FORCED_SENSOR_MODE)) {
+            mUiModeManager.setNightMode(mNightSetting);
+            if (DBG)  Log.d(CarLog.TAG_SENSOR, "CAR dayNight handleSensorEvent APPLIED");
+        } else {
+            if (DBG)  Log.d(CarLog.TAG_SENSOR, "CAR dayNight handleSensorEvent IGNORED");
+        }
+    }
+
     public synchronized int forceDayNightMode(@DayNightSensorMode int mode) {
         if (mUiModeManager == null) {
             return -1;
@@ -131,6 +135,15 @@
         }
         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);
+        }
     }
 
     @Override
diff --git a/service/src/com/android/car/CarPropertyService.java b/service/src/com/android/car/CarPropertyService.java
index 68c4b8c..785e598 100644
--- a/service/src/com/android/car/CarPropertyService.java
+++ b/service/src/com/android/car/CarPropertyService.java
@@ -159,6 +159,9 @@
         if (DBG) {
             Log.d(TAG, "registerListener: propId=0x" + toHexString(propId) + " rate=" + rate);
         }
+        if (mConfigs == null) {
+            mConfigs = mHal.getPropertyList();
+        }
         if (mConfigs.get(propId) == null) {
             // Do not attempt to register an invalid propId
             Log.e(TAG, "registerListener:  propId is not in config list:  " + propId);
@@ -197,14 +200,20 @@
                 mHal.subscribeProperty(propId, rate);
             }
         }
-
         // Send the latest value(s) to the registering listener only
         List<CarPropertyEvent> events = new LinkedList<CarPropertyEvent>();
-        for (int areaId : mConfigs.get(propId).getAreaIds()) {
-            CarPropertyValue value = mHal.getProperty(propId, areaId);
+        if (mConfigs.get(propId).isGlobalProperty()) {
+            CarPropertyValue value = mHal.getProperty(propId, 0);
             CarPropertyEvent event = new CarPropertyEvent(
                     CarPropertyEvent.PROPERTY_EVENT_PROPERTY_CHANGE, value);
             events.add(event);
+        } else {
+            for (int areaId : mConfigs.get(propId).getAreaIds()) {
+                CarPropertyValue value = mHal.getProperty(propId, areaId);
+                CarPropertyEvent event = new CarPropertyEvent(
+                        CarPropertyEvent.PROPERTY_EVENT_PROPERTY_CHANGE, value);
+                events.add(event);
+            }
         }
         try {
             listener.onEvent(events);
@@ -276,6 +285,7 @@
 
     /**
      * Return the list of properties that the caller may access.
+     * Should be called before get/setProperty().
      */
     @Override
     public List<CarPropertyConfig> getPropertyList() {
diff --git a/service/src/com/android/car/CarUxRestrictionsManagerService.java b/service/src/com/android/car/CarUxRestrictionsManagerService.java
index 23c4d32..b90939b 100644
--- a/service/src/com/android/car/CarUxRestrictionsManagerService.java
+++ b/service/src/com/android/car/CarUxRestrictionsManagerService.java
@@ -27,11 +27,17 @@
 import android.car.hardware.property.CarPropertyEvent;
 import android.car.hardware.property.ICarPropertyEventListener;
 import android.content.Context;
+import android.content.pm.PackageManager;
 import android.hardware.automotive.vehicle.V2_0.VehicleProperty;
+import android.os.Binder;
+import android.os.Build;
 import android.os.IBinder;
+import android.os.Process;
 import android.os.RemoteException;
 import android.util.Log;
 
+import com.android.internal.annotations.GuardedBy;
+
 import org.xmlpull.v1.XmlPullParserException;
 
 import java.io.IOException;
@@ -60,6 +66,9 @@
     private CarUxRestrictions mCurrentUxRestrictions;
     private float mCurrentMovingSpeed;
     private boolean mFallbackToDefaults;
+    // Flag to disable broadcasting UXR changes - for development purposes
+    @GuardedBy("this")
+    private boolean mUxRChangeBroadcastEnabled = true;
     // For dumpsys logging
     private final LinkedList<Utils.TransitionLog> mTransitionLogs = new LinkedList<>();
 
@@ -224,6 +233,42 @@
     }
 
     /**
+     * Enable/disable UX restrictions change broadcast blocking.
+     * Setting this to true will stop broadcasts of UX restriction change to listeners.
+     * This method works only on debug builds and the caller of this method needs to have the same
+     * signature of the car service.
+     *
+     */
+    public synchronized void setUxRChangeBroadcastEnabled(boolean enable) {
+        if (!isDebugBuild()) {
+            Log.e(TAG, "Cannot set UX restriction change broadcast.");
+            return;
+        }
+        // Check if the caller has the same signature as that of the car service.
+        if (mContext.getPackageManager().checkSignatures(Process.myUid(), Binder.getCallingUid())
+                != PackageManager.SIGNATURE_MATCH) {
+            throw new SecurityException(
+                    "Caller " + mContext.getPackageManager().getNameForUid(Binder.getCallingUid())
+                            + " does not have the right signature");
+        }
+        if (enable) {
+            // if enabling it back, send the current restrictions
+            mUxRChangeBroadcastEnabled = enable;
+            handleDispatchUxRestrictions(mDrivingStateService.getCurrentDrivingState().eventValue,
+                    getCurrentSpeed());
+        } else {
+            // fake parked state, so if the system is currently restricted, the restrictions are
+            // relaxed.
+            handleDispatchUxRestrictions(CarDrivingStateEvent.DRIVING_STATE_PARKED, 0);
+            mUxRChangeBroadcastEnabled = enable;
+        }
+    }
+
+    private boolean isDebugBuild() {
+        return Build.IS_USERDEBUG || Build.IS_ENG;
+    }
+
+    /**
      * Class that holds onto client related information - listener interface, process that hosts the
      * binder object etc.
      * It also registers for death notifications of the host.
@@ -282,6 +327,9 @@
         writer.println(
                 "Requires DO? " + mCurrentUxRestrictions.isRequiresDistractionOptimization());
         writer.println("Current UXR: " + mCurrentUxRestrictions.getActiveRestrictions());
+        if (isDebugBuild()) {
+            writer.println("mUxRChangeBroadcastEnabled? " + mUxRChangeBroadcastEnabled);
+        }
         mHelper.dump(writer);
         writer.println("UX Restriction change log:");
         for (Utils.TransitionLog tlog : mTransitionLogs) {
@@ -377,6 +425,11 @@
      */
     private synchronized void handleDispatchUxRestrictions(@CarDrivingState int currentDrivingState,
             float speed) {
+        if (isDebugBuild() && !mUxRChangeBroadcastEnabled) {
+            Log.d(TAG, "Not dispatching UX Restriction due to setting");
+            return;
+        }
+
         CarUxRestrictions uxRestrictions;
         // Get UX restrictions from the parsed configuration XML or fall back to defaults if not
         // available.
@@ -428,7 +481,7 @@
                 break;
             case CarDrivingStateEvent.DRIVING_STATE_IDLING:
                 restrictions = CarUxRestrictions.UX_RESTRICTIONS_BASELINE;
-                requiresOpt = true;
+                requiresOpt = false;
                 break;
             case CarDrivingStateEvent.DRIVING_STATE_MOVING:
             default:
diff --git a/service/src/com/android/car/CarUxRestrictionsServiceHelper.java b/service/src/com/android/car/CarUxRestrictionsServiceHelper.java
index 236d915..348d3be 100644
--- a/service/src/com/android/car/CarUxRestrictionsServiceHelper.java
+++ b/service/src/com/android/car/CarUxRestrictionsServiceHelper.java
@@ -83,6 +83,10 @@
     public boolean loadUxRestrictionsFromXml() throws IOException, XmlPullParserException {
         mRestrictionsMap.clear();
         XmlResourceParser parser = mContext.getResources().getXml(mXmlResource);
+        if (parser == null) {
+            Log.e(TAG, "Invalid Xml resource");
+            return  false;
+        }
         AttributeSet attrs = Xml.asAttributeSet(parser);
         int type;
         // Traverse till we get to the first tag
diff --git a/service/src/com/android/car/ICarImpl.java b/service/src/com/android/car/ICarImpl.java
index ce5ea75..67c50a7 100644
--- a/service/src/com/android/car/ICarImpl.java
+++ b/service/src/com/android/car/ICarImpl.java
@@ -36,6 +36,7 @@
 import android.util.TimingsTraceLog;
 
 import com.android.car.cluster.InstrumentClusterService;
+import com.android.car.garagemode.GarageModeService;
 import com.android.car.hal.VehicleHal;
 import com.android.car.internal.FeatureConfiguration;
 import com.android.car.pm.CarPackageManagerService;
@@ -122,8 +123,6 @@
         mCarInputService = new CarInputService(serviceContext, mHal.getInputHal());
         mCarProjectionService = new CarProjectionService(serviceContext, mCarInputService);
         mGarageModeService = new GarageModeService(mContext, mCarPowerManagementService);
-        mCarLocationService = new CarLocationService(mContext, mCarPowerManagementService,
-                mCarPropertyService);
         mAppFocusService = new AppFocusService(serviceContext, mSystemActivityMonitoringService);
         mCarAudioService = new CarAudioService(serviceContext);
         mCarNightService = new CarNightService(serviceContext, mCarPropertyService);
@@ -142,6 +141,8 @@
         mCarConfigurationService =
                 new CarConfigurationService(serviceContext, new JsonReaderImpl());
         mUserManagerHelper = new CarUserManagerHelper(serviceContext);
+        mCarLocationService = new CarLocationService(mContext, mCarPowerManagementService,
+                mCarPropertyService, mUserManagerHelper);
 
         // Be careful with order. Service depending on other service should be inited later.
         List<CarServiceBase> allServices = new ArrayList<>();
@@ -152,7 +153,6 @@
         allServices.add(mCarUXRestrictionsService);
         allServices.add(mCarPackageManagerService);
         allServices.add(mCarInputService);
-        allServices.add(mCarLocationService);
         allServices.add(mGarageModeService);
         allServices.add(mAppFocusService);
         allServices.add(mCarAudioService);
@@ -173,6 +173,7 @@
             allServices.add(mCarUserService);
         }
 
+        allServices.add(mCarLocationService);
         mAllServices = allServices.toArray(new CarServiceBase[allServices.size()]);
     }
 
diff --git a/service/src/com/android/car/garagemode/GarageModePolicy.java b/service/src/com/android/car/garagemode/GarageModePolicy.java
new file mode 100644
index 0000000..be7b713
--- /dev/null
+++ b/service/src/com/android/car/garagemode/GarageModePolicy.java
@@ -0,0 +1,157 @@
+/*
+ * 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.garagemode;
+
+import android.content.Context;
+import android.util.Log;
+
+import com.android.car.R;
+
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Default garage mode policy.
+ *
+ * The first wake up time is set to be 1am the next day. And it keeps waking up every day for a
+ * week. After that, wake up every 7 days for a month, and wake up every 30 days thereafter.
+ */
+public class GarageModePolicy {
+    private static final String TAG = "GarageModePolicy";
+    private static final Map<Character, Integer> TIME_UNITS_LOOKUP;
+    static {
+        TIME_UNITS_LOOKUP = new HashMap<>();
+        TIME_UNITS_LOOKUP.put('m', 60);
+        TIME_UNITS_LOOKUP.put('h', 3600);
+        TIME_UNITS_LOOKUP.put('d', 86400);
+    }
+
+    private LinkedList<WakeupInterval> mWakeupIntervals;
+
+    public GarageModePolicy(String[] policy) {
+        mWakeupIntervals = parsePolicy(policy);
+    }
+
+    /**
+     * Initializes GarageModePolicy from config_garageModeCadence resource array.
+     * @param context to access resources
+     * @return GarageModePolicy instance, created from values in resources
+     */
+    public static GarageModePolicy initFromResources(Context context) {
+        return new GarageModePolicy(
+                context.getResources().getStringArray(R.array.config_garageModeCadence));
+    }
+
+    /**
+     * Returns the interval in milliseconds, which defines next wake up time.
+     * @param index amount of times system woken up
+     * @return the interval in milliseconds
+     */
+    public int getNextWakeUpInterval(int index) {
+        if (mWakeupIntervals.size() == 0) {
+            Log.e(TAG, "No wake up policy configuration was loaded.");
+            return 0;
+        }
+
+        for (WakeupInterval wakeupTime : mWakeupIntervals) {
+            if (index < wakeupTime.getNumAttempts()) {
+                return wakeupTime.getWakeupInterval();
+            }
+            index -= wakeupTime.getNumAttempts();
+        }
+        Log.w(TAG, "No more garage mode wake ups scheduled; been sleeping too long.");
+        return 0;
+    }
+
+    /**
+     * Get list of {@link com.android.car.garagemode.WakeupInterval}s in this policy
+     * @return list as List\<WakeupInterval\>
+     */
+    public List<WakeupInterval> getWakeupIntervals() {
+        return mWakeupIntervals;
+    }
+
+    private LinkedList<WakeupInterval> parsePolicy(String[] policy) {
+        LinkedList<WakeupInterval> intervals = new LinkedList<>();
+        if (policy == null || policy.length == 0) {
+            Log.e(TAG, "Trying to parse empty policies!");
+            return intervals;
+        }
+
+        for (String rule : policy) {
+            WakeupInterval interval = parseRule(rule);
+            if (interval == null) {
+                Log.e(TAG, "Invalid Policy! This rule has bad format: " + rule);
+                return new LinkedList<>();
+            }
+            intervals.add(interval);
+        }
+        return intervals;
+    }
+
+    private WakeupInterval parseRule(String rule) {
+        String[] str = rule.split(",");
+
+        if (str.length != 2) {
+            Log.e(TAG, "Policy has bad format: " + rule);
+            return null;
+        }
+
+        String intervalStr = str[0];
+        String timesStr = str[1];
+
+        if (intervalStr.isEmpty() || timesStr.isEmpty()) {
+            Log.e(TAG, "One of the values is empty. Please check format: " + rule);
+            return null;
+        }
+
+        char unit = intervalStr.charAt(intervalStr.length() - 1);
+
+        // Removing last letter extension from string
+        intervalStr = intervalStr.substring(0, intervalStr.length() - 1);
+
+        int interval, times;
+        try {
+            interval = Integer.parseInt(intervalStr);
+            times = Integer.parseInt(timesStr);
+        } catch (NumberFormatException ex)  {
+            Log.d(TAG, "Invalid input Rule for interval " + rule);
+            return null;
+        }
+
+        if (!TIME_UNITS_LOOKUP.containsKey(unit)) {
+            Log.e(TAG, "Time units map does not contain extension " + unit);
+            return null;
+        }
+
+        if (interval <= 0) {
+            Log.e(TAG, "Wake up policy time must be > 0!" + interval);
+            return null;
+        }
+
+        if (times <= 0) {
+            Log.e(TAG, "Wake up attempts in policy must be > 0!" + times);
+            return null;
+        }
+
+        interval *= TIME_UNITS_LOOKUP.get(unit);
+
+        return new WakeupInterval(interval, times);
+    }
+}
diff --git a/service/src/com/android/car/GarageModeService.java b/service/src/com/android/car/garagemode/GarageModeService.java
similarity index 66%
rename from service/src/com/android/car/GarageModeService.java
rename to service/src/com/android/car/garagemode/GarageModeService.java
index 0ccddf1..c9c34dd 100644
--- a/service/src/com/android/car/GarageModeService.java
+++ b/service/src/com/android/car/garagemode/GarageModeService.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2015 The Android Open Source Project
+ * 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.
@@ -13,9 +13,13 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.car;
 
-import android.car.CarApiUtil;
+package com.android.car.garagemode;
+
+import static android.car.settings.GarageModeSettingsObserver.GARAGE_MODE_ENABLED_URI;
+import static android.car.settings.GarageModeSettingsObserver.GARAGE_MODE_MAINTENANCE_WINDOW_URI;
+import static android.car.settings.GarageModeSettingsObserver.GARAGE_MODE_WAKE_UP_TIME_URI;
+
 import android.car.settings.CarSettings;
 import android.car.settings.GarageModeSettingsObserver;
 import android.content.Context;
@@ -32,32 +36,28 @@
 import android.provider.Settings;
 import android.util.Log;
 
+import com.android.car.CarPowerManagementService;
+import com.android.car.CarServiceBase;
+import com.android.car.DeviceIdleControllerWrapper;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 
 import java.io.PrintWriter;
-import java.lang.RuntimeException;
-import java.util.Map;
-import java.util.HashMap;
-
-import static android.car.settings.GarageModeSettingsObserver.GARAGE_MODE_ENABLED_URI;
-import static android.car.settings.GarageModeSettingsObserver.GARAGE_MODE_MAINTENANCE_WINDOW_URI;
-import static android.car.settings.GarageModeSettingsObserver.GARAGE_MODE_WAKE_UP_TIME_URI;
 
 /**
  * Controls car garage mode.
  *
  * Car garage mode is a time window for the car to do maintenance work when the car is not in use.
- * The {@link com.android.car.GarageModeService} interacts with {@link com.android.car.CarPowerManagementService}
- * to start and end garage mode. A {@link com.android.car.GarageModeService.GarageModePolicy} defines
- * when the garage mode should start and how long it should last.
+ * The {@link com.android.car.garagemode.GarageModeService} interacts with
+ * {@link com.android.car.CarPowerManagementService} to start and end garage mode.
+ * A {@link com.android.car.garagemode.GarageModePolicy} defines when the garage mode should
+ * start and how long it should last.
  */
 public class GarageModeService implements CarServiceBase,
         CarPowerManagementService.PowerEventProcessingHandler,
         CarPowerManagementService.PowerServiceEventListener,
         DeviceIdleControllerWrapper.DeviceMaintenanceActivityListener {
-    private static String TAG = "GarageModeService";
-    private static final boolean DBG = false;
+    private static final String TAG = "GarageModeService";
 
     private static final int MSG_EXIT_GARAGE_MODE_EARLY = 0;
     private static final int MSG_WRITE_TO_PREF = 1;
@@ -65,7 +65,7 @@
     private static final String KEY_GARAGE_MODE_INDEX = "garage_mode_index";
 
     // wait for 10 seconds to allow maintenance activities to start (e.g., connecting to wifi).
-    protected static final int MAINTENANCE_ACTIVITY_START_GRACE_PERIOUD = 10 * 1000;
+    protected static final int MAINTENANCE_ACTIVITY_START_GRACE_PERIOD = 10 * 1000;
 
     private final CarPowerManagementService mPowerManagementService;
     protected final Context mContext;
@@ -96,7 +96,7 @@
     private final GarageModeHandler mHandler;
 
     private class GarageModeHandler extends Handler {
-        public GarageModeHandler(Looper looper) {
+        GarageModeHandler(Looper looper) {
             super(looper);
         }
 
@@ -139,21 +139,23 @@
 
     @Override
     public void init() {
-        init(mContext.getResources().getStringArray(R.array.config_garageModeCadence),
+        init(
+                GarageModePolicy.initFromResources(mContext),
                 PreferenceManager.getDefaultSharedPreferences(
                         mContext.createDeviceProtectedStorageContext()));
     }
 
     @VisibleForTesting
-    protected void init(String[] policyArray, SharedPreferences prefs) {
-        logd("init GarageMode");
+    void init(GarageModePolicy policy, SharedPreferences prefs) {
+        Log.d(TAG, "initializing GarageMode");
         mSharedPreferences = prefs;
+        mPolicy = policy;
         final int index = mSharedPreferences.getInt(KEY_GARAGE_MODE_INDEX, 0);
         synchronized (this) {
             mMaintenanceActive = mDeviceIdleController.startTracking(this);
             mGarageModeIndex = index;
-            readPolicyLocked(policyArray);
-            readFromSettingsLocked(CarSettings.Global.KEY_GARAGE_MODE_MAINTENANCE_WINDOW,
+            readFromSettingsLocked(
+                    CarSettings.Global.KEY_GARAGE_MODE_MAINTENANCE_WINDOW,
                     CarSettings.Global.KEY_GARAGE_MODE_ENABLED,
                     CarSettings.Global.KEY_GARAGE_MODE_WAKE_UP_TIME);
         }
@@ -167,7 +169,7 @@
 
     @Override
     public void release() {
-        logd("release GarageModeService");
+        Log.d(TAG, "releasing GarageModeService");
         mDeviceIdleController.stopTracking();
         mContentObserver.unregister();
     }
@@ -184,18 +186,18 @@
     public long onPrepareShutdown(boolean shuttingDown) {
         // this is the beginning of each garage mode.
         synchronized (this) {
-            logd("onPrePowerEvent " + shuttingDown);
+            Log.d(TAG, "onPrepareShutdown is triggered. System is shutting down=" + shuttingDown);
             mInGarageMode = true;
             mGarageModeIndex++;
             mHandler.removeMessages(MSG_EXIT_GARAGE_MODE_EARLY);
             if (!mMaintenanceActive) {
                 mHandler.sendMessageDelayed(
                         mHandler.obtainMessage(MSG_EXIT_GARAGE_MODE_EARLY),
-                        MAINTENANCE_ACTIVITY_START_GRACE_PERIOUD);
+                        MAINTENANCE_ACTIVITY_START_GRACE_PERIOD);
             }
             // We always reserve the maintenance window first. If later, we found no
             // maintenance work active, we will exit garage mode early after
-            // MAINTENANCE_ACTIVITY_START_GRACE_PERIOUD
+            // MAINTENANCE_ACTIVITY_START_GRACE_PERIOD
             return mMaintenanceWindow;
         }
     }
@@ -203,7 +205,7 @@
     @Override
     public void onPowerOn(boolean displayOn) {
         synchronized (this) {
-            logd("onPowerOn: " + displayOn);
+            Log.d(TAG, "onPowerOn: " + displayOn);
             if (displayOn) {
                 // the car is use now. reset the garage mode counter.
                 mGarageModeIndex = 0;
@@ -217,7 +219,7 @@
             if (!mGarageModeEnabled) {
                 return 0;
             }
-            return mPolicy.getNextWakeUpTime(mGarageModeIndex);
+            return mPolicy.getNextWakeUpInterval(mGarageModeIndex);
         }
     }
 
@@ -241,12 +243,6 @@
         }
     }
 
-    @GuardedBy("this")
-    private void readPolicyLocked(String[] policyArray) {
-        logd("readPolicy");
-        mPolicy = new GarageModePolicy(policyArray);
-    }
-
     private void writeToPref(int index) {
         SharedPreferences.Editor editor = mSharedPreferences.edit();
         editor.putInt(KEY_GARAGE_MODE_INDEX, index);
@@ -257,7 +253,7 @@
     public void onMaintenanceActivityChanged(boolean active) {
         boolean shouldReportCompletion = false;
         synchronized (this) {
-            logd("onMaintenanceActivityChanged: " + active);
+            Log.d(TAG, "onMaintenanceActivityChanged: " + active);
             mMaintenanceActive = active;
             if (!mInGarageMode) {
                 return;
@@ -277,111 +273,11 @@
         }
     }
 
-    static final class WakeupTime {
-        int mWakeupTime;
-        int mNumAttempts;
-
-        WakeupTime(int wakeupTime, int numAttempts) {
-            mWakeupTime = wakeupTime;
-            mNumAttempts = numAttempts;
-        }
-    };
-
-    /**
-     * Default garage mode policy.
-     *
-     * The first wake up time is set to be 1am the next day. And it keeps waking up every day for a
-     * week. After that, wake up every 7 days for a month, and wake up every 30 days thereafter.
-     */
-    @VisibleForTesting
-    static final class GarageModePolicy {
-        private static final Map<String, Integer> TIME_UNITS_LOOKUP;
-        static {
-            TIME_UNITS_LOOKUP = new HashMap<String, Integer>();
-            TIME_UNITS_LOOKUP.put("m", 60);
-            TIME_UNITS_LOOKUP.put("h", 3600);
-            TIME_UNITS_LOOKUP.put("d", 86400);
-        }
-        protected WakeupTime[] mWakeupTime;
-
-        public GarageModePolicy(String[] policy) {
-            if (policy == null || policy.length == 0) {
-                throw new RuntimeException("Must include valid policy.");
-            }
-
-            WakeupTime[] parsedWakeupTime = new WakeupTime[policy.length];
-            for (int i = 0; i < policy.length; i++) {
-                String[] splitString = policy[i].split(",");
-                if (splitString.length != 2) {
-                    throw new RuntimeException("Bad policy format: " + policy[i]);
-                }
-
-                int wakeupTimeMs = 0;
-                int numAttempts = 0;
-                String wakeupTime = splitString[0];
-                if (wakeupTime.isEmpty()) {
-                    throw new RuntimeException("Missing wakeup time: " + policy[i]);
-                }
-                String wakeupTimeValue = wakeupTime.substring(0, wakeupTime.length() - 1);
-
-                if (wakeupTimeValue.isEmpty()) {
-                    throw new RuntimeException("Missing wakeup time value: " + wakeupTime);
-                }
-                try {
-                    int timeCount = Integer.parseInt(wakeupTimeValue);
-                    if (timeCount <= 0) {
-                        throw new RuntimeException("Wakeup time must be > 0: " + timeCount);
-                    }
-
-                    // Last character indicates minutes, hours, or days.
-                    Integer multiplier = TIME_UNITS_LOOKUP.get(
-                            wakeupTime.substring(wakeupTime.length() - 1));
-                    if (multiplier == null) {
-                        throw new RuntimeException("Bad wakeup time units: " + wakeupTime);
-                    }
-
-                    wakeupTimeMs = timeCount * multiplier;
-                } catch (NumberFormatException e) {
-                    throw new RuntimeException("Bad wakeup time value: " + wakeupTimeValue);
-                }
-                try {
-                    numAttempts = Integer.parseInt(splitString[1]);
-                    if (numAttempts <= 0) {
-                        throw new RuntimeException(
-                            "Number of attempts must be > 0: " + numAttempts);
-                    }
-                } catch (NumberFormatException e) {
-                    throw new RuntimeException("Bad number of attempts: " + splitString[1]);
-                }
-
-                parsedWakeupTime[i] = new WakeupTime(wakeupTimeMs, numAttempts);
-            }
-            mWakeupTime = parsedWakeupTime;
-        }
-
-        public int getNextWakeUpTime(int index) {
-            if (mWakeupTime == null) {
-                Log.e(TAG, "Could not parse policy.");
-                return 0;
-            }
-
-            for (int i = 0; i < mWakeupTime.length; i++) {
-                if (index < mWakeupTime[i].mNumAttempts) {
-                    return mWakeupTime[i].mWakeupTime;
-                } else {
-                    index -= mWakeupTime[i].mNumAttempts;
-                }
-            }
-
-            Log.w(TAG, "No more garage mode wake ups scheduled; been sleeping too long.");
-            return 0;
-        }
-    }
 
     private static class DefaultDeviceIdleController extends DeviceIdleControllerWrapper {
         private IDeviceIdleController mDeviceIdleController;
-        private MaintenanceActivityListener mMaintenanceActivityListener
-                = new MaintenanceActivityListener();
+        private MaintenanceActivityListener mMaintenanceActivityListener =
+                new MaintenanceActivityListener();
 
         @Override
         public boolean startLocked() {
@@ -417,12 +313,6 @@
         }
     }
 
-    private void logd(String msg) {
-        if (DBG) {
-            Log.d(TAG, msg);
-        }
-    }
-
     @GuardedBy("this")
     private void readFromSettingsLocked(String... keys) {
         for (String key : keys) {
@@ -444,7 +334,7 @@
 
     private void onSettingsChangedInternal(Uri uri) {
         synchronized (this) {
-            logd("Content Observer onChange: " + uri);
+            Log.d(TAG, "Content Observer onChange: " + uri);
             if (uri.equals(GARAGE_MODE_ENABLED_URI)) {
                 readFromSettingsLocked(CarSettings.Global.KEY_GARAGE_MODE_ENABLED);
             } else if (uri.equals(GARAGE_MODE_WAKE_UP_TIME_URI)) {
@@ -452,7 +342,7 @@
             } else if (uri.equals(GARAGE_MODE_MAINTENANCE_WINDOW_URI)) {
                 readFromSettingsLocked(CarSettings.Global.KEY_GARAGE_MODE_MAINTENANCE_WINDOW);
             }
-            logd(String.format(
+            Log.d(TAG, String.format(
                     "onSettingsChanged %s. enabled: %s, windowSize: %d",
                     uri, mGarageModeEnabled, mMaintenanceWindow));
         }
diff --git a/service/src/com/android/car/garagemode/WakeupInterval.java b/service/src/com/android/car/garagemode/WakeupInterval.java
new file mode 100644
index 0000000..659af9e
--- /dev/null
+++ b/service/src/com/android/car/garagemode/WakeupInterval.java
@@ -0,0 +1,48 @@
+/*
+ * 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 com.android.car.garagemode;
+
+/**
+ * Defines wake up interval which then will be used by
+ * {@link com.android.car.garagemode.GarageModeService} to determine when to schedule next wake up
+ * from {@link com.android.car.CarPowerManagementService}
+ */
+class WakeupInterval {
+    private int mWakeupInterval;
+    private int mNumAttempts;
+
+    WakeupInterval(int wakeupTime, int numAttempts) {
+        mWakeupInterval = wakeupTime;
+        mNumAttempts = numAttempts;
+    }
+
+    /**
+     * Returns interval between now and next weke up.
+     * @return interval in seconds
+     */
+    public int getWakeupInterval() {
+        return mWakeupInterval;
+    }
+
+    /**
+     * Returns amount of attempts to wake up with mWakeupInterval
+     * @return amount of attempts
+     */
+    public int getNumAttempts() {
+        return mNumAttempts;
+    }
+}
diff --git a/service/src/com/android/car/hal/CarPropertyUtils.java b/service/src/com/android/car/hal/CarPropertyUtils.java
index d9151e2..2d0e6ad 100644
--- a/service/src/com/android/car/hal/CarPropertyUtils.java
+++ b/service/src/com/android/car/hal/CarPropertyUtils.java
@@ -48,13 +48,35 @@
         long timestamp = halValue.timestamp;
         VehiclePropValue.RawValue v = halValue.value;
 
+        // Handles each return value from {@link getJavaClass}.
         if (Boolean.class == clazz) {
             return new CarPropertyValue<>(propertyId, areaId, status, timestamp,
                                           v.int32Values.get(0) == 1);
-        } else if (Boolean[].class == clazz) {
-            Boolean[] values = new Boolean[v.int32Values.size()];
+        } else if (Float.class == clazz) {
+            return new CarPropertyValue<>(propertyId, areaId, status, timestamp,
+                                          v.floatValues.get(0));
+        } else if (Integer.class == clazz) {
+            return new CarPropertyValue<>(propertyId, areaId, status, timestamp,
+                                          v.int32Values.get(0));
+        } else if (Long.class == clazz) {
+            return new CarPropertyValue<>(propertyId, areaId, status, timestamp,
+                                          v.int64Values.get(0));
+        } else if (Float[].class == clazz) {
+            Float[] values = new Float[v.floatValues.size()];
             for (int i = 0; i < values.length; i++) {
-                values[i] = v.int32Values.get(i) == 1;
+                values[i] = v.floatValues.get(i);
+            }
+            return new CarPropertyValue<>(propertyId, areaId, status, timestamp, values);
+        } else if (Integer[].class == clazz) {
+            Integer[] values = new Integer[v.int32Values.size()];
+            for (int i = 0; i < values.length; i++) {
+                values[i] = v.int32Values.get(i);
+            }
+            return new CarPropertyValue<>(propertyId, areaId, status, timestamp, values);
+        } else if (Long[].class == clazz) {
+            Long[] values = new Long[v.int64Values.size()];
+            for (int i = 0; i < values.length; i++) {
+                values[i] = v.int64Values.get(i);
             }
             return new CarPropertyValue<>(propertyId, areaId, status, timestamp, values);
         } else if (String.class == clazz) {
@@ -62,13 +84,7 @@
         } else if (byte[].class == clazz) {
             byte[] halData = toByteArray(v.bytes);
             return new CarPropertyValue<>(propertyId, areaId, status, timestamp, halData);
-        } else if (Long[].class == clazz) {
-            Long[] values = new Long[v.int64Values.size()];
-            for (int i = 0; i < values.length; i++) {
-                values[i] = v.int64Values.get(i);
-            }
-            return new CarPropertyValue<>(propertyId, areaId, status, timestamp, values);
-        } else /* All list properties */ {
+        } else /* Object.class */ {
             Object[] values = getRawValueList(clazz, v).toArray();
             return new CarPropertyValue<>(propertyId, areaId, status, timestamp,
                     values.length == 1 ? values[0] : values);
@@ -88,7 +104,7 @@
             v.int32Values.add(((Boolean) o) ? 1 : 0);
         } else if (o instanceof Boolean[]) {
             for (Boolean b : (Boolean[]) o) {
-                v.int32Values.add(((Boolean) o) ? 1 : 0);
+                v.int32Values.add(b ? 1 : 0);
             }
         } else if (o instanceof Integer) {
             v.int32Values.add((Integer) o);
diff --git a/service/src/com/android/car/hal/PropertyHalServiceIds.java b/service/src/com/android/car/hal/PropertyHalServiceIds.java
index 651afea..6c5533d 100644
--- a/service/src/com/android/car/hal/PropertyHalServiceIds.java
+++ b/service/src/com/android/car/hal/PropertyHalServiceIds.java
@@ -215,9 +215,6 @@
         mProps.put(VehicleProperty.HVAC_SEAT_VENTILATION, new Pair<>(
                     Car.PERMISSION_CONTROL_CAR_CLIMATE,
                     Car.PERMISSION_CONTROL_CAR_CLIMATE));
-        mProps.put(VehicleProperty.ENV_OUTSIDE_TEMPERATURE, new Pair<>(
-                    Car.PERMISSION_CONTROL_CAR_CLIMATE,
-                    Car.PERMISSION_CONTROL_CAR_CLIMATE));
 
         // Info properties
         mProps.put(VehicleProperty.INFO_VIN, new Pair<>(
diff --git a/service/src/com/android/car/pm/CarPackageManagerService.java b/service/src/com/android/car/pm/CarPackageManagerService.java
index 930a9e4..ed51a78 100644
--- a/service/src/com/android/car/pm/CarPackageManagerService.java
+++ b/service/src/com/android/car/pm/CarPackageManagerService.java
@@ -40,11 +40,13 @@
 import android.content.pm.Signature;
 import android.content.res.Resources;
 import android.os.Binder;
+import android.os.Build;
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.Looper;
 import android.os.Message;
 import android.os.Process;
+import android.text.TextUtils;
 import android.text.format.DateFormat;
 import android.util.ArraySet;
 import android.util.Log;
@@ -92,6 +94,8 @@
     // Store the white list and black list strings from the resource file.
     private String mConfiguredWhitelist;
     private String mConfiguredBlacklist;
+    private final List<String> mAllowedAppInstallSources;
+
     /**
      * Hold policy set from policy service or client.
      * Key: packageName of policy service
@@ -152,8 +156,11 @@
         mEnableActivityBlocking = res.getBoolean(R.bool.enableActivityBlockingForSafety);
         String blockingActivity = res.getString(R.string.activityBlockingActivity);
         mActivityBlockingActivity = ComponentName.unflattenFromString(blockingActivity);
+        mAllowedAppInstallSources = Arrays.asList(
+                res.getStringArray(R.array.allowedAppInstallSources));
     }
 
+
     @Override
     public void setAppBlockingPolicy(String packageName, CarAppBlockingPolicy policy, int flags) {
         if (DBG_POLICY_SET) {
@@ -535,8 +542,10 @@
         // Add the blocking overlay activity to the whitelist, since that needs to run in a
         // restricted state to communicate the reason an app was blocked.
         Set<String> defaultActivity = new ArraySet<>();
-        defaultActivity.add(mActivityBlockingActivity.getClassName());
-        configWhitelist.put(mActivityBlockingActivity.getPackageName(), defaultActivity);
+        if (mActivityBlockingActivity != null) {
+            defaultActivity.add(mActivityBlockingActivity.getClassName());
+            configWhitelist.put(mActivityBlockingActivity.getPackageName(), defaultActivity);
+        }
 
         List<PackageInfo> packages = mPackageManager.getInstalledPackages(
                 PackageManager.GET_SIGNATURES | PackageManager.GET_ACTIVITIES
@@ -583,7 +592,31 @@
                 }
             } else {
                 /* 2. If app is not listed in the config.xml check their Manifest meta-data to
-                  see if they have any Distraction Optimized(DO) activities */
+                  see if they have any Distraction Optimized(DO) activities.
+                  For non system apps, we check if the app install source was a permittable
+                  source. This prevents side-loaded apps to fake DO.  Bypass the check
+                  for debug builds for development convenience. */
+                if (!isDebugBuild()
+                        && !info.applicationInfo.isSystemApp()
+                        && !info.applicationInfo.isUpdatedSystemApp()) {
+                    try {
+                        if (mAllowedAppInstallSources != null) {
+                            String installerName = mPackageManager.getInstallerPackageName(
+                                    info.packageName);
+                            if (installerName == null || (installerName != null
+                                    && !mAllowedAppInstallSources.contains(installerName))) {
+                                Log.w(CarLog.TAG_PACKAGE,
+                                        info.packageName + " not installed from permitted sources "
+                                                + installerName == null ? "NULL" : installerName);
+                                continue;
+                            }
+                        }
+                    } catch (IllegalArgumentException e) {
+                        Log.w(CarLog.TAG_PACKAGE, info.packageName + " not installed!");
+                        continue;
+                    }
+                }
+
                 try {
                     activities = CarAppMetadataReader.findDistractionOptimizedActivities(
                             mContext,
@@ -622,6 +655,10 @@
         }
     }
 
+    private boolean isDebugBuild() {
+        return Build.IS_USERDEBUG || Build.IS_ENG;
+    }
+
     /**
      * Generate a map of blacklisted packages and activities of the form {pkgName, Blacklisted
      * activities}.  The blacklist information comes from a configuration XML resource.
@@ -644,6 +681,11 @@
         }
 
         for (String pkg : configBlacklist.keySet()) {
+            if (TextUtils.isEmpty(pkg)) {
+                // This means there is nothing to blacklist
+                Log.d(CarLog.TAG_PACKAGE, "Empty string in blacklist pkg");
+                continue;
+            }
             int flags = 0;
             PackageInfo pkgInfo;
             String[] activities;
@@ -654,7 +696,7 @@
                                 | PackageManager.MATCH_DIRECT_BOOT_AWARE
                                 | PackageManager.MATCH_DIRECT_BOOT_UNAWARE);
             } catch (NameNotFoundException e) {
-                Log.e(CarLog.TAG_PACKAGE, pkg + " not found to blacklist " + e);
+                Log.e(CarLog.TAG_PACKAGE, pkg + " not found to blacklist ", e);
                 continue;
             }
 
@@ -943,8 +985,18 @@
         }
     }
 
+    /**
+     * Enable/Disable activity blocking by correspondingly enabling/disabling broadcasting UXR
+     * changes in {@link CarUxRestrictionsManagerService}. This is only available in
+     * engineering builds for development convenience.
+     *
+     */
     @Override
     public synchronized 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) {
@@ -952,7 +1004,7 @@
                     "Caller " + mPackageManager.getNameForUid(Binder.getCallingUid())
                             + " does not have the right signature");
         }
-        mEnableActivityBlocking = enable;
+        mCarUxRestrictionsService.setUxRChangeBroadcastEnabled(enable);
     }
 
     /**
diff --git a/service/src/com/android/car/systeminterface/DisplayInterface.java b/service/src/com/android/car/systeminterface/DisplayInterface.java
index 015f54d..8dc7bd4 100644
--- a/service/src/com/android/car/systeminterface/DisplayInterface.java
+++ b/service/src/com/android/car/systeminterface/DisplayInterface.java
@@ -16,6 +16,10 @@
 
 package com.android.car.systeminterface;
 
+import static com.android.settingslib.display.BrightnessUtils.GAMMA_SPACE_MAX;
+import static com.android.settingslib.display.BrightnessUtils.convertGammaToLinear;
+import static com.android.settingslib.display.BrightnessUtils.convertLinearToGamma;
+
 import android.content.ContentResolver;
 import android.content.Context;
 import android.database.ContentObserver;
@@ -60,20 +64,18 @@
                 new ContentObserver(new Handler(Looper.getMainLooper())) {
                     @Override
                     public void onChange(boolean selfChange) {
-                        int brightness = mMinimumBacklight;
-                        int range = mMaximumBacklight - mMinimumBacklight;
+                        int linear = GAMMA_SPACE_MAX;
 
                         try {
-                            brightness = System.getInt(mContentResolver, System.SCREEN_BRIGHTNESS);
+                            linear = System.getInt(mContentResolver, System.SCREEN_BRIGHTNESS);
                         } catch (SettingNotFoundException e) {
                             Log.e(CarLog.TAG_POWER, "Could not get SCREEN_BRIGHTNESS:  " + e);
                         }
-                        // Convert brightness from 0-255 to 0-100%
-                        brightness -= mMinimumBacklight;
-                        brightness *= 100;
-                        brightness += (range + 1) / 2;
-                        brightness /= range;
-                        mService.sendDisplayBrightness(brightness);
+                        int gamma = convertLinearToGamma(linear, mMinimumBacklight,
+                                                         mMaximumBacklight);
+                        int percentBright = (gamma * 100 + ((GAMMA_SPACE_MAX + 1) / 2))
+                                / GAMMA_SPACE_MAX;
+                        mService.sendDisplayBrightness(percentBright);
                     }
                 };
 
@@ -124,26 +126,10 @@
         }
 
         @Override
-        public void setDisplayBrightness(int brightness) {
-            // Brightness is set in percent.  Need to convert this into 0-255 scale.  The actual
-            //  brightness algorithm should look like this:
-            //
-            //      newBrightness = (brightness * (max - min)) + min
-            //
-            //  Since we're using integer arithmetic, do the multiplication first, then add 50 to
-            //  round up as needed.
-            brightness *= mMaximumBacklight - mMinimumBacklight;    // Multiply by full range
-            brightness += 50;                                       // Integer rounding
-            brightness /= 100;                                      // Divide by 100
-            brightness += mMinimumBacklight;
-            // Range checking
-            if (brightness < mMinimumBacklight) {
-                brightness = mMinimumBacklight;
-            } else if (brightness > mMaximumBacklight) {
-                brightness = mMaximumBacklight;
-            }
-            // Set the brightness
-            System.putInt(mContentResolver, System.SCREEN_BRIGHTNESS, brightness);
+        public void setDisplayBrightness(int percentBright) {
+            int gamma = (percentBright * GAMMA_SPACE_MAX + 50) / 100;
+            int linear = convertGammaToLinear(gamma, mMinimumBacklight, mMaximumBacklight);
+            System.putInt(mContentResolver, System.SCREEN_BRIGHTNESS, linear);
         }
 
         @Override
diff --git a/service/src/com/android/car/user/CarUserService.java b/service/src/com/android/car/user/CarUserService.java
index 74ff78d..82e981f 100644
--- a/service/src/com/android/car/user/CarUserService.java
+++ b/service/src/com/android/car/user/CarUserService.java
@@ -17,13 +17,15 @@
 package com.android.car.user;
 
 import android.annotation.Nullable;
-import android.car.settings.CarSettings;
 import android.car.user.CarUserManagerHelper;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.UserInfo;
+import android.location.LocationManager;
+import android.os.UserHandle;
+import android.os.UserManager;
 import android.util.Log;
 
 import com.android.car.CarServiceBase;
@@ -36,13 +38,13 @@
  *
  * <ol>
  *   <li> Creates a secondary admin user on first run.
- *   <li> Log in to a default user.
+ *   <li> Log in to the last active user.
  * <ol/>
  */
 public class CarUserService extends BroadcastReceiver implements CarServiceBase {
     // Place holder for user name of the first user created.
     @VisibleForTesting
-    static final String OWNER_NAME = "Owner";
+    static final String OWNER_NAME = "Driver";
     private static final String TAG = "CarUserService";
     private final Context mContext;
     private final CarUserManagerHelper mCarUserManagerHelper;
@@ -63,6 +65,7 @@
         }
         IntentFilter filter = new IntentFilter();
         filter.addAction(Intent.ACTION_LOCKED_BOOT_COMPLETED);
+        filter.addAction(Intent.ACTION_USER_SWITCHED);
 
         mContext.registerReceiver(this, filter);
     }
@@ -87,13 +90,38 @@
             Log.d(TAG, "onReceive " + intent);
         }
 
-        if (intent.getAction() == Intent.ACTION_LOCKED_BOOT_COMPLETED) {
+        if (Intent.ACTION_LOCKED_BOOT_COMPLETED.equals(intent.getAction())) {
             if (mCarUserManagerHelper.getAllUsers().size() == 0) {
+                setSystemUserRestrictions();
+                mCarUserManagerHelper.initDefaultGuestRestrictions();
+                // On very first boot, create an admin user and switch to that user.
                 UserInfo admin = mCarUserManagerHelper.createNewAdminUser(OWNER_NAME);
                 mCarUserManagerHelper.switchToUser(admin);
+                mCarUserManagerHelper.setLastActiveUser(
+                        admin.id, /* skipGlobalSettings= */ false);
             } else {
-                mCarUserManagerHelper.switchToUserId(CarSettings.DEFAULT_USER_ID_TO_BOOT_INTO);
+                mCarUserManagerHelper.switchToUserId(mCarUserManagerHelper.getInitialUser());
+            }
+        } else if (Intent.ACTION_USER_SWITCHED.equals(intent.getAction())) {
+            // Update last active user if the switched-to user is a persistent, non-system user.
+            int currentUser = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
+            if (currentUser > UserHandle.USER_SYSTEM
+                        && mCarUserManagerHelper.isPersistentUser(currentUser)) {
+                mCarUserManagerHelper.setLastActiveUser(
+                        currentUser, /* skipGlobalSetting= */ false);
             }
         }
     }
+
+    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));
+    }
 }
diff --git a/tests/EmbeddedKitchenSinkApp/AndroidManifest.xml b/tests/EmbeddedKitchenSinkApp/AndroidManifest.xml
index bc89ad4..6e8e80f 100644
--- a/tests/EmbeddedKitchenSinkApp/AndroidManifest.xml
+++ b/tests/EmbeddedKitchenSinkApp/AndroidManifest.xml
@@ -42,6 +42,8 @@
     <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" />
 
     <application android:label="@string/app_title"
diff --git a/tests/EmbeddedKitchenSinkApp/res/layout/connectivity_fragment.xml b/tests/EmbeddedKitchenSinkApp/res/layout/connectivity_fragment.xml
new file mode 100644
index 0000000..15422b1
--- /dev/null
+++ b/tests/EmbeddedKitchenSinkApp/res/layout/connectivity_fragment.xml
@@ -0,0 +1,55 @@
+<?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.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+              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">
+        <ListView
+                android:id="@+id/networks"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content">
+        </ListView>
+    </LinearLayout>
+    <LinearLayout
+            android:orientation="horizontal"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_margin="4dp">
+        <Button android:id="@+id/networksRefresh"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="Refresh"/>
+        <Button android:id="@+id/networkRequestOemPaid"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="Request OEM-paid"/>
+        <Button android:id="@+id/networkRequestEth1"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="Request eth1"/>
+        <Button android:id="@+id/networkReleaseNetwork"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="Release Request"/>
+    </LinearLayout>
+
+</LinearLayout>
\ No newline at end of file
diff --git a/tests/EmbeddedKitchenSinkApp/res/layout/list_item.xml b/tests/EmbeddedKitchenSinkApp/res/layout/list_item.xml
new file mode 100644
index 0000000..f517913
--- /dev/null
+++ b/tests/EmbeddedKitchenSinkApp/res/layout/list_item.xml
@@ -0,0 +1,22 @@
+<?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.
+-->
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+          android:id="@android:id/text1"
+          android:paddingTop="2dip"
+          android:paddingBottom="3dip"
+          android:layout_width="fill_parent"
+          android:layout_height="wrap_content"
+          android:textSize="24sp" />
\ No newline at end of file
diff --git a/tests/EmbeddedKitchenSinkApp/res/values/strings.xml b/tests/EmbeddedKitchenSinkApp/res/values/strings.xml
index 74c7c26..452fec8 100644
--- a/tests/EmbeddedKitchenSinkApp/res/values/strings.xml
+++ b/tests/EmbeddedKitchenSinkApp/res/values/strings.xml
@@ -211,14 +211,14 @@
     <!-- sensors test -->
     <string name="sensor_na">N/A</string>
 
-    <string name="sensor_environment">Environment[%1$s]: temperature=%2$s, pressure=%3$s</string>
+    <string name="sensor_environment">Environment[%1$s]: temperature=%2$s</string>
     <string name="sensor_night">Night[%1$s]: isNight=%2$s</string>
     <string name="sensor_gear">Gear[%1$s]: gear=%2$s</string>
     <string name="sensor_parking_brake">Parking brake[%1$s]: isEngaged=%2$s</string>
     <string name="sensor_odometer">Odometer[%1$s]: kms=%2$s</string>
     <string name="sensor_rpm">RPM[%1$s]: rpm=%2$s</string>
     <string name="sensor_speed">Speed[%1$s]: speed=%2$s</string>
-    <string name="sensor_driving_status">Driving status[%1$s]: status=%2$s [bin=%3$s]</string>
+    <string name="sensor_ignition_status">Ignition status[%1$s]: status=%2$s</string>
     <string name="sensor_compass">Compass[%1$s]: bear=%2$s, pitch=%3$s, roll=%4$s</string>
     <string name="sensor_accelerometer">Accelerometer[%1$s]: x=%2$s, y=%3$s, z=%4$s</string>
     <string name="sensor_gyroscope">Gyroscope[%1$s]: x=%2$s, y=%3$s, z=%4$s</string>
@@ -231,6 +231,7 @@
     <string name="sensor_traction_control_is_active">Traction Control[%1$s]: isActive=%2$s</string>
     <string name="sensor_fuel_level">Fuel Level[%1$s]: %2$s</string>
     <string name="sensor_fuel_door_open">Fuel Door Open[%1$s]: %2$s</string>
+    <string name="sensor_engine_oil_level">Engine Oil Level[%1$s]: %2$s</string>
     <string name="sensor_engine_is_on">Engine Is On[%1$s]: %2$s</string>
     <string name="sensor_ev_battery_level">EV Battery Level[%1$s]: %2$s</string>
     <string name="sensor_ev_charge_port_is_open">EV Charge Port Is Open[%1$s]: %2$s</string>
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 7837f2e..caca03a 100644
--- a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/KitchenSinkActivity.java
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/KitchenSinkActivity.java
@@ -23,7 +23,9 @@
 import android.car.hardware.property.CarPropertyManager;
 import android.content.Intent;
 import android.content.pm.PackageManager;
+import android.os.AsyncTask;
 import android.os.Bundle;
+import android.os.Handler;
 import android.support.car.Car;
 import android.support.car.CarAppFocusManager;
 import android.support.car.CarConnectionCallback;
@@ -42,6 +44,7 @@
 import com.google.android.car.kitchensink.bluetooth.BluetoothHeadsetFragment;
 import com.google.android.car.kitchensink.bluetooth.MapMceTestFragment;
 import com.google.android.car.kitchensink.cluster.InstrumentClusterFragment;
+import com.google.android.car.kitchensink.connectivity.ConnectivityFragment;
 import com.google.android.car.kitchensink.cube.CubesTestFragment;
 import com.google.android.car.kitchensink.diagnostic.DiagnosticTestFragment;
 import com.google.android.car.kitchensink.displayinfo.DisplayInfoFragment;
@@ -167,6 +170,7 @@
                 startActivity(intent);
             });
             add("activity view", ActivityViewTestFragment.class);
+            add("connectivity", ConnectivityFragment.class);
             add("quit", KitchenSinkActivity.this::finish);
         }
 
@@ -183,6 +187,7 @@
     private CarPropertyManager mPropertyManager;
     private CarSensorManager mSensorManager;
     private CarAppFocusManager mCarAppFocusManager;
+    private Object mPropertyManagerReady = new Object();
 
     public CarHvacManager getHvacManager() {
         return mHvacManager;
@@ -212,12 +217,20 @@
         setMainContent(R.layout.kitchen_content);
         // Connection to Car Service does not work for non-automotive yet.
         if (getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) {
-            mCarApi = Car.createCar(this, mCarConnectionCallback);
-            mCarApi.connect();
+            initCarApi();
         }
         Log.i(TAG, "onCreate");
     }
 
+    private void initCarApi() {
+        if (mCarApi != null && mCarApi.isConnected()) {
+            mCarApi.disconnect();
+            mCarApi = null;
+        }
+        mCarApi = Car.createCar(this, mCarConnectionCallback);
+        mCarApi.connect();
+    }
+
     @Override
     protected void onStart() {
         super.onStart();
@@ -268,18 +281,22 @@
         @Override
         public void onConnected(Car car) {
             Log.d(TAG, "Connected to Car Service");
-            try {
-                mHvacManager = (CarHvacManager) mCarApi.getCarManager(android.car.Car.HVAC_SERVICE);
-                mPowerManager = (CarPowerManager) mCarApi.getCarManager(
-                    android.car.Car.POWER_SERVICE);
-                mPropertyManager = (CarPropertyManager) mCarApi.getCarManager(
-                    android.car.Car.PROPERTY_SERVICE);
-                mSensorManager = (CarSensorManager) mCarApi.getCarManager(
-                    android.car.Car.SENSOR_SERVICE);
-                mCarAppFocusManager =
-                        (CarAppFocusManager) mCarApi.getCarManager(Car.APP_FOCUS_SERVICE);
-            } catch (CarNotConnectedException e) {
-                Log.e(TAG, "Car is not connected!", e);
+            synchronized (mPropertyManagerReady) {
+                try {
+                    mHvacManager = (CarHvacManager) mCarApi.getCarManager(
+                            android.car.Car.HVAC_SERVICE);
+                    mPowerManager = (CarPowerManager) mCarApi.getCarManager(
+                            android.car.Car.POWER_SERVICE);
+                    mPropertyManager = (CarPropertyManager) mCarApi.getCarManager(
+                            android.car.Car.PROPERTY_SERVICE);
+                    mSensorManager = (CarSensorManager) mCarApi.getCarManager(
+                            android.car.Car.SENSOR_SERVICE);
+                    mCarAppFocusManager =
+                            (CarAppFocusManager) mCarApi.getCarManager(Car.APP_FOCUS_SERVICE);
+                    mPropertyManagerReady.notifyAll();
+                } catch (CarNotConnectedException e) {
+                    Log.e(TAG, "Car is not connected!", e);
+                }
             }
         }
 
@@ -322,4 +339,29 @@
             getDrawerController().closeDrawer();
         }
     }
+
+    // Use AsyncTask to refresh Car*Manager after car service connected
+    public void requestRefreshManager(final Runnable r, final Handler h) {
+        final AsyncTask<Void, Void, Void> task = new AsyncTask<Void, Void, Void>() {
+            @Override
+            protected Void doInBackground(Void... unused) {
+                synchronized (mPropertyManagerReady) {
+                    while (!mCarApi.isConnected()) {
+                        try {
+                            mPropertyManagerReady.wait();
+                        } catch (InterruptedException e) {
+                            return null;
+                        }
+                    }
+                }
+                return null;
+            }
+
+            @Override
+            protected void onPostExecute(Void unused) {
+                h.post(r);
+            }
+        };
+        task.execute();
+    }
 }
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
new file mode 100644
index 0000000..0ffa6bf
--- /dev/null
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/connectivity/ConnectivityFragment.java
@@ -0,0 +1,154 @@
+/*
+ * 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.google.android.car.kitchensink.connectivity;
+
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.net.ConnectivityManager;
+import android.net.ConnectivityManager.NetworkCallback;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.net.NetworkInfo;
+import android.net.NetworkRequest;
+import android.os.Bundle;
+import android.os.Handler;
+import android.support.v4.app.Fragment;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+import android.widget.ListView;
+import android.widget.Toast;
+
+import com.google.android.car.kitchensink.R;
+
+import java.util.ArrayList;
+
+@SuppressLint("SetTextI18n")
+public class ConnectivityFragment extends Fragment {
+    private static final String TAG = ConnectivityFragment.class.getSimpleName();
+
+    private final Handler mHandler = new Handler();
+    private final ArrayList<String> mNetworks = new ArrayList<>();
+
+    private ConnectivityManager mConnectivityManager;
+    private ArrayAdapter<String> mNetworksAdapter;
+
+    private final NetworkCallback mNetworkCallback = new NetworkCallback() {
+        @Override
+        public void onAvailable(Network network) {
+            showToast("onAvailable, netId: " + network);
+            refreshNetworks();
+        }
+
+        @Override
+        public void onLost(Network network) {
+            showToast("onLost, netId: " + network);
+            refreshNetworks();
+        }
+    };
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        mConnectivityManager = getActivity().getSystemService(ConnectivityManager.class);
+
+        mConnectivityManager.addDefaultNetworkActiveListener(() -> refreshNetworks());
+    }
+
+    @Nullable
+    @Override
+    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
+            @Nullable Bundle savedInstanceState) {
+        View view = inflater.inflate(R.layout.connectivity_fragment, container, false);
+
+        ListView networksView = view.findViewById(R.id.networks);
+        mNetworksAdapter = new ArrayAdapter<>(getActivity(), R.layout.list_item, mNetworks);
+        networksView.setAdapter(mNetworksAdapter);
+
+        setClickAction(view, R.id.networksRefresh, this::refreshNetworks);
+        setClickAction(view, R.id.networkRequestOemPaid, this::requestOemPaid);
+        setClickAction(view, R.id.networkRequestEth1, this::requestEth1);
+        setClickAction(view, R.id.networkReleaseNetwork, this::releaseNetworkRequest);
+
+        return view;
+    }
+
+    private void releaseNetworkRequest() {
+        mConnectivityManager.unregisterNetworkCallback(mNetworkCallback);
+        showToast("Release request sent");
+    }
+
+    private void requestEth1() {
+        NetworkRequest request = new NetworkRequest.Builder()
+                .clearCapabilities()
+                .addTransportType(NetworkCapabilities.TRANSPORT_ETHERNET)
+                .setNetworkSpecifier("eth1")
+                .build();
+        mConnectivityManager.requestNetwork(request, mNetworkCallback, mHandler);
+    }
+
+    private void requestOemPaid() {
+        NetworkRequest request = new NetworkRequest.Builder()
+                .addCapability(NetworkCapabilities.NET_CAPABILITY_OEM_PAID)
+                .build();
+
+        mConnectivityManager.requestNetwork(request, mNetworkCallback, mHandler);
+    }
+
+    @Override
+    public void onResume() {
+        super.onResume();
+        refreshNetworks();
+    }
+
+    private void setClickAction(View view, int id, Runnable action) {
+        view.findViewById(id).setOnClickListener(v -> action.run());
+    }
+
+    private void refreshNetworks() {
+        mNetworks.clear();
+
+        for (Network network : mConnectivityManager.getAllNetworks()) {
+            boolean isDefault = sameNetworkId(network, mConnectivityManager.getActiveNetwork());
+            NetworkCapabilities nc = mConnectivityManager.getNetworkCapabilities(network);
+            boolean isOemPaid = nc.hasCapability(NetworkCapabilities.NET_CAPABILITY_OEM_PAID);
+            boolean isInternet = nc.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
+
+            NetworkInfo networkInfo = mConnectivityManager.getNetworkInfo(network);
+
+            mNetworks.add("netId: " + network.netId
+                    + (isInternet ? " [INTERNET]" : "")
+                    + (isDefault ? " [DEFAULT]" : "")
+                    + (isOemPaid ? " [OEM-paid]" : "") + nc + " " + networkInfo);
+        }
+
+        mNetworksAdapter.notifyDataSetChanged();
+    }
+
+    private void showToast(String text) {
+        Log.d(TAG, "showToast: " + text);
+        Toast.makeText(getContext(), text, Toast.LENGTH_LONG).show();
+    }
+
+    private static boolean sameNetworkId(Network net1, Network net2) {
+        return net1 != null && net2 != null && net1.netId == net2.netId;
+
+    }
+}
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/hvac/HvacTestFragment.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/hvac/HvacTestFragment.java
index a1f8e1d..c7b80e8 100644
--- a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/hvac/HvacTestFragment.java
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/hvac/HvacTestFragment.java
@@ -25,6 +25,7 @@
 import android.hardware.automotive.vehicle.V2_0.VehicleAreaSeat;
 import android.hardware.automotive.vehicle.V2_0.VehicleAreaWindow;
 import android.os.Bundle;
+import android.os.Handler;
 import android.support.v4.app.Fragment;
 import android.util.Log;
 import android.view.LayoutInflater;
@@ -76,6 +77,8 @@
     private int mZoneForSetTempP;
     private int mZoneForFanSpeed;
     private int mZoneForFanPosition;
+    private List<CarPropertyConfig> mCarPropertyConfigs;
+    private View mHvacView;
 
     private final CarHvacManager.CarHvacEventCallback mHvacCallback =
             new CarHvacManager.CarHvacEventCallback () {
@@ -171,13 +174,9 @@
 
     @Override
     public void onCreate(Bundle savedInstanceState) {
-        mCarHvacManager = ((KitchenSinkActivity)getActivity()).getHvacManager();
+
         super.onCreate(savedInstanceState);
-        try {
-            mCarHvacManager.registerCallback(mHvacCallback);
-        } catch (CarNotConnectedException e) {
-            Log.e(TAG, "Car is not connected!");
-        }
+
     }
 
     @Override
@@ -188,77 +187,85 @@
 
     @Override
     public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstance) {
-        View v = inflater.inflate(R.layout.hvac_test, container, false);
+        mHvacView = inflater.inflate(R.layout.hvac_test, container, false);
+        final Runnable r = () -> {
+            mCarHvacManager = ((KitchenSinkActivity) getActivity()).getHvacManager();
+            try {
+                mCarHvacManager.registerCallback(mHvacCallback);
+            } catch (CarNotConnectedException e) {
+                Log.e(TAG, "Car is not connected!");
+            }
+            try {
+                mCarPropertyConfigs = mCarHvacManager.getPropertyList();
+            } catch (CarNotConnectedException e) {
+                Log.e(TAG, "Failed to get list of properties", e);
+                mCarPropertyConfigs = new ArrayList<>();
+            }
+            for (CarPropertyConfig prop : mCarPropertyConfigs) {
+                int propId = prop.getPropertyId();
 
-        List<CarPropertyConfig> props;
-        try {
-            props = mCarHvacManager.getPropertyList();
-        } catch (CarNotConnectedException e) {
-            Log.e(TAG, "Failed to get list of properties", e);
-            props = new ArrayList<>();
-        }
+                if (DBG) {
+                    Log.d(TAG, prop.toString());
+                }
 
-        for(CarPropertyConfig prop : props) {
-            int propId = prop.getPropertyId();
-
-            if(DBG) {
-                Log.d(TAG, prop.toString());
+                switch(propId) {
+                    case CarHvacManager.ID_OUTSIDE_AIR_TEMP:
+                        configureOutsideTemp(mHvacView, prop);
+                        break;
+                    case CarHvacManager.ID_ZONED_DUAL_ZONE_ON:
+                        configureDualOn(mHvacView, prop);
+                        break;
+                    case CarHvacManager.ID_ZONED_AC_ON:
+                        configureAcOn(mHvacView, prop);
+                        break;
+                    case CarHvacManager.ID_ZONED_FAN_DIRECTION:
+                        configureFanPosition(mHvacView, prop);
+                        break;
+                    case CarHvacManager.ID_ZONED_FAN_SPEED_SETPOINT:
+                        configureFanSpeed(mHvacView, prop);
+                        break;
+                    case CarHvacManager.ID_ZONED_TEMP_SETPOINT:
+                        configureTempSetpoint(mHvacView, prop);
+                        break;
+                    case CarHvacManager.ID_ZONED_AUTOMATIC_MODE_ON:
+                        configureAutoModeOn(mHvacView, prop);
+                        break;
+                    case CarHvacManager.ID_ZONED_AIR_RECIRCULATION_ON:
+                        configureRecircOn(mHvacView, prop);
+                        break;
+                    case CarHvacManager.ID_ZONED_MAX_AC_ON:
+                        configureMaxAcOn(mHvacView, prop);
+                        break;
+                    case CarHvacManager.ID_ZONED_MAX_DEFROST_ON:
+                        configureMaxDefrostOn(mHvacView, prop);
+                        break;
+                    case CarHvacManager.ID_WINDOW_DEFROSTER_ON:
+                        configureDefrosterOn(mHvacView, prop);
+                        break;
+                    default:
+                        Log.w(TAG, "propertyId " + propId + " is not handled");
+                        break;
+                }
             }
 
-            switch(propId) {
-                case CarHvacManager.ID_OUTSIDE_AIR_TEMP:
-                    configureOutsideTemp(v, prop);
-                    break;
-                case CarHvacManager.ID_ZONED_DUAL_ZONE_ON:
-                    configureDualOn(v, prop);
-                    break;
-                case CarHvacManager.ID_ZONED_AC_ON:
-                    configureAcOn(v, prop);
-                    break;
-                case CarHvacManager.ID_ZONED_FAN_DIRECTION:
-                    configureFanPosition(v, prop);
-                    break;
-                case CarHvacManager.ID_ZONED_FAN_SPEED_SETPOINT:
-                    configureFanSpeed(v, prop);
-                    break;
-                case CarHvacManager.ID_ZONED_TEMP_SETPOINT:
-                    configureTempSetpoint(v, prop);
-                    break;
-                case CarHvacManager.ID_ZONED_AUTOMATIC_MODE_ON:
-                    configureAutoModeOn(v, prop);
-                    break;
-                case CarHvacManager.ID_ZONED_AIR_RECIRCULATION_ON:
-                    configureRecircOn(v, prop);
-                    break;
-                case CarHvacManager.ID_ZONED_MAX_AC_ON:
-                    configureMaxAcOn(v, prop);
-                    break;
-                case CarHvacManager.ID_ZONED_MAX_DEFROST_ON:
-                    configureMaxDefrostOn(v, prop);
-                    break;
-                case CarHvacManager.ID_WINDOW_DEFROSTER_ON:
-                    configureDefrosterOn(v, prop);
-                    break;
-                default:
-                    Log.w(TAG, "propertyId " + propId + " is not handled");
-                    break;
-            }
-        }
+            mTvFanSpeed = (TextView) mHvacView.findViewById(R.id.tvFanSpeed);
+            mTvFanSpeed.setText(String.valueOf(mCurFanSpeed));
+            mTvDTemp = (TextView) mHvacView.findViewById(R.id.tvDTemp);
+            mTvDTemp.setText(String.valueOf(mCurDTemp));
+            mTvPTemp = (TextView) mHvacView.findViewById(R.id.tvPTemp);
+            mTvPTemp.setText(String.valueOf(mCurPTemp));
+            mTvOutsideTemp = (TextView) mHvacView.findViewById(R.id.tvOutsideTemp);
+            mTvOutsideTemp.setText("N/A");
+        };
 
-        mTvFanSpeed = (TextView) v.findViewById(R.id.tvFanSpeed);
-        mTvFanSpeed.setText(String.valueOf(mCurFanSpeed));
-        mTvDTemp = (TextView) v.findViewById(R.id.tvDTemp);
-        mTvDTemp.setText(String.valueOf(mCurDTemp));
-        mTvPTemp = (TextView) v.findViewById(R.id.tvPTemp);
-        mTvPTemp.setText(String.valueOf(mCurPTemp));
-        mTvOutsideTemp = (TextView) v.findViewById(R.id.tvOutsideTemp);
-        mTvOutsideTemp.setText("N/A");
+        ((KitchenSinkActivity) getActivity()).requestRefreshManager(r,
+                new Handler(getContext().getMainLooper()));
 
         if(DBG) {
             Log.d(TAG, "Starting HvacTestFragment");
         }
 
-        return v;
+        return mHvacView;
     }
 
     private void configureOutsideTemp(View v, CarPropertyConfig prop) {
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/power/PowerTestFragment.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/power/PowerTestFragment.java
index fed1fbd..9a6c2b9 100644
--- a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/power/PowerTestFragment.java
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/power/PowerTestFragment.java
@@ -20,6 +20,7 @@
 import android.car.hardware.power.CarPowerManager;
 import android.content.Context;
 import android.os.Bundle;
+import android.os.Handler;
 import android.os.PowerManager;
 import android.os.SystemClock;
 import android.support.v4.app.Fragment;
@@ -58,16 +59,20 @@
 
     @Override
     public void onCreate(Bundle savedInstanceState) {
-        mCarPowerManager = ((KitchenSinkActivity)getActivity()).getPowerManager();
-        mExecutor = new ThreadPerTaskExecutor();
+        final Runnable r = () -> {
+            mCarPowerManager = ((KitchenSinkActivity) getActivity()).getPowerManager();
+            mExecutor = new ThreadPerTaskExecutor();
+            try {
+                mCarPowerManager.setListener(mPowerListener, mExecutor);
+            } catch (CarNotConnectedException e) {
+                Log.e(TAG, "Car is not connected!");
+            } catch (IllegalStateException e) {
+                Log.e(TAG, "CarPowerManager listener was not cleared");
+            }
+        };
+        ((KitchenSinkActivity) getActivity()).requestRefreshManager(r,
+                new Handler(getContext().getMainLooper()));
         super.onCreate(savedInstanceState);
-        try {
-            mCarPowerManager.setListener(mPowerListener, mExecutor);
-        } catch (CarNotConnectedException e) {
-            Log.e(TAG, "Car is not connected!");
-        } catch (IllegalStateException e) {
-            Log.e(TAG, "CarPowerManager listener was not cleared");
-        }
     }
 
     @Override
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/property/PropertyTestFragment.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/property/PropertyTestFragment.java
index fc6621a..ff1c402 100644
--- a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/property/PropertyTestFragment.java
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/property/PropertyTestFragment.java
@@ -26,6 +26,7 @@
 import android.hardware.automotive.vehicle.V2_0.VehicleProperty;
 import android.hardware.automotive.vehicle.V2_0.VehiclePropertyType;
 import android.os.Bundle;
+import android.os.Handler;
 import android.support.v4.app.Fragment;
 import android.util.Log;
 import android.view.LayoutInflater;
@@ -85,20 +86,23 @@
         mPropertyId = view.findViewById(R.id.sPropertyId);
         mScrollView = view.findViewById(R.id.svEventLog);
         mSetValue = view.findViewById(R.id.etSetPropertyValue);
+        mActivity = (KitchenSinkActivity) getActivity();
 
-        populateConfigList();
-        mListView.setAdapter(new PropertyListAdapter(mPropInfo, mMgr, mEventLog, mScrollView,
-                                                     mActivity));
+        final Runnable r = () -> {
+            mMgr = mActivity.getPropertyManager();
+            populateConfigList();
+            mListView.setAdapter(new PropertyListAdapter(mPropInfo, mMgr, mEventLog, mScrollView,
+                    mActivity));
 
-        // Configure dropdown menu for propertyId spinner
-        ArrayAdapter<PropertyInfo> adapter =
-                new ArrayAdapter<PropertyInfo>(mActivity, android.R.layout.simple_spinner_item,
-                                               mPropInfo);
-        adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
-        mPropertyId.setAdapter(adapter);
-        mPropertyId.setOnItemSelectedListener(this);
-
-
+            // Configure dropdown menu for propertyId spinner
+            ArrayAdapter<PropertyInfo> adapter =
+                    new ArrayAdapter<PropertyInfo>(mActivity, android.R.layout.simple_spinner_item,
+                            mPropInfo);
+            adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+            mPropertyId.setAdapter(adapter);
+            mPropertyId.setOnItemSelectedListener(this);
+        };
+        mActivity.requestRefreshManager(r, new Handler(getContext().getMainLooper()));
 
         // Configure listeners for buttons
         Button b = view.findViewById(R.id.bGetProperty);
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/sensor/SensorsTestFragment.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/sensor/SensorsTestFragment.java
index abc2c10..1440ff0 100644
--- a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/sensor/SensorsTestFragment.java
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/sensor/SensorsTestFragment.java
@@ -99,17 +99,19 @@
 
         View view = inflater.inflate(R.layout.sensors, container, false);
         mActivity = (KitchenSinkActivity) getHost();
-
         mSensorInfo = (TextView) view.findViewById(R.id.sensor_info);
         mNaString = getContext().getString(R.string.sensor_na);
-
         return view;
     }
 
     @Override
     public void onResume() {
         super.onResume();
-        initPermissions();
+        final Runnable r = () -> {
+            initPermissions();
+        };
+        ((KitchenSinkActivity) getActivity()).requestRefreshManager(r,
+                new Handler(getContext().getMainLooper()));
     }
 
     @Override
@@ -207,6 +209,12 @@
                     case CarSensorManager.SENSOR_TYPE_FUEL_DOOR_OPEN:
                         summary.add(getFuelDoorOpen(event));
                         break;
+                    case CarSensorManager.SENSOR_TYPE_IGNITION_STATE:
+                        summary.add(getContext().getString(R.string.sensor_ignition_status,
+                                getTimestamp(event),
+                                event == null ? mNaString :
+                                event.getIgnitionStateData(null).ignitionState));
+                        break;
                     case CarSensorManager.SENSOR_TYPE_PARKING_BRAKE:
                         summary.add(getContext().getString(R.string.sensor_parking_brake,
                                 getTimestamp(event),
@@ -223,18 +231,15 @@
                                 getTimestamp(event),
                                 event == null ? mNaString : event.getNightData(null).isNightMode));
                         break;
-                    case CarSensorManager.SENSOR_TYPE_ENVIRONMENT:
+                    case CarSensorManager.SENSOR_TYPE_ENV_OUTSIDE_TEMPERATURE:
                         String temperature = mNaString;
-                        String pressure = mNaString;
                         if (event != null) {
                             CarSensorEvent.EnvironmentData env = event.getEnvironmentData(null);
                             temperature = Float.isNaN(env.temperature) ? temperature :
                                     String.valueOf(env.temperature);
-                            pressure = Float.isNaN(env.pressure) ? pressure :
-                                    String.valueOf(env.pressure);
                         }
                         summary.add(getContext().getString(R.string.sensor_environment,
-                                getTimestamp(event), temperature, pressure));
+                                getTimestamp(event), temperature));
                         break;
                     case CarSensorManager.SENSOR_TYPE_WHEEL_TICK_DISTANCE:
                         if(event != null) {
diff --git a/tests/carservice_test/src/com/android/car/MockedCarTestBase.java b/tests/carservice_test/src/com/android/car/MockedCarTestBase.java
index a57d800..3a50b45 100644
--- a/tests/carservice_test/src/com/android/car/MockedCarTestBase.java
+++ b/tests/carservice_test/src/com/android/car/MockedCarTestBase.java
@@ -393,6 +393,8 @@
             switch (name) {
                 case BLUETOOTH_SERVICE:
                     return CarServiceTestApp.getAppContext().getSystemService(name);
+                case AUDIO_SERVICE:
+                    return CarServiceTestApp.getAppContext().getSystemService(name);
                 default:
                     return super.getSystemService(name);
             }
diff --git a/tests/carservice_test/src/com/android/car/GarageModeTest.java b/tests/carservice_test/src/com/android/car/garagemode/GarageModeServiceTest.java
similarity index 72%
rename from tests/carservice_test/src/com/android/car/GarageModeTest.java
rename to tests/carservice_test/src/com/android/car/garagemode/GarageModeServiceTest.java
index 23153fc..8a9a90d 100644
--- a/tests/carservice_test/src/com/android/car/GarageModeTest.java
+++ b/tests/carservice_test/src/com/android/car/garagemode/GarageModeServiceTest.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2016 The Android Open Source Project
+ * 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.
@@ -13,12 +13,10 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.car;
 
-import static org.junit.Assert.assertArrayEquals;
+package com.android.car.garagemode;
+
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 
 import android.car.settings.CarSettings;
@@ -31,16 +29,19 @@
 import android.support.test.filters.MediumTest;
 import android.support.test.runner.AndroidJUnit4;
 
-import com.android.car.GarageModeService.GarageModePolicy;
-import com.android.car.GarageModeService.WakeupTime;
+import com.android.car.CarPowerManagementService;
+import com.android.car.DeviceIdleControllerWrapper;
+import com.android.car.R;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.List;
+
 @RunWith(AndroidJUnit4.class)
 @MediumTest
-public class GarageModeTest {
-    private static final int WAIT_FOR_COMPLETION_TIME = 3000;//ms
+public class GarageModeServiceTest {
+    private static final int WAIT_FOR_COMPLETION_TIME_MS = 3000;
 
     @Test
     @UiThreadTest
@@ -93,7 +94,7 @@
         powerManagementService.doNotifyPrepareShutdown(false);
         assertTrue(garageMode.getGarageModeIndex() > 0);
         powerManagementService.doNotifyPowerOn(true);
-        assertEquals(0,garageMode.getGarageModeIndex());
+        assertEquals(0, garageMode.getGarageModeIndex());
     }
 
     @Test
@@ -111,11 +112,11 @@
                 powerManagementService,
                 controller,
                 thread.getLooper());
-        String[] policy = {
+        GarageModePolicy policy = new GarageModePolicy(new String[] {
                 "15m,1",
                 "6h,8",
                 "1d,5",
-        };
+        });
         SharedPreferences prefs =
                 getContext().getSharedPreferences("testPolicy", Context.MODE_PRIVATE);
         prefs.edit().putInt("garage_mode_index", 0).apply();
@@ -125,7 +126,7 @@
         garageMode.onPrepareShutdown(false);
         garageMode.onShutdown();
         assertEquals(6 * 60 * 60, garageMode.getWakeupTime());
-        Thread.sleep(WAIT_FOR_COMPLETION_TIME);
+        Thread.sleep(WAIT_FOR_COMPLETION_TIME_MS);
         assertEquals(1, prefs.getInt("garage_mode_index", 0));
 
         garageMode = new GarageModeServiceForTest(getContext(),
@@ -141,84 +142,78 @@
         garageMode.onPrepareShutdown(false);
         garageMode.onShutdown();
         assertEquals(24 * 60 * 60, garageMode.getWakeupTime());
-        Thread.sleep(WAIT_FOR_COMPLETION_TIME);
+        Thread.sleep(WAIT_FOR_COMPLETION_TIME_MS);
         assertEquals(9, prefs.getInt("garage_mode_index", 0));
     }
 
     @Test
     public void testPolicyParserValid() throws Exception {
-        WakeupTime expected[] = new WakeupTime[]{
-                new WakeupTime(15 * 60, 1),
-                new WakeupTime(6 * 60 * 60, 8),
-                new WakeupTime(24 * 60 * 60, 5),
+        WakeupInterval[] expected = new WakeupInterval[] {
+            new WakeupInterval(15 * 60, 1),
+            new WakeupInterval(6 * 60 * 60, 8),
+            new WakeupInterval(24 * 60 * 60, 5),
         };
-        WakeupTime received[] = new GarageModePolicy(new String[] {
+        List<WakeupInterval> received = new GarageModePolicy(new String[] {
                 "15m,1",
                 "6h,8",
                 "1d,5",
-        }).mWakeupTime;
+        }).getWakeupIntervals();
 
-        assertEquals(expected.length, received.length);
+        assertEquals(expected.length, received.size());
         for (int i = 0; i < expected.length; i++) {
-            assertEquals(expected[i].mWakeupTime, received[i].mWakeupTime);
-            assertEquals(expected[i].mNumAttempts, received[i].mNumAttempts);
+            assertEquals(expected[i].getWakeupInterval(), received.get(i).getWakeupInterval());
+            assertEquals(expected[i].getNumAttempts(), received.get(i).getNumAttempts());
         }
     }
 
-    @Test(expected=RuntimeException.class)
-    public void testPolicyParserNull() {
-        new GarageModePolicy(null);
-    }
-    @Test(expected=RuntimeException.class)
-    public void testPolicyParserEmptyArray() {
-        new GarageModePolicy(new String[] {});
-    }
-    @Test(expected=RuntimeException.class)
-    public void testPolicyParserEmptyString() {
-        new GarageModePolicy(new String[] {""});
-    }
-    @Test(expected=RuntimeException.class)
-    public void testPolicyParserMissingUnits() {
-        new GarageModePolicy(new String[] {"15,1"});
-    }
-    @Test(expected=RuntimeException.class)
-    public void testPolicyParserInvalidUnits() {
-        new GarageModePolicy(new String[] {"15y,1"});
-    }
-    @Test(expected=RuntimeException.class)
-    public void testPolicyParserNoCount() {
-        new GarageModePolicy(new String[] {"15m"});
-    }
-    @Test(expected=RuntimeException.class)
-    public void testPolicyParserBadCount() {
-        new GarageModePolicy(new String[] {"15m,Q"});
-    }
-    @Test(expected=RuntimeException.class)
-    public void testPolicyParserNegativeCount() {
-        new GarageModePolicy(new String[] {"15m,-1"});
-    }
-    @Test(expected=RuntimeException.class)
-    public void testPolicyParserNoTime() {
-        new GarageModePolicy(new String[] {",1"});
-    }
-    @Test(expected=RuntimeException.class)
-    public void testPolicyParserNoTimeValue() {
-        new GarageModePolicy(new String[] {"m,1"});
-    }
-    @Test(expected=RuntimeException.class)
-    public void testPolicyParserBadTime() {
-        new GarageModePolicy(new String[] {"Qm,1"});
-    }
-    @Test(expected=RuntimeException.class)
-    public void testPolicyParserNegativeTime() {
-        new GarageModePolicy(new String[] {"-10m,1"});
+    @Test
+    public void testPolicyParser() {
+        GarageModePolicy policy;
+
+        policy = new GarageModePolicy(null);
+        assertEquals(0, policy.getWakeupIntervals().size());
+
+        policy = new GarageModePolicy(new String[] {});
+        assertEquals(0, policy.getWakeupIntervals().size());
+
+        policy = new GarageModePolicy(new String[] {""});
+        assertEquals(0, policy.getWakeupIntervals().size());
+
+        policy = new GarageModePolicy(new String[] {"15,1"});
+        assertEquals(0, policy.getWakeupIntervals().size());
+
+        policy = new GarageModePolicy(new String[] {"15y,1"});
+        assertEquals(0, policy.getWakeupIntervals().size());
+
+        policy = new GarageModePolicy(new String[] {"15m"});
+        assertEquals(0, policy.getWakeupIntervals().size());
+
+        policy = new GarageModePolicy(new String[] {"15m,Q"});
+        assertEquals(0, policy.getWakeupIntervals().size());
+
+        policy = new GarageModePolicy(new String[] {"15m,-1"});
+        assertEquals(0, policy.getWakeupIntervals().size());
+
+        policy = new GarageModePolicy(new String[] {",1"});
+        assertEquals(0, policy.getWakeupIntervals().size());
+
+        policy = new GarageModePolicy(new String[] {"m,1"});
+        assertEquals(0, policy.getWakeupIntervals().size());
+
+        policy = new GarageModePolicy(new String[] {"Qm,1"});
+        assertEquals(0, policy.getWakeupIntervals().size());
+
+        policy = new GarageModePolicy(new String[] {"-10m,1"});
+        assertEquals(0, policy.getWakeupIntervals().size());
+
     }
 
     @Test
     public void testPolicyInResource() throws Exception {
         // Test that the policy in the resource file parses fine.
-        assertNotNull(new GarageModePolicy(getContext().getResources().getStringArray(
-                R.array.config_garageModeCadence)).mWakeupTime);
+        GarageModePolicy policy = new GarageModePolicy(getContext().getResources().getStringArray(
+                R.array.config_garageModeCadence));
+        assertTrue(policy.getWakeupIntervals().size() > 0);
     }
 
     private static class MockCarPowerManagementService extends CarPowerManagementService {
@@ -232,14 +227,14 @@
     }
 
     private static class GarageModeServiceForTest extends GarageModeService {
-        public GarageModeServiceForTest(Context context,
+        GarageModeServiceForTest(Context context,
                 CarPowerManagementService powerManagementService,
                 DeviceIdleControllerWrapper controllerWrapper,
                 Looper looper) {
             super(context, powerManagementService, controllerWrapper, looper);
         }
 
-        public GarageModeServiceForTest(Context context,
+        GarageModeServiceForTest(Context context,
                 CarPowerManagementService powerManagementService,
                 DeviceIdleControllerWrapper controllerWrapper) {
             super(context, powerManagementService, controllerWrapper, Looper.myLooper());
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 8ac7d9b..b071e99 100644
--- a/tests/carservice_unit_test/src/com/android/car/CarLocationServiceTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/CarLocationServiceTest.java
@@ -31,6 +31,7 @@
 import android.car.hardware.CarSensorManager;
 import android.car.hardware.property.CarPropertyEvent;
 import android.car.hardware.property.ICarPropertyEventListener;
+import android.car.user.CarUserManagerHelper;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
@@ -72,7 +73,9 @@
  * The following mocks are used:
  * 1. {@link Context} provides files and a mocked {@link LocationManager}.
  * 2. {@link LocationManager} provides dummy {@link Location}s.
- * 3. {@link CarSensorService} registers a handler for sensor events and sends ignition-off events.
+ * 3. {@link CarPropertyService} registers a listener for ignition state events.
+ * 3. {@link CarPowerManagementService} registers a handler for power events.
+ * 4. {@link CarUserManagerHelper} tells whether or not the system user is headless.
  */
 @RunWith(AndroidJUnit4.class)
 public class CarLocationServiceTest {
@@ -85,6 +88,7 @@
     @Mock private LocationManager mMockLocationManager;
     @Mock private CarPropertyService mMockCarPropertyService;
     @Mock private CarPowerManagementService mMockCarPowerManagementService;
+    @Mock private CarUserManagerHelper mMockCarUserManagerHelper;
 
     /**
      * Initialize all of the objects with the @Mock annotation.
@@ -95,7 +99,7 @@
         mContext = InstrumentationRegistry.getTargetContext();
         mLatch = new CountDownLatch(1);
         mCarLocationService = new CarLocationService(mMockContext, mMockCarPowerManagementService,
-                mMockCarPropertyService) {
+                mMockCarPropertyService, mMockCarUserManagerHelper) {
             @Override
             void asyncOperation(Runnable operation) {
                 super.asyncOperation(() -> {
@@ -143,12 +147,13 @@
                 mCarLocationService);
         verify(mMockContext).registerReceiver(eq(mCarLocationService), argument.capture());
         IntentFilter intentFilter = argument.getValue();
-        assertEquals(3, intentFilter.countActions());
+        assertEquals(4, intentFilter.countActions());
         String[] actions = {intentFilter.getAction(0), intentFilter.getAction(1),
-                intentFilter.getAction(2)};
+                intentFilter.getAction(2), intentFilter.getAction(3)};
         assertTrue(ArrayUtils.contains(actions, Intent.ACTION_LOCKED_BOOT_COMPLETED));
         assertTrue(ArrayUtils.contains(actions, LocationManager.MODE_CHANGED_ACTION));
         assertTrue(ArrayUtils.contains(actions, LocationManager.GPS_ENABLED_CHANGE_ACTION));
+        assertTrue(ArrayUtils.contains(actions, Intent.ACTION_USER_SWITCHED));
         verify(mMockCarPropertyService).registerListener(
                 eq(CarSensorManager.SENSOR_TYPE_IGNITION_STATE), eq(0.0f), any());
     }
@@ -166,10 +171,11 @@
 
     /**
      * Test that the {@link CarLocationService} parses a location from a JSON serialization and then
-     * injects it into the {@link LocationManager} upon boot complete.
+     * injects it into the {@link LocationManager} upon boot complete if the system user is not
+     * headless.
      */
     @Test
-    public void testLoadsLocation() throws IOException, InterruptedException {
+    public void testLoadsLocationOnLockedBootComplete() throws IOException, InterruptedException {
         long currentTime = System.currentTimeMillis();
         long elapsedTime = SystemClock.elapsedRealtimeNanos();
         long pastTime = currentTime - 60000;
@@ -181,6 +187,7 @@
         when(mMockLocationManager.injectLocation(argument.capture())).thenReturn(true);
         when(mMockContext.getFileStreamPath("location_cache.json"))
                 .thenReturn(mContext.getFileStreamPath(TEST_FILENAME));
+        when(mMockCarUserManagerHelper.isHeadlessSystemUser()).thenReturn(false);
 
         mCarLocationService.onReceive(mMockContext,
                 new Intent(Intent.ACTION_LOCKED_BOOT_COMPLETED));
@@ -196,6 +203,39 @@
     }
 
     /**
+     * Test that the {@link CarLocationService} parses a location from a JSON seialization and then
+     * injects it into the {@link LocationManager} upon user switch if the system user is headless.
+     */
+    @Test
+    public void testLoadsLocationWithHeadlessSystemUser() throws IOException, InterruptedException {
+        long currentTime = System.currentTimeMillis();
+        long elapsedTime = SystemClock.elapsedRealtimeNanos();
+        long pastTime = currentTime - 60000;
+        writeCacheFile("{\"provider\": \"gps\", \"latitude\": 16.7666, \"longitude\": 3.0026,"
+                + "\"accuracy\":12.3, \"captureTime\": " + pastTime + "}");
+        ArgumentCaptor<Location> argument = ArgumentCaptor.forClass(Location.class);
+        when(mMockContext.getSystemService(Context.LOCATION_SERVICE))
+                .thenReturn(mMockLocationManager);
+        when(mMockLocationManager.injectLocation(argument.capture())).thenReturn(true);
+        when(mMockContext.getFileStreamPath("location_cache.json"))
+                .thenReturn(mContext.getFileStreamPath(TEST_FILENAME));
+        when(mMockCarUserManagerHelper.isHeadlessSystemUser()).thenReturn(true);
+
+        Intent userSwitchedIntent = new Intent(Intent.ACTION_USER_SWITCHED);
+        userSwitchedIntent.putExtra(Intent.EXTRA_USER_HANDLE, 11);
+        mCarLocationService.onReceive(mMockContext, userSwitchedIntent);
+        mLatch.await();
+
+        Location location = argument.getValue();
+        assertEquals("gps", location.getProvider());
+        assertEquals(16.7666, location.getLatitude());
+        assertEquals(3.0026, location.getLongitude());
+        assertEquals(12.3f, location.getAccuracy());
+        assertTrue(location.getTime() >= currentTime);
+        assertTrue(location.getElapsedRealtimeNanos() >= elapsedTime);
+    }
+
+    /**
      * Test that the {@link CarLocationService} does not inject a location if there is no location
      * cache file.
      */
diff --git a/tests/carservice_unit_test/src/com/android/car/CarUserManagerHelperTest.java b/tests/carservice_unit_test/src/com/android/car/CarUserManagerHelperTest.java
index 44cc0d6..7f53da5 100644
--- a/tests/carservice_unit_test/src/com/android/car/CarUserManagerHelperTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/CarUserManagerHelperTest.java
@@ -18,8 +18,12 @@
 
 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.car.user.CarUserManagerHelper;
@@ -30,11 +34,13 @@
 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.SystemProperties;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
 
 import org.junit.Before;
@@ -42,6 +48,7 @@
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
+import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 
 import java.util.ArrayList;
@@ -54,10 +61,11 @@
  * The following mocks are used:
  * 1. {@link Context} provides system services and resources.
  * 2. {@link UserManager} provides dummy users and user info.
- * 3. {@link ActivityManager} provides dummy current process user.
+ * 3. {@link ActivityManager} to verify user switch is invoked.
  * 4. {@link CarUserManagerHelper.OnUsersUpdateListener} registers a listener for user updates.
  */
 @RunWith(AndroidJUnit4.class)
+@SmallTest
 public class CarUserManagerHelperTest {
     @Mock
     private Context mContext;
@@ -68,25 +76,37 @@
     @Mock
     private CarUserManagerHelper.OnUsersUpdateListener mTestListener;
 
-    private CarUserManagerHelper mHelper;
+    private CarUserManagerHelper mCarUserManagerHelper;
     private UserInfo mCurrentProcessUser;
     private UserInfo mSystemUser;
     private String mGuestUserName = "testGuest";
     private String mTestUserName = "testUser";
+    private int mForegroundUserId;
+    private UserInfo mForegroundUser;
 
     @Before
     public void setUpMocksAndVariables() throws Exception {
         MockitoAnnotations.initMocks(this);
-        when(mContext.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager);
-        when(mContext.getSystemService(Context.ACTIVITY_SERVICE)).thenReturn(mActivityManager);
-        when(mContext.getResources())
-            .thenReturn(InstrumentationRegistry.getTargetContext().getResources());
-        when(mContext.getApplicationContext()).thenReturn(mContext);
-        mHelper = new CarUserManagerHelper(mContext);
+        doReturn(mUserManager).when(mContext).getSystemService(Context.USER_SERVICE);
+        doReturn(mActivityManager).when(mContext).getSystemService(Context.ACTIVITY_SERVICE);
+        doReturn(InstrumentationRegistry.getTargetContext().getResources())
+                .when(mContext).getResources();
+        doReturn(mContext).when(mContext).getApplicationContext();
+        mCarUserManagerHelper = new CarUserManagerHelper(mContext);
 
         mCurrentProcessUser = createUserInfoForId(UserHandle.myUserId());
         mSystemUser = createUserInfoForId(UserHandle.USER_SYSTEM);
-        when(mUserManager.getUserInfo(UserHandle.myUserId())).thenReturn(mCurrentProcessUser);
+        doReturn(mCurrentProcessUser).when(mUserManager).getUserInfo(UserHandle.myUserId());
+
+        // Get the ID of the foreground user running this test.
+        // 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);
+
+        // Restore the non-headless state before every test. Individual tests can set the property
+        // to true to test the headless system user scenario.
+        SystemProperties.set("android.car.systemuser.headless", "false");
     }
 
     @Test
@@ -94,10 +114,10 @@
         UserInfo testInfo = new UserInfo();
 
         testInfo.id = UserHandle.USER_SYSTEM;
-        assertThat(mHelper.isSystemUser(testInfo)).isTrue();
+        assertThat(mCarUserManagerHelper.isSystemUser(testInfo)).isTrue();
 
         testInfo.id = UserHandle.USER_SYSTEM + 2; // Make it different than system id.
-        assertThat(mHelper.isSystemUser(testInfo)).isFalse();
+        assertThat(mCarUserManagerHelper.isSystemUser(testInfo)).isFalse();
     }
 
     // System user will not be returned when calling get all users.
@@ -108,92 +128,67 @@
         UserInfo otherUser2 = createUserInfoForId(11);
         UserInfo otherUser3 = createUserInfoForId(12);
 
-        List<UserInfo> testUsers = new ArrayList<>();
-        testUsers.add(mSystemUser);
-        testUsers.add(otherUser1);
-        testUsers.add(otherUser2);
-        testUsers.add(otherUser3);
+        mockGetUsers(mSystemUser, otherUser1, otherUser2, otherUser3);
 
-        when(mUserManager.getUsers(true)).thenReturn(testUsers);
-
-        // Should return 3 users that don't have SYSTEM USER id.
-        assertThat(mHelper.getAllUsers()).hasSize(3);
-        assertThat(mHelper.getAllUsers())
-            .containsExactly(otherUser1, otherUser2, otherUser3);
-    }
-
-    @Test
-    public void testHeadlessUser0GetAllUsersWithActiveForegroundUser_NotReturnSystemUser() {
-        SystemProperties.set("android.car.systemuser.headless", "true");
-        mCurrentProcessUser = createUserInfoForId(10);
-
-        UserInfo otherUser1 = createUserInfoForId(11);
-        UserInfo otherUser2 = createUserInfoForId(12);
-        UserInfo otherUser3 = createUserInfoForId(13);
-
-        List<UserInfo> testUsers = new ArrayList<>();
-        testUsers.add(mSystemUser);
-        testUsers.add(mCurrentProcessUser);
-        testUsers.add(otherUser1);
-        testUsers.add(otherUser2);
-        testUsers.add(otherUser3);
-
-        when(mUserManager.getUsers(true)).thenReturn(testUsers);
-
-        assertThat(mHelper.getAllUsers().size()).isEqualTo(4);
-        assertThat(mHelper.getAllUsers())
-            .containsExactly(mCurrentProcessUser, otherUser1, otherUser2, otherUser3);
+        assertThat(mCarUserManagerHelper.getAllUsers())
+                .containsExactly(otherUser1, otherUser2, otherUser3);
     }
 
     @Test
     public void testGetAllSwitchableUsers() {
-        UserInfo user1 = createUserInfoForId(10);
-        UserInfo user2 = createUserInfoForId(11);
-        UserInfo user3 = createUserInfoForId(12);
+        // Create two non-foreground users.
+        UserInfo user1 = createUserInfoForId(mForegroundUserId + 1);
+        UserInfo user2 = createUserInfoForId(mForegroundUserId + 2);
 
-        List<UserInfo> testUsers = new ArrayList<>();
-        testUsers.add(mSystemUser);
-        testUsers.add(user1);
-        testUsers.add(user2);
-        testUsers.add(user3);
+        mockGetUsers(mForegroundUser, user1, user2);
 
-        when(mUserManager.getUsers(true)).thenReturn(new ArrayList<>(testUsers));
-
-        // Should return all 3 non-system users.
-        assertThat(mHelper.getAllUsers().size())
-                .isEqualTo(3);
-
-        when(mUserManager.getUserInfo(UserHandle.myUserId())).thenReturn(user1);
-        // Should return user 10, 11 and 12.
-        assertThat(mHelper.getAllSwitchableUsers().size())
-                .isEqualTo(3);
-        assertThat(mHelper.getAllSwitchableUsers()).contains(user1);
-        assertThat(mHelper.getAllSwitchableUsers()).contains(user2);
-        assertThat(mHelper.getAllSwitchableUsers()).contains(user3);
+        // Should return all non-foreground users.
+        assertThat(mCarUserManagerHelper.getAllSwitchableUsers()).containsExactly(user1, user2);
     }
 
-    // Get all users for headless user 0 model should exclude system user by default.
     @Test
-    public void testHeadlessUser0GetAllSwitchableUsers() {
-        SystemProperties.set("android.car.systemuser.headless", "true");
-        UserInfo user1 = createUserInfoForId(10);
+    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);
 
-        List<UserInfo> testUsers = new ArrayList<>();
-        testUsers.add(mSystemUser);
-        testUsers.add(user1);
-        testUsers.add(user2);
-        testUsers.add(user3);
+        mockGetUsers(user1, user2, user3, user4);
 
-        when(mUserManager.getUsers(true)).thenReturn(new ArrayList<>(testUsers));
+        // Should return only admin users.
+        assertThat(mCarUserManagerHelper.getAllAdminUsers()).containsExactly(user1, user4);
+    }
 
-        // Should return all 3 non-system users.
-        assertThat(mHelper.getAllUsers()).hasSize(3);
+    @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);
 
-        when(mUserManager.getUserInfo(UserHandle.myUserId())).thenReturn(user1);
-        // Should return user 10, 11 and 12.
-        assertThat(mHelper.getAllSwitchableUsers()).containsExactly(user1, user2, user3);
+        mockGetUsers(user1, user2, user3);
+
+        // Should not return guests.
+        assertThat(mCarUserManagerHelper.getAllUsersExceptGuests())
+                .containsExactly(user1, user2);
     }
 
     @Test
@@ -202,127 +197,304 @@
 
         // System user cannot be removed.
         testInfo.id = UserHandle.USER_SYSTEM;
-        assertThat(mHelper.canUserBeRemoved(testInfo)).isFalse();
+        assertThat(mCarUserManagerHelper.canUserBeRemoved(testInfo)).isFalse();
 
         testInfo.id = UserHandle.USER_SYSTEM + 2; // Make it different than system id.
-        assertThat(mHelper.canUserBeRemoved(testInfo)).isTrue();
+        assertThat(mCarUserManagerHelper.canUserBeRemoved(testInfo)).isTrue();
     }
 
     @Test
     public void testCurrentProcessCanAddUsers() {
-        when(mUserManager.hasUserRestriction(UserManager.DISALLOW_ADD_USER)).thenReturn(false);
-        assertThat(mHelper.canCurrentProcessAddUsers()).isTrue();
+        doReturn(false).when(mUserManager)
+            .hasUserRestriction(UserManager.DISALLOW_ADD_USER);
+        assertThat(mCarUserManagerHelper.canCurrentProcessAddUsers()).isTrue();
 
-        when(mUserManager.hasUserRestriction(UserManager.DISALLOW_ADD_USER)).thenReturn(true);
-        assertThat(mHelper.canCurrentProcessAddUsers()).isFalse();
+        doReturn(true).when(mUserManager)
+            .hasUserRestriction(UserManager.DISALLOW_ADD_USER);
+        assertThat(mCarUserManagerHelper.canCurrentProcessAddUsers()).isFalse();
     }
 
     @Test
     public void testCurrentProcessCanRemoveUsers() {
-        when(mUserManager.hasUserRestriction(UserManager.DISALLOW_REMOVE_USER)).thenReturn(false);
-        assertThat(mHelper.canCurrentProcessRemoveUsers()).isTrue();
+        doReturn(false).when(mUserManager)
+            .hasUserRestriction(UserManager.DISALLOW_REMOVE_USER);
+        assertThat(mCarUserManagerHelper.canCurrentProcessRemoveUsers()).isTrue();
 
-        when(mUserManager.hasUserRestriction(UserManager.DISALLOW_REMOVE_USER)).thenReturn(true);
-        assertThat(mHelper.canCurrentProcessRemoveUsers()).isFalse();
+        doReturn(true).when(mUserManager)
+            .hasUserRestriction(UserManager.DISALLOW_REMOVE_USER);
+        assertThat(mCarUserManagerHelper.canCurrentProcessRemoveUsers()).isFalse();
     }
 
     @Test
     public void testCurrentProcessCanSwitchUsers() {
-        when(mUserManager.hasUserRestriction(UserManager.DISALLOW_USER_SWITCH)).thenReturn(false);
-        assertThat(mHelper.canCurrentProcessSwitchUsers()).isTrue();
+        doReturn(false).when(mUserManager)
+            .hasUserRestriction(UserManager.DISALLOW_USER_SWITCH);
+        assertThat(mCarUserManagerHelper.canCurrentProcessSwitchUsers()).isTrue();
 
-        when(mUserManager.hasUserRestriction(UserManager.DISALLOW_USER_SWITCH)).thenReturn(true);
-        assertThat(mHelper.canCurrentProcessSwitchUsers()).isFalse();
+        doReturn(true).when(mUserManager)
+            .hasUserRestriction(UserManager.DISALLOW_USER_SWITCH);
+        assertThat(mCarUserManagerHelper.canCurrentProcessSwitchUsers()).isFalse();
     }
 
     @Test
     public void testCurrentGuestProcessCannotModifyAccounts() {
-        assertThat(mHelper.canCurrentProcessModifyAccounts()).isTrue();
+        assertThat(mCarUserManagerHelper.canCurrentProcessModifyAccounts()).isTrue();
 
-        when(mUserManager.isGuestUser()).thenReturn(true);
-        assertThat(mHelper.canCurrentProcessModifyAccounts()).isFalse();
+        doReturn(true).when(mUserManager).isGuestUser();
+
+        assertThat(mCarUserManagerHelper.canCurrentProcessModifyAccounts()).isFalse();
     }
 
     @Test
     public void testCurrentDemoProcessCannotModifyAccounts() {
-        assertThat(mHelper.canCurrentProcessModifyAccounts()).isTrue();
+        assertThat(mCarUserManagerHelper.canCurrentProcessModifyAccounts()).isTrue();
 
-        when(mUserManager.isDemoUser()).thenReturn(true);
-        assertThat(mHelper.canCurrentProcessModifyAccounts()).isFalse();
+        doReturn(true).when(mUserManager).isDemoUser();
+
+        assertThat(mCarUserManagerHelper.canCurrentProcessModifyAccounts()).isFalse();
     }
 
     @Test
     public void testCurrentDisallowModifyAccountsProcessIsEnforced() {
-        assertThat(mHelper.canCurrentProcessModifyAccounts()).isTrue();
+        assertThat(mCarUserManagerHelper.canCurrentProcessModifyAccounts()).isTrue();
 
-        when(mUserManager.hasUserRestriction(UserManager.DISALLOW_MODIFY_ACCOUNTS))
-            .thenReturn(true);
-        assertThat(mHelper.canCurrentProcessModifyAccounts()).isFalse();
+        doReturn(true).when(mUserManager)
+                .hasUserRestriction(UserManager.DISALLOW_MODIFY_ACCOUNTS);
+
+        assertThat(mCarUserManagerHelper.canCurrentProcessModifyAccounts()).isFalse();
+    }
+
+    @Test
+    public void testGetMaxSupportedUsers() {
+        SystemProperties.set("fw.max_users", "11");
+
+        assertThat(mCarUserManagerHelper.getMaxSupportedUsers()).isEqualTo(11);
+
+        // In headless user 0 model, we want to exclude the system user.
+        SystemProperties.set("android.car.systemuser.headless", "true");
+        assertThat(mCarUserManagerHelper.getMaxSupportedUsers()).isEqualTo(10);
+    }
+
+    @Test
+    public void testGetMaxSupportedRealUsers() {
+        SystemProperties.set("fw.max_users", "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.
+        assertThat(mCarUserManagerHelper.getMaxSupportedRealUsers()).isEqualTo(4);
+    }
+
+    @Test
+    public void testIsUserLimitReached() {
+        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(user1, user2, user3, user4);
+
+        SystemProperties.set("fw.max_users", "5");
+        assertThat(mCarUserManagerHelper.isUserLimitReached()).isFalse();
+
+        SystemProperties.set("fw.max_users", "4");
+        assertThat(mCarUserManagerHelper.isUserLimitReached()).isTrue();
+    }
+
+    @Test
+    public void testHeadlessSystemUser_IsUserLimitReached() {
+        SystemProperties.set("android.car.systemuser.headless", "true");
+        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);
+
+        SystemProperties.set("fw.max_users", "6");
+        assertThat(mCarUserManagerHelper.isUserLimitReached()).isFalse();
+
+        SystemProperties.set("fw.max_users", "5");
+        assertThat(mCarUserManagerHelper.isUserLimitReached()).isTrue();
+    }
+
+    @Test
+    public void testIsUserLimitReachedIgnoresGuests() {
+        SystemProperties.set("fw.max_users", "5");
+
+        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 testCreateNewAdminUser() {
+        // Make sure current user is admin, since only admins can create other admins.
+        doReturn(true).when(mUserManager).isAdminUser();
+
         // Verify createUser on UserManager gets called.
-        mHelper.createNewAdminUser(mTestUserName);
+        mCarUserManagerHelper.createNewAdminUser(mTestUserName);
         verify(mUserManager).createUser(mTestUserName, UserInfo.FLAG_ADMIN);
 
-        when(mUserManager.createUser(mTestUserName, UserInfo.FLAG_ADMIN)).thenReturn(null);
-        assertThat(mHelper.createNewAdminUser(mTestUserName)).isNull();
+        doReturn(null).when(mUserManager).createUser(mTestUserName, UserInfo.FLAG_ADMIN);
+        assertThat(mCarUserManagerHelper.createNewAdminUser(mTestUserName)).isNull();
 
         UserInfo newUser = new UserInfo();
         newUser.name = mTestUserName;
-        when(mUserManager.createUser(mTestUserName, UserInfo.FLAG_ADMIN)).thenReturn(newUser);
-        assertThat(mHelper.createNewAdminUser(mTestUserName)).isEqualTo(newUser);
+        doReturn(newUser).when(mUserManager).createUser(mTestUserName, UserInfo.FLAG_ADMIN);
+        assertThat(mCarUserManagerHelper.createNewAdminUser(mTestUserName)).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.
-        mHelper.createNewNonAdminUser(mTestUserName);
+        mCarUserManagerHelper.createNewNonAdminUser(mTestUserName);
         verify(mUserManager).createUser(mTestUserName, 0);
 
-        when(mUserManager.createUser(mTestUserName, 0)).thenReturn(null);
-        assertThat(mHelper.createNewNonAdminUser(mTestUserName)).isNull();
+        doReturn(null).when(mUserManager).createUser(mTestUserName, 0);
+        assertThat(mCarUserManagerHelper.createNewNonAdminUser(mTestUserName)).isNull();
 
         UserInfo newUser = new UserInfo();
         newUser.name = mTestUserName;
-        when(mUserManager.createUser(mTestUserName, 0)).thenReturn(newUser);
-        assertThat(mHelper.createNewNonAdminUser(mTestUserName)).isEqualTo(newUser);
+        doReturn(newUser).when(mUserManager).createUser(mTestUserName, 0);
+        assertThat(mCarUserManagerHelper.createNewNonAdminUser(mTestUserName)).isEqualTo(newUser);
     }
 
     @Test
-    public void testRemoveUser() {
+    public void testCannotRemoveSystemUser() {
+        assertThat(mCarUserManagerHelper.removeUser(mSystemUser, mGuestUserName)).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, mGuestUserName)).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, mGuestUserName)).isFalse();
+        verify(mUserManager, never()).removeUser(otherUser.id);
+    }
+
+    @Test
+    public void testRemoveLastActiveUser() {
         // Cannot remove system user.
-        assertThat(mHelper.removeUser(mSystemUser, mGuestUserName)).isFalse();
+        assertThat(mCarUserManagerHelper.removeUser(mSystemUser, mGuestUserName)).isFalse();
 
-        // Removing non-current, non-system user, simply calls removeUser.
-        UserInfo userToRemove = createUserInfoForId(mCurrentProcessUser.id + 2);
+        UserInfo adminInfo = new UserInfo(/* id= */10, "admin", UserInfo.FLAG_ADMIN);
+        mockGetUsers(adminInfo);
 
-        mHelper.removeUser(userToRemove, mGuestUserName);
-        verify(mUserManager).removeUser(mCurrentProcessUser.id + 2);
+        assertThat(mCarUserManagerHelper.removeUser(adminInfo, mGuestUserName))
+            .isEqualTo(false);
     }
 
     @Test
     public void testSwitchToGuest() {
-        mHelper.startNewGuestSession(mGuestUserName);
+        mCarUserManagerHelper.startNewGuestSession(mGuestUserName);
         verify(mUserManager).createGuest(mContext, mGuestUserName);
 
-        UserInfo guestInfo = new UserInfo(21, mGuestUserName, UserInfo.FLAG_GUEST);
-        when(mUserManager.createGuest(mContext, mGuestUserName)).thenReturn(guestInfo);
-        mHelper.startNewGuestSession(mGuestUserName);
+        UserInfo guestInfo = new UserInfo(/* id= */21, mGuestUserName, UserInfo.FLAG_GUEST);
+        doReturn(guestInfo).when(mUserManager).createGuest(mContext, mGuestUserName);
+        mCarUserManagerHelper.startNewGuestSession(mGuestUserName);
         verify(mActivityManager).switchUser(21);
     }
 
     @Test
     public void testGetUserIcon() {
-        mHelper.getUserIcon(mCurrentProcessUser);
+        mCarUserManagerHelper.getUserIcon(mCurrentProcessUser);
         verify(mUserManager).getUserIcon(mCurrentProcessUser.id);
     }
 
     @Test
     public void testScaleUserIcon() {
         Bitmap fakeIcon = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);
-        Drawable scaledIcon = mHelper.scaleUserIcon(fakeIcon, 300);
+        Drawable scaledIcon = mCarUserManagerHelper.scaleUserIcon(fakeIcon, 300);
         assertThat(scaledIcon.getIntrinsicWidth()).isEqualTo(300);
         assertThat(scaledIcon.getIntrinsicHeight()).isEqualTo(300);
     }
@@ -331,13 +503,107 @@
     public void testSetUserName() {
         UserInfo testInfo = createUserInfoForId(mCurrentProcessUser.id + 3);
         String newName = "New Test Name";
-        mHelper.setUserName(testInfo, newName);
+        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 testAssignAdminPrivileges() {
+        int userId = 30;
+        UserInfo testInfo = createUserInfoForId(userId);
+
+        // Test that non-admins cannot assign admin privileges.
+        doReturn(false).when(mUserManager).isAdminUser(); // Current user non-admin.
+        mCarUserManagerHelper.assignAdminPrivileges(testInfo);
+        verify(mUserManager, never()).setUserAdmin(userId);
+
+        // Admins can assign admin privileges.
+        doReturn(true).when(mUserManager).isAdminUser();
+        mCarUserManagerHelper.assignAdminPrivileges(testInfo);
+        verify(mUserManager).setUserAdmin(userId);
+    }
+
+    @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;
+        UserInfo newNonAdmin = createUserInfoForId(userId);
+
+        doReturn(newNonAdmin).when(mUserManager).createUser(testUserName, /* flags= */ 0);
+
+        mCarUserManagerHelper.createNewNonAdminUser(testUserName);
+
+        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 = 7;
+
+        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_OUTGOING_CALLS)).isTrue();
+        assertThat(guestRestrictions.getBoolean(UserManager.DISALLOW_SMS)).isTrue();
+        assertThat(guestRestrictions.getBoolean(UserManager.DISALLOW_INSTALL_APPS)).isTrue();
+        assertThat(guestRestrictions.getBoolean(UserManager.DISALLOW_UNINSTALL_APPS)).isTrue();
+    }
+
+    @Test
+    public void testAssigningAdminPrivilegesRemovesNonAdminRestrictions() {
+        int testUserId = 30;
+        boolean restrictionEnabled = false;
+        UserInfo testInfo = createUserInfoForId(testUserId);
+
+        // Only admins can assign privileges.
+        doReturn(true).when(mUserManager).isAdminUser();
+
+        mCarUserManagerHelper.assignAdminPrivileges(testInfo);
+
+        verify(mUserManager).setUserRestriction(
+                UserManager.DISALLOW_FACTORY_RESET, restrictionEnabled, UserHandle.of(testUserId));
+    }
+
+    @Test
     public void testRegisterUserChangeReceiver() {
-        mHelper.registerOnUsersUpdateListener(mTestListener);
+        mCarUserManagerHelper.registerOnUsersUpdateListener(mTestListener);
 
         ArgumentCaptor<BroadcastReceiver> receiverCaptor =
                 ArgumentCaptor.forClass(BroadcastReceiver.class);
@@ -375,13 +641,148 @@
         assertThat(handlerCaptor.getValue()).isNull();
 
         // Unregister the receiver.
-        mHelper.unregisterOnUsersUpdateListener();
+        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 testGetInitialUserWithValidLastActiveUser() {
+        SystemProperties.set("android.car.systemuser.headless", "true");
+        int lastActiveUserId = 12;
+
+        UserInfo otherUser1 = createUserInfoForId(lastActiveUserId - 2);
+        UserInfo otherUser2 = createUserInfoForId(lastActiveUserId - 1);
+        UserInfo otherUser3 = createUserInfoForId(lastActiveUserId);
+
+        mCarUserManagerHelper.setLastActiveUser(
+                lastActiveUserId, /* skipGlobalSettings= */ true);
+        mockGetUsers(mSystemUser, otherUser1, otherUser2, otherUser3);
+
+        assertThat(mCarUserManagerHelper.getInitialUser()).isEqualTo(lastActiveUserId);
+    }
+
+    @Test
+    public void testGetInitialUserWithNonExistLastActiveUser() {
+        SystemProperties.set("android.car.systemuser.headless", "true");
+        int lastActiveUserId = 12;
+
+        UserInfo otherUser1 = createUserInfoForId(lastActiveUserId - 2);
+        UserInfo otherUser2 = createUserInfoForId(lastActiveUserId - 1);
+
+        mCarUserManagerHelper.setLastActiveUser(
+                lastActiveUserId, /* skipGlobalSettings= */ true);
+        mockGetUsers(mSystemUser, otherUser1, otherUser2);
+
+        assertThat(mCarUserManagerHelper.getInitialUser()).isEqualTo(lastActiveUserId - 2);
+    }
+
     private UserInfo createUserInfoForId(int id) {
         UserInfo userInfo = new UserInfo();
         userInfo.id = id;
         return userInfo;
     }
+
+    private void mockGetUsers(UserInfo... users) {
+        List<UserInfo> testUsers = new ArrayList<>();
+        for (UserInfo user: users) {
+            testUsers.add(user);
+        }
+        doReturn(testUsers).when(mUserManager).getUsers(true);
+    }
 }
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 faa7bd0..48d447b 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
@@ -21,18 +21,17 @@
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
 
 import android.car.user.CarUserManagerHelper;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.UserInfo;
+import android.location.LocationManager;
+import android.os.UserHandle;
+import android.os.UserManager;
 import android.support.test.runner.AndroidJUnit4;
 
-import java.util.ArrayList;
-import java.util.List;
-
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -40,6 +39,9 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import java.util.ArrayList;
+import java.util.List;
+
 /**
  * This class contains unit tests for the {@link CarUserService}.
  *
@@ -60,17 +62,23 @@
     private Context mApplicationContext;
 
     @Mock
+    private LocationManager mLocationManager;
+
+    @Mock
     private CarUserManagerHelper mCarUserManagerHelper;
 
     /**
      * Initialize all of the objects with the @Mock annotation.
      */
     @Before
-    public void setUp() throws Exception {
+    public void setUpMocks() throws Exception {
         MockitoAnnotations.initMocks(this);
-        when(mMockContext.getApplicationContext()).thenReturn(mApplicationContext);
+        doReturn(mApplicationContext).when(mMockContext).getApplicationContext();
+        doReturn(mLocationManager).when(mMockContext).getSystemService(Context.LOCATION_SERVICE);
 
         mCarUserService = new CarUserService(mMockContext, mCarUserManagerHelper);
+
+        doReturn(new ArrayList<>()).when(mCarUserManagerHelper).getAllUsers();
     }
 
     /**
@@ -83,9 +91,10 @@
         mCarUserService.init();
         verify(mMockContext).registerReceiver(eq(mCarUserService), argument.capture());
         IntentFilter intentFilter = argument.getValue();
-        assertThat(intentFilter.countActions()).isEqualTo(1);
+        assertThat(intentFilter.countActions()).isEqualTo(2);
 
         assertThat(intentFilter.getAction(0)).isEqualTo(Intent.ACTION_LOCKED_BOOT_COMPLETED);
+        assertThat(intentFilter.getAction(1)).isEqualTo(Intent.ACTION_USER_SWITCHED);
     }
 
     /**
@@ -102,15 +111,7 @@
      */
     @Test
     public void testStartsSecondaryAdminUserOnFirstRun() {
-        List<UserInfo> users = new ArrayList<>();
-
-        int adminUserId = 10;
-        UserInfo admin = new UserInfo(adminUserId, CarUserService.OWNER_NAME, UserInfo.FLAG_ADMIN);
-
-        doReturn(users).when(mCarUserManagerHelper).getAllUsers();
-        // doReturn(users).when(mCarUserManagerHelper.getAllUsers());
-        doReturn(admin).when(mCarUserManagerHelper).createNewAdminUser(CarUserService.OWNER_NAME);
-        doReturn(true).when(mCarUserManagerHelper).switchToUser(admin);
+        UserInfo admin = mockAdmin(/* adminId= */ 10);
 
         mCarUserService.onReceive(mMockContext,
                 new Intent(Intent.ACTION_LOCKED_BOOT_COMPLETED));
@@ -118,4 +119,115 @@
         verify(mCarUserManagerHelper).createNewAdminUser(CarUserService.OWNER_NAME);
         verify(mCarUserManagerHelper).switchToUser(admin);
     }
+
+    /**
+     * Test that the {@link CarUserService} disable modify account for user 0 upon first run.
+     */
+    @Test
+    public void testDisableModifyAccountsForSystemUserOnFirstRun() {
+        // Mock system user.
+        UserInfo systemUser = new UserInfo();
+        systemUser.id = UserHandle.USER_SYSTEM;
+        doReturn(systemUser).when(mCarUserManagerHelper).getSystemUserInfo();
+
+        mockAdmin(10);
+
+        mCarUserService.onReceive(mMockContext,
+                new Intent(Intent.ACTION_LOCKED_BOOT_COMPLETED));
+
+        verify(mCarUserManagerHelper)
+                .setUserRestriction(systemUser, UserManager.DISALLOW_MODIFY_ACCOUNTS, true);
+    }
+
+    /**
+     * Test that the {@link CarUserService} disable location service for user 0 upon first run.
+     */
+    @Test
+    public void testDisableLocationForSystemUserOnFirstRun() {
+        mockAdmin(/* adminId= */ 10);
+
+        mCarUserService.onReceive(mMockContext,
+                new Intent(Intent.ACTION_LOCKED_BOOT_COMPLETED));
+
+        verify(mLocationManager).setLocationEnabledForUser(
+                /* enabled= */ false, UserHandle.of(UserHandle.USER_SYSTEM));
+    }
+
+    /**
+     * Test that the {@link CarUserService} updates last active user to the first admin user
+     * on first run.
+     */
+    @Test
+    public void testUpdateLastActiveUserOnFirstRun() {
+        UserInfo admin = mockAdmin(/* adminId= */ 10);
+
+        mCarUserService.onReceive(mMockContext,
+                new Intent(Intent.ACTION_LOCKED_BOOT_COMPLETED));
+
+        verify(mCarUserManagerHelper)
+                .setLastActiveUser(admin.id, /* skipGlobalSetting= */ false);
+    }
+
+    /**
+     * Test that the {@link CarUserService} starts up the last active user on reboot.
+     */
+    @Test
+    public void testStartsLastActiveUserOnReboot() {
+        List<UserInfo> users = new ArrayList<>();
+
+        int adminUserId = 10;
+        UserInfo admin = new UserInfo(adminUserId, CarUserService.OWNER_NAME, UserInfo.FLAG_ADMIN);
+
+        int secUserId = 11;
+        UserInfo secUser =
+                new UserInfo(secUserId, CarUserService.OWNER_NAME, UserInfo.FLAG_ADMIN);
+
+        users.add(admin);
+        users.add(secUser);
+
+        doReturn(users).when(mCarUserManagerHelper).getAllUsers();
+        doReturn(secUserId).when(mCarUserManagerHelper).getInitialUser();
+
+        mCarUserService.onReceive(mMockContext,
+                new Intent(Intent.ACTION_LOCKED_BOOT_COMPLETED));
+
+        verify(mCarUserManagerHelper).switchToUserId(secUserId);
+    }
+
+    /**
+     * Test that the {@link CarUserService} updates last active user on user switch intent.
+     */
+    @Test
+    public void testLastActiveUserUpdatedOnUserSwitch() {
+        int lastActiveUserId = 11;
+
+        Intent intent = new Intent(Intent.ACTION_USER_SWITCHED);
+        intent.putExtra(Intent.EXTRA_USER_HANDLE, lastActiveUserId);
+
+        doReturn(true).when(mCarUserManagerHelper).isPersistentUser(lastActiveUserId);
+
+        mCarUserService.onReceive(mMockContext, intent);
+
+        verify(mCarUserManagerHelper).setLastActiveUser(
+                lastActiveUserId, /* skipGlobalSetting= */ false);
+    }
+
+    /**
+     * Test that the {@link CarUserService} sets default guest restrictions on first boot.
+     */
+    @Test
+    public void testInitializeGuestRestrictionsOnFirstRun() {
+        mockAdmin(/* adminId= */ 10);
+
+        mCarUserService.onReceive(mMockContext,
+                new Intent(Intent.ACTION_LOCKED_BOOT_COMPLETED));
+
+        verify(mCarUserManagerHelper).initDefaultGuestRestrictions();
+    }
+
+    private UserInfo mockAdmin(int adminId) {
+        UserInfo admin = new UserInfo(adminId, CarUserService.OWNER_NAME, UserInfo.FLAG_ADMIN);
+        doReturn(admin).when(mCarUserManagerHelper).createNewAdminUser(CarUserService.OWNER_NAME);
+        return admin;
+    }
 }
diff --git a/tests/robotests/src/com/android/car/users/CarUserManagerHelperRoboTest.java b/tests/robotests/src/com/android/car/users/CarUserManagerHelperRoboTest.java
index 9c39445..fcfd6dc 100644
--- a/tests/robotests/src/com/android/car/users/CarUserManagerHelperRoboTest.java
+++ b/tests/robotests/src/com/android/car/users/CarUserManagerHelperRoboTest.java
@@ -122,7 +122,7 @@
     }
 
     @Test
-    public void testGetAllUsersExcludesForegroundUser() {
+    public void testGetAllUsersExceptForegroundUser() {
         ShadowActivityManager.setCurrentUser(11);
         ShadowUserManager userManager = ShadowUserManager.getShadow();