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;
}
/**