Merge "Sets EVS owners"
diff --git a/car-lib/src/android/car/Car.java b/car-lib/src/android/car/Car.java
index b44969b..7430230 100644
--- a/car-lib/src/android/car/Car.java
+++ b/car-lib/src/android/car/Car.java
@@ -34,6 +34,7 @@
 import android.car.hardware.power.CarPowerManager;
 import android.car.hardware.property.CarPropertyManager;
 import android.car.media.CarAudioManager;
+import android.car.media.CarMediaManager;
 import android.car.navigation.CarNavigationStatusManager;
 import android.car.settings.CarConfigurationManager;
 import android.car.storagemonitoring.CarStorageMonitoringManager;
@@ -172,6 +173,12 @@
     public static final String CAR_CONFIGURATION_SERVICE = "configuration";
 
     /**
+     * Service name for {@link android.car.media.CarMediaManager}
+     * @hide
+     */
+    public static final String CAR_MEDIA_SERVICE = "car_media";
+
+    /**
      * @hide
      */
     @SystemApi
@@ -855,6 +862,9 @@
             case CAR_TRUST_AGENT_ENROLLMENT_SERVICE:
                 manager = new CarTrustAgentEnrollmentManager(binder, mContext, mEventHandler);
                 break;
+            case CAR_MEDIA_SERVICE:
+                manager = new CarMediaManager(binder);
+                break;
             default:
                 break;
         }
diff --git a/car-lib/src/android/car/media/CarMediaManager.java b/car-lib/src/android/car/media/CarMediaManager.java
new file mode 100644
index 0000000..fbddd22
--- /dev/null
+++ b/car-lib/src/android/car/media/CarMediaManager.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.media;
+
+import android.annotation.RequiresPermission;
+import android.car.Car;
+import android.car.CarManagerBase;
+import android.os.IBinder;
+import android.os.RemoteException;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * API for updating and receiving updates to the primary media source in the car.
+ * @hide
+ */
+public final class CarMediaManager implements CarManagerBase {
+
+    private final ICarMedia mService;
+    private Map<MediaSourceChangedListener, ICarMediaSourceListener> mCallbackMap = new HashMap();
+
+    /**
+     * Get an instance of the CarPowerManager.
+     *
+     * Should not be obtained directly by clients, use {@link Car#getCarManager(String)} instead.
+     * @hide
+     */
+    public CarMediaManager(IBinder service) {
+        mService = ICarMedia.Stub.asInterface(service);
+    }
+
+    /**
+     * Listener for updates to the primary media source
+     * @hide
+     */
+    public interface MediaSourceChangedListener {
+
+        /**
+         * Called when the primary media source is changed
+         * @hide
+         */
+        void onMediaSourceChanged(String packageName);
+    }
+
+    /**
+     * Gets the currently active media source, or null if none exists
+     * Requires android.Manifest.permission.MEDIA_CONTENT_CONTROL permission
+     * @hide
+     */
+    @RequiresPermission(value = android.Manifest.permission.MEDIA_CONTENT_CONTROL)
+    public synchronized String getMediaSource() {
+        try {
+            return mService.getMediaSource();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Sets the currently active media source
+     * Requires android.Manifest.permission.MEDIA_CONTENT_CONTROL permission
+     * @hide
+     */
+    @RequiresPermission(value = android.Manifest.permission.MEDIA_CONTENT_CONTROL)
+    public synchronized void setMediaSource(String packageName) {
+        try {
+            mService.setMediaSource(packageName);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Register a callback that receives updates to the active media source.
+     * Requires android.Manifest.permission.MEDIA_CONTENT_CONTROL permission
+     * @hide
+     */
+    @RequiresPermission(value = android.Manifest.permission.MEDIA_CONTENT_CONTROL)
+    public synchronized void registerMediaSourceListener(MediaSourceChangedListener callback) {
+        try {
+            ICarMediaSourceListener binderCallback = new ICarMediaSourceListener.Stub() {
+                @Override
+                public void onMediaSourceChanged(String packageName) {
+                    callback.onMediaSourceChanged(packageName);
+                }
+            };
+            mCallbackMap.put(callback, binderCallback);
+            mService.registerMediaSourceListener(binderCallback);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Unregister a callback that receives updates to the active media source.
+     * Requires android.Manifest.permission.MEDIA_CONTENT_CONTROL permission
+     * @hide
+     */
+    @RequiresPermission(value = android.Manifest.permission.MEDIA_CONTENT_CONTROL)
+    public synchronized void unregisterMediaSourceListener(MediaSourceChangedListener callback) {
+        try {
+            ICarMediaSourceListener binderCallback = mCallbackMap.remove(callback);
+            mService.unregisterMediaSourceListener(binderCallback);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /** @hide */
+    @Override
+    public synchronized void onCarDisconnected() {
+    }
+}
diff --git a/car-lib/src/android/car/media/ICarMedia.aidl b/car-lib/src/android/car/media/ICarMedia.aidl
new file mode 100644
index 0000000..fb1392d
--- /dev/null
+++ b/car-lib/src/android/car/media/ICarMedia.aidl
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.car.media;
+
+import android.car.media.ICarMediaSourceListener;
+
+/**
+ * Binder interface for {@link android.car.media.CarMediaManager}.
+ * Check {@link android.car.media.CarMediaManager} APIs for expected behavior of each call.
+ *
+ * @hide
+ */
+interface ICarMedia {
+    String getMediaSource();
+    void setMediaSource(in String mediaSource);
+    void registerMediaSourceListener(in ICarMediaSourceListener callback);
+    void unregisterMediaSourceListener(in ICarMediaSourceListener callback);
+}
diff --git a/car-lib/src/android/car/media/ICarMediaSourceListener.aidl b/car-lib/src/android/car/media/ICarMediaSourceListener.aidl
new file mode 100644
index 0000000..22c81eb
--- /dev/null
+++ b/car-lib/src/android/car/media/ICarMediaSourceListener.aidl
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.car.media;
+
+/**
+ * Binder interface to listen for media source changes
+ *
+ * @hide
+ */
+oneway interface ICarMediaSourceListener {
+    void onMediaSourceChanged(in String newSource) = 0;
+}
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 1de7df0..a6c680b 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
@@ -61,11 +61,6 @@
     <!-- The action buttons should always take the default color. -->
     <bool name="config_tintNotificationActionButtons">false</bool>
 
-    <!-- Flag indicating that this device does not rotate and will always remain in its default
-         orientation. Activities that desire to run in a non-compatible orientation will find that
-         they are not able to. -->
-    <bool name="config_forceDefaultOrientation">true</bool>
-
     <!-- Corner radius of system dialogs -->
     <dimen name="config_dialogCornerRadius">16dp</dimen>
 
diff --git a/service/Android.mk b/service/Android.mk
index 39c7386..fa4ba77 100644
--- a/service/Android.mk
+++ b/service/Android.mk
@@ -46,6 +46,8 @@
         android.hidl.base-V1.0-java \
         android.hardware.automotive.audiocontrol-V1.0-java \
         android.hardware.automotive.vehicle-V2.0-java \
+	android.hardware.health-V1.0-java \
+	android.hardware.health-V2.0-java \
         vehicle-hal-support-lib \
         car-frameworks-service \
         car-systemtest \
@@ -76,6 +78,8 @@
         android.hidl.base-V1.0-java \
         android.hardware.automotive.audiocontrol-V1.0-java \
         android.hardware.automotive.vehicle-V2.0-java \
+	android.hardware.health-V1.0-java \
+	android.hardware.health-V2.0-java \
         vehicle-hal-support-lib \
         car-systemtest \
         com.android.car.procfsinspector-client \
diff --git a/service/src/com/android/car/CarLocationService.java b/service/src/com/android/car/CarLocationService.java
index e0f227f..09c2bbf 100644
--- a/service/src/com/android/car/CarLocationService.java
+++ b/service/src/com/android/car/CarLocationService.java
@@ -44,8 +44,10 @@
 import android.util.JsonWriter;
 import android.util.Log;
 
+import com.android.car.systeminterface.SystemInterface;
 import com.android.internal.annotations.VisibleForTesting;
 
+import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileNotFoundException;
 import java.io.FileOutputStream;
@@ -61,10 +63,10 @@
  * and restores the location when the car is powered on.
  */
 public class CarLocationService extends BroadcastReceiver implements
-            CarServiceBase, CarPowerStateListener {
+        CarServiceBase, CarPowerStateListener {
     private static final String TAG = "CarLocationService";
     private static final String FILENAME = "location_cache.json";
-    private static final boolean DBG = false;
+    private static final boolean DBG = true;
     // The accuracy for the stored timestamp
     private static final long GRANULARITY_ONE_DAY_MS = 24 * 60 * 60 * 1000L;
     // The time-to-live for the cached location
@@ -240,7 +242,7 @@
             deleteCacheFile();
         } else {
             logd("Storing location: " + location);
-            AtomicFile atomicFile = new AtomicFile(mContext.getFileStreamPath(FILENAME));
+            AtomicFile atomicFile = new AtomicFile(getLocationCacheFile());
             FileOutputStream fos = null;
             try {
                 fos = atomicFile.startWrite();
@@ -295,7 +297,7 @@
      */
     private void loadLocation() {
         Location location = readLocationFromCacheFile();
-        logd("Read location from " + location.getTime());
+        logd("Read location from timestamp " + location.getTime());
         long currentTime = System.currentTimeMillis();
         if (location.getTime() + TTL_THIRTY_DAYS_MS < currentTime) {
             logd("Location expired.");
@@ -311,7 +313,7 @@
 
     private Location readLocationFromCacheFile() {
         Location location = new Location((String) null);
-        AtomicFile atomicFile = new AtomicFile(mContext.getFileStreamPath(FILENAME));
+        AtomicFile atomicFile = new AtomicFile(getLocationCacheFile());
         try (FileInputStream fis = atomicFile.openRead()) {
             JsonReader reader = new JsonReader(new InputStreamReader(fis, "UTF-8"));
             reader.beginObject();
@@ -358,8 +360,8 @@
     }
 
     private void deleteCacheFile() {
-        logd("Deleting cache file");
-        mContext.deleteFile(FILENAME);
+        boolean deleted = getLocationCacheFile().delete();
+        logd("Deleted cache file: " + deleted);
     }
 
     /**
@@ -383,6 +385,13 @@
         }
     }
 
+    private File getLocationCacheFile() {
+        SystemInterface systemInterface = CarLocalServices.getService(SystemInterface.class);
+        File file = new File(systemInterface.getSystemCarDir(), FILENAME);
+        logd("File: " + file);
+        return file;
+    }
+
     @VisibleForTesting
     void asyncOperation(Runnable operation) {
         asyncOperation(operation, 0);
diff --git a/service/src/com/android/car/CarLog.java b/service/src/com/android/car/CarLog.java
index 97734a8..ede5565 100644
--- a/service/src/com/android/car/CarLog.java
+++ b/service/src/com/android/car/CarLog.java
@@ -31,6 +31,7 @@
     public static final String TAG_VENDOR_EXT = "CAR.VENDOR_EXT";
     public static final String TAG_INFO = "CAR.INFO";
     public static final String TAG_INPUT = "CAR.INPUT";
+    public static final String TAG_MEDIA = "CAR.MEDIA";
     public static final String TAG_MONITORING = "CAR.MONITORING";
     public static final String TAG_NAV = "CAR.NAV";
     public static final String TAG_PACKAGE = "CAR.PACKAGE";
diff --git a/service/src/com/android/car/CarMediaService.java b/service/src/com/android/car/CarMediaService.java
new file mode 100644
index 0000000..4dd0a53
--- /dev/null
+++ b/service/src/com/android/car/CarMediaService.java
@@ -0,0 +1,269 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.car;
+
+import android.car.media.CarMediaManager;
+import android.car.media.CarMediaManager.MediaSourceChangedListener;
+import android.car.media.ICarMedia;
+import android.car.media.ICarMediaSourceListener;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.media.session.MediaController;
+import android.media.session.MediaSession;
+import android.media.session.MediaSession.Token;
+import android.media.session.MediaSessionManager;
+import android.media.session.PlaybackState;
+import android.os.RemoteCallbackList;
+import android.os.RemoteException;
+import android.util.Log;
+
+import androidx.annotation.Nullable;
+
+import com.android.car.user.CarUserService;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * CarMediaService manages the currently active media source for car apps. This is different from
+ * the MediaSessionManager's active sessions, as there can only be one active source in the car,
+ * through both browse and playback.
+ *
+ * In the car, the active media source does not necessarily have an active MediaSession, e.g. if
+ * it were being browsed only. However, that source is still considered the active source, and
+ * should be the source displayed in any Media related UIs (Media Center, home screen, etc).
+ */
+public class CarMediaService extends ICarMedia.Stub implements CarServiceBase {
+
+    private static final String SOURCE_KEY = "media_source";
+    private static final String SHARED_PREF = "com.android.car.media.car_media_service";
+
+    private final Context mContext;
+    private final MediaSessionManager mMediaSessionManager;
+    private final MediaSessionUpdater mMediaSessionUpdater;
+    private String mPrimaryMediaPackage;
+    private SharedPreferences mSharedPrefs;
+    // MediaController for the primary media source. Can be null if the primary media source has not
+    // played any media yet.
+    private MediaController mPrimaryMediaController;
+
+    private RemoteCallbackList<ICarMediaSourceListener> mMediaSourceListeners =
+            new RemoteCallbackList();
+
+    public CarMediaService(Context context) {
+        mContext = context;
+        mMediaSessionManager = mContext.getSystemService(MediaSessionManager.class);
+        mMediaSessionUpdater = new MediaSessionUpdater();
+        mMediaSessionUpdater.registerCallbacks(mMediaSessionManager.getActiveSessions(null));
+        mMediaSessionManager.addOnActiveSessionsChangedListener(
+                controllers -> mMediaSessionUpdater.registerCallbacks(controllers), null);
+    }
+
+    @Override
+    public void init() {
+        CarLocalServices.getService(CarUserService.class).runOnUser0Unlock(() -> {
+            mSharedPrefs = mContext.getSharedPreferences(SHARED_PREF, Context.MODE_PRIVATE);
+            mPrimaryMediaPackage = mSharedPrefs.getString(SOURCE_KEY, null);
+        });
+    }
+
+    @Override
+    public void release() {
+        mMediaSessionUpdater.unregisterCallbacks();
+    }
+
+    @Override
+    public void dump(PrintWriter writer) {
+        writer.println("*CarMediaService*");
+        writer.println("\tCurrent media package: " + mPrimaryMediaPackage);
+        writer.println("\tNumber of active media sessions: "
+                + mMediaSessionManager.getActiveSessions(null).size());
+    }
+
+    /**
+     * @see {@link CarMediaManager#setMediaSource(String)}
+     */
+    @Override
+    public synchronized void setMediaSource(String packageName) {
+        ICarImpl.assertPermission(mContext, android.Manifest.permission.MEDIA_CONTENT_CONTROL);
+        setPrimaryMediaSource(packageName);
+    }
+
+    /**
+     * @see {@link CarMediaManager#getMediaSource()}
+     */
+    @Override
+    public synchronized String getMediaSource() {
+        ICarImpl.assertPermission(mContext, android.Manifest.permission.MEDIA_CONTENT_CONTROL);
+        return mPrimaryMediaPackage;
+    }
+
+    /**
+     * @see {@link CarMediaManager#registerMediaSourceListener(MediaSourceChangedListener)}
+     */
+    @Override
+    public synchronized void registerMediaSourceListener(ICarMediaSourceListener callback) {
+        ICarImpl.assertPermission(mContext, android.Manifest.permission.MEDIA_CONTENT_CONTROL);
+        mMediaSourceListeners.register(callback);
+    }
+
+    /**
+     * @see {@link CarMediaManager#unregisterMediaSourceListener(ICarMediaSourceListener)}
+     */
+    @Override
+    public synchronized void unregisterMediaSourceListener(ICarMediaSourceListener callback) {
+        ICarImpl.assertPermission(mContext, android.Manifest.permission.MEDIA_CONTENT_CONTROL);
+        mMediaSourceListeners.unregister(callback);
+    }
+
+    private class MediaControllerCallback extends MediaController.Callback {
+
+        private final MediaController mMediaController;
+
+        private MediaControllerCallback(MediaController mediaController) {
+            mMediaController = mediaController;
+        }
+
+        private void register() {
+            mMediaController.registerCallback(this);
+        }
+
+        private void unregister() {
+            mMediaController.unregisterCallback(this);
+        }
+
+        @Override
+        public void onPlaybackStateChanged(@Nullable PlaybackState state) {
+            if (state.getState() == PlaybackState.STATE_PLAYING) {
+                updatePrimaryMediaSourceWithCurrentlyPlaying(
+                        Collections.singletonList(mMediaController));
+            }
+        }
+    }
+
+    private class MediaSessionUpdater {
+        private Map<Token, MediaControllerCallback> mCallbacks = new HashMap<>();
+
+        /**
+         * Register a {@link MediaControllerCallback} for each given controller. Note that if a
+         * controller was already watched, we don't register a callback again. This prevents an
+         * undesired revert of the primary media source. Callbacks for previously watched
+         * controllers that are not present in the given list are unregistered.
+         */
+        private void registerCallbacks(List<MediaController> newControllers) {
+
+            List<MediaController> additions = new ArrayList<>(newControllers.size());
+            Map<MediaSession.Token, MediaControllerCallback> updatedCallbacks =
+                    new HashMap<>(newControllers.size());
+
+            for (MediaController controller : newControllers) {
+                MediaSession.Token token = controller.getSessionToken();
+                MediaControllerCallback callback = mCallbacks.get(token);
+                if (callback == null) {
+                    callback = new MediaControllerCallback(controller);
+                    callback.register();
+                    additions.add(controller);
+                }
+                updatedCallbacks.put(token, callback);
+            }
+
+            for (MediaSession.Token token : mCallbacks.keySet()) {
+                if (!updatedCallbacks.containsKey(token)) {
+                    mCallbacks.get(token).unregister();
+                }
+            }
+
+            mCallbacks = updatedCallbacks;
+            updatePrimaryMediaSourceWithCurrentlyPlaying(additions);
+        }
+
+        /**
+         * Unregister all MediaController callbacks
+         */
+        private void unregisterCallbacks() {
+            for (Map.Entry<Token, MediaControllerCallback> entry : mCallbacks.entrySet()) {
+                entry.getValue().unregister();
+            }
+        }
+    }
+
+    /**
+     * Updates the primary media source, then notifies content observers of the change
+     */
+    private synchronized void setPrimaryMediaSource(@Nullable String packageName) {
+        if (mPrimaryMediaPackage != null && mPrimaryMediaPackage.equals((packageName))) {
+            return;
+        }
+
+        if (mPrimaryMediaController != null) {
+            MediaController.TransportControls controls =
+                    mPrimaryMediaController.getTransportControls();
+            if (controls != null) {
+                controls.pause();
+            }
+        }
+
+        mPrimaryMediaPackage = packageName;
+        mPrimaryMediaController = null;
+
+        if (mSharedPrefs != null) {
+            mSharedPrefs.edit().putString(SOURCE_KEY, mPrimaryMediaPackage).apply();
+        } else {
+            // Shouldn't reach this unless there is some other error in CarService
+            Log.e(CarLog.TAG_MEDIA, "Error trying to save last media source, prefs uninitialized");
+        }
+
+        int i = mMediaSourceListeners.beginBroadcast();
+        while (i-- > 0) {
+            try {
+                ICarMediaSourceListener callback = mMediaSourceListeners.getBroadcastItem(i);
+                callback.onMediaSourceChanged(mPrimaryMediaPackage);
+            } catch (RemoteException e) {
+                Log.e(CarLog.TAG_MEDIA, "calling onMediaSourceChanged failed " + e);
+            }
+        }
+        mMediaSourceListeners.finishBroadcast();
+    }
+
+    /**
+     * Finds the currently playing media source, then updates the active source if different
+     */
+    private synchronized void updatePrimaryMediaSourceWithCurrentlyPlaying(
+            List<MediaController> controllers) {
+        for (MediaController controller : controllers) {
+            if (controller.getPlaybackState() != null
+                    && controller.getPlaybackState().getState() == PlaybackState.STATE_PLAYING) {
+                if (mPrimaryMediaPackage == null || !mPrimaryMediaPackage.equals(
+                        controller.getPackageName())) {
+                    setPrimaryMediaSource(controller.getPackageName());
+                }
+                // The primary MediaSource can be set via api call (e.g from app picker)
+                // and the MediaController will enter playing state some time after. This avoids
+                // re-setting the primary media source every time the MediaController changes state.
+                // Also, it's possible that a MediaSource will create a new MediaSession without
+                // us ever changing sources, which is we overwrite our previously saved controller.
+                if (mPrimaryMediaPackage.equals(controller.getPackageName())) {
+                    mPrimaryMediaController = controller;
+                }
+                return;
+            }
+        }
+    }
+}
diff --git a/service/src/com/android/car/ICarImpl.java b/service/src/com/android/car/ICarImpl.java
index e02c65c..c41672a 100644
--- a/service/src/com/android/car/ICarImpl.java
+++ b/service/src/com/android/car/ICarImpl.java
@@ -86,6 +86,7 @@
     private final CarStorageMonitoringService mCarStorageMonitoringService;
     private final CarConfigurationService mCarConfigurationService;
     private final CarTrustAgentEnrollmentService mCarTrustAgentEnrollmentService;
+    private final CarMediaService mCarMediaService;
     private final CarUserManagerHelper mUserManagerHelper;
     private final CarUserService mCarUserService;
     private final VmsClientManager mVmsClientManager;
@@ -149,11 +150,13 @@
                 systemInterface);
         mCarConfigurationService =
                 new CarConfigurationService(serviceContext, new JsonReaderImpl());
-        mCarLocationService = new CarLocationService(
-                mContext, mCarPropertyService, mUserManagerHelper);
+        mCarLocationService = new CarLocationService(mContext, mCarPropertyService,
+                mUserManagerHelper);
         mCarTrustAgentEnrollmentService = new CarTrustAgentEnrollmentService(serviceContext);
+        mCarMediaService = new CarMediaService(serviceContext);
 
         CarLocalServices.addService(CarUserService.class, mCarUserService);
+        CarLocalServices.addService(SystemInterface.class, mSystemInterface);
 
         // Be careful with order. Service depending on other service should be inited later.
         List<CarServiceBase> allServices = new ArrayList<>();
@@ -181,6 +184,7 @@
         allServices.add(mVmsSubscriberService);
         allServices.add(mVmsPublisherService);
         allServices.add(mCarTrustAgentEnrollmentService);
+        allServices.add(mCarMediaService);
         allServices.add(mCarLocationService);
         mAllServices = allServices.toArray(new CarServiceBase[allServices.size()]);
     }
@@ -295,6 +299,8 @@
             case Car.CAR_TRUST_AGENT_ENROLLMENT_SERVICE:
                 assertTrustAgentEnrollmentPermission(mContext);
                 return mCarTrustAgentEnrollmentService;
+            case Car.CAR_MEDIA_SERVICE:
+                return mCarMediaService;
             default:
                 Log.w(CarLog.TAG_SERVICE, "getCarService for unknown service:" + serviceName);
                 return null;
diff --git a/service/src/com/android/car/storagemonitoring/HealthServiceWearInfoProvider.java b/service/src/com/android/car/storagemonitoring/HealthServiceWearInfoProvider.java
new file mode 100644
index 0000000..f0b8b97
--- /dev/null
+++ b/service/src/com/android/car/storagemonitoring/HealthServiceWearInfoProvider.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.storagemonitoring;
+
+import android.annotation.Nullable;
+import android.annotation.TestApi;
+import android.hardware.health.V2_0.IHealth;
+import android.hardware.health.V2_0.Result;
+import android.hardware.health.V2_0.StorageInfo;
+import android.os.RemoteException;
+import android.util.Log;
+import android.util.MutableInt;
+
+import com.android.car.CarLog;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.NoSuchElementException;
+
+/**
+ * Loads wear information from the Health service.
+ */
+public class HealthServiceWearInfoProvider implements WearInformationProvider {
+
+    private static final String INSTANCE_HEALTHD = "backup";
+    private static final String INSTANCE_VENDOR = "default";
+
+    private static final List<String> sAllInstances =
+                Arrays.asList(INSTANCE_VENDOR, INSTANCE_HEALTHD);
+
+    private IHealthSupplier mHealthSupplier;
+
+    public HealthServiceWearInfoProvider() {
+        mHealthSupplier = new IHealthSupplier() {};
+    }
+
+    @Nullable
+    @Override
+    public WearInformation load() {
+        IHealth healthService = getHealthService();
+        final MutableInt success = new MutableInt(Result.NOT_SUPPORTED);
+        final MutableInt foundInternalStorageDeviceInfo = new MutableInt(0);
+        final MutableInt lifetimeA = new MutableInt(0);
+        final MutableInt lifetimeB = new MutableInt(0);
+        final MutableInt preEol = new MutableInt(0);
+
+        final IHealth.getStorageInfoCallback getStorageInfoCallback =
+                new IHealth.getStorageInfoCallback() {
+            @Override
+            public void onValues(int result, ArrayList<StorageInfo> value) {
+                success.value = result;
+                if (result == Result.SUCCESS) {
+                    int len = value.size();
+                    for (int i = 0; i < len; i++) {
+                        StorageInfo value2 = value.get(i);
+                        if (value2.attr.isInternal) {
+                            lifetimeA.value = value2.lifetimeA;
+                            lifetimeB.value = value2.lifetimeB;
+                            preEol.value = value2.eol;
+                            foundInternalStorageDeviceInfo.value = 1;
+                        }
+                    }
+                }
+            }};
+
+        if (healthService == null) {
+            Log.w(CarLog.TAG_STORAGE, "No health service is available to fetch wear information.");
+            return null;
+        }
+
+        try {
+            healthService.getStorageInfo(getStorageInfoCallback);
+        } catch (Exception e) {
+            Log.w(CarLog.TAG_STORAGE, "Failed to get storage information from"
+                    + "health service, exception :" + e);
+            return null;
+        }
+
+        if (success.value != Result.SUCCESS) {
+            Log.w(CarLog.TAG_STORAGE, "Health service returned result :" + success.value);
+            return null;
+        } else if (foundInternalStorageDeviceInfo.value == 0) {
+            Log.w(CarLog.TAG_STORAGE, "Failed to find storage information for"
+                    + "internal storage device");
+            return null;
+        } else {
+            return new WearInformation(lifetimeA.value, lifetimeB.value,
+                                      preEol.value);
+        }
+    }
+
+    @Nullable
+    private IHealth getHealthService() {
+        for (String name : sAllInstances) {
+            IHealth newService = null;
+            try {
+                newService = mHealthSupplier.get(name);
+            } catch (Exception e) {
+                /* ignored, handled below */
+            }
+            if (newService != null) {
+                return newService;
+            }
+        }
+        return null;
+    }
+
+    @TestApi
+    public void setHealthSupplier(IHealthSupplier healthSupplier) {
+        mHealthSupplier = healthSupplier;
+    }
+
+    /**
+     * Supplier of services.
+     * Must not return null; throw {@link NoSuchElementException} if a service is not available.
+     */
+    interface IHealthSupplier {
+        default IHealth get(String name) throws NoSuchElementException, RemoteException {
+            return IHealth.getService(name, false /* retry */);
+        }
+    }
+}
diff --git a/service/src/com/android/car/systeminterface/StorageMonitoringInterface.java b/service/src/com/android/car/systeminterface/StorageMonitoringInterface.java
index c0cceb0..78d6b13 100644
--- a/service/src/com/android/car/systeminterface/StorageMonitoringInterface.java
+++ b/service/src/com/android/car/systeminterface/StorageMonitoringInterface.java
@@ -17,6 +17,7 @@
 package com.android.car.systeminterface;
 
 import com.android.car.storagemonitoring.EMmcWearInformationProvider;
+import com.android.car.storagemonitoring.HealthServiceWearInfoProvider;
 import com.android.car.storagemonitoring.LifetimeWriteInfoProvider;
 import com.android.car.storagemonitoring.ProcfsUidIoStatsProvider;
 import com.android.car.storagemonitoring.SysfsLifetimeWriteInfoProvider;
@@ -31,7 +32,8 @@
     default WearInformationProvider[] getFlashWearInformationProviders() {
         return new WearInformationProvider[] {
             new EMmcWearInformationProvider(),
-            new UfsWearInformationProvider()
+            new UfsWearInformationProvider(),
+            new HealthServiceWearInfoProvider()
         };
     }
 
diff --git a/service/src/com/android/car/systeminterface/SystemInterface.java b/service/src/com/android/car/systeminterface/SystemInterface.java
index f8d6d17..a617a5a 100644
--- a/service/src/com/android/car/systeminterface/SystemInterface.java
+++ b/service/src/com/android/car/systeminterface/SystemInterface.java
@@ -34,7 +34,7 @@
  * This class contains references to all the different wrapper interfaces between
  * CarService and the Android OS APIs.
  */
-public final class SystemInterface implements DisplayInterface, IOInterface,
+public class SystemInterface implements DisplayInterface, IOInterface,
         StorageMonitoringInterface, SystemStateInterface, TimeInterface,
         WakeLockInterface {
     private final DisplayInterface mDisplayInterface;
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 a61edcf..22b8d15 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
@@ -295,8 +295,7 @@
 
         mTbDual.setOnClickListener(view -> {
             // TODO handle zone properly
-            mCarHvacManager.setBooleanProperty(CarHvacManager.ID_ZONED_DUAL_ZONE_ON, temp,
-                    mTbDual.isChecked());
+            setBooleanProperty(CarHvacManager.ID_ZONED_DUAL_ZONE_ON, temp, mTbDual.isChecked());
         });
     }
 
@@ -307,8 +306,7 @@
 
         mTbAc.setOnClickListener(view -> {
             // TODO handle zone properly
-            mCarHvacManager.setBooleanProperty(CarHvacManager.ID_ZONED_AC_ON, mZoneForAcOn,
-                    mTbAc.isChecked());
+            setBooleanProperty(CarHvacManager.ID_ZONED_AC_ON, mZoneForAcOn, mTbAc.isChecked());
         });
     }
 
@@ -319,7 +317,7 @@
 
         mTbAuto.setOnClickListener(view -> {
             // TODO handle zone properly
-            mCarHvacManager.setBooleanProperty(CarHvacManager.ID_ZONED_AUTOMATIC_MODE_ON, temp,
+            setBooleanProperty(CarHvacManager.ID_ZONED_AUTOMATIC_MODE_ON, temp,
                     mTbAuto.isChecked());
         });
     }
@@ -380,9 +378,13 @@
         mMinFanSpeed = ((Integer) prop.getMinValue()).intValue();
         mMaxFanSpeed = ((Integer) prop.getMaxValue()).intValue();
         mZoneForFanSpeed = prop.getFirstAndOnlyAreaId();
-        mCurFanSpeed = mCarHvacManager.getIntProperty(
-                CarHvacManager.ID_ZONED_FAN_SPEED_SETPOINT,
-                mZoneForFanSpeed);
+        try {
+            mCurFanSpeed = mCarHvacManager.getIntProperty(
+                    CarHvacManager.ID_ZONED_FAN_SPEED_SETPOINT,
+                    mZoneForFanSpeed);
+        } catch (IllegalStateException e) {
+            Log.e(TAG, "Failed to get HVAC int property", e);
+        }
 
         Button btnFanSpeedUp = (Button) v.findViewById(R.id.btnFanSpeedUp);
         btnFanSpeedUp.setEnabled(true);
@@ -441,13 +443,16 @@
         }
         Button btnDTempUp = (Button) v.findViewById(R.id.btnDTempUp);
         if (mZoneForSetTempD != 0) {
-            mCurDTemp = mCarHvacManager.getFloatProperty(
-                    CarHvacManager.ID_ZONED_TEMP_SETPOINT,
-                    mZoneForSetTempD);
-            if (mCurDTemp < mMinTemp) {
-                mCurDTemp = mMinTemp;
-            } else if (mCurDTemp > mMaxTemp) {
-                mCurDTemp = mMaxTemp;
+            try {
+                mCurDTemp = mCarHvacManager.getFloatProperty(CarHvacManager.ID_ZONED_TEMP_SETPOINT,
+                        mZoneForSetTempD);
+                if (mCurDTemp < mMinTemp) {
+                    mCurDTemp = mMinTemp;
+                } else if (mCurDTemp > mMaxTemp) {
+                    mCurDTemp = mMaxTemp;
+                }
+            } catch (IllegalStateException e) {
+                Log.e(TAG, "Failed to get HVAC zoned temp property", e);
             }
             btnDTempUp.setEnabled(true);
             btnDTempUp.setOnClickListener(view -> changeDriverTemperature(mTempStep));
@@ -461,13 +466,16 @@
 
         Button btnPTempUp = (Button) v.findViewById(R.id.btnPTempUp);
         if (mZoneForSetTempP != 0) {
-            mCurPTemp = mCarHvacManager.getFloatProperty(
-                    CarHvacManager.ID_ZONED_TEMP_SETPOINT,
-                    mZoneForSetTempP);
-            if (mCurPTemp < mMinTemp) {
-                mCurPTemp = mMinTemp;
-            } else if (mCurPTemp > mMaxTemp) {
-                mCurPTemp = mMaxTemp;
+            try {
+                mCurPTemp = mCarHvacManager.getFloatProperty(CarHvacManager.ID_ZONED_TEMP_SETPOINT,
+                        mZoneForSetTempP);
+                if (mCurPTemp < mMinTemp) {
+                    mCurPTemp = mMinTemp;
+                } else if (mCurPTemp > mMaxTemp) {
+                    mCurPTemp = mMaxTemp;
+                }
+            } catch (IllegalStateException e) {
+                Log.e(TAG, "Failed to get HVAC zoned temp property", e);
             }
             btnPTempUp.setEnabled(true);
             btnPTempUp.setOnClickListener(view -> changePassengerTemperature(mTempStep));
@@ -503,8 +511,7 @@
         if (mMinTemp < targetTemp && targetTemp < mMaxTemp) {
             mCurDTemp = targetTemp;
             mTvDTemp.setText(String.valueOf(mCurDTemp));
-            mCarHvacManager.setFloatProperty(CarHvacManager.ID_ZONED_TEMP_SETPOINT,
-                    mZoneForSetTempD, mCurDTemp);
+            setFloatProperty(CarHvacManager.ID_ZONED_TEMP_SETPOINT, mZoneForSetTempD, mCurDTemp);
         }
     }
 
@@ -513,8 +520,7 @@
         if (mMinTemp < targetTemp && targetTemp < mMaxTemp) {
             mCurPTemp = targetTemp;
             mTvPTemp.setText(String.valueOf(mCurPTemp));
-            mCarHvacManager.setFloatProperty(CarHvacManager.ID_ZONED_TEMP_SETPOINT,
-                    mZoneForSetTempP, mCurPTemp);
+            setFloatProperty(CarHvacManager.ID_ZONED_TEMP_SETPOINT, mZoneForSetTempP, mCurPTemp);
         }
     }
 
@@ -523,18 +529,16 @@
             mTbDefrostFront = (ToggleButton) v.findViewById(R.id.tbDefrostFront);
             mTbDefrostFront.setEnabled(true);
             mTbDefrostFront.setOnClickListener(view -> {
-                mCarHvacManager.setBooleanProperty(CarHvacManager.ID_WINDOW_DEFROSTER_ON,
-                        VehicleAreaWindow.FRONT_WINDSHIELD,
-                        mTbDefrostFront.isChecked());
+                setBooleanProperty(CarHvacManager.ID_WINDOW_DEFROSTER_ON,
+                        VehicleAreaWindow.FRONT_WINDSHIELD, mTbDefrostFront.isChecked());
             });
         }
         if (prop1.hasArea(VehicleAreaWindow.REAR_WINDSHIELD)) {
             mTbDefrostRear = (ToggleButton) v.findViewById(R.id.tbDefrostRear);
             mTbDefrostRear.setEnabled(true);
             mTbDefrostRear.setOnClickListener(view -> {
-                mCarHvacManager.setBooleanProperty(CarHvacManager.ID_WINDOW_DEFROSTER_ON,
-                        VehicleAreaWindow.REAR_WINDSHIELD,
-                        mTbDefrostRear.isChecked());
+                setBooleanProperty(CarHvacManager.ID_WINDOW_DEFROSTER_ON,
+                        VehicleAreaWindow.REAR_WINDSHIELD, mTbDefrostRear.isChecked());
             });
         }
     }
@@ -546,8 +550,8 @@
 
         mTbRecirc.setOnClickListener(view -> {
             // TODO handle zone properly
-            mCarHvacManager.setBooleanProperty(CarHvacManager.ID_ZONED_AIR_RECIRCULATION_ON,
-                    temp, mTbRecirc.isChecked());
+            setBooleanProperty(CarHvacManager.ID_ZONED_AIR_RECIRCULATION_ON, temp,
+                    mTbRecirc.isChecked());
         });
     }
 
@@ -558,8 +562,7 @@
 
         mTbMaxAc.setOnClickListener(view -> {
             // TODO handle zone properly
-            mCarHvacManager.setBooleanProperty(CarHvacManager.ID_ZONED_MAX_AC_ON, temp,
-                    mTbMaxAc.isChecked());
+            setBooleanProperty(CarHvacManager.ID_ZONED_MAX_AC_ON, temp, mTbMaxAc.isChecked());
         });
     }
 
@@ -570,7 +573,7 @@
 
         mTbMaxDefrost.setOnClickListener(view -> {
             // TODO handle zone properly
-            mCarHvacManager.setBooleanProperty(CarHvacManager.ID_ZONED_MAX_DEFROST_ON, temp,
+            setBooleanProperty(CarHvacManager.ID_ZONED_MAX_DEFROST_ON, temp,
                     mTbMaxDefrost.isChecked());
         });
     }
@@ -582,8 +585,8 @@
         mTbAutoRecirc.setEnabled(true);
 
         mTbAutoRecirc.setOnClickListener(view -> {
-            mCarHvacManager.setBooleanProperty(CarHvacManager.ID_ZONED_HVAC_AUTO_RECIRC_ON,
-                    areaId, mTbAutoRecirc.isChecked());
+            setBooleanProperty(CarHvacManager.ID_ZONED_HVAC_AUTO_RECIRC_ON, areaId,
+                    mTbAutoRecirc.isChecked());
         });
     }
 
@@ -593,10 +596,9 @@
         mTbTempDisplayUnit.setEnabled(true);
 
         mTbTempDisplayUnit.setOnClickListener(view -> {
-            int unit = (mTbTempDisplayUnit.isChecked() ? VehicleUnit.FAHRENHEIT
-                    : VehicleUnit.CELSIUS);
-            mCarHvacManager.setIntProperty(CarHvacManager.ID_TEMPERATURE_DISPLAY_UNITS, areaId,
-                    unit);
+            int unit =
+                    (mTbTempDisplayUnit.isChecked() ? VehicleUnit.FAHRENHEIT : VehicleUnit.CELSIUS);
+            setIntProperty(CarHvacManager.ID_TEMPERATURE_DISPLAY_UNITS, areaId, unit);
         });
     }
 
@@ -607,8 +609,7 @@
         mTbPower.setEnabled(true);
 
         mTbPower.setOnClickListener(view -> {
-            mCarHvacManager.setBooleanProperty(CarHvacManager.ID_ZONED_HVAC_POWER_ON, areaId,
-                    mTbPower.isChecked());
+            setBooleanProperty(CarHvacManager.ID_ZONED_HVAC_POWER_ON, areaId, mTbPower.isChecked());
         });
     }
 
@@ -619,9 +620,35 @@
         mTbPowerAndAc.setEnabled(true);
 
         mTbPowerAndAc.setOnClickListener(view -> {
-            mCarHvacManager.setBooleanProperty(CarHvacManager.ID_ZONED_HVAC_POWER_ON, areaId,
-                    true);
-            mCarHvacManager.setBooleanProperty(CarHvacManager.ID_ZONED_AC_ON, areaId, true);
+            setBooleanProperty(CarHvacManager.ID_ZONED_HVAC_POWER_ON, areaId, true);
+            setBooleanProperty(CarHvacManager.ID_ZONED_AC_ON, areaId, true);
         });
     }
+
+    private void setBooleanProperty(int propertyId, int areaId, boolean value) {
+        try {
+            mCarHvacManager.setBooleanProperty(propertyId, areaId, value);
+        } catch (IllegalStateException e) {
+            Log.e(TAG, "Failed to set boolean property", e);
+            Toast.makeText(getContext(), e.getMessage(), Toast.LENGTH_SHORT).show();
+        }
+    }
+
+    private void setIntProperty(int propertyId, int areaId, int value) {
+        try {
+            mCarHvacManager.setIntProperty(propertyId, areaId, value);
+        } catch (IllegalStateException e) {
+            Log.e(TAG, "Failed to set int property", e);
+            Toast.makeText(getContext(), e.getMessage(), Toast.LENGTH_SHORT).show();
+        }
+    }
+
+    private void setFloatProperty(int propertyId, int areaId, float value) {
+        try {
+            mCarHvacManager.setFloatProperty(propertyId, areaId, value);
+        } catch (IllegalStateException e) {
+            Log.e(TAG, "Failed to set float property", e);
+            Toast.makeText(getContext(), e.getMessage(), Toast.LENGTH_SHORT).show();
+        }
+    }
 }
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 13da4bb..6122330 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
@@ -51,7 +51,7 @@
 import java.util.List;
 import java.util.stream.Collectors;
 
-public class PropertyTestFragment extends Fragment implements OnItemSelectedListener{
+public class PropertyTestFragment extends Fragment implements OnItemSelectedListener {
     private static final String TAG = "PropertyTestFragment";
 
     private KitchenSinkActivity mActivity;
@@ -67,7 +67,8 @@
 
     private final OnClickListener mNopOnClickListener = new OnClickListener() {
         @Override
-        public void onClick(DialogInterface dialog, int which) { }
+        public void onClick(DialogInterface dialog, int which) {
+        }
     };
 
     @Nullable
@@ -113,19 +114,21 @@
                 if (propId == VehicleProperty.WHEEL_TICK) {
                     Object[] ticks = (Object[]) value.getValue();
                     mGetValue.setText("Timestamp=" + value.getTimestamp()
-                                      + "\nstatus=" + value.getStatus()
-                                      + "\n[0]=" + (Long) ticks[0]
-                                      + "\n[1]=" + (Long) ticks[1] + " [2]=" + (Long) ticks[2]
-                                      + "\n[3]=" + (Long) ticks[3] + " [4]=" + (Long) ticks[4]);
+                            + "\nstatus=" + value.getStatus()
+                            + "\n[0]=" + (Long) ticks[0]
+                            + "\n[1]=" + (Long) ticks[1] + " [2]=" + (Long) ticks[2]
+                            + "\n[3]=" + (Long) ticks[3] + " [4]=" + (Long) ticks[4]);
                 } else {
                     mGetValue.setText("Timestamp=" + value.getTimestamp()
-                                      + "\nstatus=" + value.getStatus()
-                                      + "\nvalue=" + value.getValue()
-                                      + "\nread=" + mMgr.getReadPermission(propId)
-                                      + "\nwrite=" + mMgr.getWritePermission(propId));
+                            + "\nstatus=" + value.getStatus()
+                            + "\nvalue=" + value.getValue()
+                            + "\nread=" + mMgr.getReadPermission(propId)
+                            + "\nwrite=" + mMgr.getWritePermission(propId));
                 }
             } catch (Exception e) {
-                Log.e(TAG, "Failed to get property", e);
+                Log.e(TAG, "Failed to get VHAL property", e);
+                Toast.makeText(mActivity, "Failed to get VHAL property: " + e.getMessage(),
+                        Toast.LENGTH_SHORT).show();
             }
         });
 
@@ -152,12 +155,14 @@
                         break;
                     default:
                         Toast.makeText(mActivity, "PropertyType=0x" + toHexString(propId
-                                & VehiclePropertyType.MASK) + " is not handled!",
+                                        & VehiclePropertyType.MASK) + " is not handled!",
                                 Toast.LENGTH_LONG).show();
                         break;
                 }
             } catch (Exception e) {
-                Log.e(TAG, "Failed to set HVAC boolean property", e);
+                Log.e(TAG, "Failed to set VHAL property", e);
+                Toast.makeText(mActivity, "Failed to set VHAL property: " + e.getMessage(),
+                        Toast.LENGTH_SHORT).show();
             }
         });
 
@@ -196,9 +201,8 @@
         }
 
         // Configure dropdown menu for propertyId spinner
-        ArrayAdapter<String> adapter =
-                new ArrayAdapter<String>(mActivity, android.R.layout.simple_spinner_item,
-                                         areaString);
+        ArrayAdapter<String> adapter = new ArrayAdapter<String>(mActivity,
+                android.R.layout.simple_spinner_item, areaString);
         adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
         mAreaId.setAdapter(adapter);
     }
diff --git a/tests/carservice_unit_test/src/android/car/userlib/CarUserManagerHelperTest.java b/tests/carservice_unit_test/src/android/car/userlib/CarUserManagerHelperTest.java
index 19ae7f4..3128859 100644
--- a/tests/carservice_unit_test/src/android/car/userlib/CarUserManagerHelperTest.java
+++ b/tests/carservice_unit_test/src/android/car/userlib/CarUserManagerHelperTest.java
@@ -114,6 +114,9 @@
         // Restore the non-headless state before every test. Individual tests can set the property
         // to true to test the headless system user scenario.
         CarProperties.headless_system_user(false);
+
+        // Clear boot override for every test
+        CarProperties.boot_user_override_id(null);
     }
 
     @Test
@@ -857,32 +860,85 @@
     }
 
     @Test
-    public void testGetInitialUserWithValidLastActiveUser() {
+    public void test_GetInitialUserWithValidLastActiveUser_ReturnsLastActiveUser() {
         CarProperties.headless_system_user(true);
         int lastActiveUserId = 12;
 
-        UserInfo otherUser1 = createUserInfoForId(lastActiveUserId - 2);
-        UserInfo otherUser2 = createUserInfoForId(lastActiveUserId - 1);
-        UserInfo otherUser3 = createUserInfoForId(lastActiveUserId);
+        UserInfo user10 = createUserInfoForId(10);
+        UserInfo user11 = createUserInfoForId(11);
+        UserInfo user12 = createUserInfoForId(12);
 
         setLastActiveUser(lastActiveUserId);
-        mockGetUsers(mSystemUser, otherUser1, otherUser2, otherUser3);
+        mockGetUsers(mSystemUser, user10, user11, user12);
 
         assertThat(mCarUserManagerHelper.getInitialUser()).isEqualTo(lastActiveUserId);
     }
 
     @Test
-    public void testGetInitialUserWithNonExistLastActiveUser() {
+    public void test_GetInitialUserWithNonExistLastActiveUser_ReturnsSamllestUserId() {
         CarProperties.headless_system_user(true);
         int lastActiveUserId = 12;
+        int minimumUserId = 10;
 
-        UserInfo otherUser1 = createUserInfoForId(lastActiveUserId - 2);
-        UserInfo otherUser2 = createUserInfoForId(lastActiveUserId - 1);
+        UserInfo smallestUser = createUserInfoForId(minimumUserId);
+        UserInfo notSmallestUser = createUserInfoForId(minimumUserId + 1);
 
         setLastActiveUser(lastActiveUserId);
-        mockGetUsers(mSystemUser, otherUser1, otherUser2);
+        mockGetUsers(mSystemUser, smallestUser, notSmallestUser);
 
-        assertThat(mCarUserManagerHelper.getInitialUser()).isEqualTo(lastActiveUserId - 2);
+        assertThat(mCarUserManagerHelper.getInitialUser()).isEqualTo(minimumUserId);
+    }
+
+    @Test
+    public void test_GetInitialUserWithOverrideId_ReturnsOverrideId() {
+        CarProperties.headless_system_user(true);
+        int lastActiveUserId = 12;
+        int overrideUserId = 11;
+
+        UserInfo user10 = createUserInfoForId(10);
+        UserInfo user11 = createUserInfoForId(11);
+        UserInfo user12 = createUserInfoForId(12);
+
+        setDefaultBootUserOverride(overrideUserId);
+        setLastActiveUser(lastActiveUserId);
+        mockGetUsers(mSystemUser, user10, user11, user12);
+
+        assertThat(mCarUserManagerHelper.getInitialUser()).isEqualTo(overrideUserId);
+    }
+
+    @Test
+    public void test_GetInitialUserWithInvalidOverrideId_ReturnsLastActiveUserId() {
+        CarProperties.headless_system_user(true);
+        int lastActiveUserId = 12;
+        int overrideUserId = 15;
+
+        UserInfo user10 = createUserInfoForId(10);
+        UserInfo user11 = createUserInfoForId(11);
+        UserInfo user12 = createUserInfoForId(12);
+
+        setDefaultBootUserOverride(overrideUserId);
+        setLastActiveUser(lastActiveUserId);
+        mockGetUsers(mSystemUser, user10, user11, user12);
+
+        assertThat(mCarUserManagerHelper.getInitialUser()).isEqualTo(lastActiveUserId);
+    }
+
+    @Test
+    public void test_GetInitialUserWithInvalidOverrideAndLastActiveUserIds_ReturnsSmallestUserId() {
+        CarProperties.headless_system_user(true);
+        int minimumUserId = 10;
+        int invalidLastActiveUserId = 14;
+        int invalidOverrideUserId = 15;
+
+        UserInfo minimumUser = createUserInfoForId(minimumUserId);
+        UserInfo user11 = createUserInfoForId(minimumUserId + 1);
+        UserInfo user12 = createUserInfoForId(minimumUserId + 2);
+
+        setDefaultBootUserOverride(invalidOverrideUserId);
+        setLastActiveUser(invalidLastActiveUserId);
+        mockGetUsers(mSystemUser, minimumUser, user11, user12);
+
+        assertThat(mCarUserManagerHelper.getInitialUser()).isEqualTo(minimumUserId);
     }
 
     @Test
@@ -934,4 +990,8 @@
         Settings.Global.putInt(InstrumentationRegistry.getTargetContext().getContentResolver(),
                 Settings.Global.LAST_ACTIVE_USER_ID, userId);
     }
+
+    private void setDefaultBootUserOverride(int userId) {
+        CarProperties.boot_user_override_id(userId);
+    }
 }
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 dabb617..ba0ff50 100644
--- a/tests/carservice_unit_test/src/com/android/car/CarLocationServiceTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/CarLocationServiceTest.java
@@ -17,6 +17,7 @@
 package com.android.car;
 
 import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
 import static junit.framework.Assert.assertTrue;
 import static junit.framework.Assert.fail;
 
@@ -47,6 +48,8 @@
 import androidx.test.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.car.systeminterface.SystemInterface;
+import com.android.car.test.utils.TemporaryDirectory;
 import com.android.internal.util.ArrayUtils;
 
 import org.junit.After;
@@ -58,8 +61,8 @@
 import org.mockito.MockitoAnnotations;
 
 import java.io.BufferedReader;
+import java.io.File;
 import java.io.FileInputStream;
-import java.io.FileNotFoundException;
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStreamReader;
@@ -76,23 +79,31 @@
  * file upon appropriate system events.
  *
  * The following mocks are used:
- * 1. {@link Context} provides files and a mocked {@link LocationManager}.
+ * 1. {@link Context} provides a mocked {@link LocationManager}.
  * 2. {@link LocationManager} provides dummy {@link Location}s.
  * 3. {@link CarPropertyService} registers a listener for ignition state events.
  * 4. {@link CarUserManagerHelper} tells whether or not the system user is headless.
+ * 5. {@link SystemInterface} tells where to store system files.
  */
 @RunWith(AndroidJUnit4.class)
 public class CarLocationServiceTest {
-    private static String TAG = "CarLocationServiceTest";
-    private static String TEST_FILENAME = "location_cache_test.json";
+    private static final String TAG = "CarLocationServiceTest";
+    private static final String TEST_FILENAME = "location_cache.json";
     private CarLocationService mCarLocationService;
     private Context mContext;
     private CountDownLatch mLatch;
     private Car mCar;
-    @Mock private Context mMockContext;
-    @Mock private LocationManager mMockLocationManager;
-    @Mock private CarPropertyService mMockCarPropertyService;
-    @Mock private CarUserManagerHelper mMockCarUserManagerHelper;
+    private File mTempDirectory;
+    @Mock
+    private Context mMockContext;
+    @Mock
+    private LocationManager mMockLocationManager;
+    @Mock
+    private CarPropertyService mMockCarPropertyService;
+    @Mock
+    private CarUserManagerHelper mMockCarUserManagerHelper;
+    @Mock
+    private SystemInterface mMockSystemInterface;
 
     /**
      * Initialize all of the objects with the @Mock annotation.
@@ -101,6 +112,7 @@
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
         mContext = InstrumentationRegistry.getTargetContext();
+        mTempDirectory = new TemporaryDirectory(TAG).getDirectory();
         mLatch = new CountDownLatch(1);
         mCar = new Car(mContext, null, null);
         mCarLocationService = new CarLocationService(
@@ -113,6 +125,8 @@
                 });
             }
         };
+        CarLocalServices.removeServiceForTest(SystemInterface.class);
+        CarLocalServices.addService(SystemInterface.class, mMockSystemInterface);
     }
 
     @After
@@ -120,7 +134,6 @@
         if (mCarLocationService != null) {
             mCarLocationService.release();
         }
-        mContext.deleteFile(TEST_FILENAME);
     }
 
     /**
@@ -178,7 +191,7 @@
      * headless.
      */
     @Test
-    public void testLoadsLocationOnLockedBootComplete() throws IOException, InterruptedException {
+    public void testLoadsLocationOnLockedBootComplete() throws Exception {
         long currentTime = System.currentTimeMillis();
         long elapsedTime = SystemClock.elapsedRealtimeNanos();
         long pastTime = currentTime - 60000;
@@ -187,9 +200,8 @@
         ArgumentCaptor<Location> argument = ArgumentCaptor.forClass(Location.class);
         when(mMockContext.getSystemService(Context.LOCATION_SERVICE))
                 .thenReturn(mMockLocationManager);
+        when(mMockSystemInterface.getSystemCarDir()).thenReturn(mTempDirectory);
         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,
@@ -210,7 +222,7 @@
      * injects it into the {@link LocationManager} upon user switch if the system user is headless.
      */
     @Test
-    public void testLoadsLocationWithHeadlessSystemUser() throws IOException, InterruptedException {
+    public void testLoadsLocationWithHeadlessSystemUser() throws Exception {
         long currentTime = System.currentTimeMillis();
         long elapsedTime = SystemClock.elapsedRealtimeNanos();
         long pastTime = currentTime - 60000;
@@ -219,9 +231,8 @@
         ArgumentCaptor<Location> argument = ArgumentCaptor.forClass(Location.class);
         when(mMockContext.getSystemService(Context.LOCATION_SERVICE))
                 .thenReturn(mMockLocationManager);
+        when(mMockSystemInterface.getSystemCarDir()).thenReturn(mTempDirectory);
         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);
@@ -243,14 +254,11 @@
      * cache file.
      */
     @Test
-    public void testDoesNotLoadLocationWhenNoFileExists()
-            throws FileNotFoundException, InterruptedException {
+    public void testDoesNotLoadLocationWhenNoFileExists() throws Exception {
         when(mMockContext.getSystemService(Context.LOCATION_SERVICE))
                 .thenReturn(mMockLocationManager);
         when(mMockLocationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER))
                 .thenReturn(null);
-        when(mMockContext.getFileStreamPath("location_cache.json"))
-                .thenReturn(mContext.getFileStreamPath(TEST_FILENAME));
         mCarLocationService.onReceive(mMockContext,
                 new Intent(Intent.ACTION_LOCKED_BOOT_COMPLETED));
         mLatch.await();
@@ -261,15 +269,12 @@
      * Test that the {@link CarLocationService} handles an incomplete JSON file gracefully.
      */
     @Test
-    public void testDoesNotLoadLocationFromIncompleteFile() throws IOException,
-            InterruptedException {
+    public void testDoesNotLoadLocationFromIncompleteFile() throws Exception {
         writeCacheFile("{\"provider\": \"gps\", \"latitude\": 16.7666, \"longitude\": 3.0026,");
         when(mMockContext.getSystemService(Context.LOCATION_SERVICE))
                 .thenReturn(mMockLocationManager);
         when(mMockLocationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER))
                 .thenReturn(null);
-        when(mMockContext.getFileStreamPath("location_cache.json"))
-                .thenReturn(mContext.getFileStreamPath(TEST_FILENAME));
         mCarLocationService.onReceive(mMockContext,
                 new Intent(Intent.ACTION_LOCKED_BOOT_COMPLETED));
         mLatch.await();
@@ -280,14 +285,12 @@
      * Test that the {@link CarLocationService} handles a corrupt JSON file gracefully.
      */
     @Test
-    public void testDoesNotLoadLocationFromCorruptFile() throws IOException, InterruptedException {
+    public void testDoesNotLoadLocationFromCorruptFile() throws Exception {
         writeCacheFile("{\"provider\":\"latitude\":16.7666,\"longitude\": \"accuracy\":1.0}");
         when(mMockContext.getSystemService(Context.LOCATION_SERVICE))
                 .thenReturn(mMockLocationManager);
         when(mMockLocationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER))
                 .thenReturn(null);
-        when(mMockContext.getFileStreamPath("location_cache.json"))
-                .thenReturn(mContext.getFileStreamPath(TEST_FILENAME));
         mCarLocationService.onReceive(mMockContext,
                 new Intent(Intent.ACTION_LOCKED_BOOT_COMPLETED));
         mLatch.await();
@@ -299,14 +302,12 @@
      * accuracy.
      */
     @Test
-    public void testDoesNotLoadIncompleteLocation() throws IOException, InterruptedException {
+    public void testDoesNotLoadIncompleteLocation() throws Exception {
         writeCacheFile("{\"provider\": \"gps\", \"latitude\": 16.7666, \"longitude\": 3.0026}");
         when(mMockContext.getSystemService(Context.LOCATION_SERVICE))
                 .thenReturn(mMockLocationManager);
         when(mMockLocationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER))
                 .thenReturn(null);
-        when(mMockContext.getFileStreamPath("location_cache.json"))
-                .thenReturn(mContext.getFileStreamPath(TEST_FILENAME));
         mCarLocationService.onReceive(mMockContext,
                 new Intent(Intent.ACTION_LOCKED_BOOT_COMPLETED));
         mLatch.await();
@@ -318,7 +319,7 @@
      * thirty days.
      */
     @Test
-    public void testDoesNotLoadOldLocation() throws IOException, InterruptedException {
+    public void testDoesNotLoadOldLocation() throws Exception {
         long thirtyThreeDaysMs = 33 * 24 * 60 * 60 * 1000L;
         long oldTime = System.currentTimeMillis() - thirtyThreeDaysMs;
         writeCacheFile("{\"provider\": \"gps\", \"latitude\": 16.7666, \"longitude\": 3.0026,"
@@ -327,8 +328,6 @@
                 .thenReturn(mMockLocationManager);
         when(mMockLocationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER))
                 .thenReturn(null);
-        when(mMockContext.getFileStreamPath("location_cache.json"))
-                .thenReturn(mContext.getFileStreamPath(TEST_FILENAME));
         mCarLocationService.onReceive(mMockContext,
                 new Intent(Intent.ACTION_LOCKED_BOOT_COMPLETED));
         mLatch.await();
@@ -340,8 +339,7 @@
      * location in a JSON file upon ignition-off events.
      */
     @Test
-    public void testStoresLocationUponIgnitionOff()
-            throws IOException, RemoteException, InterruptedException {
+    public void testStoresLocationUponIgnitionOff() throws Exception {
         long currentTime = System.currentTimeMillis();
         long elapsedTime = SystemClock.elapsedRealtimeNanos();
         Location timbuktu = new Location(LocationManager.GPS_PROVIDER);
@@ -350,12 +348,11 @@
         timbuktu.setAccuracy(13.75f);
         timbuktu.setTime(currentTime);
         timbuktu.setElapsedRealtimeNanos(elapsedTime);
+        when(mMockSystemInterface.getSystemCarDir()).thenReturn(mTempDirectory);
         when(mMockContext.getSystemService(Context.LOCATION_SERVICE))
                 .thenReturn(mMockLocationManager);
         when(mMockLocationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER))
                 .thenReturn(timbuktu);
-        when(mMockContext.getFileStreamPath("location_cache.json"))
-                .thenReturn(mContext.getFileStreamPath(TEST_FILENAME));
         sendIgnitionOffEvent();
         mLatch.await();
         verify(mMockLocationManager).getLastKnownLocation(LocationManager.GPS_PROVIDER);
@@ -372,8 +369,7 @@
      * location upon power state-changed SUSPEND events.
      */
     @Test
-    public void testStoresLocationUponStateChanged()
-            throws IOException, RemoteException, InterruptedException {
+    public void testStoresLocationUponStateChanged() throws Exception {
         long currentTime = System.currentTimeMillis();
         long elapsedTime = SystemClock.elapsedRealtimeNanos();
         Location timbuktu = new Location(LocationManager.GPS_PROVIDER);
@@ -382,12 +378,11 @@
         timbuktu.setAccuracy(13.75f);
         timbuktu.setTime(currentTime);
         timbuktu.setElapsedRealtimeNanos(elapsedTime);
+        when(mMockSystemInterface.getSystemCarDir()).thenReturn(mTempDirectory);
         when(mMockContext.getSystemService(Context.LOCATION_SERVICE))
                 .thenReturn(mMockLocationManager);
         when(mMockLocationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER))
                 .thenReturn(timbuktu);
-        when(mMockContext.getFileStreamPath("location_cache.json"))
-                .thenReturn(mContext.getFileStreamPath(TEST_FILENAME));
         CompletableFuture<Void> future = new CompletableFuture<>();
         mCarLocationService.onStateChanged(CarPowerStateListener.SHUTDOWN_PREPARE, future);
         mLatch.await();
@@ -427,12 +422,10 @@
                 .thenReturn(mMockLocationManager);
         when(mMockLocationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER))
                 .thenReturn(null);
-        when(mMockContext.getFileStreamPath("location_cache.json"))
-                .thenReturn(mContext.getFileStreamPath(TEST_FILENAME));
         sendIgnitionOffEvent();
         mLatch.await();
         verify(mMockLocationManager).getLastKnownLocation(LocationManager.GPS_PROVIDER);
-        verify(mMockContext).deleteFile("location_cache.json");
+        assertFalse(getLocationCacheFile().exists());
     }
 
     /**
@@ -449,7 +442,7 @@
                 new Intent(LocationManager.MODE_CHANGED_ACTION));
         mLatch.await();
         verify(mMockLocationManager, times(1)).isLocationEnabled();
-        verify(mMockContext).deleteFile("location_cache.json");
+        assertFalse(getLocationCacheFile().exists());
     }
 
     /**
@@ -468,23 +461,27 @@
         mLatch.await();
         verify(mMockLocationManager, times(1))
                 .isProviderEnabled(LocationManager.GPS_PROVIDER);
-        verify(mMockContext).deleteFile("location_cache.json");
+        assertFalse(getLocationCacheFile().exists());
     }
 
     private void writeCacheFile(String json) throws IOException {
-        FileOutputStream fos = mContext.openFileOutput(TEST_FILENAME, Context.MODE_PRIVATE);
+        FileOutputStream fos = new FileOutputStream(getLocationCacheFile());
         fos.write(json.getBytes());
         fos.close();
     }
 
     private String readCacheFile() throws IOException {
-        FileInputStream fis = mContext.openFileInput(TEST_FILENAME);
+        FileInputStream fis = new FileInputStream(getLocationCacheFile());
         String json = new BufferedReader(new InputStreamReader(fis)).lines()
                 .parallel().collect(Collectors.joining("\n"));
         fis.close();
         return json;
     }
 
+    private File getLocationCacheFile() {
+        return new File(mTempDirectory, TEST_FILENAME);
+    }
+
     private void sendIgnitionOffEvent() throws RemoteException {
         mCarLocationService.init();
         ArgumentCaptor<ICarPropertyEventListener> argument =
diff --git a/tests/carservice_unit_test/src/com/android/car/storagemonitoring/CarStorageMonitoringTest.java b/tests/carservice_unit_test/src/com/android/car/storagemonitoring/CarStorageMonitoringTest.java
index deb24f6..50490e9 100644
--- a/tests/carservice_unit_test/src/com/android/car/storagemonitoring/CarStorageMonitoringTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/storagemonitoring/CarStorageMonitoringTest.java
@@ -16,20 +16,32 @@
 
 package com.android.car.storagemonitoring;
 
-import android.car.storagemonitoring.IoStatsEntry;
+import static org.mockito.Mockito.*;
+
 import android.car.storagemonitoring.IoStats;
+import android.car.storagemonitoring.IoStatsEntry;
 import android.car.storagemonitoring.LifetimeWriteInfo;
 import android.car.storagemonitoring.UidIoRecord;
 import android.car.storagemonitoring.WearEstimate;
 import android.car.storagemonitoring.WearEstimateChange;
+import android.hardware.health.V2_0.IHealth;
+import android.hardware.health.V2_0.IHealth.getStorageInfoCallback;
+import android.hardware.health.V2_0.Result;
+import android.hardware.health.V2_0.StorageInfo;
 import android.os.Parcel;
 import android.test.suitebuilder.annotation.MediumTest;
-
-import android.util.SparseArray;
-import com.android.car.test.utils.TemporaryDirectory;
-import com.android.car.test.utils.TemporaryFile;
 import android.util.JsonReader;
 import android.util.JsonWriter;
+import android.util.SparseArray;
+
+import com.android.car.test.utils.TemporaryDirectory;
+import com.android.car.test.utils.TemporaryFile;
+
+import junit.framework.TestCase;
+
+import org.json.JSONObject;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
 
 import java.io.FileWriter;
 import java.io.StringReader;
@@ -40,8 +52,7 @@
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
-import junit.framework.TestCase;
-import org.json.JSONObject;
+
 
 /**
  * Tests the storage monitoring API in CarService.
@@ -50,6 +61,14 @@
 public class CarStorageMonitoringTest extends TestCase {
     static final String TAG = CarStorageMonitoringTest.class.getSimpleName();
 
+    @Mock private IHealth mMockedHal;
+    @Mock private HealthServiceWearInfoProvider.IHealthSupplier mHealthServiceSupplier;
+
+    @Override
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+    }
+
     public void testEMmcWearInformationProvider() throws Exception {
         try (TemporaryFile lifetimeFile = new TemporaryFile(TAG)) {
             try (TemporaryFile eolFile = new TemporaryFile(TAG)) {
@@ -93,6 +112,32 @@
         }
     }
 
+    public void testHealthServiceWearInformationProvider() throws Exception {
+        StorageInfo storageInfo = new StorageInfo();
+        storageInfo.eol = WearInformation.PRE_EOL_INFO_NORMAL;
+        storageInfo.lifetimeA = 3;
+        storageInfo.lifetimeB = WearInformation.UNKNOWN_LIFETIME_ESTIMATE;
+        storageInfo.attr.isInternal = true;
+        HealthServiceWearInfoProvider wearInfoProvider = new HealthServiceWearInfoProvider();
+        wearInfoProvider.setHealthSupplier(mHealthServiceSupplier);
+
+        doReturn(mMockedHal)
+            .when(mHealthServiceSupplier).get(anyString());
+        doAnswer((invocation) -> {
+            ArrayList<StorageInfo> list = new ArrayList<StorageInfo>();
+            list.add(storageInfo);
+            ((IHealth.getStorageInfoCallback) invocation.getArguments()[0])
+                .onValues(Result.SUCCESS, list);
+            return null;
+        }).when(mMockedHal).getStorageInfo(any(getStorageInfoCallback.class));
+        WearInformation wearInformation = wearInfoProvider.load();
+
+        assertNotNull(wearInformation);
+        assertEquals(storageInfo.lifetimeA, wearInformation.lifetimeEstimateA);
+        assertEquals(storageInfo.lifetimeB, wearInformation.lifetimeEstimateB);
+        assertEquals(storageInfo.eol, wearInformation.preEolInfo);
+    }
+
     public void testWearEstimateEquality() {
         WearEstimate wearEstimate1 = new WearEstimate(10, 20);
         WearEstimate wearEstimate2 = new WearEstimate(10, 20);
diff --git a/user/car-user-lib/src/android/car/userlib/CarUserManagerHelper.java b/user/car-user-lib/src/android/car/userlib/CarUserManagerHelper.java
index 2d8ea5d..a411109 100644
--- a/user/car-user-lib/src/android/car/userlib/CarUserManagerHelper.java
+++ b/user/car-user-lib/src/android/car/userlib/CarUserManagerHelper.java
@@ -19,6 +19,7 @@
 import android.Manifest;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
 import android.app.ActivityManager;
 import android.content.BroadcastReceiver;
 import android.content.Context;
@@ -42,6 +43,7 @@
 import com.google.android.collect.Sets;
 
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Set;
@@ -59,6 +61,8 @@
 public class CarUserManagerHelper {
     private static final String TAG = "CarUserManagerHelper";
 
+    private static final int BOOT_USER_NOT_FOUND = -1;
+
     /**
      * Default set of restrictions for Non-Admin users.
      */
@@ -223,36 +227,64 @@
     }
 
     /**
-     * Get user id for the initial user to boot into. This is only applicable for headless
-     * system user model.
+     * Gets the user id for the initial user to boot into. This is only applicable for headless
+     * system user model. This method checks for a system property and will only work for system
+     * apps.
      *
-     * <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.
+     * This method checks for the initial user via three mechanisms in this order:
+     * <ol>
+     *     <li>Check for a boot user override via {@link KEY_BOOT_USER_OVERRIDE_ID}</li>
+     *     <li>Check for the last active user in the system</li>
+     *     <li>Fallback to the smallest user id that is not {@link UserHandle.USER_SYSTEM}</li>
+     * </ol>
      *
-     * @return user id of the last active user or the smallest user id on the device.
+     * If any step fails to retrieve the stored id or the retrieved id does not exist on device,
+     * then it will move onto the next step.
+     *
+     * @return user id of the initial user to boot into on the device.
      */
+    @SystemApi
     public int getInitialUser() {
-        int lastActiveUserId = getLastActiveUser();
+        List<Integer> allUsers = userInfoListToUserIdList(getAllPersistentUsers());
 
-        boolean isUserExist = false;
-        List<UserInfo> allUsers = getAllPersistentUsers();
-        int smallestUserId = Integer.MAX_VALUE;
-        for (UserInfo user : allUsers) {
-            if (user.id == lastActiveUserId) {
-                isUserExist = true;
+        int bootUserOverride = CarProperties.boot_user_override_id().orElse(BOOT_USER_NOT_FOUND);
+
+        // If an override user is present and a real user, return it
+        if (bootUserOverride != BOOT_USER_NOT_FOUND
+                && allUsers.contains(bootUserOverride)) {
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG, "Boot user id override found for initial user, user id: "
+                        + bootUserOverride);
             }
-            smallestUserId = Math.min(user.id, smallestUserId);
+            return bootUserOverride;
         }
 
-        // 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;
+        // If the last active user is not the SYSTEM user and is a real user, return it
+        int lastActiveUser = getLastActiveUser();
+        if (lastActiveUser != UserHandle.USER_SYSTEM
+                && allUsers.contains(lastActiveUser)) {
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG, "Last active user loaded for initial user, user id: "
+                        + lastActiveUser);
+            }
+            return lastActiveUser;
         }
 
-        return lastActiveUserId;
+        // If all else fails, return the smallest user id
+        int returnId = Collections.min(allUsers);
+        if (Log.isLoggable(TAG, Log.DEBUG)) {
+            Log.d(TAG, "Saved ids were invalid. Returning smallest user id, user id: "
+                    + returnId);
+        }
+        return returnId;
+    }
+
+    private List<Integer> userInfoListToUserIdList(List<UserInfo> allUsers) {
+        ArrayList<Integer> list = new ArrayList<>(allUsers.size());
+        for (UserInfo userInfo : allUsers) {
+            list.add(userInfo.id);
+        }
+        return list;
     }
 
     /**