Merge "Make drivingstate parcelable classes final."
diff --git a/car-lib/Android.bp b/car-lib/Android.bp
index 1d887fa..4e03d8c 100644
--- a/car-lib/Android.bp
+++ b/car-lib/Android.bp
@@ -109,17 +109,6 @@
],
}
-genrule {
- name: "android-car-last-released-test-api",
- srcs: [
- "api/test-released/*.txt",
- ],
- cmd: "cp -f $$(echo $(in) | tr \" \" \"\\n\" | sort -n | tail -1) $(genDir)/last-released-test-api.txt",
- out: [
- "last-released-test-api.txt",
- ],
-}
-
droidstubs {
name: "android.car-stubs-docs",
defaults: ["android.car-docs-default"],
diff --git a/car-lib/api/current.txt b/car-lib/api/current.txt
index 239ea00..19823a8 100644
--- a/car-lib/api/current.txt
+++ b/car-lib/api/current.txt
@@ -144,6 +144,7 @@
field public static final int EV_CHARGE_PORT_OPEN = 287310602; // 0x1120030a
field public static final int FOG_LIGHTS_STATE = 289410562; // 0x11400e02
field public static final int FOG_LIGHTS_SWITCH = 289410578; // 0x11400e12
+ field public static final int FUEL_CONSUMPTION_UNITS_DISTANCE_OVER_VOLUME = 287311364; // 0x11200604
field public static final int FUEL_DOOR_OPEN = 287310600; // 0x11200308
field public static final int FUEL_LEVEL = 291504903; // 0x11600307
field public static final int FUEL_LEVEL_LOW = 287310853; // 0x11200405
@@ -394,28 +395,8 @@
package android.car.media {
public final class CarAudioManager {
- method @RequiresPermission(android.car.Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS) public android.car.media.CarAudioPatchHandle createAudioPatch(String, int, int);
- method @RequiresPermission(android.car.Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS) @NonNull public String[] getExternalSources();
- method public int getGroupMaxVolume(int);
- method @RequiresPermission(android.car.Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME) public int getGroupMaxVolume(int, int);
- method public int getGroupMinVolume(int);
- method @RequiresPermission(android.car.Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME) public int getGroupMinVolume(int, int);
- method public int getGroupVolume(int);
- method @RequiresPermission(android.car.Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME) public int getGroupVolume(int, int);
- method @NonNull public int[] getUsagesForVolumeGroupId(int);
- method @RequiresPermission(android.car.Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME) @NonNull public int[] getUsagesForVolumeGroupId(int, int);
- method public int getVolumeGroupCount();
- method @RequiresPermission(android.car.Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME) public int getVolumeGroupCount(int);
- method public int getVolumeGroupIdForUsage(int);
- method @RequiresPermission(android.car.Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME) public int getVolumeGroupIdForUsage(int, int);
method public void registerCarVolumeCallback(@NonNull android.car.media.CarAudioManager.CarVolumeCallback);
- method @RequiresPermission(android.car.Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS) public void releaseAudioPatch(android.car.media.CarAudioPatchHandle);
- method @RequiresPermission(android.car.Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME) public void setBalanceTowardRight(float);
- method @RequiresPermission(android.car.Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME) public void setFadeTowardFront(float);
- method public void setGroupVolume(int, int, int);
- method @RequiresPermission(android.car.Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME) public void setGroupVolume(int, int, int, int);
method public void unregisterCarVolumeCallback(@NonNull android.car.media.CarAudioManager.CarVolumeCallback);
- field public static final int PRIMARY_AUDIO_ZONE = 0; // 0x0
}
public abstract static class CarAudioManager.CarVolumeCallback {
@@ -424,13 +405,6 @@
method public void onMasterMuteChanged(int, int);
}
- public final class CarAudioPatchHandle implements android.os.Parcelable {
- ctor public CarAudioPatchHandle(android.media.AudioPatch);
- method public int describeContents();
- method public void writeToParcel(android.os.Parcel, int);
- field public static final android.os.Parcelable.Creator<android.car.media.CarAudioPatchHandle> CREATOR;
- }
-
}
package android.car.navigation {
diff --git a/car-lib/api/system-current.txt b/car-lib/api/system-current.txt
index e468d3b..905e0f5 100644
--- a/car-lib/api/system-current.txt
+++ b/car-lib/api/system-current.txt
@@ -17,6 +17,7 @@
field public static final String PERMISSION_CAR_INSTRUMENT_CLUSTER_CONTROL = "android.car.permission.CAR_INSTRUMENT_CLUSTER_CONTROL";
field public static final String PERMISSION_CAR_POWER = "android.car.permission.CAR_POWER";
field public static final String PERMISSION_CAR_PROJECTION = "android.car.permission.CAR_PROJECTION";
+ field public static final String PERMISSION_CAR_PROJECTION_STATUS = "android.car.permission.ACCESS_CAR_PROJECTION_STATUS";
field public static final String PERMISSION_CAR_TEST_SERVICE = "android.car.permission.CAR_TEST_SERVICE";
field public static final String PERMISSION_CONTROL_APP_BLOCKING = "android.car.permission.CONTROL_APP_BLOCKING";
field public static final String PERMISSION_CONTROL_CAR_CLIMATE = "android.car.permission.CONTROL_CAR_CLIMATE";
@@ -43,15 +44,19 @@
}
public final class CarProjectionManager {
- method public void onCarDisconnected();
- method public void registerProjectionListener(android.car.CarProjectionManager.CarProjectionListener, int);
- method public void registerProjectionRunner(android.content.Intent);
- method public boolean releaseBluetoothProfileInhibit(android.bluetooth.BluetoothDevice, int, android.os.IBinder);
- method public boolean requestBluetoothProfileInhibit(android.bluetooth.BluetoothDevice, int, android.os.IBinder);
- method public void startProjectionAccessPoint(android.car.CarProjectionManager.ProjectionAccessPointCallback);
- method public void stopProjectionAccessPoint();
- method public void unregisterProjectionListener();
- method public void unregisterProjectionRunner(android.content.Intent);
+ method @RequiresPermission(android.car.Car.PERMISSION_CAR_PROJECTION) @NonNull public java.util.List<java.lang.Integer> getAvailableWifiChannels(int);
+ method @RequiresPermission(android.car.Car.PERMISSION_CAR_PROJECTION) @NonNull public android.os.Bundle getProjectionOptions();
+ method @RequiresPermission(android.car.Car.PERMISSION_CAR_PROJECTION) public void registerProjectionListener(@NonNull android.car.CarProjectionManager.CarProjectionListener, int);
+ method @RequiresPermission(android.car.Car.PERMISSION_CAR_PROJECTION) public void registerProjectionRunner(@NonNull android.content.Intent);
+ method @RequiresPermission(android.car.Car.PERMISSION_CAR_PROJECTION_STATUS) public void registerProjectionStatusListener(@NonNull android.car.CarProjectionManager.ProjectionStatusListener);
+ method @RequiresPermission(android.car.Car.PERMISSION_CAR_PROJECTION) public boolean releaseBluetoothProfileInhibit(android.bluetooth.BluetoothDevice, int, android.os.IBinder);
+ method @RequiresPermission(android.car.Car.PERMISSION_CAR_PROJECTION) public boolean requestBluetoothProfileInhibit(@NonNull android.bluetooth.BluetoothDevice, int, @NonNull android.os.IBinder);
+ method @RequiresPermission(android.car.Car.PERMISSION_CAR_PROJECTION) public void startProjectionAccessPoint(@NonNull android.car.CarProjectionManager.ProjectionAccessPointCallback);
+ method @RequiresPermission(android.car.Car.PERMISSION_CAR_PROJECTION) public void stopProjectionAccessPoint();
+ method @RequiresPermission(android.car.Car.PERMISSION_CAR_PROJECTION) public void unregisterProjectionListener();
+ method @RequiresPermission(android.car.Car.PERMISSION_CAR_PROJECTION) public void unregisterProjectionRunner(@NonNull android.content.Intent);
+ method @RequiresPermission(android.car.Car.PERMISSION_CAR_PROJECTION_STATUS) public void unregisterProjectionStatusListener(@NonNull android.car.CarProjectionManager.ProjectionStatusListener);
+ method @RequiresPermission(android.car.Car.PERMISSION_CAR_PROJECTION) public void updateProjectionStatus(@NonNull android.car.projection.ProjectionStatus);
field public static final int PROJECTION_LONG_PRESS_VOICE_SEARCH = 2; // 0x2
field public static final int PROJECTION_VOICE_SEARCH = 1; // 0x1
}
@@ -71,6 +76,10 @@
field public static final int ERROR_TETHERING_DISALLOWED = 4; // 0x4
}
+ public static interface CarProjectionManager.ProjectionStatusListener {
+ method public void onProjectionStatusChanged(int, @Nullable String, @NonNull java.util.List<android.car.projection.ProjectionStatus>);
+ }
+
public final class VehicleAreaDoor {
field public static final int DOOR_HOOD = 268435456; // 0x10000000
field public static final int DOOR_REAR = 536870912; // 0x20000000
@@ -169,6 +178,7 @@
public abstract class InstrumentClusterRenderingService extends android.app.Service {
ctor public InstrumentClusterRenderingService();
+ method @Nullable public android.graphics.Bitmap getBitmap(android.net.Uri);
method @MainThread @Nullable public abstract android.car.cluster.renderer.NavigationRenderer getNavigationRenderer();
method @CallSuper public android.os.IBinder onBind(android.content.Intent);
method @MainThread public void onKeyEvent(@NonNull android.view.KeyEvent);
@@ -735,6 +745,40 @@
}
+package android.car.media {
+
+ public final class CarAudioManager {
+ method @RequiresPermission(android.car.Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS) public android.car.media.CarAudioPatchHandle createAudioPatch(String, int, int);
+ method @NonNull @RequiresPermission(android.car.Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS) public String[] getExternalSources();
+ method @RequiresPermission(android.car.Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME) public int getGroupMaxVolume(int);
+ method @RequiresPermission(android.car.Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME) public int getGroupMaxVolume(int, int);
+ method @RequiresPermission(android.car.Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME) public int getGroupMinVolume(int);
+ method @RequiresPermission(android.car.Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME) public int getGroupMinVolume(int, int);
+ method @RequiresPermission(android.car.Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME) public int getGroupVolume(int);
+ method @RequiresPermission(android.car.Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME) public int getGroupVolume(int, int);
+ method @NonNull @RequiresPermission(android.car.Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME) public int[] getUsagesForVolumeGroupId(int);
+ method @NonNull @RequiresPermission(android.car.Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME) public int[] getUsagesForVolumeGroupId(int, int);
+ method @RequiresPermission(android.car.Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME) public int getVolumeGroupCount();
+ method @RequiresPermission(android.car.Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME) public int getVolumeGroupCount(int);
+ method @RequiresPermission(android.car.Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME) public int getVolumeGroupIdForUsage(int);
+ method @RequiresPermission(android.car.Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME) public int getVolumeGroupIdForUsage(int, int);
+ method @RequiresPermission(android.car.Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS) public void releaseAudioPatch(android.car.media.CarAudioPatchHandle);
+ method @RequiresPermission(android.car.Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME) public void setBalanceTowardRight(float);
+ method @RequiresPermission(android.car.Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME) public void setFadeTowardFront(float);
+ method @RequiresPermission(android.car.Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME) public void setGroupVolume(int, int, int);
+ method @RequiresPermission(android.car.Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME) public void setGroupVolume(int, int, int, int);
+ field public static final int PRIMARY_AUDIO_ZONE = 0; // 0x0
+ }
+
+ public final class CarAudioPatchHandle implements android.os.Parcelable {
+ ctor public CarAudioPatchHandle(android.media.AudioPatch);
+ method public int describeContents();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.car.media.CarAudioPatchHandle> CREATOR;
+ }
+
+}
+
package android.car.navigation {
public class CarNavigationInstrumentCluster implements android.os.Parcelable {
@@ -756,6 +800,66 @@
}
+package android.car.projection {
+
+ public class ProjectionOptions {
+ ctor public ProjectionOptions(android.os.Bundle);
+ method @Nullable public android.app.ActivityOptions getActivityOptions();
+ method @Nullable public android.content.ComponentName getConsentActivity();
+ method public int getUiMode();
+ method @NonNull public android.os.Bundle toBundle();
+ field public static final int UI_MODE_BLENDED = 1; // 0x1
+ field public static final int UI_MODE_FULL_SCREEN = 0; // 0x0
+ }
+
+ public final class ProjectionStatus implements android.os.Parcelable {
+ method @NonNull public static android.car.projection.ProjectionStatus.Builder builder(String, int);
+ method public int describeContents();
+ method @NonNull public java.util.List<android.car.projection.ProjectionStatus.MobileDevice> getConnectedMobileDevices();
+ method @NonNull public android.os.Bundle getExtras();
+ method @NonNull public String getPackageName();
+ method public int getState();
+ method public int getTransport();
+ method public boolean isActive();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.car.projection.ProjectionStatus> CREATOR;
+ field public static final int PROJECTION_STATE_ACTIVE_BACKGROUND = 3; // 0x3
+ field public static final int PROJECTION_STATE_ACTIVE_FOREGROUND = 2; // 0x2
+ field public static final int PROJECTION_STATE_INACTIVE = 0; // 0x0
+ field public static final int PROJECTION_STATE_READY_TO_PROJECT = 1; // 0x1
+ field public static final int PROJECTION_TRANSPORT_NONE = 0; // 0x0
+ field public static final int PROJECTION_TRANSPORT_USB = 1; // 0x1
+ field public static final int PROJECTION_TRANSPORT_WIFI = 2; // 0x2
+ }
+
+ public static final class ProjectionStatus.Builder {
+ method @NonNull public android.car.projection.ProjectionStatus.Builder addMobileDevice(android.car.projection.ProjectionStatus.MobileDevice);
+ method public android.car.projection.ProjectionStatus build();
+ method @NonNull public android.car.projection.ProjectionStatus.Builder setExtras(android.os.Bundle);
+ method @NonNull public android.car.projection.ProjectionStatus.Builder setProjectionTransport(int);
+ }
+
+ public static final class ProjectionStatus.MobileDevice implements android.os.Parcelable {
+ method @NonNull public static android.car.projection.ProjectionStatus.MobileDevice.Builder builder(int, String);
+ method public int describeContents();
+ method @NonNull public java.util.List<java.lang.Integer> getAvailableTransports();
+ method @NonNull public android.os.Bundle getExtras();
+ method public int getId();
+ method @NonNull public String getName();
+ method public boolean isProjecting();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.car.projection.ProjectionStatus.MobileDevice> CREATOR;
+ }
+
+ public static final class ProjectionStatus.MobileDevice.Builder {
+ method @NonNull public android.car.projection.ProjectionStatus.MobileDevice.Builder addTransport(int);
+ method @NonNull public android.car.projection.ProjectionStatus.MobileDevice build();
+ method @NonNull public android.car.projection.ProjectionStatus.MobileDevice.Builder setExtras(android.os.Bundle);
+ method @NonNull public android.car.projection.ProjectionStatus.MobileDevice.Builder setProjecting(boolean);
+ }
+
+}
+
package android.car.storagemonitoring {
public final class CarStorageMonitoringManager {
@@ -895,10 +999,10 @@
package android.car.trust {
public final class CarTrustAgentEnrollmentManager {
- method @RequiresPermission(android.car.Car.PERMISSION_CAR_ENROLL_TRUST) public void activateToken(long);
method @RequiresPermission(android.car.Car.PERMISSION_CAR_ENROLL_TRUST) public void enrollmentHandshakeAccepted();
- method @RequiresPermission(android.car.Car.PERMISSION_CAR_ENROLL_TRUST) public java.util.List<java.lang.Integer> getEnrollmentHandlesForUser(int);
+ method @RequiresPermission(android.car.Car.PERMISSION_CAR_ENROLL_TRUST) @NonNull public java.util.List<java.lang.Long> getEnrollmentHandlesForUser(int);
method @RequiresPermission(android.car.Car.PERMISSION_CAR_ENROLL_TRUST) public void initiateEnrollmentHandshake(android.bluetooth.BluetoothDevice);
+ method @RequiresPermission(android.car.Car.PERMISSION_CAR_ENROLL_TRUST) public boolean isEscrowTokenActive(long, int);
method @RequiresPermission(android.car.Car.PERMISSION_CAR_ENROLL_TRUST) public void revokeTrust(long);
method @RequiresPermission(android.car.Car.PERMISSION_CAR_ENROLL_TRUST) public void setBleCallback(@Nullable android.car.trust.CarTrustAgentEnrollmentManager.CarTrustAgentBleCallback);
method @RequiresPermission(android.car.Car.PERMISSION_CAR_ENROLL_TRUST) public void setEnrollmentCallback(@Nullable android.car.trust.CarTrustAgentEnrollmentManager.CarTrustAgentEnrollmentCallback);
diff --git a/car-lib/src/android/car/Car.java b/car-lib/src/android/car/Car.java
index ab6aa9f..0ab23b7 100644
--- a/car-lib/src/android/car/Car.java
+++ b/car-lib/src/android/car/Car.java
@@ -393,6 +393,14 @@
public static final String PERMISSION_CAR_PROJECTION = "android.car.permission.CAR_PROJECTION";
/**
+ * Permission necessary to access projection status.
+ * @hide
+ */
+ @SystemApi
+ public static final String PERMISSION_CAR_PROJECTION_STATUS =
+ "android.car.permission.ACCESS_CAR_PROJECTION_STATUS";
+
+ /**
* Permission necessary to mock vehicle hal for testing.
* @hide
* @deprecated mocking vehicle HAL in car service is no longer supported.
diff --git a/car-lib/src/android/car/CarProjectionManager.java b/car-lib/src/android/car/CarProjectionManager.java
index 50606de..7706bf7 100644
--- a/car-lib/src/android/car/CarProjectionManager.java
+++ b/car-lib/src/android/car/CarProjectionManager.java
@@ -16,11 +16,18 @@
package android.car;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.bluetooth.BluetoothDevice;
+import android.car.projection.ProjectionOptions;
+import android.car.projection.ProjectionStatus;
+import android.car.projection.ProjectionStatus.ProjectionState;
import android.content.Intent;
import android.net.wifi.WifiConfiguration;
import android.os.Binder;
+import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
@@ -29,7 +36,14 @@
import android.os.RemoteException;
import android.util.Log;
+import com.android.internal.util.Preconditions;
+
import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
/**
* CarProjectionManager allows applications implementing projection to register/unregister itself
@@ -44,6 +58,9 @@
public final class CarProjectionManager implements CarManagerBase {
private static final String TAG = CarProjectionManager.class.getSimpleName();
+ private final Binder mToken = new Binder();
+ private final Object mLock = new Object();
+
/**
* Listener to get projected notifications.
*
@@ -81,10 +98,30 @@
private ProjectionAccessPointCallbackProxy mProjectionAccessPointCallbackProxy;
+ private final Set<ProjectionStatusListener> mProjectionStatusListeners = new LinkedHashSet<>();
+ private CarProjectionStatusListenerImpl mCarProjectionStatusListener;
+
// Only one access point proxy object per process.
private static final IBinder mAccessPointProxyToken = new Binder();
/**
+ * Interface to receive for projection status updates.
+ */
+ public interface ProjectionStatusListener {
+ /**
+ * This method gets invoked if projection status has been changed.
+ *
+ * @param state - current projection state
+ * @param packageName - if projection is currently running either in the foreground or
+ * in the background this argument will contain its package name
+ * @param details - contains detailed information about all currently registered projection
+ * receivers.
+ */
+ void onProjectionStatusChanged(@ProjectionState int state, @Nullable String packageName,
+ @NonNull List<ProjectionStatus> details);
+ }
+
+ /**
* @hide
*/
public CarProjectionManager(IBinder service, Handler handler) {
@@ -107,11 +144,11 @@
* @param listener
* @param voiceSearchFilter Flags of voice search requests to get notification.
*/
- public void registerProjectionListener(CarProjectionListener listener, int voiceSearchFilter) {
- if (listener == null) {
- throw new IllegalArgumentException("null listener");
- }
- synchronized (this) {
+ @RequiresPermission(Car.PERMISSION_CAR_PROJECTION)
+ public void registerProjectionListener(@NonNull CarProjectionListener listener,
+ int voiceSearchFilter) {
+ Preconditions.checkNotNull(listener, "listener cannot be null");
+ synchronized (mLock) {
if (mListener == null || mVoiceSearchFilter != voiceSearchFilter) {
try {
mService.registerProjectionListener(mBinderListener, voiceSearchFilter);
@@ -135,8 +172,9 @@
/**
* Unregister listener and stop listening projection events.
*/
+ @RequiresPermission(Car.PERMISSION_CAR_PROJECTION)
public void unregisterProjectionListener() {
- synchronized (this) {
+ synchronized (mLock) {
try {
mService.unregisterProjectionListener(mBinderListener);
} catch (RemoteException e) {
@@ -152,11 +190,10 @@
* to create reverse binding.
* @param serviceIntent
*/
- public void registerProjectionRunner(Intent serviceIntent) {
- if (serviceIntent == null) {
- throw new IllegalArgumentException("null serviceIntent");
- }
- synchronized (this) {
+ @RequiresPermission(Car.PERMISSION_CAR_PROJECTION)
+ public void registerProjectionRunner(@NonNull Intent serviceIntent) {
+ Preconditions.checkNotNull("serviceIntent cannot be null");
+ synchronized (mLock) {
try {
mService.registerProjectionRunner(serviceIntent);
} catch (RemoteException e) {
@@ -170,11 +207,10 @@
* reverse binding.
* @param serviceIntent
*/
- public void unregisterProjectionRunner(Intent serviceIntent) {
- if (serviceIntent == null) {
- throw new IllegalArgumentException("null serviceIntent");
- }
- synchronized (this) {
+ @RequiresPermission(Car.PERMISSION_CAR_PROJECTION)
+ public void unregisterProjectionRunner(@NonNull Intent serviceIntent) {
+ Preconditions.checkNotNull("serviceIntent cannot be null");
+ synchronized (mLock) {
try {
mService.unregisterProjectionRunner(serviceIntent);
} catch (RemoteException e) {
@@ -183,6 +219,7 @@
}
}
+ /** @hide */
@Override
public void onCarDisconnected() {
// nothing to do
@@ -194,9 +231,13 @@
*
* <p>A process can have only one request to start an access point, subsequent call of this
* method will invalidate previous calls.
+ *
+ * @param callback to receive notifications when access point status changed for the request
*/
- public void startProjectionAccessPoint(ProjectionAccessPointCallback callback) {
- synchronized (this) {
+ @RequiresPermission(Car.PERMISSION_CAR_PROJECTION)
+ public void startProjectionAccessPoint(@NonNull ProjectionAccessPointCallback callback) {
+ Preconditions.checkNotNull(callback, "callback cannot be null");
+ synchronized (mLock) {
Looper looper = mHandler.getLooper();
ProjectionAccessPointCallbackProxy proxy =
new ProjectionAccessPointCallbackProxy(this, looper, callback);
@@ -210,11 +251,32 @@
}
/**
+ * Returns a list of available Wi-Fi channels. A channel is specified as frequency in MHz,
+ * e.g. channel 1 will be represented as 2412 in the list.
+ *
+ * @param band one of the values from {@code android.net.wifi.WifiScanner#WIFI_BAND_*}
+ */
+ @RequiresPermission(Car.PERMISSION_CAR_PROJECTION)
+ public @NonNull List<Integer> getAvailableWifiChannels(int band) {
+ try {
+ int[] channels = mService.getAvailableWifiChannels(band);
+ List<Integer> channelList = new ArrayList<>(channels.length);
+ for (int v : channels) {
+ channelList.add(v);
+ }
+ return channelList;
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Stop Wi-Fi Access Point for wireless projection receiver app.
*/
+ @RequiresPermission(Car.PERMISSION_CAR_PROJECTION)
public void stopProjectionAccessPoint() {
ProjectionAccessPointCallbackProxy proxy;
- synchronized (this) {
+ synchronized (mLock) {
proxy = mProjectionAccessPointCallbackProxy;
mProjectionAccessPointCallbackProxy = null;
}
@@ -239,8 +301,11 @@
* owning the token dies, the request will automatically be released.
* @return True if the profile was successfully inhibited, false if an error occurred.
*/
+ @RequiresPermission(Car.PERMISSION_CAR_PROJECTION)
public boolean requestBluetoothProfileInhibit(
- BluetoothDevice device, int profile, IBinder token) {
+ @NonNull BluetoothDevice device, int profile, @NonNull IBinder token) {
+ Preconditions.checkNotNull(device, "device cannot be null");
+ Preconditions.checkNotNull(token, "token cannot be null");
try {
return mService.requestBluetoothProfileInhibit(device, profile, token);
} catch (RemoteException e) {
@@ -258,8 +323,11 @@
* {@link #requestBluetoothProfileInhibit}.
* @return True if the request was released, false if an error occurred.
*/
+ @RequiresPermission(Car.PERMISSION_CAR_PROJECTION)
public boolean releaseBluetoothProfileInhibit(
BluetoothDevice device, int profile, IBinder token) {
+ Preconditions.checkNotNull(device, "device cannot be null");
+ Preconditions.checkNotNull(token, "token cannot be null");
try {
return mService.releaseBluetoothProfileInhibit(device, profile, token);
} catch (RemoteException e) {
@@ -268,6 +336,108 @@
}
/**
+ * Call this method to report projection status of your app. The aggregated status (from other
+ * projection apps if available) will be broadcasted to interested parties.
+ *
+ * @param status the reported status that will be distributed to the interested listeners
+ *
+ * @see #registerProjectionListener(CarProjectionListener, int)
+ */
+ @RequiresPermission(Car.PERMISSION_CAR_PROJECTION)
+ public void updateProjectionStatus(@NonNull ProjectionStatus status) {
+ Preconditions.checkNotNull(status, "status cannot be null");
+ try {
+ mService.updateProjectionStatus(status, mToken);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Register projection status listener. See {@link ProjectionStatusListener} for details. It is
+ * allowed to register multiple listeners.
+ *
+ * <p>Note: provided listener will be called immediately with the most recent status.
+ *
+ * @param listener the listener to receive notification for any projection status changes
+ */
+ @RequiresPermission(Car.PERMISSION_CAR_PROJECTION_STATUS)
+ public void registerProjectionStatusListener(@NonNull ProjectionStatusListener listener) {
+ Preconditions.checkNotNull(listener, "listener cannot be null");
+ synchronized (mLock) {
+ mProjectionStatusListeners.add(listener);
+
+ if (mCarProjectionStatusListener == null) {
+ mCarProjectionStatusListener = new CarProjectionStatusListenerImpl(this);
+ try {
+ mService.registerProjectionStatusListener(mCarProjectionStatusListener);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ } else {
+ // Already subscribed to Car Service, immediately notify listener with the current
+ // projection status in the event handler thread.
+ mHandler.post(() ->
+ listener.onProjectionStatusChanged(
+ mCarProjectionStatusListener.mCurrentState,
+ mCarProjectionStatusListener.mCurrentPackageName,
+ mCarProjectionStatusListener.mDetails));
+ }
+ }
+ }
+
+ /**
+ * Unregister provided listener from projection status notifications
+ *
+ * @param listener the listener for projection status notifications that was previously
+ * registered with {@link #unregisterProjectionStatusListener(ProjectionStatusListener)}
+ */
+ @RequiresPermission(Car.PERMISSION_CAR_PROJECTION_STATUS)
+ public void unregisterProjectionStatusListener(@NonNull ProjectionStatusListener listener) {
+ Preconditions.checkNotNull(listener, "listener cannot be null");
+ synchronized (mLock) {
+ if (!mProjectionStatusListeners.remove(listener)
+ || !mProjectionStatusListeners.isEmpty()) {
+ return;
+ }
+ unregisterProjectionStatusListenerFromCarServiceLocked();
+ }
+ }
+
+ private void unregisterProjectionStatusListenerFromCarServiceLocked() {
+ try {
+ mService.unregisterProjectionStatusListener(mCarProjectionStatusListener);
+ mCarProjectionStatusListener = null;
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ private void handleProjectionStatusChanged(@ProjectionState int state,
+ String packageName, List<ProjectionStatus> details) {
+ List<ProjectionStatusListener> listeners;
+ synchronized (mLock) {
+ listeners = new ArrayList<>(mProjectionStatusListeners);
+ }
+ for (ProjectionStatusListener listener : listeners) {
+ listener.onProjectionStatusChanged(state, packageName, details);
+ }
+ }
+
+ /**
+ * Returns {@link Bundle} object that contains customization for projection app. This bundle
+ * can be parsed using {@link ProjectionOptions}.
+ */
+ @RequiresPermission(Car.PERMISSION_CAR_PROJECTION)
+ public @NonNull Bundle getProjectionOptions() {
+ try {
+ return mService.getProjectionOptions();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Callback class for applications to receive updates about the LocalOnlyHotspot status.
*/
public abstract static class ProjectionAccessPointCallback {
@@ -345,7 +515,7 @@
private void handleVoiceAssistantRequest(boolean fromLongPress) {
CarProjectionListener listener;
- synchronized (this) {
+ synchronized (mLock) {
if (mListener == null) {
return;
}
@@ -371,4 +541,34 @@
manager.mHandler.post(() -> manager.handleVoiceAssistantRequest(fromLongPress));
}
}
+
+ private static class CarProjectionStatusListenerImpl
+ extends ICarProjectionStatusListener.Stub {
+
+ private @ProjectionState int mCurrentState;
+ private @Nullable String mCurrentPackageName;
+ private List<ProjectionStatus> mDetails = new ArrayList<>(0);
+
+ private final WeakReference<CarProjectionManager> mManagerRef;
+
+ private CarProjectionStatusListenerImpl(CarProjectionManager mgr) {
+ mManagerRef = new WeakReference<>(mgr);
+ }
+
+ @Override
+ public void onProjectionStatusChanged(int projectionState,
+ String packageName,
+ List<ProjectionStatus> details) {
+ CarProjectionManager mgr = mManagerRef.get();
+ if (mgr != null) {
+ mgr.mHandler.post(() -> {
+ mCurrentState = projectionState;
+ mCurrentPackageName = packageName;
+ mDetails = Collections.unmodifiableList(details);
+
+ mgr.handleProjectionStatusChanged(projectionState, packageName, mDetails);
+ });
+ }
+ }
+ }
}
diff --git a/car-lib/src/android/car/ICarProjection.aidl b/car-lib/src/android/car/ICarProjection.aidl
index efaac0d..6e85c45 100644
--- a/car-lib/src/android/car/ICarProjection.aidl
+++ b/car-lib/src/android/car/ICarProjection.aidl
@@ -17,8 +17,11 @@
package android.car;
import android.bluetooth.BluetoothDevice;
+import android.car.projection.ProjectionStatus;
import android.car.ICarProjectionCallback;
+import android.car.ICarProjectionStatusListener;
import android.content.Intent;
+import android.os.Bundle;
import android.os.Messenger;
/**
@@ -69,4 +72,22 @@
/** Undo the effects of requestBluetoothProfileInhibit. */
boolean releaseBluetoothProfileInhibit(
in BluetoothDevice device, in int profile, in IBinder token) = 7;
+
+ /** Reports projection status for a given projection receiver app. */
+ void updateProjectionStatus(in ProjectionStatus status, in IBinder token) = 8;
+
+ /** Registers projection status listener */
+ void registerProjectionStatusListener(in ICarProjectionStatusListener listener) = 9;
+
+ /** Unregister projection status listener */
+ void unregisterProjectionStatusListener(in ICarProjectionStatusListener listener) = 10;
+
+ /**
+ * Returns options for projection receiver app that can be un-packed using
+ * {@link android.car.projection.ProjectionOptions} class.
+ */
+ Bundle getProjectionOptions() = 11;
+
+ /** Returns a list of available Wi-Fi channels */
+ int[] getAvailableWifiChannels(int band) = 12;
}
diff --git a/car-lib/src/android/car/ICarProjectionStatusListener.aidl b/car-lib/src/android/car/ICarProjectionStatusListener.aidl
new file mode 100644
index 0000000..cac48a0
--- /dev/null
+++ b/car-lib/src/android/car/ICarProjectionStatusListener.aidl
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.car;
+
+import android.car.projection.ProjectionStatus;
+
+/**
+ * Listener interface to notify interested parties of projection status change.
+ *
+ * @hide
+ */
+oneway interface ICarProjectionStatusListener {
+ void onProjectionStatusChanged(int projectionState,
+ in String activeProjectionPackageName,
+ in List<ProjectionStatus> details) = 0;
+}
diff --git a/car-lib/src/android/car/VehiclePropertyIds.java b/car-lib/src/android/car/VehiclePropertyIds.java
index ce10d2a..cfc6e34 100644
--- a/car-lib/src/android/car/VehiclePropertyIds.java
+++ b/car-lib/src/android/car/VehiclePropertyIds.java
@@ -295,6 +295,10 @@
*/
public static final int EV_BATTERY_DISPLAY_UNITS = 289408515;
/**
+ * Fuel consumption units for display
+ */
+ public static final int FUEL_CONSUMPTION_UNITS_DISTANCE_OVER_VOLUME = 287311364;
+ /**
* Outside temperature
*/
public static final int ENV_OUTSIDE_TEMPERATURE = 291505923;
@@ -776,6 +780,9 @@
if (o == EV_BATTERY_DISPLAY_UNITS) {
return "EV_BATTERY_DISPLAY_UNITS";
}
+ if (o == FUEL_CONSUMPTION_UNITS_DISTANCE_OVER_VOLUME) {
+ return "FUEL_CONSUMPTION_UNITS_DISTANCE_OVER_VOLUME";
+ }
if (o == ENV_OUTSIDE_TEMPERATURE) {
return "ENV_OUTSIDE_TEMPERATURE";
}
diff --git a/car-lib/src/android/car/VehicleUnit.java b/car-lib/src/android/car/VehicleUnit.java
index c9859dc..463d53b 100644
--- a/car-lib/src/android/car/VehicleUnit.java
+++ b/car-lib/src/android/car/VehicleUnit.java
@@ -43,7 +43,8 @@
public static final int KELVIN = 0x32;
public static final int MILLILITER = 0x40;
public static final int LITER = 0x41;
- public static final int GALLON = 0x42;
+ public static final int US_GALLON = 0x42;
+ public static final int IMPERIAL_GALLON = 0x43;
public static final int NANO_SECS = 0x50;
public static final int SECS = 0x53;
public static final int YEAR = 0x59;
@@ -74,7 +75,8 @@
KELVIN,
MILLILITER,
LITER,
- GALLON,
+ US_GALLON,
+ IMPERIAL_GALLON,
NANO_SECS,
SECS,
YEAR,
diff --git a/car-lib/src/android/car/cluster/renderer/InstrumentClusterRenderingService.java b/car-lib/src/android/car/cluster/renderer/InstrumentClusterRenderingService.java
index 4a9e5f5..f00d6ff 100644
--- a/car-lib/src/android/car/cluster/renderer/InstrumentClusterRenderingService.java
+++ b/car-lib/src/android/car/cluster/renderer/InstrumentClusterRenderingService.java
@@ -32,11 +32,16 @@
import android.content.ComponentName;
import android.content.Intent;
import android.content.pm.PackageManager;
+import android.content.pm.ProviderInfo;
import android.content.pm.ResolveInfo;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
+import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.os.UserHandle;
import android.util.Log;
@@ -47,14 +52,19 @@
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
import java.util.List;
import java.util.Objects;
+import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Supplier;
+import java.util.stream.Collectors;
/**
- * A service that used for interaction between Car Service and Instrument Cluster. Car Service may
+ * A service used for interaction between Car Service and Instrument Cluster. Car Service may
* provide internal navigation binder interface to Navigation App and all notifications will be
* eventually land in the {@link NavigationRenderer} returned by {@link #getNavigationRenderer()}.
*
@@ -85,15 +95,45 @@
private static class ContextOwner {
final int mUid;
final int mPid;
+ final Set<String> mPackageNames;
+ final Set<String> mAuthorities;
- ContextOwner(int uid, int pid) {
+ ContextOwner(int uid, int pid, PackageManager packageManager) {
mUid = uid;
mPid = pid;
+ String[] packageNames = uid != 0 ? packageManager.getPackagesForUid(uid)
+ : null;
+ mPackageNames = packageNames != null
+ ? Collections.unmodifiableSet(new HashSet<>(Arrays.asList(packageNames)))
+ : Collections.emptySet();
+ mAuthorities = Collections.unmodifiableSet(mPackageNames.stream()
+ .map(packageName -> getAuthoritiesForPackage(packageManager, packageName))
+ .flatMap(Collection::stream)
+ .collect(Collectors.toSet()));
}
@Override
public String toString() {
- return "{uid: " + mUid + ", pid: " + mPid + "}";
+ return "{uid: " + mUid + ", pid: " + mPid + ", packagenames: " + mPackageNames
+ + ", authorities: " + mAuthorities + "}";
+ }
+
+ private List<String> getAuthoritiesForPackage(PackageManager packageManager,
+ String packageName) {
+ try {
+ ProviderInfo[] providers = packageManager.getPackageInfo(packageName,
+ PackageManager.GET_PROVIDERS).providers;
+ if (providers == null) {
+ return Collections.emptyList();
+ }
+ return Arrays.stream(providers)
+ .map(provider -> provider.authority)
+ .collect(Collectors.toList());
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.w(TAG, "Package name not found while retrieving content provider authorities: "
+ + packageName);
+ return Collections.emptyList();
+ }
}
}
@@ -199,7 +239,7 @@
*/
@Nullable
private ComponentName getNavigationComponentByOwner(ContextOwner contextOwner) {
- for (String packageName : getPackageNamesForUid(contextOwner)) {
+ for (String packageName : contextOwner.mPackageNames) {
ComponentName component = getComponentFromPackage(packageName);
if (component != null) {
if (Log.isLoggable(TAG, Log.DEBUG)) {
@@ -211,14 +251,6 @@
return null;
}
- private String[] getPackageNamesForUid(ContextOwner contextOwner) {
- if (contextOwner == null || contextOwner.mUid == 0 || contextOwner.mPid == 0) {
- return new String[0];
- }
- String[] packageNames = getPackageManager().getPackagesForUid(contextOwner.mUid);
- return packageNames != null ? packageNames : new String[0];
- }
-
private ContextOwner getNavigationContextOwner() {
synchronized (mLock) {
return mNavContextOwner;
@@ -338,8 +370,7 @@
writer.println("activity options: " + mActivityOptions);
writer.println("activity state: " + mActivityState);
writer.println("current nav component: " + mNavigationComponent);
- writer.println("current nav packages: " + Arrays.toString(getPackageNamesForUid(
- getNavigationContextOwner())));
+ writer.println("current nav packages: " + getNavigationContextOwner().mPackageNames);
}
private class RendererBinder extends IInstrumentCluster.Stub {
@@ -356,8 +387,11 @@
@Override
public void setNavigationContextOwner(int uid, int pid) throws RemoteException {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "Updating navigation ownership to uid: " + uid + ", pid: " + pid);
+ }
synchronized (mLock) {
- mNavContextOwner = new ContextOwner(uid, pid);
+ mNavContextOwner = new ContextOwner(uid, pid, getPackageManager());
}
mUiHandler.post(InstrumentClusterRenderingService.this::updateNavigationActivity);
}
@@ -419,4 +453,57 @@
}
return result.get();
}
+
+ /**
+ * Fetches a bitmap from the navigation context owner (application holding navigation focus).
+ * It returns null if:
+ * <ul>
+ * <li>there is no navigation context owner
+ * <li>or if the {@link Uri} is invalid
+ * <li>or if it references a process other than the current navigation context owner
+ * </ul>
+ * This is a costly operation. Returned bitmaps should be cached and fetching should be done on
+ * a secondary thread.
+ */
+ @Nullable
+ public Bitmap getBitmap(Uri uri) {
+ try {
+ ContextOwner contextOwner = getNavigationContextOwner();
+ if (contextOwner == null) {
+ Log.e(TAG, "No context owner available while fetching: " + uri);
+ return null;
+ }
+
+ String host = uri.getHost();
+
+ if (!contextOwner.mAuthorities.contains(host)) {
+ Log.e(TAG, "Uri points to an authority not handled by the current context owner: "
+ + uri + " (valid authorities: " + contextOwner.mAuthorities + ")");
+ return null;
+ }
+
+ // Add user to URI to make the request to the right instance of content provider
+ // (see ContentProvider#getUserIdFromAuthority()).
+ int userId = UserHandle.getUserId(contextOwner.mUid);
+ Uri filteredUid = uri.buildUpon().encodedAuthority(userId + "@" + host).build();
+
+ // Fetch the bitmap
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "Requesting bitmap: " + uri);
+ }
+ ParcelFileDescriptor fileDesc = getContentResolver()
+ .openFileDescriptor(filteredUid, "r");
+ if (fileDesc != null) {
+ Bitmap bitmap = BitmapFactory.decodeFileDescriptor(fileDesc.getFileDescriptor());
+ fileDesc.close();
+ return bitmap;
+ } else {
+ Log.e(TAG, "Failed to create pipe for uri string: " + uri);
+ }
+ } catch (Throwable e) {
+ Log.e(TAG, "Unable to fetch uri: " + uri, e);
+ }
+
+ return null;
+ }
}
diff --git a/car-lib/src/android/car/cluster/renderer/NavigationRenderer.java b/car-lib/src/android/car/cluster/renderer/NavigationRenderer.java
index 4681a8b..644e17e 100644
--- a/car-lib/src/android/car/cluster/renderer/NavigationRenderer.java
+++ b/car-lib/src/android/car/cluster/renderer/NavigationRenderer.java
@@ -18,7 +18,6 @@
import android.annotation.SystemApi;
import android.annotation.UiThread;
import android.car.navigation.CarNavigationInstrumentCluster;
-import android.graphics.Bitmap;
import android.os.Bundle;
/**
@@ -32,10 +31,10 @@
/**
* Returns properties of instrument cluster for navigation.
*/
- abstract public CarNavigationInstrumentCluster getNavigationProperties();
+ public abstract CarNavigationInstrumentCluster getNavigationProperties();
/**
* Called when an event is fired to change the navigation state.
*/
- abstract public void onEvent(int eventType, Bundle bundle);
+ public abstract void onEvent(int eventType, Bundle bundle);
}
diff --git a/car-lib/src/android/car/media/CarAudioManager.java b/car-lib/src/android/car/media/CarAudioManager.java
index d3fb4bd..9fb1fa5 100644
--- a/car-lib/src/android/car/media/CarAudioManager.java
+++ b/car-lib/src/android/car/media/CarAudioManager.java
@@ -17,6 +17,7 @@
import android.annotation.NonNull;
import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.car.Car;
import android.car.CarLibLog;
@@ -52,7 +53,9 @@
/**
* Zone id of the primary audio zone.
+ * @hide
*/
+ @SystemApi
public static final int PRIMARY_AUDIO_ZONE = 0x0;
private final ICarAudio mService;
@@ -91,7 +94,10 @@
* Sets the volume index for a volume group in primary zone.
*
* @see {@link #setGroupVolume(int, int, int, int)}
+ * @hide
*/
+ @SystemApi
+ @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME)
public void setGroupVolume(int groupId, int index, int flags) {
setGroupVolume(PRIMARY_AUDIO_ZONE, groupId, index, flags);
}
@@ -105,7 +111,9 @@
* {@link #getGroupMaxVolume(int, int)} for the largest valid value.
* @param flags One or more flags (e.g., {@link android.media.AudioManager#FLAG_SHOW_UI},
* {@link android.media.AudioManager#FLAG_PLAY_SOUND})
+ * @hide
*/
+ @SystemApi
@RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME)
public void setGroupVolume(int zoneId, int groupId, int index, int flags) {
try {
@@ -119,7 +127,10 @@
* Returns the maximum volume index for a volume group in primary zone.
*
* @see {@link #getGroupMaxVolume(int, int)}
+ * @hide
*/
+ @SystemApi
+ @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME)
public int getGroupMaxVolume(int groupId) {
return getGroupMaxVolume(PRIMARY_AUDIO_ZONE, groupId);
}
@@ -130,7 +141,9 @@
* @param zoneId The zone id whose volume group is queried.
* @param groupId The volume group id whose maximum volume index is returned.
* @return The maximum valid volume index for the given group.
+ * @hide
*/
+ @SystemApi
@RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME)
public int getGroupMaxVolume(int zoneId, int groupId) {
try {
@@ -144,7 +157,10 @@
* Returns the minimum volume index for a volume group in primary zone.
*
* @see {@link #getGroupMinVolume(int, int)}
+ * @hide
*/
+ @SystemApi
+ @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME)
public int getGroupMinVolume(int groupId) {
return getGroupMinVolume(PRIMARY_AUDIO_ZONE, groupId);
}
@@ -155,7 +171,9 @@
* @param zoneId The zone id whose volume group is queried.
* @param groupId The volume group id whose minimum volume index is returned.
* @return The minimum valid volume index for the given group, non-negative
+ * @hide
*/
+ @SystemApi
@RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME)
public int getGroupMinVolume(int zoneId, int groupId) {
try {
@@ -169,7 +187,10 @@
* Returns the current volume index for a volume group in primary zone.
*
* @see {@link #getGroupVolume(int, int)}
+ * @hide
*/
+ @SystemApi
+ @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME)
public int getGroupVolume(int groupId) {
return getGroupVolume(PRIMARY_AUDIO_ZONE, groupId);
}
@@ -183,7 +204,9 @@
*
* @see #getGroupMaxVolume(int, int)
* @see #setGroupVolume(int, int, int, int)
+ * @hide
*/
+ @SystemApi
@RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME)
public int getGroupVolume(int zoneId, int groupId) {
try {
@@ -200,7 +223,9 @@
* fully toward the front. 0.0 means evenly balanced.
*
* @see #setBalanceTowardRight(float)
+ * @hide
*/
+ @SystemApi
@RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME)
public void setFadeTowardFront(float value) {
try {
@@ -217,7 +242,9 @@
* fully toward the right. 0.0 means evenly balanced.
*
* @see #setFadeTowardFront(float)
+ * @hide
*/
+ @SystemApi
@RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME)
public void setBalanceTowardRight(float value) {
try {
@@ -237,7 +264,9 @@
*
* @see #createAudioPatch(String, int, int)
* @see #releaseAudioPatch(CarAudioPatchHandle)
+ * @hide
*/
+ @SystemApi
@RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS)
public @NonNull String[] getExternalSources() {
try {
@@ -264,7 +293,9 @@
*
* @see #getExternalSources()
* @see #releaseAudioPatch(CarAudioPatchHandle)
+ * @hide
*/
+ @SystemApi
@RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS)
public CarAudioPatchHandle createAudioPatch(String sourceAddress,
@AudioAttributes.AttributeUsage int usage, int gainInMillibels) {
@@ -283,7 +314,9 @@
*
* @see #getExternalSources()
* @see #createAudioPatch(String, int, int)
+ * @hide
*/
+ @SystemApi
@RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS)
public void releaseAudioPatch(CarAudioPatchHandle patch) {
try {
@@ -297,7 +330,10 @@
* Gets the count of available volume groups in primary zone.
*
* @see {@link #getVolumeGroupCount(int)}
+ * @hide
*/
+ @SystemApi
+ @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME)
public int getVolumeGroupCount() {
return getVolumeGroupCount(PRIMARY_AUDIO_ZONE);
}
@@ -307,7 +343,9 @@
*
* @param zoneId The zone id whois count of volume groups is queried.
* @return Count of volume groups
+ * @hide
*/
+ @SystemApi
@RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME)
public int getVolumeGroupCount(int zoneId) {
try {
@@ -321,7 +359,10 @@
* Gets the volume group id for a given {@link AudioAttributes} usage in primary zone.
*
* @see {@link #getVolumeGroupIdForUsage(int, int)}
+ * @hide
*/
+ @SystemApi
+ @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME)
public int getVolumeGroupIdForUsage(@AudioAttributes.AttributeUsage int usage) {
return getVolumeGroupIdForUsage(PRIMARY_AUDIO_ZONE, usage);
}
@@ -332,7 +373,9 @@
* @param zoneId The zone id whose volume group is queried.
* @param usage The {@link AudioAttributes} usage to get a volume group from.
* @return The volume group id where the usage belongs to
+ * @hide
*/
+ @SystemApi
@RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME)
public int getVolumeGroupIdForUsage(int zoneId, @AudioAttributes.AttributeUsage int usage) {
try {
@@ -346,7 +389,10 @@
* Gets array of {@link AudioAttributes} usages for a volume group in primary zone.
*
* @see {@link #getUsagesForVolumeGroupId(int, int)}
+ * @hide
*/
+ @SystemApi
+ @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME)
public @NonNull int[] getUsagesForVolumeGroupId(int groupId) {
return getUsagesForVolumeGroupId(PRIMARY_AUDIO_ZONE, groupId);
}
@@ -357,7 +403,9 @@
* @param zoneId The zone id whose volume group is queried.
* @param groupId The volume group id whose associated audio usages is returned.
* @return Array of {@link AudioAttributes} usages for a given volume group id
+ * @hide
*/
+ @SystemApi
@RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME)
public @NonNull int[] getUsagesForVolumeGroupId(int zoneId, int groupId) {
try {
diff --git a/car-lib/src/android/car/media/CarAudioPatchHandle.java b/car-lib/src/android/car/media/CarAudioPatchHandle.java
index bbcc791..77dfc23 100644
--- a/car-lib/src/android/car/media/CarAudioPatchHandle.java
+++ b/car-lib/src/android/car/media/CarAudioPatchHandle.java
@@ -16,6 +16,7 @@
package android.car.media;
+import android.annotation.SystemApi;
import android.media.AudioDevicePort;
import android.media.AudioPatch;
import android.os.Parcel;
@@ -27,7 +28,9 @@
* A class to encapsulate the handle for a system level audio patch. This is used
* to provide a "safe" way for permitted applications to route automotive audio sources
* outside of android.
+ * @hide
*/
+@SystemApi
public final class CarAudioPatchHandle implements Parcelable {
// This is enough information to uniquely identify a patch to the system
diff --git a/car-lib/src/android/car/projection/ProjectionOptions.java b/car-lib/src/android/car/projection/ProjectionOptions.java
new file mode 100644
index 0000000..26b8140
--- /dev/null
+++ b/car-lib/src/android/car/projection/ProjectionOptions.java
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.projection;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.app.ActivityOptions;
+import android.content.ComponentName;
+import android.os.Bundle;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * This class holds OEM customization for projection receiver app. It is created by Car Service.
+ *
+ * @hide
+ */
+@SystemApi
+public class ProjectionOptions {
+ private static final String KEY_PREFIX = "android.car.projection.";
+
+ /** Immersive full screen mode (all system bars are hidden) */
+ public static final int UI_MODE_FULL_SCREEN = 0;
+
+ /** Show status and navigation bars. */
+ public static final int UI_MODE_BLENDED = 1;
+
+ private static final int UI_MODE_DEFAULT = UI_MODE_FULL_SCREEN;
+
+ /** @hide */
+ @IntDef({UI_MODE_FULL_SCREEN, UI_MODE_BLENDED})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ProjectionUiMode {}
+
+ private static final String KEY_ACTIVITY_OPTIONS = KEY_PREFIX + "activityOptions";
+ private static final String KEY_UI_MODE = KEY_PREFIX + "systemUiFlags";
+ private static final String KEY_CONSENT_ACTIVITY = KEY_PREFIX + "consentActivity";
+
+ private final ActivityOptions mActivityOptions;
+ private final int mUiMode;
+ private final ComponentName mConsentActivity;
+
+ /**
+ * Creates new instance for given {@code Bundle}
+ *
+ * @param bundle contains OEM specific information
+ */
+ public ProjectionOptions(Bundle bundle) {
+ Bundle activityOptionsBundle = bundle.getBundle(KEY_ACTIVITY_OPTIONS);
+ mActivityOptions = activityOptionsBundle != null
+ ? new ActivityOptions(activityOptionsBundle) : null;
+ mUiMode = bundle.getInt(KEY_UI_MODE, UI_MODE_DEFAULT);
+ mConsentActivity = bundle.getParcelable(KEY_CONSENT_ACTIVITY);
+ }
+
+ private ProjectionOptions(Builder builder) {
+ mActivityOptions = builder.mActivityOptions;
+ mUiMode = builder.mUiMode;
+ mConsentActivity = builder.mConsentActivity;
+ }
+
+ /**
+ * Returns combination of flags from View.SYSTEM_UI_FLAG_* which will be used by projection
+ * receiver app during rendering.
+ */
+ public @ProjectionUiMode int getUiMode() {
+ return mUiMode;
+ }
+
+ /**
+ * Returns {@link ActivityOptions} that needs to be applied when launching projection activity
+ */
+ public @Nullable ActivityOptions getActivityOptions() {
+ return mActivityOptions;
+ }
+
+ /**
+ * Returns package/activity name of the consent activity provided by OEM which needs to be shown
+ * for all mobile devices unless user accepted the consent.
+ *
+ * <p>If the method returns null then consent dialog should not be shown.
+ */
+ public @Nullable ComponentName getConsentActivity() {
+ return mConsentActivity;
+ }
+
+ /** Converts current object to {@link Bundle} */
+ public @NonNull Bundle toBundle() {
+ Bundle bundle = new Bundle();
+ if (mActivityOptions != null) {
+ bundle.putBundle(KEY_ACTIVITY_OPTIONS, mActivityOptions.toBundle());
+ }
+ bundle.putParcelable(KEY_CONSENT_ACTIVITY, mConsentActivity);
+ if (mUiMode != UI_MODE_DEFAULT) {
+ bundle.putInt(KEY_UI_MODE, mUiMode);
+ }
+ return bundle;
+ }
+
+ /** @hide */
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ /** @hide */
+ public static class Builder {
+ private ActivityOptions mActivityOptions;
+ private int mUiMode = UI_MODE_DEFAULT;
+ private ComponentName mConsentActivity;
+
+ /** Sets {@link ActivityOptions} to launch projection activity. */
+ public Builder setProjectionActivityOptions(ActivityOptions activityOptions) {
+ mActivityOptions = activityOptions;
+ return this;
+ }
+
+ /** Set UI for projection activity. It can be one of {@code UI_MODE_*} constants. */
+ public Builder setUiMode(@ProjectionUiMode int uiMode) {
+ mUiMode = uiMode;
+ return this;
+ }
+
+ /** Sets consent activity which will be shown before starting projection. */
+ public Builder setConsentActivity(ComponentName consentActivity) {
+ mConsentActivity = consentActivity;
+ return this;
+ }
+
+ /** Creates an instance of {@link android.car.projection.ProjectionOptions} */
+ public ProjectionOptions build() {
+ return new ProjectionOptions(this);
+ }
+ }
+
+ /** @hide */
+ @Override
+ public String toString() {
+ return toBundle().toString();
+ }
+}
diff --git a/car-lib/src/android/car/projection/ProjectionStatus.aidl b/car-lib/src/android/car/projection/ProjectionStatus.aidl
new file mode 100644
index 0000000..f2ad77a
--- /dev/null
+++ b/car-lib/src/android/car/projection/ProjectionStatus.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.car.projection;
+
+parcelable ProjectionStatus;
\ No newline at end of file
diff --git a/car-lib/src/android/car/projection/ProjectionStatus.java b/car-lib/src/android/car/projection/ProjectionStatus.java
new file mode 100644
index 0000000..37d0aee
--- /dev/null
+++ b/car-lib/src/android/car/projection/ProjectionStatus.java
@@ -0,0 +1,424 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.projection;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.IntArray;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * This class encapsulates information about projection status and connected mobile devices.
+ *
+ * <p>Since the number of connected devices expected to be small we include information about
+ * connected devices in every status update.
+ *
+ * @hide
+ */
+@SystemApi
+public final class ProjectionStatus implements Parcelable {
+ /** This state indicates that projection is not actively running and no compatible mobile
+ * devices available. */
+ public static final int PROJECTION_STATE_INACTIVE = 0;
+
+ /** At least one phone connected and ready to project. */
+ public static final int PROJECTION_STATE_READY_TO_PROJECT = 1;
+
+ /** Projecting in the foreground */
+ public static final int PROJECTION_STATE_ACTIVE_FOREGROUND = 2;
+
+ /** Projection is running in the background */
+ public static final int PROJECTION_STATE_ACTIVE_BACKGROUND = 3;
+
+ private static final int PROJECTION_STATE_MAX = PROJECTION_STATE_ACTIVE_BACKGROUND;
+
+ /** This status is used when projection is not actively running */
+ public static final int PROJECTION_TRANSPORT_NONE = 0;
+
+ /** This status is used when projection is not actively running */
+ public static final int PROJECTION_TRANSPORT_USB = 1;
+
+ /** This status is used when projection is not actively running */
+ public static final int PROJECTION_TRANSPORT_WIFI = 2;
+
+ private static final int PROJECTION_TRANSPORT_MAX = PROJECTION_TRANSPORT_WIFI;
+
+ /** @hide */
+ @IntDef(value = {
+ PROJECTION_TRANSPORT_NONE,
+ PROJECTION_TRANSPORT_USB,
+ PROJECTION_TRANSPORT_WIFI,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ProjectionTransport {}
+
+ /** @hide */
+ @IntDef(value = {
+ PROJECTION_STATE_INACTIVE,
+ PROJECTION_STATE_READY_TO_PROJECT,
+ PROJECTION_STATE_ACTIVE_FOREGROUND,
+ PROJECTION_STATE_ACTIVE_BACKGROUND,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ProjectionState {}
+
+ private final String mPackageName;
+ private final int mState;
+ private final int mTransport;
+ private final List<MobileDevice> mConnectedMobileDevices;
+ private final Bundle mExtras;
+
+ /** Creator for this class. Required to have in parcelable implementations. */
+ public static final Creator<ProjectionStatus> CREATOR = new Creator<ProjectionStatus>() {
+ @Override
+ public ProjectionStatus createFromParcel(Parcel source) {
+ return new ProjectionStatus(source);
+ }
+
+ @Override
+ public ProjectionStatus[] newArray(int size) {
+ return new ProjectionStatus[size];
+ }
+ };
+
+ private ProjectionStatus(Builder builder) {
+ mPackageName = builder.mPackageName;
+ mState = builder.mState;
+ mTransport = builder.mTransport;
+ mConnectedMobileDevices = new ArrayList<>(builder.mMobileDevices);
+ mExtras = builder.mExtras == null ? null : new Bundle(builder.mExtras);
+ }
+
+ private ProjectionStatus(Parcel source) {
+ mPackageName = source.readString();
+ mState = source.readInt();
+ mTransport = source.readInt();
+ mExtras = source.readBundle(getClass().getClassLoader());
+ mConnectedMobileDevices = source.createTypedArrayList(MobileDevice.CREATOR);
+ }
+
+ /** Parcelable implementation */
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(mPackageName);
+ dest.writeInt(mState);
+ dest.writeInt(mTransport);
+ dest.writeBundle(mExtras);
+ dest.writeTypedList(mConnectedMobileDevices);
+ }
+
+ /** Returns projection state which could be one of the constants starting with
+ * {@code #PROJECTION_STATE_}.
+ */
+ public @ProjectionState int getState() {
+ return mState;
+ }
+
+ /** Returns package name of the projection receiver app. */
+ public @NonNull String getPackageName() {
+ return mPackageName;
+ }
+
+ /** Returns extra information provided by projection receiver app */
+ public @NonNull Bundle getExtras() {
+ return mExtras == null ? new Bundle() : new Bundle(mExtras);
+ }
+
+ /** Returns true if currently projecting either in the foreground or in the background. */
+ public boolean isActive() {
+ return mState == PROJECTION_STATE_ACTIVE_BACKGROUND
+ || mState == PROJECTION_STATE_ACTIVE_FOREGROUND;
+ }
+
+ /** Returns transport which is used for active projection or
+ * {@link #PROJECTION_TRANSPORT_NONE} if projection is not running.
+ */
+ public @ProjectionTransport int getTransport() {
+ return mTransport;
+ }
+
+ /** Returns a list of currently connected mobile devices. */
+ public @NonNull List<MobileDevice> getConnectedMobileDevices() {
+ return new ArrayList<>(mConnectedMobileDevices);
+ }
+
+ /**
+ * Returns new {@link Builder} instance.
+ *
+ * @param packageName package name that will be associated with this status
+ * @param state current projection state, must be one of the {@code PROJECTION_STATE_*}
+ */
+ @NonNull
+ public static Builder builder(String packageName, @ProjectionState int state) {
+ return new Builder(packageName, state);
+ }
+
+ /** Builder class for {@link ProjectionStatus} */
+ public static final class Builder {
+ private final int mState;
+ private final String mPackageName;
+ private int mTransport = PROJECTION_TRANSPORT_NONE;
+ private List<MobileDevice> mMobileDevices = new ArrayList<>();
+ private Bundle mExtras;
+
+ private Builder(String packageName, @ProjectionState int state) {
+ if (packageName == null) {
+ throw new IllegalArgumentException("Package name can't be null");
+ }
+ if (state < 0 || state > PROJECTION_STATE_MAX) {
+ throw new IllegalArgumentException("Invalid projection state: " + state);
+ }
+ mPackageName = packageName;
+ mState = state;
+ }
+
+ /**
+ * Sets the transport which is used for currently projecting phone if any.
+ *
+ * @param transport transport of current projection, must be one of the
+ * {@code PROJECTION_TRANSPORT_*}
+ */
+ public @NonNull Builder setProjectionTransport(@ProjectionTransport int transport) {
+ checkProjectionTransport(transport);
+ mTransport = transport;
+ return this;
+ }
+
+ /**
+ * Add connected mobile device
+ *
+ * @param mobileDevice connected mobile device
+ * @return this builder
+ */
+ public @NonNull Builder addMobileDevice(MobileDevice mobileDevice) {
+ mMobileDevices.add(mobileDevice);
+ return this;
+ }
+
+ /**
+ * Add extra information.
+ *
+ * @param extras may contain an extra information that can be passed from the projection
+ * app to the projection status listeners
+ * @return this builder
+ */
+ public @NonNull Builder setExtras(Bundle extras) {
+ mExtras = extras;
+ return this;
+ }
+
+ /** Creates {@link ProjectionStatus} object. */
+ public ProjectionStatus build() {
+ return new ProjectionStatus(this);
+ }
+ }
+
+ private static void checkProjectionTransport(@ProjectionTransport int transport) {
+ if (transport < 0 || transport > PROJECTION_TRANSPORT_MAX) {
+ throw new IllegalArgumentException("Invalid projection transport: " + transport);
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "ProjectionStatus{"
+ + "mPackageName='" + mPackageName + '\''
+ + ", mState=" + mState
+ + ", mTransport=" + mTransport
+ + ", mConnectedMobileDevices=" + mConnectedMobileDevices
+ + (mExtras != null ? " (has extras)" : "")
+ + '}';
+ }
+
+ /** Class that represents information about connected mobile device. */
+ public static final class MobileDevice implements Parcelable {
+ private final int mId;
+ private final String mName;
+ private final int[] mAvailableTransports;
+ private final boolean mProjecting;
+ private final Bundle mExtras;
+
+ /** Creator for this class. Required to have in parcelable implementations. */
+ public static final Creator<MobileDevice> CREATOR = new Creator<MobileDevice>() {
+ @Override
+ public MobileDevice createFromParcel(Parcel source) {
+ return new MobileDevice(source);
+ }
+
+ @Override
+ public MobileDevice[] newArray(int size) {
+ return new MobileDevice[size];
+ }
+ };
+
+ private MobileDevice(Builder builder) {
+ mId = builder.mId;
+ mName = builder.mName;
+ mAvailableTransports = builder.mAvailableTransports.toArray();
+ mProjecting = builder.mProjecting;
+ mExtras = builder.mExtras == null ? null : new Bundle(builder.mExtras);
+ }
+
+ private MobileDevice(Parcel source) {
+ mId = source.readInt();
+ mName = source.readString();
+ mAvailableTransports = source.createIntArray();
+ mProjecting = source.readBoolean();
+ mExtras = source.readBundle(getClass().getClassLoader());
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mId);
+ dest.writeString(mName);
+ dest.writeIntArray(mAvailableTransports);
+ dest.writeBoolean(mProjecting);
+ dest.writeBundle(mExtras);
+ }
+
+ /** Returns the device id which uniquely identifies the mobile device within projection */
+ public int getId() {
+ return mId;
+ }
+
+ /** Returns the name of the device */
+ public @NonNull String getName() {
+ return mName;
+ }
+
+ /** Returns a list of available projection transports. See {@code PROJECTION_TRANSPORT_*}
+ * for possible values. */
+ public @NonNull List<Integer> getAvailableTransports() {
+ List<Integer> transports = new ArrayList<>(mAvailableTransports.length);
+ for (int transport : mAvailableTransports) {
+ transports.add(transport);
+ }
+ return transports;
+ }
+
+ /** Indicates whether this mobile device is currently projecting */
+ public boolean isProjecting() {
+ return mProjecting;
+ }
+
+ /** Returns extra information for mobile device */
+ public @NonNull Bundle getExtras() {
+ return mExtras == null ? new Bundle() : new Bundle(mExtras);
+ }
+
+ /** Parcelable implementation */
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /**
+ * Creates new instance of {@link Builder}
+ *
+ * @param id uniquely identifies the device
+ * @param name name of the connected device
+ * @return the instance of {@link Builder}
+ */
+ public static @NonNull Builder builder(int id, String name) {
+ return new Builder(id, name);
+ }
+
+ @Override
+ public String toString() {
+ return "MobileDevice{"
+ + "mId=" + mId
+ + ", mName='" + mName + '\''
+ + ", mAvailableTransports=" + Arrays.toString(mAvailableTransports)
+ + ", mProjecting=" + mProjecting
+ + (mExtras != null ? ", (has extras)" : "")
+ + '}';
+ }
+
+ /**
+ * Builder class for {@link MobileDevice}
+ */
+ public static final class Builder {
+ private int mId;
+ private String mName;
+ private IntArray mAvailableTransports = new IntArray();
+ private boolean mProjecting;
+ private Bundle mExtras;
+
+ private Builder(int id, String name) {
+ mId = id;
+ if (name == null) {
+ throw new IllegalArgumentException("Name of the device can't be null");
+ }
+ mName = name;
+ }
+
+ /**
+ * Add supported transport
+ *
+ * @param transport supported transport by given device, must be one of the
+ * {@code PROJECTION_TRANSPORT_*}
+ * @return this builder
+ */
+ public @NonNull Builder addTransport(@ProjectionTransport int transport) {
+ checkProjectionTransport(transport);
+ mAvailableTransports.add(transport);
+ return this;
+ }
+
+ /**
+ * Indicate whether the mobile device currently projecting or not.
+ *
+ * @param projecting {@code True} if this mobile device currently projecting
+ * @return this builder
+ */
+ public @NonNull Builder setProjecting(boolean projecting) {
+ mProjecting = projecting;
+ return this;
+ }
+
+ /**
+ * Add extra information for mobile device
+ *
+ * @param extras provides an arbitrary extra information about this mobile device
+ * @return this builder
+ */
+ public @NonNull Builder setExtras(Bundle extras) {
+ mExtras = extras;
+ return this;
+ }
+
+ /** Creates new instance of {@link MobileDevice} */
+ public @NonNull MobileDevice build() {
+ return new MobileDevice(this);
+ }
+ }
+ }
+}
diff --git a/car-lib/src/android/car/trust/CarTrustAgentEnrollmentManager.java b/car-lib/src/android/car/trust/CarTrustAgentEnrollmentManager.java
index 1bb3ba0..e10c280 100644
--- a/car-lib/src/android/car/trust/CarTrustAgentEnrollmentManager.java
+++ b/car-lib/src/android/car/trust/CarTrustAgentEnrollmentManager.java
@@ -18,6 +18,7 @@
import static android.car.Car.PERMISSION_CAR_ENROLL_TRUST;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
@@ -172,14 +173,21 @@
}
/**
- * Activate the newly added escrow token.
+ * Returns {@code true} if the escrow token associated with the given handle is active.
+ * <p>
+ * When a new escrow token has been added as part of the Trusted device enrollment, the client
+ * will receive {@link CarTrustAgentEnrollmentCallback#onEscrowTokenAdded(long)} and
+ * {@link CarTrustAgentEnrollmentCallback#onEscrowTokenActiveStateChanged(long, boolean)}
+ * callbacks. This method provides a way to query for the token state at a later point of time.
*
* @param handle the handle corresponding to the escrow token
+ * @param uid user id associated with the token
+ * @return true if the token is active, false if not
*/
@RequiresPermission(PERMISSION_CAR_ENROLL_TRUST)
- public void activateToken(long handle) {
+ public boolean isEscrowTokenActive(long handle, int uid) {
try {
- mEnrollmentService.activateToken(handle);
+ return mEnrollmentService.isEscrowTokenActive(handle, uid);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -281,17 +289,19 @@
/**
* Provides a list of enrollment handles for the given user id.
+ * <p>
* Each enrollment handle corresponds to a trusted device for the given user.
*
* @param uid user id.
* @return list of the Enrollment handles for the user id.
*/
@RequiresPermission(PERMISSION_CAR_ENROLL_TRUST)
- public List<Integer> getEnrollmentHandlesForUser(int uid) {
+ @NonNull
+ public List<Long> getEnrollmentHandlesForUser(int uid) {
try {
- return Arrays.stream(
- mEnrollmentService.getEnrollmentHandlesForUser(uid)).boxed().collect(
- Collectors.toList());
+ return Arrays.stream(mEnrollmentService.getEnrollmentHandlesForUser(uid))
+ .boxed()
+ .collect(Collectors.toList());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -346,7 +356,6 @@
* @param active True if token has been activated, false if not.
*/
void onEscrowTokenActiveStateChanged(long handle, boolean active);
-
}
/**
diff --git a/car-lib/src/android/car/trust/ICarTrustAgentEnrollment.aidl b/car-lib/src/android/car/trust/ICarTrustAgentEnrollment.aidl
index 0476f70..5606c06 100644
--- a/car-lib/src/android/car/trust/ICarTrustAgentEnrollment.aidl
+++ b/car-lib/src/android/car/trust/ICarTrustAgentEnrollment.aidl
@@ -32,9 +32,9 @@
void initiateEnrollmentHandshake(in BluetoothDevice device);
void enrollmentHandshakeAccepted();
void terminateEnrollmentHandshake();
- void activateToken(in long handle);
+ boolean isEscrowTokenActive(in long handle, int uid);
void revokeTrust(in long handle);
- int[] getEnrollmentHandlesForUser(in int uid);
+ long[] getEnrollmentHandlesForUser(in int uid);
void registerEnrollmentCallback(in ICarTrustAgentEnrollmentCallback callback);
void unregisterEnrollmentCallback(in ICarTrustAgentEnrollmentCallback callback);
void registerBleCallback(in ICarTrustAgentBleCallback callback);
diff --git a/car-test-lib/src/android/car/testapi/CarProjectionController.java b/car-test-lib/src/android/car/testapi/CarProjectionController.java
index 91de599..c3fada6 100644
--- a/car-test-lib/src/android/car/testapi/CarProjectionController.java
+++ b/car-test-lib/src/android/car/testapi/CarProjectionController.java
@@ -16,10 +16,16 @@
package android.car.testapi;
+import android.car.projection.ProjectionOptions;
import android.net.wifi.WifiConfiguration;
/** Controller to change behavior of {@link android.car.CarProjectionManager} */
public interface CarProjectionController {
/** Set WifiConfiguration for wireless projection or null to simulate failure to start AP */
void setWifiConfiguration(WifiConfiguration wifiConfiguration);
+ /**
+ * Sets {@link ProjectionOptions} object returns by
+ * {@link android.car.CarProjectionManager#getProjectionOptions()} call
+ */
+ void setProjectionOptions(ProjectionOptions projectionOptions);
}
diff --git a/car-test-lib/src/android/car/testapi/FakeCarProjectionService.java b/car-test-lib/src/android/car/testapi/FakeCarProjectionService.java
index 1bf15cd..bba5a04 100644
--- a/car-test-lib/src/android/car/testapi/FakeCarProjectionService.java
+++ b/car-test-lib/src/android/car/testapi/FakeCarProjectionService.java
@@ -21,16 +21,25 @@
import android.car.CarProjectionManager.ProjectionAccessPointCallback;
import android.car.ICarProjection;
import android.car.ICarProjectionCallback;
+import android.car.ICarProjectionStatusListener;
+import android.car.projection.ProjectionOptions;
+import android.car.projection.ProjectionStatus;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.net.wifi.WifiConfiguration;
+import android.os.Bundle;
import android.os.IBinder;
import android.os.Message;
import android.os.Messenger;
import android.os.RemoteException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
/**
* Fake implementation of {@link ICarProjection} interface.
*
@@ -44,6 +53,11 @@
private WifiConfiguration mWifiConfiguration;
private Messenger mApMessenger;
private IBinder mApBinder;
+ private List<ICarProjectionStatusListener> mStatusListeners = new ArrayList<>();
+ private Map<IBinder, ProjectionStatus> mProjectionStatusMap = new HashMap<>();
+ private ProjectionStatus mCurrentProjectionStatus = ProjectionStatus.builder(
+ "", ProjectionStatus.PROJECTION_STATE_INACTIVE).build();
+ private ProjectionOptions mProjectionOptions;
private final ServiceConnection mServiceConnection = new ServiceConnection() {
@Override
@@ -55,6 +69,7 @@
FakeCarProjectionService(Context context) {
mContext = context;
+ mProjectionOptions = ProjectionOptions.builder().build();
}
@Override
@@ -118,7 +133,55 @@
}
@Override
+ public void updateProjectionStatus(ProjectionStatus status, IBinder token)
+ throws RemoteException {
+ mCurrentProjectionStatus = status;
+ mProjectionStatusMap.put(token, status);
+ notifyStatusListeners(status,
+ mStatusListeners.toArray(new ICarProjectionStatusListener[0]));
+ }
+
+ private void notifyStatusListeners(ProjectionStatus status,
+ ICarProjectionStatusListener... listeners) throws RemoteException {
+ for (ICarProjectionStatusListener listener : listeners) {
+ listener.onProjectionStatusChanged(
+ status.getState(),
+ status.getPackageName(),
+ new ArrayList<>(mProjectionStatusMap.values()));
+ }
+ }
+
+ @Override
+ public void registerProjectionStatusListener(ICarProjectionStatusListener listener)
+ throws RemoteException {
+ mStatusListeners.add(listener);
+ notifyStatusListeners(mCurrentProjectionStatus, listener);
+ }
+
+ @Override
+ public void unregisterProjectionStatusListener(ICarProjectionStatusListener listener)
+ throws RemoteException {
+ mStatusListeners.remove(listener);
+ }
+
+ @Override
public void setWifiConfiguration(WifiConfiguration wifiConfiguration) {
mWifiConfiguration = wifiConfiguration;
}
+
+ @Override
+ public Bundle getProjectionOptions() throws RemoteException {
+ return mProjectionOptions.toBundle();
+ }
+
+ @Override
+ public int[] getAvailableWifiChannels(int band) throws RemoteException {
+ return new int[] {2412 /* Channel 1 */, 5180 /* Channel 36 */};
+ }
+
+
+ @Override
+ public void setProjectionOptions(ProjectionOptions projectionOptions) {
+ mProjectionOptions = projectionOptions;
+ }
}
diff --git a/car_product/init/init.bootstat.rc b/car_product/init/init.bootstat.rc
index 430a96b..5c5e796 100644
--- a/car_product/init/init.bootstat.rc
+++ b/car_product/init/init.bootstat.rc
@@ -3,31 +3,5 @@
# IMPORTANT: Do not create world writable files or directories.
# This is a common source of Android security bugs.
#
-on post-fs-data
- mkdir /data/misc/bootstat 0700 root root
-
-# Record the time at which the user has successfully entered the pin to decrypt
-# the device, /data is decrypted, and the system is entering the main boot phase.
-#
-# post-fs-data: /data is writable
-# property:init.svc.bootanim=running: The boot animation is running
-on post-fs-data && property:init.svc.bootanim=running
- exec - root root -- /system/bin/bootstat -r post_decrypt_time_elapsed
-
-# Boot animation stopped, is considered the point at which
-# the user may interact with the device, so it is a good proxy for the boot
-# complete signal.
-on property:init.svc.bootanim=stopped
- # Record boot_complete and related stats (decryption, etc).
- exec - root root -- /system/bin/bootstat --record_boot_complete
-
-on property:dev.bootcomplete=1
- exec - root root -- /system/bin/bootstat -r dev_bootcomplete
- # Log all boot events.
- exec - root root -- /system/bin/bootstat -l
-
on property:boot.car_service_created=1
exec - root root -- /system/bin/bootstat -r car_service_created
-
-on property:init.svc.zygote=running
- exec - root root -- /system/bin/bootstat -r zygote_running
diff --git a/car_product/overlay/frameworks/base/core/res/res/values/styles.xml b/car_product/overlay/frameworks/base/core/res/res/values/styles.xml
index 1362b97..20d6b49 100644
--- a/car_product/overlay/frameworks/base/core/res/res/values/styles.xml
+++ b/car_product/overlay/frameworks/base/core/res/res/values/styles.xml
@@ -24,7 +24,7 @@
<!-- Override the default activity transitions. We have to do a full copy and not just inherit
and override because we're replacing the default style across the system.
-->
- <style name="Animation.DeviceDefault.Activity" parent="*android:Animation.Material.Activity">
+ <style name="Animation.Activity" parent="*android:Animation.Material.Activity">
<item name="android:activityOpenEnterAnimation">@*android:anim/fade_in</item>
<item name="android:activityOpenExitAnimation">@*android:anim/fade_out</item>
<item name="android:activityCloseEnterAnimation">@*android:anim/fade_in</item>
diff --git a/car_product/overlay/frameworks/base/core/res/res/values/styles_device_default.xml b/car_product/overlay/frameworks/base/core/res/res/values/styles_device_default.xml
index e2f59d8..ea83ad3 100644
--- a/car_product/overlay/frameworks/base/core/res/res/values/styles_device_default.xml
+++ b/car_product/overlay/frameworks/base/core/res/res/values/styles_device_default.xml
@@ -76,6 +76,12 @@
<item name="android:textColor">@*android:color/car_button_text_color</item>
</style>
+ <style name="Widget.DeviceDefault.TextView" parent="android:Widget.Material.TextView">
+ <item name="android:ellipsize">none</item>
+ <item name="android:requiresFadingEdge">horizontal</item>
+ <item name="android:fadingEdgeLength">@*android:dimen/car_textview_fading_edge_length</item>
+ </style>
+
<style name="Widget.DeviceDefault.Button" parent="android:Widget.Material.Button">
<item name="android:background">@*android:drawable/car_button_background</item>
<item name="android:layout_height">@*android:dimen/car_button_height</item>
diff --git a/evs/sampleDriver/EvsEnumerator.cpp b/evs/sampleDriver/EvsEnumerator.cpp
index 9838258..96fd067 100644
--- a/evs/sampleDriver/EvsEnumerator.cpp
+++ b/evs/sampleDriver/EvsEnumerator.cpp
@@ -35,9 +35,10 @@
std::list<EvsEnumerator::CameraRecord> EvsEnumerator::sCameraList;
wp<EvsGlDisplay> EvsEnumerator::sActiveDisplay;
+// Number of trials to open the camera.
+static const unsigned int kMaxRetry = 3;
-EvsEnumerator::EvsEnumerator()
-: kMaxRetry(3) {
+EvsEnumerator::EvsEnumerator() {
ALOGD("EvsEnumerator created");
enumerateDevices();
@@ -80,6 +81,21 @@
Return<void> EvsEnumerator::getCameraList(getCameraList_cb _hidl_cb) {
ALOGD("getCameraList");
+ if (sCameraList.size() < 1) {
+ // WAR: this assumes that the device has at least one compatible camera and
+ // therefore keeps trying until it succeeds to open.
+ // TODO: this is required for external USB camera so would be better to
+ // subscribe hot-plug event.
+ unsigned tries = 0;
+ ALOGI("No camera is available; enumerate devices again.");
+ while (sCameraList.size() < 1 && tries++ < kMaxRetry) {
+ enumerateDevices();
+
+ // TODO: remove this.
+ usleep(5000);
+ }
+ }
+
const unsigned numCameras = sCameraList.size();
// Build up a packed array of CameraDesc for return
@@ -104,19 +120,6 @@
// Is this a recognized camera id?
CameraRecord *pRecord = findCameraById(cameraId);
- // WAR: this assumes that the device has at least one compatible camera and
- // therefore keeps trying until it succeeds to open.
- // TODO: this is required for external USB camera so would be better to
- // subscribe hot-plug event.
- unsigned tries = 0;
- while (!pRecord && tries++ < kMaxRetry) {
- ALOGI("Requested camera %s not found, enumerate again, %d", cameraId.c_str(), tries);
- enumerateDevices();
- pRecord = findCameraById(cameraId);
-
- // TODO: remove this.
- usleep(5000);
- }
// Has this camera already been instantiated by another caller?
sp<EvsV4lCamera> pActiveCamera = pRecord->activeInstance.promote();
diff --git a/evs/sampleDriver/EvsEnumerator.h b/evs/sampleDriver/EvsEnumerator.h
index 96c2e91..e7e94d4 100644
--- a/evs/sampleDriver/EvsEnumerator.h
+++ b/evs/sampleDriver/EvsEnumerator.h
@@ -48,8 +48,6 @@
// Implementation details
EvsEnumerator();
- const unsigned kMaxRetry;
-
private:
struct CameraRecord {
CameraDesc desc;
diff --git a/service/AndroidManifest.xml b/service/AndroidManifest.xml
index 9086a4a..76c4d37 100644
--- a/service/AndroidManifest.xml
+++ b/service/AndroidManifest.xml
@@ -119,6 +119,16 @@
android:label="@string/car_permission_label_projection"
android:description="@string/car_permission_desc_projection" />
<permission
+ android:name="android.car.permission.ACCESS_CAR_PROJECTION_STATUS"
+ android:protectionLevel="system|signature"
+ android:label="@string/car_permission_label_access_projection_status"
+ android:description="@string/car_permission_desc_access_projection_status" />
+ <permission
+ android:name="android.car.permission.BIND_PROJECTION_SERVICE"
+ android:protectionLevel="signature"
+ android:label="@string/car_permission_label_bind_projection_service"
+ android:description="@string/car_permission_desc_bind_projection_service" />
+ <permission
android:name="android.car.permission.CAR_MOCK_VEHICLE_HAL"
android:protectionLevel="system|signature"
android:label="@string/car_permission_label_mock_vehicle_hal"
@@ -179,6 +189,11 @@
android:label="@string/car_permission_label_diag_clear"
android:description="@string/car_permission_desc_diag_clear" />
<permission
+ android:name="android.car.permission.BIND_VMS_CLIENT"
+ android:protectionLevel="signature"
+ android:label="@string/car_permission_label_bind_vms_client"
+ android:description="@string/car_permission_desc_bind_vms_client" />
+ <permission
android:name="android.car.permission.VMS_PUBLISHER"
android:protectionLevel="system|signature"
android:label="@string/car_permission_label_vms_publisher"
diff --git a/service/res/values/config.xml b/service/res/values/config.xml
index 6f5975a..48f346e 100644
--- a/service/res/values/config.xml
+++ b/service/res/values/config.xml
@@ -143,4 +143,19 @@
<string name="config_TetheredProjectionAccessPointSsid" translatable="false">CarAP</string>
+ <!-- The consent activity that must be shown for every unknown mobile device before projection
+ gets started. The format is: com.your.package/com.your.Activity -->
+ <string name="config_projectionConsentActivity" translatable="false"/>
+
+ <!-- Display Id where projection rendering activity needs to be shown, Specify -1 to use system
+ defaults -->
+ <integer name="config_projectionActivityDisplayId" translatable="false">-1</integer>
+
+ <!-- Bounds of the projection activity on the screen. It should be in the pixels and screen
+ coordinates in the following order: left, top, right, bottom. -->
+ <integer-array name="config_projectionActivityLaunchBounds" translatable="false"/>
+
+ <!-- UI mode for projection activity. See ProjectionOptions class for possible values. -->
+ <integer name="config_projectionUiMode" translatable="false">0</integer>
+
</resources>
diff --git a/service/res/values/strings.xml b/service/res/values/strings.xml
index 0855727..f74710c 100644
--- a/service/res/values/strings.xml
+++ b/service/res/values/strings.xml
@@ -60,12 +60,20 @@
<string name="car_permission_desc_radio">Access your car\u2019s radio.</string>
<!-- Permission text: apps can control car-projection [CHAR LIMIT=NONE] -->
<string name="car_permission_label_projection">Car Projection</string>
+ <!-- Permission text: apps can control car-projection [CHAR LIMIT=NONE] -->
+ <string name="car_permission_desc_projection">Allows an app to project an interface from a phone on the car\u2019s display.</string>
+ <!-- Permission text: apps can listen car-projection status[CHAR LIMIT=NONE] -->
+ <string name="car_permission_label_access_projection_status">Access projection status</string>
+ <!-- Permission text: apps can listen car-projection status[CHAR LIMIT=NONE] -->
+ <string name="car_permission_desc_access_projection_status">Allows an app to get the status of other apps projecting to the car\u2019s display.</string>
+ <!-- Permission text: allows framework to bind to the services in projection apps[CHAR LIMIT=NONE] -->
+ <string name="car_permission_label_bind_projection_service">Bind to a projection service</string>
+ <!-- Permission text: allows framework to bind to the services in projection apps[CHAR LIMIT=NONE] -->
+ <string name="car_permission_desc_bind_projection_service">Allows the holder to bind to the top-level interface of a projection service. Should never be needed for normal apps."</string>
<!-- Permission text: apps can control car-audio-volume [CHAR LIMIT=NONE] -->
<string name="car_permission_label_audio_volume">Car Audio Volume</string>
<!-- Permission text: apps can control car-audio-settings [CHAR LIMIT=NONE] -->
<string name="car_permission_label_audio_settings">Car Audio Settings</string>
- <!-- Permission text: apps can control car-projection [CHAR LIMIT=NONE] -->
- <string name="car_permission_desc_projection">Project phone interface on car display.</string>
<string name="car_permission_label_mock_vehicle_hal">Emulate vehicle HAL</string>
<!-- Permission text: can emulate information from your car [CHAR LIMIT=NONE] -->
<string name="car_permission_desc_mock_vehicle_hal">Emulate your car\u2019s vehicle HAL for internal
@@ -124,14 +132,19 @@
<string name="car_permission_desc_diag_clear">Clear diagnostic data from the car</string>
<!-- Permission text: apps can publish VMS data [CHAR LIMIT=NONE] -->
- <string name="car_permission_label_vms_publisher">VMS publisher</string>
+ <string name="car_permission_label_vms_publisher">VMS Publisher</string>
<!-- Permission text: apps can send VMS messages to the car [CHAR LIMIT=NONE] -->
- <string name="car_permission_desc_vms_publisher">Publish vms messages</string>
+ <string name="car_permission_desc_vms_publisher">Publish VMS messages</string>
<!-- Permission text: apps can subscribe to VMS data [CHAR LIMIT=NONE] -->
- <string name="car_permission_label_vms_subscriber">VMS subscriber</string>
+ <string name="car_permission_label_vms_subscriber">VMS Subscriber</string>
<!-- Permission text: apps can receive VMS messages from the car [CHAR LIMIT=NONE] -->
- <string name="car_permission_desc_vms_subscriber">Subscribe to vms messages</string>
+ <string name="car_permission_desc_vms_subscriber">Subscribe to VMS messages</string>
+
+ <!-- Permission text: apps can act as VMS router core [CHAR LIMIT=NONE] -->
+ <string name="car_permission_label_bind_vms_client">VMS Client Service</string>
+ <!-- Permission text: apps can act as VMS router core [CHAR LIMIT=NONE] -->
+ <string name="car_permission_desc_bind_vms_client">Bind to VMS clients</string>
<!-- Permission text: apps can monitor flash storage usage [CHAR LIMIT=NONE] -->
<string name="car_permission_label_storage_monitoring">Flash storage monitoring</string>
diff --git a/service/src/com/android/car/BluetoothDeviceConnectionPolicy.java b/service/src/com/android/car/BluetoothDeviceConnectionPolicy.java
index 5d7fc0e..6b2169a 100644
--- a/service/src/com/android/car/BluetoothDeviceConnectionPolicy.java
+++ b/service/src/com/android/car/BluetoothDeviceConnectionPolicy.java
@@ -33,18 +33,24 @@
import android.bluetooth.BluetoothPbapClient;
import android.bluetooth.BluetoothProfile;
import android.bluetooth.BluetoothUuid;
+import android.car.Car;
import android.car.CarBluetoothManager;
+import android.car.CarNotConnectedException;
import android.car.ICarBluetoothUserService;
import android.car.ICarUserService;
import android.car.drivingstate.CarUxRestrictions;
import android.car.drivingstate.ICarUxRestrictionsChangeListener;
import android.car.hardware.CarPropertyValue;
+import android.car.hardware.power.CarPowerManager;
+import android.car.hardware.power.CarPowerManager.CarPowerStateListener;
import android.car.hardware.property.CarPropertyEvent;
import android.car.hardware.property.ICarPropertyEventListener;
import android.content.BroadcastReceiver;
+import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.ServiceConnection;
import android.hardware.automotive.vehicle.V2_0.VehicleIgnitionState;
import android.hardware.automotive.vehicle.V2_0.VehicleProperty;
import android.os.Binder;
@@ -71,10 +77,10 @@
import java.util.Map;
import java.util.Objects;
import java.util.Set;
+import java.util.concurrent.CompletableFuture;
import java.util.concurrent.locks.ReentrantLock;
import java.util.stream.Collectors;
-
/**
* A Bluetooth Device Connection policy that is specific to the use cases of a Car. A car's
* bluetooth capabilities in terms of the profiles it supports and its use cases are unique.
@@ -112,12 +118,15 @@
// The main data structure that holds on to the {profile:list of known and connectible devices}
HashMap<Integer, BluetoothDevicesInfo> mProfileToConnectableDevicesMap;
- /// TODO(vnori): fix this. b/70029056
- private static final int NUM_SUPPORTED_PHONE_CONNECTIONS = 4; // num of HFP and PBAP connections
- private static final int NUM_SUPPORTED_MSG_CONNECTIONS = 4; // num of MAP connections
- private static final int NUM_SUPPORTED_MUSIC_CONNECTIONS = 1; // num of A2DP connections
- private static final int NUM_SUPPORTED_NETWORK_CONNECTIONS = 1; // num of PAN connections
- private Map<Integer, Integer> mNumSupportedActiveConnections;
+ // Keep a map of the maximum number of connections allowed for any profile we plan to support.
+ private static final Map<Integer, Integer> sNumSupportedActiveConnections = new HashMap<>();
+ static {
+ sNumSupportedActiveConnections.put(BluetoothProfile.HEADSET_CLIENT, 4);
+ sNumSupportedActiveConnections.put(BluetoothProfile.PBAP_CLIENT, 4);
+ sNumSupportedActiveConnections.put(BluetoothProfile.A2DP_SINK, 1);
+ sNumSupportedActiveConnections.put(BluetoothProfile.MAP_CLIENT, 4);
+ sNumSupportedActiveConnections.put(BluetoothProfile.PAN, 1);
+ }
private BluetoothAutoConnectStateMachine mBluetoothAutoConnectStateMachine;
private final BluetoothAdapter mBluetoothAdapter;
@@ -133,6 +142,63 @@
private final CarPropertyService mCarPropertyService;
private final CarPropertyListener mPropertyEventListener;
+ // Car service binder to setup listening for power manager updates
+ private final Car mCar;
+ private CarPowerManager mCarPowerManager;
+ private final CarPowerStateListener mCarPowerStateListener = new CarPowerStateListener() {
+ @Override
+ public void onStateChanged(int state, CompletableFuture<Void> future) {
+ if (DBG) Log.d(TAG, "Car power state has changed to " + state);
+
+ // ON is the state when user turned on the car (it can be either ignition or
+ // door unlock) the policy for ON is defined by OEMs and we can rely on that.
+ if (state == CarPowerManager.CarPowerStateListener.ON) {
+ Log.i(TAG, "Car is powering on. Enable Bluetooth and auto-connect to devices.");
+ if (isBluetoothPersistedOn()) {
+ enabledBluetooth();
+ }
+ initiateConnection();
+ return;
+ }
+
+ // Since we're appearing to be off after shutdown prepare, but may stay on in idle mode,
+ // we'll turn off Bluetooth to disconnect devices and better the "off" illusion
+ if (state == CarPowerManager.CarPowerStateListener.SHUTDOWN_PREPARE) {
+ Log.i(TAG, "Car is preparing for shutdown. Disable bluetooth adapter.");
+ disableBluetooth();
+
+ // Let CPMS know we're ready to shutdown. Otherwise, CPMS will get stuck for
+ // up to an hour.
+ if (future != null) {
+ future.complete(null);
+ }
+ return;
+ }
+ }
+ };
+
+
+ private final ServiceConnection mCarServiceConnection = new ServiceConnection() {
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ Log.i(TAG, "Car is now connected, getting CarPowerManager service");
+ try {
+ mCarPowerManager = (CarPowerManager) mCar.getCarManager(Car.POWER_SERVICE);
+ mCarPowerManager.setListener(mCarPowerStateListener);
+ } catch (CarNotConnectedException e) {
+ Log.e(TAG, "Failed to get CarPowerManager instance", e);
+ }
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ Log.i(TAG, "Car is now disconnected");
+ if (mCarPowerManager != null) {
+ mCarPowerManager.clearListener();
+ }
+ }
+ };
+
// PerUserCarService related listeners
private final UserServiceConnectionCallback mServiceCallback;
@@ -193,33 +259,6 @@
CarBluetoothManager.BLUETOOTH_DEVICE_CONNECTION_PRIORITY_0,
CarBluetoothManager.BLUETOOTH_DEVICE_CONNECTION_PRIORITY_1
);
- // mNumSupportedActiveConnections is a HashMap of mProfilesToConnect and the number of
- // connections each profile supports currently.
- mNumSupportedActiveConnections = new HashMap<>(mProfilesToConnect.size());
- for (Integer profile : mProfilesToConnect) {
- switch (profile) {
- case BluetoothProfile.HEADSET_CLIENT:
- mNumSupportedActiveConnections.put(BluetoothProfile.HEADSET_CLIENT,
- NUM_SUPPORTED_PHONE_CONNECTIONS);
- break;
- case BluetoothProfile.PBAP_CLIENT:
- mNumSupportedActiveConnections.put(BluetoothProfile.PBAP_CLIENT,
- NUM_SUPPORTED_PHONE_CONNECTIONS);
- break;
- case BluetoothProfile.A2DP_SINK:
- mNumSupportedActiveConnections.put(BluetoothProfile.A2DP_SINK,
- NUM_SUPPORTED_MUSIC_CONNECTIONS);
- break;
- case BluetoothProfile.MAP_CLIENT:
- mNumSupportedActiveConnections.put(BluetoothProfile.MAP_CLIENT,
- NUM_SUPPORTED_MSG_CONNECTIONS);
- break;
- case BluetoothProfile.PAN:
- mNumSupportedActiveConnections.put(BluetoothProfile.PAN,
- NUM_SUPPORTED_NETWORK_CONNECTIONS);
- break;
- }
- }
// Listen to events for triggering auto connect
mPropertyEventListener = new CarPropertyListener();
@@ -232,6 +271,10 @@
Log.w(TAG, "No Bluetooth Adapter Available");
}
mFastPairProvider = new FastPairProvider(mContext);
+
+ // Connect to car
+ mCar = Car.createCar(context, mCarServiceConnection);
+ mCar.connect();
}
/**
@@ -746,7 +789,7 @@
for (Integer profile : mProfilesToConnect) {
// Build the BluetoothDevicesInfo for this profile.
BluetoothDevicesInfo devicesInfo = new BluetoothDevicesInfo(profile,
- mNumSupportedActiveConnections.get(profile));
+ sNumSupportedActiveConnections.get(profile));
mProfileToConnectableDevicesMap.put(profile, devicesInfo);
}
if (DBG) {
@@ -851,6 +894,7 @@
writeDeviceInfoToSettings();
cleanupUserSpecificInfo();
closeEventListeners();
+ mCar.disconnect();
}
/**
@@ -1796,6 +1840,41 @@
}
/**
+ * Get the persisted Bluetooth state from Settings
+ */
+ private boolean isBluetoothPersistedOn() {
+ return (Settings.Global.getInt(
+ mContext.getContentResolver(), Settings.Global.BLUETOOTH_ON, -1) != 0);
+ }
+
+ /**
+ * Turn on the Bluetooth Adapter.
+ */
+ private void enabledBluetooth() {
+ if (DBG) Log.d(TAG, "Enable bluetooth adapter");
+ if (mBluetoothAdapter == null) {
+ Log.e(TAG, "Cannot enable Bluetooth adapter. The object is null.");
+ return;
+ }
+ mBluetoothAdapter.enable();
+ }
+
+ /**
+ * Turn off the Bluetooth Adapter.
+ *
+ * Tells BluetoothAdapter to shut down _without_ persisting the off state as the desired state
+ * of the Bluetooth adapter for next start up.
+ */
+ private void disableBluetooth() {
+ if (DBG) Log.d(TAG, "Disable bluetooth, do not persist state across reboot");
+ if (mBluetoothAdapter == null) {
+ Log.e(TAG, "Cannot disable Bluetooth adapter. The object is null.");
+ return;
+ }
+ mBluetoothAdapter.disable(false);
+ }
+
+ /**
* Write the device list for all bluetooth profiles that connected.
*
* @return true if the write was successful, false otherwise
diff --git a/service/src/com/android/car/CarPowerManagementService.java b/service/src/com/android/car/CarPowerManagementService.java
index 0bec14c..8cf7c46 100644
--- a/service/src/com/android/car/CarPowerManagementService.java
+++ b/service/src/com/android/car/CarPowerManagementService.java
@@ -19,6 +19,7 @@
import android.car.hardware.power.CarPowerManager.CarPowerStateListener;
import android.car.hardware.power.ICarPower;
import android.car.hardware.power.ICarPowerStateListener;
+import android.car.userlib.CarUserManagerHelper;
import android.content.Context;
import android.hardware.automotive.vehicle.V2_0.VehicleApPowerStateReq;
import android.os.Handler;
@@ -29,6 +30,7 @@
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.os.SystemClock;
+import android.os.UserHandle;
import android.util.Log;
import com.android.car.hal.PowerHalService;
@@ -75,6 +77,9 @@
private int mNextWakeupSec = 0;
private int mTokenValue = 1;
private boolean mShutdownOnFinish = false;
+ private boolean mIsBooting = true;
+
+ private final CarUserManagerHelper mCarUserManagerHelper;
// TODO: Make this OEM configurable.
private static final int SHUTDOWN_POLLING_INTERVAL_MS = 2000;
@@ -96,10 +101,12 @@
}
public CarPowerManagementService(
- Context context, PowerHalService powerHal, SystemInterface systemInterface) {
+ Context context, PowerHalService powerHal, SystemInterface systemInterface,
+ CarUserManagerHelper carUserManagerHelper) {
mContext = context;
mHal = powerHal;
mSystemInterface = systemInterface;
+ mCarUserManagerHelper = carUserManagerHelper;
}
/**
@@ -113,6 +120,7 @@
mSystemInterface = null;
mHandlerThread = null;
mHandler = new PowerHandler(Looper.getMainLooper());
+ mCarUserManagerHelper = null;
}
@VisibleForTesting
@@ -186,6 +194,11 @@
handler.handlePowerStateChange();
}
+ @VisibleForTesting
+ protected void clearIsBooting() {
+ mIsBooting = false;
+ }
+
/**
* Initiate state change from CPMS directly.
*/
@@ -262,6 +275,17 @@
}
private void handleOn() {
+ // Do not switch user if it is booting as there can be a race with CarServiceHelperService
+ if (mIsBooting) {
+ mIsBooting = false;
+ } else {
+ int targetUserId = mCarUserManagerHelper.getInitialUser();
+ if (targetUserId != UserHandle.USER_SYSTEM
+ && targetUserId != mCarUserManagerHelper.getCurrentForegroundUserId()) {
+ Log.i(CarLog.TAG_POWER, "Desired user changed, switching to user:" + targetUserId);
+ mCarUserManagerHelper.switchToUserId(targetUserId);
+ }
+ }
mSystemInterface.setDisplayState(true);
sendPowerManagerEvent(CarPowerStateListener.ON);
mHal.sendOn();
diff --git a/service/src/com/android/car/CarProjectionService.java b/service/src/com/android/car/CarProjectionService.java
index 582d535..9570b2a 100644
--- a/service/src/com/android/car/CarProjectionService.java
+++ b/service/src/com/android/car/CarProjectionService.java
@@ -18,6 +18,7 @@
import static android.car.CarProjectionManager.PROJECTION_LONG_PRESS_VOICE_SEARCH;
import static android.car.CarProjectionManager.PROJECTION_VOICE_SEARCH;
import static android.car.CarProjectionManager.ProjectionAccessPointCallback.ERROR_GENERIC;
+import static android.car.projection.ProjectionStatus.PROJECTION_STATE_INACTIVE;
import static android.net.wifi.WifiManager.EXTRA_PREVIOUS_WIFI_AP_STATE;
import static android.net.wifi.WifiManager.EXTRA_WIFI_AP_FAILURE_REASON;
import static android.net.wifi.WifiManager.EXTRA_WIFI_AP_INTERFACE_NAME;
@@ -28,17 +29,25 @@
import static android.net.wifi.WifiManager.WIFI_AP_STATE_ENABLING;
import android.annotation.Nullable;
+import android.app.ActivityOptions;
import android.bluetooth.BluetoothDevice;
import android.car.CarProjectionManager;
import android.car.CarProjectionManager.ProjectionAccessPointCallback;
import android.car.ICarProjection;
import android.car.ICarProjectionCallback;
+import android.car.ICarProjectionStatusListener;
+import android.car.projection.ProjectionOptions;
+import android.car.projection.ProjectionStatus;
+import android.car.projection.ProjectionStatus.ProjectionState;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.ServiceConnection;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.graphics.Rect;
import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiConfiguration.GroupCipher;
import android.net.wifi.WifiConfiguration.KeyMgmt;
@@ -47,13 +56,16 @@
import android.net.wifi.WifiManager.LocalOnlyHotspotCallback;
import android.net.wifi.WifiManager.LocalOnlyHotspotReservation;
import android.net.wifi.WifiManager.SoftApCallback;
+import android.net.wifi.WifiScanner;
import android.os.Binder;
+import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.Messenger;
import android.os.RemoteException;
import android.os.UserHandle;
+import android.text.TextUtils;
import android.util.Log;
import com.android.internal.annotations.GuardedBy;
@@ -67,6 +79,7 @@
import java.util.HashMap;
import java.util.List;
import java.util.Random;
+import java.util.concurrent.CopyOnWriteArrayList;
/**
* Car projection service allows to bound to projected app to boost it prioirity.
@@ -88,17 +101,36 @@
@GuardedBy("mLock")
private final HashMap<IBinder, WirelessClient> mWirelessClients = new HashMap<>();
- @Nullable
@GuardedBy("mLock")
- private LocalOnlyHotspotReservation mLocalOnlyHotspotReservation;
+ private @Nullable LocalOnlyHotspotReservation mLocalOnlyHotspotReservation;
- @Nullable
+
@GuardedBy("mLock")
- private SoftApCallback mSoftApCallback;
+ private @Nullable SoftApCallback mSoftApCallback;
+
+ @GuardedBy("mLock")
+ private final HashMap<IBinder, ProjectionReceiverClient> mProjectionReceiverClients =
+ new HashMap<>();
@Nullable
private String mApBssid;
+ @GuardedBy("mLock")
+ private @Nullable WifiScanner mWifiScanner;
+
+ @GuardedBy("mLock")
+ private @ProjectionState int mCurrentProjectionState = PROJECTION_STATE_INACTIVE;
+
+ @GuardedBy("mLock")
+ private ProjectionOptions mProjectionOptions;
+
+ @GuardedBy("mLock")
+ private @Nullable String mCurrentProjectionPackage;
+
+ private final List<ICarProjectionStatusListener> mProjectionStatusListeners =
+ new CopyOnWriteArrayList<>();
+
+
private static final int WIFI_MODE_TETHERED = 1;
private static final int WIFI_MODE_LOCALONLY = 2;
@@ -139,10 +171,10 @@
private boolean mBound;
private Intent mRegisteredService;
- CarProjectionService(Context context, CarInputService carInputService,
- CarBluetoothService carBluetoothService) {
+ CarProjectionService(Context context, @Nullable Handler handler,
+ CarInputService carInputService, CarBluetoothService carBluetoothService) {
mContext = context;
- mHandler = new Handler();
+ mHandler = handler == null ? new Handler() : handler;
mCarInputService = carInputService;
mCarBluetoothService = carBluetoothService;
mProjectionCallbacks = new ProjectionCallbackHolder(this);
@@ -152,6 +184,7 @@
@Override
public void registerProjectionRunner(Intent serviceIntent) {
+ ICarImpl.assertProjectionPermission(mContext);
// We assume one active projection app running in the system at one time.
synchronized (mLock) {
if (serviceIntent.filterEquals(mRegisteredService) && mBound) {
@@ -168,6 +201,7 @@
@Override
public void unregisterProjectionRunner(Intent serviceIntent) {
+ ICarImpl.assertProjectionPermission(mContext);
synchronized (mLock) {
if (!serviceIntent.filterEquals(mRegisteredService)) {
Log.w(CarLog.TAG_PROJECTION, "Request to unbind unregistered service["
@@ -218,6 +252,7 @@
@Override
public void registerProjectionListener(ICarProjectionCallback callback, int filter) {
+ ICarImpl.assertProjectionPermission(mContext);
synchronized (mLock) {
ProjectionCallback info = mProjectionCallbacks.get(callback);
if (info == null) {
@@ -232,6 +267,7 @@
@Override
public void unregisterProjectionListener(ICarProjectionCallback listener) {
+ ICarImpl.assertProjectionPermission(mContext);
synchronized (mLock) {
mProjectionCallbacks.removeBinder(listener);
}
@@ -241,6 +277,7 @@
@Override
public void startProjectionAccessPoint(final Messenger messenger, IBinder binder)
throws RemoteException {
+ ICarImpl.assertProjectionPermission(mContext);
//TODO: check if access point already started with the desired configuration.
registerWirelessClient(WirelessClient.of(messenger, binder));
startAccessPoint();
@@ -248,6 +285,7 @@
@Override
public void stopProjectionAccessPoint(IBinder token) {
+ ICarImpl.assertProjectionPermission(mContext);
Log.i(TAG, "Received stop access point request from " + token);
boolean shouldReleaseAp;
@@ -264,6 +302,35 @@
}
}
+ @Override
+ public int[] getAvailableWifiChannels(int band) {
+ ICarImpl.assertProjectionPermission(mContext);
+ WifiScanner scanner;
+ synchronized (mLock) {
+ // Lazy initialization
+ if (mWifiScanner == null) {
+ mWifiScanner = mContext.getSystemService(WifiScanner.class);
+ }
+ scanner = mWifiScanner;
+ }
+ if (scanner == null) {
+ Log.w(TAG, "Unable to get WifiScanner");
+ return new int[0];
+ }
+
+ List<Integer> channels = scanner.getAvailableChannels(band);
+ if (channels == null || channels.isEmpty()) {
+ Log.w(TAG, "WifiScanner reported no available channels");
+ return new int[0];
+ }
+
+ int[] array = new int[channels.size()];
+ for (int i = 0; i < channels.size(); i++) {
+ array[i] = channels.get(i);
+ }
+ return array;
+ }
+
/**
* Request to disconnect the given profile on the given device, and prevent it from reconnecting
* until either the request is released, or the process owning the given token dies.
@@ -281,6 +348,7 @@
Log.d(TAG, "requestBluetoothProfileInhibit device=" + device + " profile=" + profile
+ " from uid " + Binder.getCallingUid());
}
+ ICarImpl.assertProjectionPermission(mContext);
try {
if (device == null) {
// Will be caught by AIDL and thrown to caller.
@@ -313,6 +381,7 @@
Log.d(TAG, "releaseBluetoothProfileInhibit device=" + device + " profile=" + profile
+ " from uid " + Binder.getCallingUid());
}
+ ICarImpl.assertProjectionPermission(mContext);
try {
if (device == null) {
// Will be caught by AIDL and thrown to caller.
@@ -328,6 +397,155 @@
}
}
+ @Override
+ public void updateProjectionStatus(ProjectionStatus status, IBinder token)
+ throws RemoteException {
+ if (DBG) {
+ Log.d(TAG, "updateProjectionStatus, status: " + status + ", token: " + token);
+ }
+ ICarImpl.assertProjectionPermission(mContext);
+ final String packageName = status.getPackageName();
+ final int uid = Binder.getCallingUid();
+ try {
+ if (uid != mContext.getPackageManager().getPackageUid(packageName, 0)) {
+ throw new SecurityException(
+ "UID " + uid + " cannot update status for package " + packageName);
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ throw new SecurityException("Package " + packageName + " does not exist", e);
+ }
+
+ synchronized (mLock) {
+ ProjectionReceiverClient client = getOrCreateProjectionReceiverClientLocked(token);
+ client.mProjectionStatus = status;
+
+ if (status.isActive() || TextUtils.equals(packageName, mCurrentProjectionPackage)) {
+ mCurrentProjectionState = status.getState();
+ mCurrentProjectionPackage = packageName;
+ }
+ }
+ notifyProjectionStatusChanged(null /* notify all listeners */);
+ }
+
+ @Override
+ public void registerProjectionStatusListener(ICarProjectionStatusListener listener)
+ throws RemoteException {
+ ICarImpl.assertProjectionStatusPermission(mContext);
+ mProjectionStatusListeners.add(listener);
+
+ // Immediately notify listener with the current status.
+ notifyProjectionStatusChanged(listener);
+ }
+
+ @Override
+ public void unregisterProjectionStatusListener(ICarProjectionStatusListener listener)
+ throws RemoteException {
+ ICarImpl.assertProjectionStatusPermission(mContext);
+ mProjectionStatusListeners.remove(listener);
+ }
+
+ private ProjectionReceiverClient getOrCreateProjectionReceiverClientLocked(
+ IBinder token) throws RemoteException {
+ ProjectionReceiverClient client;
+ client = mProjectionReceiverClients.get(token);
+ if (client == null) {
+ client = new ProjectionReceiverClient(() -> unregisterProjectionReceiverClient(token));
+ token.linkToDeath(client.mDeathRecipient, 0 /* flags */);
+ mProjectionReceiverClients.put(token, client);
+ }
+ return client;
+ }
+
+ private void unregisterProjectionReceiverClient(IBinder token) {
+ synchronized (mLock) {
+ ProjectionReceiverClient client = mProjectionReceiverClients.remove(token);
+ if (client != null && TextUtils.equals(
+ client.mProjectionStatus.getPackageName(), mCurrentProjectionPackage)) {
+ mCurrentProjectionPackage = null;
+ mCurrentProjectionState = PROJECTION_STATE_INACTIVE;
+ }
+ }
+ }
+
+ private void notifyProjectionStatusChanged(
+ @Nullable ICarProjectionStatusListener singleListenerToNotify)
+ throws RemoteException {
+ int currentState;
+ String currentPackage;
+ List<ProjectionStatus> statuses = new ArrayList<>();
+ synchronized (mLock) {
+ for (ProjectionReceiverClient client : mProjectionReceiverClients.values()) {
+ statuses.add(client.mProjectionStatus);
+ }
+ currentState = mCurrentProjectionState;
+ currentPackage = mCurrentProjectionPackage;
+ }
+
+ if (DBG) {
+ Log.d(TAG, "Notify projection status change, state: " + currentState + ", pkg: "
+ + currentPackage + ", listeners: " + mProjectionStatusListeners.size()
+ + ", listenerToNotify: " + singleListenerToNotify);
+ }
+
+ if (singleListenerToNotify == null) {
+ for (ICarProjectionStatusListener listener : mProjectionStatusListeners) {
+ listener.onProjectionStatusChanged(currentState, currentPackage, statuses);
+ }
+ } else {
+ singleListenerToNotify.onProjectionStatusChanged(
+ currentState, currentPackage, statuses);
+ }
+ }
+
+ @Override
+ public Bundle getProjectionOptions() {
+ ICarImpl.assertProjectionPermission(mContext);
+ synchronized (mLock) {
+ if (mProjectionOptions == null) {
+ mProjectionOptions = createProjectionOptionsBuilder()
+ .build();
+ }
+ }
+ return mProjectionOptions.toBundle();
+ }
+
+ private ProjectionOptions.Builder createProjectionOptionsBuilder() {
+ Resources res = mContext.getResources();
+
+ ProjectionOptions.Builder builder = ProjectionOptions.builder();
+
+ ActivityOptions activityOptions = createActivityOptions(res);
+ if (activityOptions != null) {
+ builder.setProjectionActivityOptions(activityOptions);
+ }
+
+ String consentActivity = res.getString(R.string.config_projectionConsentActivity);
+ if (!TextUtils.isEmpty(consentActivity)) {
+ builder.setConsentActivity(ComponentName.unflattenFromString(consentActivity));
+ }
+
+ builder.setUiMode(res.getInteger(R.integer.config_projectionUiMode));
+ return builder;
+ }
+
+ @Nullable
+ private static ActivityOptions createActivityOptions(Resources res) {
+ ActivityOptions activityOptions = ActivityOptions.makeBasic();
+ boolean changed = false;
+ int displayId = res.getInteger(R.integer.config_projectionActivityDisplayId);
+ if (displayId != -1) {
+ activityOptions.setLaunchDisplayId(displayId);
+ changed = true;
+ }
+ int[] rawBounds = res.getIntArray(R.array.config_projectionActivityLaunchBounds);
+ if (rawBounds != null && rawBounds.length == 4) {
+ Rect bounds = new Rect(rawBounds[0], rawBounds[1], rawBounds[2], rawBounds[3]);
+ activityOptions.setLaunchBounds(bounds);
+ changed = true;
+ }
+ return changed ? activityOptions : null;
+ }
+
private void startAccessPoint() {
synchronized (mLock) {
switch (mWifiMode) {
@@ -588,6 +806,10 @@
writer.println("SoftApCallback: " + mSoftApCallback);
writer.println("Bound to projection app: " + mBound);
writer.println("Registered Service: " + mRegisteredService);
+ writer.println("Current projection state: " + mCurrentProjectionState);
+ writer.println("Current projection package: " + mCurrentProjectionPackage);
+ writer.println("Projection status: " + mProjectionReceiverClients);
+ writer.println("WifiScanner: " + mWifiScanner);
}
}
@@ -599,6 +821,14 @@
}
}
+ void setUiMode(Integer uiMode) {
+ synchronized (mLock) {
+ mProjectionOptions = createProjectionOptionsBuilder()
+ .setUiMode(uiMode)
+ .build();
+ }
+ }
+
private static class ProjectionCallbackHolder
extends BinderInterfaceContainer<ICarProjectionCallback> {
ProjectionCallbackHolder(CarProjectionService service) {
@@ -796,4 +1026,21 @@
Random random = new Random();
return random.nextInt((RAND_SSID_INT_MAX - RAND_SSID_INT_MIN) + 1) + RAND_SSID_INT_MIN;
}
+
+ private static class ProjectionReceiverClient {
+ private final DeathRecipient mDeathRecipient;
+ private ProjectionStatus mProjectionStatus;
+
+ ProjectionReceiverClient(DeathRecipient deathRecipient) {
+ mDeathRecipient = deathRecipient;
+ }
+
+ @Override
+ public String toString() {
+ return "ProjectionReceiverClient{"
+ + "mDeathRecipient=" + mDeathRecipient
+ + ", mProjectionStatus=" + mProjectionStatus
+ + '}';
+ }
+ }
}
diff --git a/service/src/com/android/car/ICarImpl.java b/service/src/com/android/car/ICarImpl.java
index 034a881..2df562e 100644
--- a/service/src/com/android/car/ICarImpl.java
+++ b/service/src/com/android/car/ICarImpl.java
@@ -44,6 +44,7 @@
import com.android.car.systeminterface.SystemInterface;
import com.android.car.trust.CarTrustAgentEnrollmentService;
import com.android.car.user.CarUserService;
+import com.android.car.vms.VmsBrokerService;
import com.android.car.vms.VmsClientManager;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.car.ICarServiceHelper;
@@ -90,6 +91,7 @@
private final CarUserManagerHelper mUserManagerHelper;
private final CarUserService mCarUserService;
private final VmsClientManager mVmsClientManager;
+ private final VmsBrokerService mVmsBrokerService;
private final VmsSubscriberService mVmsSubscriberService;
private final VmsPublisherService mVmsPublisherService;
@@ -119,7 +121,7 @@
mCarUserService = new CarUserService(serviceContext, mUserManagerHelper);
mSystemActivityMonitoringService = new SystemActivityMonitoringService(serviceContext);
mCarPowerManagementService = new CarPowerManagementService(mContext, mHal.getPowerHal(),
- systemInterface);
+ systemInterface, mUserManagerHelper);
mCarPropertyService = new CarPropertyService(serviceContext, mHal.getPropertyHal());
mCarDrivingStateService = new CarDrivingStateService(serviceContext, mCarPropertyService);
mCarUXRestrictionsService = new CarUxRestrictionsManagerService(serviceContext,
@@ -132,7 +134,7 @@
mPerUserCarServiceHelper, mCarUXRestrictionsService);
mCarInputService = new CarInputService(serviceContext, mHal.getInputHal());
mCarProjectionService = new CarProjectionService(
- serviceContext, mCarInputService, mCarBluetoothService);
+ serviceContext, null /* handler */, mCarInputService, mCarBluetoothService);
mGarageModeService = new GarageModeService(mContext);
mAppFocusService = new AppFocusService(serviceContext, mSystemActivityMonitoringService);
mCarAudioService = new CarAudioService(serviceContext);
@@ -141,10 +143,12 @@
mAppFocusService, mCarInputService);
mSystemStateControllerService = new SystemStateControllerService(
serviceContext, mCarAudioService, this);
+ mVmsBrokerService = new VmsBrokerService();
mVmsClientManager = new VmsClientManager(serviceContext, mUserManagerHelper);
- mVmsSubscriberService = new VmsSubscriberService(serviceContext, mHal.getVmsHal());
- mVmsPublisherService = new VmsPublisherService(serviceContext, mVmsClientManager,
- mHal.getVmsHal());
+ mVmsSubscriberService = new VmsSubscriberService(
+ serviceContext, mVmsBrokerService, mHal.getVmsHal());
+ mVmsPublisherService = new VmsPublisherService(
+ serviceContext, mVmsBrokerService, mVmsClientManager, mHal.getVmsHal());
mCarDiagnosticService = new CarDiagnosticService(serviceContext, mHal.getDiagnosticHal());
mCarStorageMonitoringService = new CarStorageMonitoringService(serviceContext,
systemInterface);
@@ -270,7 +274,6 @@
assertClusterManagerPermission(mContext);
return mInstrumentClusterService.getManagerService();
case Car.PROJECTION_SERVICE:
- assertProjectionPermission(mContext);
return mCarProjectionService;
case Car.VMS_SUBSCRIBER_SERVICE:
assertVmsSubscriberPermission(mContext);
@@ -345,6 +348,11 @@
assertPermission(context, Car.PERMISSION_CAR_PROJECTION);
}
+ /** Verify the calling context has the {@link Car#PERMISSION_CAR_PROJECTION_STATUS} */
+ public static void assertProjectionStatusPermission(Context context) {
+ assertPermission(context, Car.PERMISSION_CAR_PROJECTION_STATUS);
+ }
+
public static void assertAnyDiagnosticPermission(Context context) {
assertAnyPermission(context,
Car.PERMISSION_CAR_DIAGNOSTIC_READ_ALL,
@@ -466,6 +474,8 @@
private static final String COMMAND_ENABLE_UXR = "enable-uxr";
private static final String COMMAND_GARAGE_MODE = "garage-mode";
private static final String COMMAND_GET_DO_ACTIVITIES = "get-do-activities";
+ private static final String COMMAND_GET_CARPROPERTYCONFIG = "get-carpropertyconfig";
+ private static final String COMMAND_PROJECTION_UI_MODE = "projection-ui-mode";
private static final String PARAM_DAY_MODE = "day";
private static final String PARAM_NIGHT_MODE = "night";
@@ -490,6 +500,8 @@
pw.println("\t Force into garage mode or check status.");
pw.println("\tget-do-activities pkgname");
pw.println("\t Get Distraction Optimized activities in given package.");
+ pw.println("\tget-carpropertyconfig [propertyId]");
+ pw.println("\t Get a CarPropertyConfig by Id in Hex or list all CarPropertyConfigs");
}
public void exec(String[] args, PrintWriter writer) {
@@ -557,6 +569,17 @@
}
}
break;
+ case COMMAND_GET_CARPROPERTYCONFIG:
+ String propertyId = args.length < 2 ? "" : args[1];
+ mHal.dumpPropertyConfigs(writer, propertyId);
+ break;
+ case COMMAND_PROJECTION_UI_MODE:
+ if (args.length != 2) {
+ writer.println("Incorrect number of arguments");
+ dumpHelp(writer);
+ break;
+ }
+ mCarProjectionService.setUiMode(Integer.valueOf(args[1]));
default:
writer.println("Unknown command: \"" + arg + "\"");
dumpHelp(writer);
diff --git a/service/src/com/android/car/SystemActivityMonitoringService.java b/service/src/com/android/car/SystemActivityMonitoringService.java
index c2abdc6..11b6887 100644
--- a/service/src/com/android/car/SystemActivityMonitoringService.java
+++ b/service/src/com/android/car/SystemActivityMonitoringService.java
@@ -425,6 +425,10 @@
}
@Override
+ public void onForegroundServicesChanged(int pid, int uid, int fgServiceTypes) {
+ }
+
+ @Override
public void onProcessDied(int pid, int uid) {
mHandler.requestProcessDied(pid, uid);
}
diff --git a/service/src/com/android/car/VmsPublisherService.java b/service/src/com/android/car/VmsPublisherService.java
index 6bc8d3e..3c650d8 100644
--- a/service/src/com/android/car/VmsPublisherService.java
+++ b/service/src/com/android/car/VmsPublisherService.java
@@ -24,15 +24,13 @@
import android.car.vms.VmsSubscriptionState;
import android.content.Context;
import android.os.Binder;
-import android.os.Handler;
import android.os.IBinder;
-import android.os.Message;
import android.os.RemoteException;
import android.util.ArrayMap;
import android.util.Log;
import com.android.car.hal.VmsHalService;
-import com.android.car.hal.VmsHalService.VmsHalPublisherListener;
+import com.android.car.vms.VmsBrokerService;
import com.android.car.vms.VmsClientManager;
import java.io.PrintWriter;
@@ -45,40 +43,40 @@
* Binds to publishers and configures them to use this service.
* Notifies publishers of subscription changes.
*/
-public class VmsPublisherService extends IVmsPublisherService.Stub implements CarServiceBase {
+public class VmsPublisherService extends IVmsPublisherService.Stub implements CarServiceBase,
+ VmsBrokerService.PublisherListener {
private static final boolean DBG = true;
private static final String TAG = "VmsPublisherService";
- private static final int MSG_HAL_SUBSCRIPTION_CHANGED = 1;
-
private final Context mContext;
private final VmsClientManager mClientManager;
- private final VmsListener mClientListener = new VmsListener();
+ private final VmsBrokerService mBrokerService;
private final VmsHalService mHal;
- private final VmsHalPublisherListener mHalPublisherListener;
+ private final VmsListener mClientListener = new VmsListener();
private final Map<String, IVmsPublisherClient> mPublisherMap = Collections.synchronizedMap(
new ArrayMap<>());
- private final Handler mHandler = new EventHandler();
- public VmsPublisherService(Context context, VmsClientManager clientManager, VmsHalService hal) {
+ public VmsPublisherService(Context context, VmsBrokerService brokerService,
+ VmsClientManager clientManager,
+ VmsHalService hal) {
mContext = context;
mClientManager = clientManager;
+ mBrokerService = brokerService;
mHal = hal;
- mHalPublisherListener = subscriptionState -> mHandler.sendMessage(
- mHandler.obtainMessage(MSG_HAL_SUBSCRIPTION_CHANGED, subscriptionState));
}
@Override
public void init() {
+ mClientListener.onClientConnected("VmsHalService", mHal.getPublisherClient());
mClientManager.registerConnectionListener(mClientListener);
- mHal.addPublisherListener(mHalPublisherListener);
- mHal.signalPublisherServiceIsReady();
+ mBrokerService.addPublisherListener(this);
}
@Override
public void release() {
+ mClientListener.onClientDisconnected("VmsHalService");
mClientManager.unregisterConnectionListener(mClientListener);
- mHal.removePublisherListener(mHalPublisherListener);
+ mBrokerService.removePublisherListener(this);
mPublisherMap.clear();
}
@@ -88,13 +86,12 @@
writer.println("mPublisherMap:" + mPublisherMap.keySet());
}
- /* Called in arbitrary binder thread */
@Override
public void setLayersOffering(IBinder token, VmsLayersOffering offering) {
- mHal.setPublisherLayersOffering(token, offering);
+ ICarImpl.assertVmsPublisherPermission(mContext);
+ mBrokerService.setPublisherLayersOffering(token, offering);
}
- /* Called in arbitrary binder thread */
@Override
public void publish(IBinder token, VmsLayer layer, int publisherId, byte[] payload) {
if (DBG) {
@@ -104,7 +101,7 @@
// Send the message to application listeners.
Set<IVmsSubscriberClient> listeners =
- mHal.getSubscribersForLayerFromPublisher(layer, publisherId);
+ mBrokerService.getSubscribersForLayerFromPublisher(layer, publisherId);
if (DBG) {
Log.d(TAG, "Number of subscribed apps: " + listeners.size());
@@ -116,35 +113,22 @@
Log.e(TAG, "unable to publish to listener: " + listener);
}
}
-
- // Send the message to HAL
- if (mHal.isHalSubscribed(layer)) {
- Log.d(TAG, "HAL is subscribed");
- mHal.setDataMessage(layer, payload);
- } else {
- Log.d(TAG, "HAL is NOT subscribed");
- }
}
- /* Called in arbitrary binder thread */
@Override
public VmsSubscriptionState getSubscriptions() {
ICarImpl.assertVmsPublisherPermission(mContext);
- return mHal.getSubscriptionState();
+ return mBrokerService.getSubscriptionState();
}
- /* Called in arbitrary binder thread */
@Override
public int getPublisherId(byte[] publisherInfo) {
ICarImpl.assertVmsPublisherPermission(mContext);
- return mHal.getPublisherId(publisherInfo);
+ return mBrokerService.getPublisherId(publisherInfo);
}
- /**
- * This method is only invoked by VmsHalService.notifyPublishers which is synchronized.
- * Therefore this method only sees a non-decreasing sequence.
- */
- private void handleHalSubscriptionChanged(VmsSubscriptionState subscriptionState) {
+ @Override
+ public void onSubscriptionChange(VmsSubscriptionState subscriptionState) {
// Send the message to application listeners.
synchronized (mPublisherMap) {
for (IVmsPublisherClient client : mPublisherMap.values()) {
@@ -183,16 +167,4 @@
mPublisherMap.remove(publisherName);
}
}
-
- private class EventHandler extends Handler {
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case MSG_HAL_SUBSCRIPTION_CHANGED:
- handleHalSubscriptionChanged((VmsSubscriptionState) msg.obj);
- return;
- }
- super.handleMessage(msg);
- }
- }
}
diff --git a/service/src/com/android/car/VmsSubscriberService.java b/service/src/com/android/car/VmsSubscriberService.java
index 61c7929..fc28978 100644
--- a/service/src/com/android/car/VmsSubscriberService.java
+++ b/service/src/com/android/car/VmsSubscriberService.java
@@ -27,6 +27,7 @@
import android.util.Log;
import com.android.car.hal.VmsHalService;
+import com.android.car.vms.VmsBrokerService;
import com.android.internal.annotations.GuardedBy;
import java.io.PrintWriter;
@@ -41,14 +42,14 @@
* + Receives HAL updates by implementing VmsHalService.VmsHalListener.
* + Offers subscriber/publisher services by implementing IVmsService.Stub.
*/
-public class VmsSubscriberService extends IVmsSubscriberService.Stub
- implements CarServiceBase, VmsHalService.VmsHalSubscriberListener {
+public class VmsSubscriberService extends IVmsSubscriberService.Stub implements CarServiceBase,
+ VmsBrokerService.SubscriberListener {
private static final boolean DBG = true;
private static final String PERMISSION = Car.PERMISSION_VMS_SUBSCRIBER;
private static final String TAG = "VmsSubscriberService";
private final Context mContext;
- private final VmsHalService mHal;
+ private final VmsBrokerService mBrokerService;
@GuardedBy("mSubscriberServiceLock")
private final VmsSubscribersManager mSubscribersManager = new VmsSubscribersManager();
@@ -90,7 +91,7 @@
// Remove the subscriber subscriptions.
if (subscriber != null) {
Log.d(TAG, "Removing subscriptions for dead subscriber: " + subscriber);
- mHal.removeDeadSubscriber(subscriber);
+ mBrokerService.removeDeadSubscriber(subscriber);
} else {
Log.d(TAG, "Handling dead binder with no matching subscriber");
@@ -195,24 +196,23 @@
}
}
- public VmsSubscriberService(Context context, VmsHalService hal) {
+ public VmsSubscriberService(Context context, VmsBrokerService brokerService,
+ VmsHalService hal) {
mContext = context;
- mHal = hal;
+ mBrokerService = brokerService;
+ hal.setVmsSubscriberService(this);
}
// Implements CarServiceBase interface.
@Override
public void init() {
- mHal.addSubscriberListener(this);
-
- // Signal to subscribers that the SubscriberService is ready.
- mHal.signalSubscriberServiceIsReady();
+ mBrokerService.addSubscriberListener(this);
}
@Override
public void release() {
+ mBrokerService.removeSubscriberListener(this);
mSubscribersManager.release();
- mHal.removeSubscriberListener(this);
}
@Override
@@ -233,9 +233,6 @@
public void removeVmsSubscriberToNotifications(IVmsSubscriberClient subscriber) {
ICarImpl.assertVmsSubscriberPermission(mContext);
synchronized (mSubscriberServiceLock) {
- if (mHal.containsSubscriber(subscriber)) {
- throw new IllegalArgumentException("Subscriber has active subscriptions.");
- }
mSubscribersManager.remove(subscriber);
}
}
@@ -248,7 +245,7 @@
mSubscribersManager.add(subscriber);
// Add the subscription for the layer.
- mHal.addSubscription(subscriber, layer);
+ mBrokerService.addSubscription(subscriber, layer);
}
}
@@ -257,32 +254,32 @@
ICarImpl.assertVmsSubscriberPermission(mContext);
synchronized (mSubscriberServiceLock) {
// Remove the subscription.
- mHal.removeSubscription(subscriber, layer);
+ mBrokerService.removeSubscription(subscriber, layer);
}
}
@Override
public void addVmsSubscriberToPublisher(IVmsSubscriberClient subscriber,
- VmsLayer layer,
- int publisherId) {
+ VmsLayer layer,
+ int publisherId) {
ICarImpl.assertVmsSubscriberPermission(mContext);
synchronized (mSubscriberServiceLock) {
// Add the subscriber so it can subscribe.
mSubscribersManager.add(subscriber);
// Add the subscription for the layer.
- mHal.addSubscription(subscriber, layer, publisherId);
+ mBrokerService.addSubscription(subscriber, layer, publisherId);
}
}
@Override
public void removeVmsSubscriberToPublisher(IVmsSubscriberClient subscriber,
- VmsLayer layer,
- int publisherId) {
+ VmsLayer layer,
+ int publisherId) {
ICarImpl.assertVmsSubscriberPermission(mContext);
synchronized (mSubscriberServiceLock) {
// Remove the subscription.
- mHal.removeSubscription(subscriber, layer, publisherId);
+ mBrokerService.removeSubscription(subscriber, layer, publisherId);
}
}
@@ -291,7 +288,7 @@
ICarImpl.assertVmsSubscriberPermission(mContext);
synchronized (mSubscriberServiceLock) {
mSubscribersManager.add(subscriber);
- mHal.addSubscription(subscriber);
+ mBrokerService.addSubscription(subscriber);
}
}
@@ -300,7 +297,7 @@
ICarImpl.assertVmsSubscriberPermission(mContext);
synchronized (mSubscriberServiceLock) {
// Remove the subscription.
- mHal.removeSubscription(subscriber);
+ mBrokerService.removeSubscription(subscriber);
}
}
@@ -308,25 +305,22 @@
public byte[] getPublisherInfo(int publisherId) {
ICarImpl.assertVmsSubscriberPermission(mContext);
synchronized (mSubscriberServiceLock) {
- return mHal.getPublisherInfo(publisherId);
+ return mBrokerService.getPublisherInfo(publisherId);
}
}
@Override
public VmsAvailableLayers getAvailableLayers() {
- return mHal.getAvailableLayers();
+ return mBrokerService.getAvailableLayers();
}
- // Implements VmsHalSubscriberListener interface
@Override
- public void onDataMessage(VmsLayer layer, int publisherId, byte[] payload) {
- if (DBG) {
- Log.d(TAG, "Publishing a message for layer: " + layer);
- }
+ public void onMessageReceived(VmsLayer layer, int publisherId, byte[] payload) {
+ if (DBG) Log.d(TAG, "Publishing a message for layer: " + layer);
Set<IVmsSubscriberClient> subscribers =
- mHal.getSubscribersForLayerFromPublisher(layer, publisherId);
+ mBrokerService.getSubscribersForLayerFromPublisher(layer, publisherId);
for (IVmsSubscriberClient subscriber : subscribers) {
try {
@@ -340,10 +334,8 @@
}
@Override
- public void onLayersAvaiabilityChange(VmsAvailableLayers availableLayers) {
- if (DBG) {
- Log.d(TAG, "Publishing layers availability change: " + availableLayers);
- }
+ public void onLayersAvailabilityChange(VmsAvailableLayers availableLayers) {
+ if (DBG) Log.d(TAG, "Publishing layers availability change: " + availableLayers);
Set<IVmsSubscriberClient> subscribers;
subscribers = new HashSet<>(mSubscribersManager.getListeners());
diff --git a/service/src/com/android/car/hal/PropertyHalServiceIds.java b/service/src/com/android/car/hal/PropertyHalServiceIds.java
index dd38c33..ca90f78 100644
--- a/service/src/com/android/car/hal/PropertyHalServiceIds.java
+++ b/service/src/com/android/car/hal/PropertyHalServiceIds.java
@@ -397,6 +397,9 @@
mProps.put(VehicleProperty.EV_BATTERY_DISPLAY_UNITS, new Pair<>(
Car.PERMISSION_READ_DISPLAY_UNITS,
Car.PERMISSION_CONTROL_DISPLAY_UNITS));
+ mProps.put(VehicleProperty.FUEL_CONSUMPTION_UNITS_DISTANCE_OVER_VOLUME, new Pair<>(
+ Car.PERMISSION_READ_DISPLAY_UNITS,
+ Car.PERMISSION_CONTROL_DISPLAY_UNITS));
}
/**
diff --git a/service/src/com/android/car/hal/VehicleHal.java b/service/src/com/android/car/hal/VehicleHal.java
index f73d07c..286ca79 100644
--- a/service/src/com/android/car/hal/VehicleHal.java
+++ b/service/src/com/android/car/hal/VehicleHal.java
@@ -387,7 +387,7 @@
}
}
- void set(VehiclePropValue propValue) throws PropertyTimeoutException {
+ protected void set(VehiclePropValue propValue) throws PropertyTimeoutException {
mHalClient.setValue(propValue);
}
@@ -467,33 +467,8 @@
for (HalServiceBase service: mAllServices) {
service.dump(writer);
}
-
- List<VehiclePropConfig> configList;
- synchronized (this) {
- configList = new ArrayList<>(mAllProperties.values());
- }
-
- writer.println("**All properties**");
- for (VehiclePropConfig config : configList) {
- StringBuilder builder = new StringBuilder()
- .append("Property:0x").append(toHexString(config.prop))
- .append(",Property name:").append(VehicleProperty.toString(config.prop))
- .append(",access:0x").append(toHexString(config.access))
- .append(",changeMode:0x").append(toHexString(config.changeMode))
- .append(",config:0x").append(Arrays.toString(config.configArray.toArray()))
- .append(",fs min:").append(config.minSampleRate)
- .append(",fs max:").append(config.maxSampleRate);
- for (VehicleAreaConfig area : config.areaConfigs) {
- builder.append(",areaId :").append(toHexString(area.areaId))
- .append(",f min:").append(area.minFloatValue)
- .append(",f max:").append(area.maxFloatValue)
- .append(",i min:").append(area.minInt32Value)
- .append(",i max:").append(area.maxInt32Value)
- .append(",i64 min:").append(area.minInt64Value)
- .append(",i64 max:").append(area.maxInt64Value);
- }
- writer.println(builder.toString());
- }
+ // Dump all VHAL property configure.
+ dumpPropertyConfigs(writer, "");
writer.println(String.format("**All Events, now ns:%d**",
SystemClock.elapsedRealtimeNanos()));
for (VehiclePropertyEventInfo info : mEventLog.values()) {
@@ -510,6 +485,55 @@
}
/**
+ * Dump VHAL property configs.
+ *
+ * @param writer
+ * @param propId Property ID in Hex. If propid is empty string, dump all properties.
+ */
+ public void dumpPropertyConfigs(PrintWriter writer, String propId) {
+ List<VehiclePropConfig> configList;
+ synchronized (this) {
+ configList = new ArrayList<>(mAllProperties.values());
+ }
+
+ if (propId.equals("")) {
+ writer.println("**All properties**");
+ for (VehiclePropConfig config : configList) {
+ writer.println(dumpPropertyConfigsHelp(config));
+ }
+ return;
+ }
+ for (VehiclePropConfig config : configList) {
+ if (toHexString(config.prop).equals(propId)) {
+ writer.println(dumpPropertyConfigsHelp(config));
+ return;
+ }
+ }
+
+ }
+
+ /** Use VehiclePropertyConfig to construct string for dumping */
+ private String dumpPropertyConfigsHelp(VehiclePropConfig config) {
+ StringBuilder builder = new StringBuilder()
+ .append("Property:0x").append(toHexString(config.prop))
+ .append(",Property name:").append(VehicleProperty.toString(config.prop))
+ .append(",access:0x").append(toHexString(config.access))
+ .append(",changeMode:0x").append(toHexString(config.changeMode))
+ .append(",config:0x").append(Arrays.toString(config.configArray.toArray()))
+ .append(",fs min:").append(config.minSampleRate)
+ .append(",fs max:").append(config.maxSampleRate);
+ for (VehicleAreaConfig area : config.areaConfigs) {
+ builder.append(",areaId :").append(toHexString(area.areaId))
+ .append(",f min:").append(area.minFloatValue)
+ .append(",f max:").append(area.maxFloatValue)
+ .append(",i min:").append(area.minInt32Value)
+ .append(",i max:").append(area.maxInt32Value)
+ .append(",i64 min:").append(area.minInt64Value)
+ .append(",i64 max:").append(area.maxInt64Value);
+ }
+ return builder.toString();
+ }
+ /**
* Inject a VHAL event
*
* @param property the Vehicle property Id as defined in the HAL
diff --git a/service/src/com/android/car/hal/VmsHalService.java b/service/src/com/android/car/hal/VmsHalService.java
index 71e1efc..1f6c2e2 100644
--- a/service/src/com/android/car/hal/VmsHalService.java
+++ b/service/src/com/android/car/hal/VmsHalService.java
@@ -19,9 +19,11 @@
import static java.lang.Integer.toHexString;
-import android.annotation.SystemApi;
import android.car.VehicleAreaType;
+import android.car.vms.IVmsPublisherClient;
+import android.car.vms.IVmsPublisherService;
import android.car.vms.IVmsSubscriberClient;
+import android.car.vms.IVmsSubscriberService;
import android.car.vms.VmsAssociatedLayer;
import android.car.vms.VmsAvailableLayers;
import android.car.vms.VmsLayer;
@@ -37,864 +39,751 @@
import android.hardware.automotive.vehicle.V2_0.VmsMessageWithLayerAndPublisherIdIntegerValuesIndex;
import android.hardware.automotive.vehicle.V2_0.VmsMessageWithLayerIntegerValuesIndex;
import android.hardware.automotive.vehicle.V2_0.VmsOfferingMessageIntegerValuesIndex;
-import android.os.Binder;
+import android.hardware.automotive.vehicle.V2_0.VmsPublisherInformationIntegerValuesIndex;
+import android.os.Handler;
+import android.os.HandlerThread;
import android.os.IBinder;
+import android.os.Message;
+import android.os.RemoteException;
+import android.util.ArraySet;
import android.util.Log;
+import androidx.annotation.GuardedBy;
+import androidx.annotation.VisibleForTesting;
+
import com.android.car.CarLog;
-import com.android.car.VmsLayersAvailability;
-import com.android.car.VmsPublishersInfo;
-import com.android.car.VmsRouting;
-import com.android.internal.annotations.GuardedBy;
import java.io.PrintWriter;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
-import java.util.HashMap;
import java.util.HashSet;
-import java.util.LinkedList;
import java.util.List;
-import java.util.Map;
import java.util.Set;
-import java.util.concurrent.CopyOnWriteArrayList;
/**
- * This is a glue layer between the VehicleHal and the VmsService. It sends VMS properties back and
- * forth.
+ * VMS client implementation that proxies VmsPublisher/VmsSubscriber API calls to the Vehicle HAL
+ * using HAL-specific message encodings.
+ *
+ * @see android.hardware.automotive.vehicle.V2_0
*/
-@SystemApi
public class VmsHalService extends HalServiceBase {
-
private static final boolean DBG = true;
- private static final int HAL_PROPERTY_ID = VehicleProperty.VEHICLE_MAP_SERVICE;
private static final String TAG = "VmsHalService";
+ private static final int HAL_PROPERTY_ID = VehicleProperty.VEHICLE_MAP_SERVICE;
+ private static final int NUM_INTEGERS_IN_VMS_LAYER = 3;
- private final static List<Integer> AVAILABILITY_MESSAGE_TYPES = Collections.unmodifiableList(
- Arrays.asList(
- VmsMessageType.AVAILABILITY_RESPONSE,
- VmsMessageType.AVAILABILITY_CHANGE));
-
- private boolean mIsSupported = false;
- private CopyOnWriteArrayList<VmsHalPublisherListener> mPublisherListeners =
- new CopyOnWriteArrayList<>();
- private CopyOnWriteArrayList<VmsHalSubscriberListener> mSubscriberListeners =
- new CopyOnWriteArrayList<>();
-
- private final IBinder mHalPublisherToken = new Binder();
private final VehicleHal mVehicleHal;
+ private volatile boolean mIsSupported = false;
- private final Object mLock = new Object();
- private final VmsRouting mRouting = new VmsRouting();
- @GuardedBy("mLock")
- private final Map<IBinder, Map<Integer, VmsLayersOffering>> mOfferings = new HashMap<>();
- @GuardedBy("mLock")
- private final VmsLayersAvailability mAvailableLayers = new VmsLayersAvailability();
- private final VmsPublishersInfo mPublishersInfo = new VmsPublishersInfo();
+ private IBinder mPublisherToken;
+ private IVmsPublisherService mPublisherService;
+ private IVmsSubscriberService mSubscriberService;
- /**
- * The VmsPublisherService implements this interface to receive data from the HAL.
- */
- public interface VmsHalPublisherListener {
- void onChange(VmsSubscriptionState subscriptionState);
- }
+ @GuardedBy("this")
+ private HandlerThread mHandlerThread;
+ @GuardedBy("this")
+ private Handler mHandler;
- /**
- * The VmsSubscriberService implements this interface to receive data from the HAL.
- */
- public interface VmsHalSubscriberListener {
- // Notifies the listener on a data Message from a publisher.
- void onDataMessage(VmsLayer layer, int publisherId, byte[] payload);
+ private int mSubscriptionStateSequence = -1;
+ private int mAvailableLayersSequence = -1;
- // Notifies the listener on a change in available layers.
- void onLayersAvaiabilityChange(VmsAvailableLayers availableLayers);
- }
-
- /**
- * The VmsService implements this interface to receive data from the HAL.
- */
- protected VmsHalService(VehicleHal vehicleHal) {
- mVehicleHal = vehicleHal;
- if (DBG) {
- Log.d(TAG, "Started VmsHalService!");
+ private final IVmsPublisherClient.Stub mPublisherClient = new IVmsPublisherClient.Stub() {
+ @Override
+ public void setVmsPublisherService(IBinder token, IVmsPublisherService service) {
+ mPublisherToken = token;
+ mPublisherService = service;
}
- }
- /**
- * VMS subscribers should wait for a layers availability message which indicates
- * the subscriber service is ready to handle subscription requests.
- */
- public void signalSubscriberServiceIsReady() {
- notifyOfAvailabilityChange();
- }
-
- /**
- * VMS publishers should wait for a subscription state message which indicates
- * the publisher service is ready to handle offerings and publishing.
- */
- public void signalPublisherServiceIsReady() {
- notifyOfSubscriptionChange();
- }
-
- public void addPublisherListener(VmsHalPublisherListener listener) {
- mPublisherListeners.add(listener);
- }
-
- public void addSubscriberListener(VmsHalSubscriberListener listener) {
- mSubscriberListeners.add(listener);
- }
-
- public void removePublisherListener(VmsHalPublisherListener listener) {
- mPublisherListeners.remove(listener);
- }
-
- public void removeSubscriberListener(VmsHalSubscriberListener listener) {
- mSubscriberListeners.remove(listener);
- }
-
- public void addSubscription(IVmsSubscriberClient listener, VmsLayer layer) {
- boolean firstSubscriptionForLayer = false;
- if (DBG) {
- Log.d(TAG, "Checking for first subscription. Layer: " + layer);
+ @Override
+ public void onVmsSubscriptionChange(VmsSubscriptionState subscriptionState) {
+ // Registration of this callback is handled by VmsPublisherService.
+ // As a result, HAL support must be checked whenever the callback is triggered.
+ if (!mIsSupported) {
+ return;
+ }
+ if (DBG) Log.d(TAG, "Handling a subscription state change");
+ Message.obtain(mHandler, VmsMessageType.SUBSCRIPTIONS_CHANGE, subscriptionState)
+ .sendToTarget();
}
- synchronized (mLock) {
- // Check if publishers need to be notified about this change in subscriptions.
- firstSubscriptionForLayer = !mRouting.hasLayerSubscriptions(layer);
+ };
- // Add the listeners subscription to the layer
- mRouting.addSubscription(listener, layer);
+ private final IVmsSubscriberClient.Stub mSubscriberClient = new IVmsSubscriberClient.Stub() {
+ @Override
+ public void onVmsMessageReceived(VmsLayer layer, byte[] payload) {
+ if (DBG) Log.d(TAG, "Handling a data message for Layer: " + layer);
+ // TODO(b/124130256): Set publisher ID of data message
+ Message.obtain(mHandler, VmsMessageType.DATA, createDataMessage(layer, 0, payload))
+ .sendToTarget();
}
- if (firstSubscriptionForLayer) {
- notifyHalPublishers(layer, true);
- notifyOfSubscriptionChange();
- }
- }
- public void removeSubscription(IVmsSubscriberClient listener, VmsLayer layer) {
- boolean layerHasSubscribers = true;
- synchronized (mLock) {
- if (!mRouting.hasLayerSubscriptions(layer)) {
- if (DBG) {
- Log.d(TAG, "Trying to remove a layer with no subscription: " + layer);
+ @Override
+ public void onLayersAvailabilityChanged(VmsAvailableLayers availableLayers) {
+ if (DBG) Log.d(TAG, "Handling a layer availability change");
+ Message.obtain(mHandler, VmsMessageType.AVAILABILITY_CHANGE, availableLayers)
+ .sendToTarget();
+ }
+ };
+
+ private final Handler.Callback mHandlerCallback = msg -> {
+ int messageType = msg.what;
+ VehiclePropValue vehicleProp = null;
+ switch (messageType) {
+ case VmsMessageType.DATA:
+ vehicleProp = (VehiclePropValue) msg.obj;
+ break;
+ case VmsMessageType.SUBSCRIPTIONS_CHANGE:
+ VmsSubscriptionState subscriptionState = (VmsSubscriptionState) msg.obj;
+ // Drop out-of-order notifications
+ if (subscriptionState.getSequenceNumber() <= mSubscriptionStateSequence) {
+ break;
}
- return;
+ vehicleProp = createSubscriptionStateMessage(
+ VmsMessageType.SUBSCRIPTIONS_CHANGE,
+ subscriptionState);
+ mSubscriptionStateSequence = subscriptionState.getSequenceNumber();
+ break;
+ case VmsMessageType.AVAILABILITY_CHANGE:
+ VmsAvailableLayers availableLayers = (VmsAvailableLayers) msg.obj;
+ // Drop out-of-order notifications
+ if (availableLayers.getSequence() <= mAvailableLayersSequence) {
+ break;
+ }
+ vehicleProp = createAvailableLayersMessage(
+ VmsMessageType.AVAILABILITY_CHANGE,
+ availableLayers);
+ mAvailableLayersSequence = availableLayers.getSequence();
+ break;
+ default:
+ Log.e(TAG, "Unexpected message type: " + messageType);
+ }
+ if (vehicleProp != null) {
+ if (DBG) Log.d(TAG, "Sending " + VmsMessageType.toString(messageType) + " message");
+ try {
+ setPropertyValue(vehicleProp);
+ } catch (RemoteException e) {
+ Log.e(TAG, "While sending " + VmsMessageType.toString(messageType));
}
-
- // Remove the listeners subscription to the layer
- mRouting.removeSubscription(listener, layer);
-
- // Check if publishers need to be notified about this change in subscriptions.
- layerHasSubscribers = mRouting.hasLayerSubscriptions(layer);
}
- if (!layerHasSubscribers) {
- notifyHalPublishers(layer, false);
- notifyOfSubscriptionChange();
- }
- }
+ return true;
+ };
- public void addSubscription(IVmsSubscriberClient listener) {
- synchronized (mLock) {
- mRouting.addSubscription(listener);
- }
- }
-
- public void removeSubscription(IVmsSubscriberClient listener) {
- synchronized (mLock) {
- mRouting.removeSubscription(listener);
- }
- }
-
- public void addSubscription(IVmsSubscriberClient listener, VmsLayer layer, int publisherId) {
- boolean firstSubscriptionForLayer = false;
- synchronized (mLock) {
- // Check if publishers need to be notified about this change in subscriptions.
- firstSubscriptionForLayer = !(mRouting.hasLayerSubscriptions(layer) ||
- mRouting.hasLayerFromPublisherSubscriptions(layer, publisherId));
-
- // Add the listeners subscription to the layer
- mRouting.addSubscription(listener, layer, publisherId);
- }
- if (firstSubscriptionForLayer) {
- notifyHalPublishers(layer, true);
- notifyOfSubscriptionChange();
- }
- }
-
- public void removeSubscription(IVmsSubscriberClient listener, VmsLayer layer, int publisherId) {
- boolean layerHasSubscribers = true;
- synchronized (mLock) {
- if (!mRouting.hasLayerFromPublisherSubscriptions(layer, publisherId)) {
- Log.i(TAG, "Trying to remove a layer with no subscription: " +
- layer + ", publisher ID:" + publisherId);
- return;
- }
-
- // Remove the listeners subscription to the layer
- mRouting.removeSubscription(listener, layer, publisherId);
-
- // Check if publishers need to be notified about this change in subscriptions.
- layerHasSubscribers = mRouting.hasLayerSubscriptions(layer) ||
- mRouting.hasLayerFromPublisherSubscriptions(layer, publisherId);
- }
- if (!layerHasSubscribers) {
- notifyHalPublishers(layer, false);
- notifyOfSubscriptionChange();
- }
- }
-
- public void removeDeadSubscriber(IVmsSubscriberClient listener) {
- synchronized (mLock) {
- mRouting.removeDeadSubscriber(listener);
- }
- }
-
- public Set<IVmsSubscriberClient> getSubscribersForLayerFromPublisher(VmsLayer layer,
- int publisherId) {
- synchronized (mLock) {
- return mRouting.getSubscribersForLayerFromPublisher(layer, publisherId);
- }
- }
-
- public Set<IVmsSubscriberClient> getAllSubscribers() {
- synchronized (mLock) {
- return mRouting.getAllSubscribers();
- }
- }
-
- public boolean isHalSubscribed(VmsLayer layer) {
- synchronized (mLock) {
- return mRouting.isHalSubscribed(layer);
- }
- }
-
- public VmsSubscriptionState getSubscriptionState() {
- synchronized (mLock) {
- return mRouting.getSubscriptionState();
- }
+ /**
+ * Constructor used by {@link VehicleHal}
+ */
+ VmsHalService(VehicleHal vehicleHal) {
+ mVehicleHal = vehicleHal;
}
/**
- * Assigns an idempotent ID for publisherInfo and stores it. The idempotency in this case means
- * that the same publisherInfo will always, within a trip of the vehicle, return the same ID.
- * The publisherInfo should be static for a binary and should only change as part of a software
- * update. The publisherInfo is a serialized proto message which VMS clients can interpret.
+ * Retrieves the callback message handler for use by unit tests.
*/
- public int getPublisherId(byte[] publisherInfo) {
- if (DBG) {
- Log.i(TAG, "Getting publisher static ID");
- }
- synchronized (mLock) {
- return mPublishersInfo.getIdForInfo(publisherInfo);
- }
- }
-
- public byte[] getPublisherInfo(int publisherId) {
- if (DBG) {
- Log.i(TAG, "Getting information for publisher ID: " + publisherId);
- }
- synchronized (mLock) {
- return mPublishersInfo.getPublisherInfo(publisherId);
- }
- }
-
- private void addHalSubscription(VmsLayer layer) {
- boolean firstSubscriptionForLayer = true;
- synchronized (mLock) {
- // Check if publishers need to be notified about this change in subscriptions.
- firstSubscriptionForLayer = !mRouting.hasLayerSubscriptions(layer);
-
- // Add the listeners subscription to the layer
- mRouting.addHalSubscription(layer);
- }
- if (firstSubscriptionForLayer) {
- notifyHalPublishers(layer, true);
- notifyOfSubscriptionChange();
- }
- }
-
- private void addHalSubscriptionToPublisher(VmsLayer layer, int publisherId) {
- boolean firstSubscriptionForLayer = true;
- synchronized (mLock) {
- // Check if publishers need to be notified about this change in subscriptions.
- firstSubscriptionForLayer = !(mRouting.hasLayerSubscriptions(layer) ||
- mRouting.hasLayerFromPublisherSubscriptions(layer, publisherId));
-
- // Add the listeners subscription to the layer
- mRouting.addHalSubscriptionToPublisher(layer, publisherId);
- }
- if (firstSubscriptionForLayer) {
- notifyHalPublishers(layer, publisherId, true);
- notifyOfSubscriptionChange();
- }
- }
-
- private void removeHalSubscription(VmsLayer layer) {
- boolean layerHasSubscribers = true;
- synchronized (mLock) {
- if (!mRouting.hasLayerSubscriptions(layer)) {
- Log.i(TAG, "Trying to remove a layer with no subscription: " + layer);
- return;
- }
-
- // Remove the listeners subscription to the layer
- mRouting.removeHalSubscription(layer);
-
- // Check if publishers need to be notified about this change in subscriptions.
- layerHasSubscribers = mRouting.hasLayerSubscriptions(layer);
- }
- if (!layerHasSubscribers) {
- notifyHalPublishers(layer, false);
- notifyOfSubscriptionChange();
- }
- }
-
- public void removeHalSubscriptionFromPublisher(VmsLayer layer, int publisherId) {
- boolean layerHasSubscribers = true;
- synchronized (mLock) {
- if (!mRouting.hasLayerSubscriptions(layer)) {
- Log.i(TAG, "Trying to remove a layer with no subscription: " + layer);
- return;
- }
-
- // Remove the listeners subscription to the layer
- mRouting.removeHalSubscriptionToPublisher(layer, publisherId);
-
- // Check if publishers need to be notified about this change in subscriptions.
- layerHasSubscribers = mRouting.hasLayerSubscriptions(layer) ||
- mRouting.hasLayerFromPublisherSubscriptions(layer, publisherId);
- }
- if (!layerHasSubscribers) {
- notifyHalPublishers(layer, publisherId, false);
- notifyOfSubscriptionChange();
- }
- }
-
- public boolean containsSubscriber(IVmsSubscriberClient subscriber) {
- synchronized (mLock) {
- return mRouting.containsSubscriber(subscriber);
- }
- }
-
- public void setPublisherLayersOffering(IBinder publisherToken, VmsLayersOffering offering) {
- synchronized (mLock) {
- updateOffering(publisherToken, offering);
- VmsOperationRecorder.get().setPublisherLayersOffering(offering);
- }
- }
-
- public VmsAvailableLayers getAvailableLayers() {
- synchronized (mLock) {
- return mAvailableLayers.getAvailableLayers();
- }
+ @VisibleForTesting
+ Handler getHandler() {
+ return mHandler;
}
/**
- * Notify all the publishers and the HAL on subscription changes regardless of who triggered
- * the change.
- *
- * @param layer layer which is being subscribed to or unsubscribed from.
- * @param hasSubscribers indicates if the notification is for subscription or unsubscription.
+ * Gets the {@link IVmsPublisherClient} implementation for the HAL's publisher callback.
*/
- private void notifyHalPublishers(VmsLayer layer, boolean hasSubscribers) {
- // notify the HAL
- setSubscriptionRequest(layer, hasSubscribers);
- }
-
- private void notifyHalPublishers(VmsLayer layer, int publisherId, boolean hasSubscribers) {
- // notify the HAL
- setSubscriptionToPublisherRequest(layer, publisherId, hasSubscribers);
- }
-
- private void notifyOfSubscriptionChange() {
- if (DBG) {
- Log.d(TAG, "Notifying publishers on subscriptions");
- }
-
- // Notify the App publishers
- for (VmsHalPublisherListener listener : mPublisherListeners) {
- // Besides the list of layers, also a timestamp is provided to the clients.
- // They should ignore any notification with a timestamp that is older than the most
- // recent timestamp they have seen.
- listener.onChange(getSubscriptionState());
- }
+ public IBinder getPublisherClient() {
+ return mPublisherClient.asBinder();
}
/**
- * Notify all the subscribers and the HAL on layers availability change.
- *
- * @param availableLayers the layers which publishers claim they made publish.
+ * Sets a reference to the {@link IVmsSubscriberService} implementation for use by the HAL.
*/
- private void notifyOfAvailabilityChange() {
- if (DBG) {
- Log.d(TAG, "Notifying subscribers on layers availability");
- }
-
- VmsAvailableLayers availableLayers;
- synchronized (mLock) {
- availableLayers = mAvailableLayers.getAvailableLayers();
- }
-
- // notify the HAL
- notifyAvailabilityChangeToHal(availableLayers);
-
- // Notify the App subscribers
- for (VmsHalSubscriberListener listener : mSubscriberListeners) {
- listener.onLayersAvaiabilityChange(availableLayers);
- }
- }
-
- @Override
- public void init() {
- if (mIsSupported) {
- mVehicleHal.subscribeProperty(this, HAL_PROPERTY_ID);
- if (DBG) {
- Log.d(TAG, "Initializing VmsHalService VHAL property");
- }
- } else {
- if (DBG) {
- Log.d(TAG, "VmsHalService VHAL property not supported");
- }
- }
- }
-
- @Override
- public void release() {
- if (DBG) {
- Log.d(TAG, "Releasing VmsHalService");
- }
- if (mIsSupported) {
- mVehicleHal.unsubscribeProperty(this, HAL_PROPERTY_ID);
- }
- mPublisherListeners.clear();
- mSubscriberListeners.clear();
+ public void setVmsSubscriberService(IVmsSubscriberService service) {
+ mSubscriberService = service;
}
@Override
public Collection<VehiclePropConfig> takeSupportedProperties(
Collection<VehiclePropConfig> allProperties) {
- List<VehiclePropConfig> taken = new LinkedList<>();
for (VehiclePropConfig p : allProperties) {
if (p.prop == HAL_PROPERTY_ID) {
- taken.add(p);
mIsSupported = true;
- if (DBG) {
- Log.d(TAG, "takeSupportedProperties: " + toHexString(p.prop));
- }
- break;
+ return Collections.singleton(p);
}
}
- return taken;
+ return Collections.emptySet();
}
- /**
- * Consumes/produces HAL messages. The format of these messages is defined in:
- * hardware/interfaces/automotive/vehicle/2.1/types.hal
- */
@Override
- public void handleHalEvents(List<VehiclePropValue> values) {
- if (DBG) {
- Log.d(TAG, "Handling a VMS property change");
+ public void init() {
+ if (mIsSupported) {
+ if (DBG) Log.d(TAG, "Initializing VmsHalService VHAL property");
+ mVehicleHal.subscribeProperty(this, HAL_PROPERTY_ID);
+ } else {
+ if (DBG) Log.d(TAG, "VmsHalService VHAL property not supported");
+ return; // Do not continue initialization
}
- for (VehiclePropValue v : values) {
- ArrayList<Integer> vec = v.value.int32Values;
- int messageType = vec.get(VmsBaseMessageIntegerValuesIndex.MESSAGE_TYPE);
- if (DBG) {
- Log.d(TAG, "Handling VMS message type: " + messageType);
+ synchronized (this) {
+ mHandlerThread = new HandlerThread(TAG);
+ mHandlerThread.start();
+ mHandler = new Handler(mHandlerThread.getLooper(), mHandlerCallback);
+ }
+
+ if (mSubscriberService != null) {
+ try {
+ mSubscriberService.addVmsSubscriberToNotifications(mSubscriberClient);
+ } catch (RemoteException e) {
+ Log.e(TAG, "While adding subscriber callback", e);
}
- switch (messageType) {
- case VmsMessageType.DATA:
- handleDataEvent(vec, toByteArray(v.value.bytes));
- break;
- case VmsMessageType.SUBSCRIBE:
- handleSubscribeEvent(vec);
- break;
- case VmsMessageType.UNSUBSCRIBE:
- handleUnsubscribeEvent(vec);
- break;
- case VmsMessageType.SUBSCRIBE_TO_PUBLISHER:
- handleSubscribeToPublisherEvent(vec);
- break;
- case VmsMessageType.UNSUBSCRIBE_TO_PUBLISHER:
- handleUnsubscribeFromPublisherEvent(vec);
- break;
- case VmsMessageType.OFFERING:
- handleOfferingEvent(vec);
- break;
- case VmsMessageType.AVAILABILITY_REQUEST:
- handleHalAvailabilityRequestEvent();
- break;
- case VmsMessageType.SUBSCRIPTIONS_REQUEST:
- handleSubscriptionsRequestEvent();
- break;
- default:
- throw new IllegalArgumentException("Unexpected message type: " + messageType);
+
+ // Publish layer availability to HAL clients (this triggers HAL client initialization)
+ try {
+ mSubscriberClient.onLayersAvailabilityChanged(
+ mSubscriberService.getAvailableLayers());
+ } catch (RemoteException e) {
+ Log.e(TAG, "While publishing layer availability", e);
+ }
+ } else if (DBG) {
+ Log.d(TAG, "VmsSubscriberService not registered");
+ }
+ }
+
+ @Override
+ public void release() {
+ synchronized (this) {
+ if (mHandlerThread != null) {
+ mHandlerThread.quitSafely();
}
}
- }
- private VmsLayer parseVmsLayerFromSimpleMessageIntegerValues(List<Integer> integerValues) {
- return new VmsLayer(integerValues.get(VmsMessageWithLayerIntegerValuesIndex.LAYER_TYPE),
- integerValues.get(VmsMessageWithLayerIntegerValuesIndex.LAYER_SUBTYPE),
- integerValues.get(VmsMessageWithLayerIntegerValuesIndex.LAYER_VERSION));
- }
+ mSubscriptionStateSequence = -1;
+ mAvailableLayersSequence = -1;
- private VmsLayer parseVmsLayerFromDataMessageIntegerValues(List<Integer> integerValues) {
- return parseVmsLayerFromSimpleMessageIntegerValues(integerValues);
- }
-
- private int parsePublisherIdFromDataMessageIntegerValues(List<Integer> integerValues) {
- return integerValues.get(VmsMessageWithLayerAndPublisherIdIntegerValuesIndex.PUBLISHER_ID);
- }
-
-
- /**
- * Data message format:
- * <ul>
- * <li>Message type.
- * <li>Layer id.
- * <li>Layer version.
- * <li>Layer subtype.
- * <li>Publisher ID.
- * <li>Payload.
- * </ul>
- */
- private void handleDataEvent(List<Integer> integerValues, byte[] payload) {
- VmsLayer vmsLayer = parseVmsLayerFromDataMessageIntegerValues(integerValues);
- int publisherId = parsePublisherIdFromDataMessageIntegerValues(integerValues);
- if (DBG) {
- Log.d(TAG, "Handling a data event for Layer: " + vmsLayer);
+ if (mIsSupported) {
+ if (DBG) Log.d(TAG, "Releasing VmsHalService VHAL property");
+ mVehicleHal.unsubscribeProperty(this, HAL_PROPERTY_ID);
+ } else {
+ return;
}
- // Send the message.
- for (VmsHalSubscriberListener listener : mSubscriberListeners) {
- listener.onDataMessage(vmsLayer, publisherId, payload);
- }
- }
-
- /**
- * Subscribe message format:
- * <ul>
- * <li>Message type.
- * <li>Layer id.
- * <li>Layer version.
- * <li>Layer subtype.
- * </ul>
- */
- private void handleSubscribeEvent(List<Integer> integerValues) {
- VmsLayer vmsLayer = parseVmsLayerFromSimpleMessageIntegerValues(integerValues);
- if (DBG) {
- Log.d(TAG, "Handling a subscribe event for Layer: " + vmsLayer);
- }
- addHalSubscription(vmsLayer);
- }
-
- /**
- * Subscribe message format:
- * <ul>
- * <li>Message type.
- * <li>Layer id.
- * <li>Layer version.
- * <li>Layer subtype.
- * <li>Publisher ID
- * </ul>
- */
- private void handleSubscribeToPublisherEvent(List<Integer> integerValues) {
- VmsLayer vmsLayer = parseVmsLayerFromSimpleMessageIntegerValues(integerValues);
- if (DBG) {
- Log.d(TAG, "Handling a subscribe event for Layer: " + vmsLayer);
- }
- int publisherId =
- integerValues.get(VmsMessageWithLayerAndPublisherIdIntegerValuesIndex.PUBLISHER_ID);
- addHalSubscriptionToPublisher(vmsLayer, publisherId);
- }
-
- /**
- * Unsubscribe message format:
- * <ul>
- * <li>Message type.
- * <li>Layer id.
- * <li>Layer version.
- * </ul>
- */
- private void handleUnsubscribeEvent(List<Integer> integerValues) {
- VmsLayer vmsLayer = parseVmsLayerFromSimpleMessageIntegerValues(integerValues);
- if (DBG) {
- Log.d(TAG, "Handling an unsubscribe event for Layer: " + vmsLayer);
- }
- removeHalSubscription(vmsLayer);
- }
-
- /**
- * Unsubscribe message format:
- * <ul>
- * <li>Message type.
- * <li>Layer id.
- * <li>Layer version.
- * </ul>
- */
- private void handleUnsubscribeFromPublisherEvent(List<Integer> integerValues) {
- VmsLayer vmsLayer = parseVmsLayerFromSimpleMessageIntegerValues(integerValues);
- int publisherId =
- integerValues.get(VmsMessageWithLayerAndPublisherIdIntegerValuesIndex.PUBLISHER_ID);
- if (DBG) {
- Log.d(TAG, "Handling an unsubscribe event for Layer: " + vmsLayer);
- }
- removeHalSubscriptionFromPublisher(vmsLayer, publisherId);
- }
-
- private static int NUM_INTEGERS_IN_VMS_LAYER = 3;
-
- private VmsLayer parseVmsLayerFromIndex(List<Integer> integerValues, int index) {
- int layerType = integerValues.get(index++);
- int layerSutype = integerValues.get(index++);
- int layerVersion = integerValues.get(index++);
- return new VmsLayer(layerType, layerSutype, layerVersion);
- }
-
- /**
- * Offering message format:
- * <ul>
- * <li>Message type.
- * <li>Publisher ID.
- * <li>Number of offerings.
- * <li>Each offering consists of:
- * <ul>
- * <li>Layer id.
- * <li>Layer version.
- * <li>Number of layer dependencies.
- * <li>Layer type/subtype/version.
- * </ul>
- * </ul>
- */
- private void handleOfferingEvent(List<Integer> integerValues) {
- int publisherId = integerValues.get(VmsOfferingMessageIntegerValuesIndex.PUBLISHER_ID);
- int numLayersDependencies =
- integerValues.get(
- VmsOfferingMessageIntegerValuesIndex.NUMBER_OF_OFFERS);
- int idx = VmsOfferingMessageIntegerValuesIndex.OFFERING_START;
-
- Set<VmsLayerDependency> offeredLayers = new HashSet<>();
-
- // An offering is layerId, LayerVersion, LayerType, NumDeps, <LayerId, LayerVersion> X NumDeps.
- for (int i = 0; i < numLayersDependencies; i++) {
- VmsLayer offeredLayer = parseVmsLayerFromIndex(integerValues, idx);
- idx += NUM_INTEGERS_IN_VMS_LAYER;
-
- int numDependenciesForLayer = integerValues.get(idx++);
- if (numDependenciesForLayer == 0) {
- offeredLayers.add(new VmsLayerDependency(offeredLayer));
- } else {
- Set<VmsLayer> dependencies = new HashSet<>();
-
- for (int j = 0; j < numDependenciesForLayer; j++) {
- VmsLayer dependantLayer = parseVmsLayerFromIndex(integerValues, idx);
- idx += NUM_INTEGERS_IN_VMS_LAYER;
- dependencies.add(dependantLayer);
- }
- offeredLayers.add(new VmsLayerDependency(offeredLayer, dependencies));
+ if (mSubscriberService != null) {
+ try {
+ mSubscriberService.removeVmsSubscriberToNotifications(mSubscriberClient);
+ } catch (RemoteException e) {
+ Log.e(TAG, "While removing subscriber callback", e);
}
}
- // Store the HAL offering.
- VmsLayersOffering offering = new VmsLayersOffering(offeredLayers, publisherId);
- synchronized (mLock) {
- updateOffering(mHalPublisherToken, offering);
- VmsOperationRecorder.get().setHalPublisherLayersOffering(offering);
- }
- }
-
- /**
- * Availability message format:
- * <ul>
- * <li>Message type.
- * <li>Number of layers.
- * <li>Layer type/subtype/version.
- * </ul>
- */
- private void handleHalAvailabilityRequestEvent() {
- synchronized (mLock) {
- VmsAvailableLayers availableLayers = mAvailableLayers.getAvailableLayers();
- VehiclePropValue vehiclePropertyValue =
- toAvailabilityUpdateVehiclePropValue(
- availableLayers,
- VmsMessageType.AVAILABILITY_RESPONSE);
-
- setPropertyValue(vehiclePropertyValue);
- }
- }
-
- /**
- * VmsSubscriptionRequestFormat:
- * <ul>
- * <li>Message type.
- * </ul>
- * <p>
- * VmsSubscriptionResponseFormat:
- * <ul>
- * <li>Message type.
- * <li>Sequence number.
- * <li>Number of layers.
- * <li>Layer type/subtype/version.
- * </ul>
- */
- private void handleSubscriptionsRequestEvent() {
- VmsSubscriptionState subscription = getSubscriptionState();
- VehiclePropValue vehicleProp =
- toTypedVmsVehiclePropValue(VmsMessageType.SUBSCRIPTIONS_RESPONSE);
- VehiclePropValue.RawValue v = vehicleProp.value;
- v.int32Values.add(subscription.getSequenceNumber());
- Set<VmsLayer> layers = subscription.getLayers();
- v.int32Values.add(layers.size());
-
- //TODO(asafro): get the real number of associated layers in the subscriptions
- // state and send the associated layers themselves.
- v.int32Values.add(0);
-
- for (VmsLayer layer : layers) {
- v.int32Values.add(layer.getType());
- v.int32Values.add(layer.getSubtype());
- v.int32Values.add(layer.getVersion());
- }
- setPropertyValue(vehicleProp);
- }
-
- private void updateOffering(IBinder publisherToken, VmsLayersOffering offering) {
- synchronized (mLock) {
- Map<Integer, VmsLayersOffering> publisherOfferings = mOfferings.get(publisherToken);
- if (publisherOfferings == null) {
- publisherOfferings = new HashMap<>();
- mOfferings.put(publisherToken, publisherOfferings);
- }
- publisherOfferings.put(offering.getPublisherId(), offering);
-
- // Update layers availability.
- Set<VmsLayersOffering> allPublisherOfferings = new HashSet<>();
- for (Map<Integer, VmsLayersOffering> offerings : mOfferings.values()) {
- allPublisherOfferings.addAll(offerings.values());
- }
- if (DBG) {
- Log.d(TAG, "New layer availability: " + allPublisherOfferings);
- }
- mAvailableLayers.setPublishersOffering(allPublisherOfferings);
- }
- notifyOfAvailabilityChange();
}
@Override
public void dump(PrintWriter writer) {
writer.println(TAG);
writer.println("VmsProperty " + (mIsSupported ? "" : "not") + " supported.");
+
+ writer.println(
+ "VmsPublisherService " + (mPublisherService != null ? "" : "not") + " registered.");
+ writer.println("mSubscriptionStateSequence: " + mSubscriptionStateSequence);
+
+ writer.println("VmsSubscriberService " + (mSubscriberService != null ? "" : "not")
+ + " registered.");
+ writer.println("mAvailableLayersSequence: " + mAvailableLayersSequence);
}
/**
- * Updates the VMS HAL property with the given value.
+ * Consumes/produces HAL messages.
*
- * @param layer layer data to update the hal property.
- * @param hasSubscribers if it is a subscribe or unsubscribe message.
- * @return true if the call to the HAL to update the property was successful.
+ * The format of these messages is defined in:
+ * hardware/interfaces/automotive/vehicle/2.0/types.hal
*/
- public boolean setSubscriptionRequest(VmsLayer layer, boolean hasSubscribers) {
- VehiclePropValue vehiclePropertyValue = toTypedVmsVehiclePropValueWithLayer(
- hasSubscribers ? VmsMessageType.SUBSCRIBE : VmsMessageType.UNSUBSCRIBE, layer);
- return setPropertyValue(vehiclePropertyValue);
- }
+ @Override
+ public void handleHalEvents(List<VehiclePropValue> values) {
+ if (DBG) Log.d(TAG, "Handling a VMS property change");
+ for (VehiclePropValue v : values) {
+ ArrayList<Integer> vec = v.value.int32Values;
+ int messageType = vec.get(VmsBaseMessageIntegerValuesIndex.MESSAGE_TYPE);
- public boolean setSubscriptionToPublisherRequest(VmsLayer layer,
- int publisherId,
- boolean hasSubscribers) {
- VehiclePropValue vehiclePropertyValue = toTypedVmsVehiclePropValueWithLayer(
- hasSubscribers ?
- VmsMessageType.SUBSCRIBE_TO_PUBLISHER :
- VmsMessageType.UNSUBSCRIBE_TO_PUBLISHER, layer);
- vehiclePropertyValue.value.int32Values.add(publisherId);
- return setPropertyValue(vehiclePropertyValue);
- }
-
- public boolean setDataMessage(VmsLayer layer, byte[] payload) {
- VehiclePropValue vehiclePropertyValue =
- toTypedVmsVehiclePropValueWithLayer(VmsMessageType.DATA, layer);
- VehiclePropValue.RawValue v = vehiclePropertyValue.value;
- v.bytes.ensureCapacity(payload.length);
- for (byte b : payload) {
- v.bytes.add(b);
- }
- return setPropertyValue(vehiclePropertyValue);
- }
-
- public boolean notifyAvailabilityChangeToHal(VmsAvailableLayers availableLayers) {
- VehiclePropValue vehiclePropertyValue =
- toAvailabilityUpdateVehiclePropValue(
- availableLayers,
- VmsMessageType.AVAILABILITY_CHANGE);
-
- return setPropertyValue(vehiclePropertyValue);
- }
-
- private boolean setPropertyValue(VehiclePropValue vehiclePropertyValue) {
- if (mIsSupported) {
+ if (DBG) Log.d(TAG, "Received " + VmsMessageType.toString(messageType) + " message");
try {
- mVehicleHal.set(vehiclePropertyValue);
- return true;
- } catch (PropertyTimeoutException e) {
- Log.e(CarLog.TAG_PROPERTY,
- "set, property not ready 0x" + toHexString(HAL_PROPERTY_ID));
+ switch (messageType) {
+ case VmsMessageType.DATA:
+ handleDataEvent(vec, toByteArray(v.value.bytes));
+ break;
+ case VmsMessageType.SUBSCRIBE:
+ handleSubscribeEvent(vec);
+ break;
+ case VmsMessageType.UNSUBSCRIBE:
+ handleUnsubscribeEvent(vec);
+ break;
+ case VmsMessageType.SUBSCRIBE_TO_PUBLISHER:
+ handleSubscribeToPublisherEvent(vec);
+ break;
+ case VmsMessageType.UNSUBSCRIBE_TO_PUBLISHER:
+ handleUnsubscribeFromPublisherEvent(vec);
+ break;
+ case VmsMessageType.PUBLISHER_ID_REQUEST:
+ handlePublisherIdRequest(toByteArray(v.value.bytes));
+ break;
+ case VmsMessageType.PUBLISHER_INFORMATION_REQUEST:
+ handlePublisherInfoRequest(vec);
+ case VmsMessageType.OFFERING:
+ handleOfferingEvent(vec);
+ break;
+ case VmsMessageType.AVAILABILITY_REQUEST:
+ handleAvailabilityRequestEvent();
+ break;
+ case VmsMessageType.SUBSCRIPTIONS_REQUEST:
+ handleSubscriptionsRequestEvent();
+ break;
+ default:
+ Log.e(TAG, "Unexpected message type: " + messageType);
+ }
+ } catch (IndexOutOfBoundsException | RemoteException e) {
+ Log.e(TAG, "While handling: " + messageType, e);
}
}
- return false;
}
- private static VehiclePropValue toTypedVmsVehiclePropValue(int messageType) {
+ /**
+ * DATA message format:
+ * <ul>
+ * <li>Message type
+ * <li>Layer ID
+ * <li>Layer subtype
+ * <li>Layer version
+ * <li>Publisher ID
+ * <li>Payload
+ * </ul>
+ */
+ private void handleDataEvent(List<Integer> message, byte[] payload)
+ throws RemoteException {
+ VmsLayer vmsLayer = parseVmsLayerFromMessage(message);
+ int publisherId = parsePublisherIdFromMessage(message);
+ if (DBG) {
+ Log.d(TAG,
+ "Handling a data event for Layer: " + vmsLayer + " Publisher: " + publisherId);
+ }
+ mPublisherService.publish(mPublisherToken, vmsLayer, publisherId, payload);
+ }
+
+ /**
+ * SUBSCRIBE message format:
+ * <ul>
+ * <li>Message type
+ * <li>Layer ID
+ * <li>Layer subtype
+ * <li>Layer version
+ * </ul>
+ */
+ private void handleSubscribeEvent(List<Integer> message) throws RemoteException {
+ VmsLayer vmsLayer = parseVmsLayerFromMessage(message);
+ if (DBG) Log.d(TAG, "Handling a subscribe event for Layer: " + vmsLayer);
+ mSubscriberService.addVmsSubscriber(mSubscriberClient, vmsLayer);
+ }
+
+ /**
+ * SUBSCRIBE_TO_PUBLISHER message format:
+ * <ul>
+ * <li>Message type
+ * <li>Layer ID
+ * <li>Layer subtype
+ * <li>Layer version
+ * <li>Publisher ID
+ * </ul>
+ */
+ private void handleSubscribeToPublisherEvent(List<Integer> message)
+ throws RemoteException {
+ VmsLayer vmsLayer = parseVmsLayerFromMessage(message);
+ int publisherId = parsePublisherIdFromMessage(message);
+ if (DBG) {
+ Log.d(TAG,
+ "Handling a subscribe event for Layer: " + vmsLayer + " Publisher: "
+ + publisherId);
+ }
+ mSubscriberService.addVmsSubscriberToPublisher(mSubscriberClient, vmsLayer, publisherId);
+ }
+
+ /**
+ * UNSUBSCRIBE message format:
+ * <ul>
+ * <li>Message type
+ * <li>Layer ID
+ * <li>Layer subtype
+ * <li>Layer version
+ * </ul>
+ */
+ private void handleUnsubscribeEvent(List<Integer> message) throws RemoteException {
+ VmsLayer vmsLayer = parseVmsLayerFromMessage(message);
+ if (DBG) Log.d(TAG, "Handling an unsubscribe event for Layer: " + vmsLayer);
+ mSubscriberService.removeVmsSubscriber(mSubscriberClient, vmsLayer);
+ }
+
+ /**
+ * UNSUBSCRIBE_TO_PUBLISHER message format:
+ * <ul>
+ * <li>Message type
+ * <li>Layer ID
+ * <li>Layer subtype
+ * <li>Layer version
+ * <li>Publisher ID
+ * </ul>
+ */
+ private void handleUnsubscribeFromPublisherEvent(List<Integer> message)
+ throws RemoteException {
+ VmsLayer vmsLayer = parseVmsLayerFromMessage(message);
+ int publisherId = parsePublisherIdFromMessage(message);
+ if (DBG) {
+ Log.d(TAG, "Handling an unsubscribe event for Layer: " + vmsLayer + " Publisher: "
+ + publisherId);
+ }
+ mSubscriberService.removeVmsSubscriberToPublisher(mSubscriberClient, vmsLayer, publisherId);
+ }
+
+ /**
+ * PUBLISHER_ID_REQUEST message format:
+ * <ul>
+ * <li>Message type
+ * <li>Publisher info (bytes)
+ * </ul>
+ *
+ * PUBLISHER_ID_RESPONSE message format:
+ * <ul>
+ * <li>Message type
+ * <li>Publisher ID
+ * </ul>
+ */
+ private void handlePublisherIdRequest(byte[] payload)
+ throws RemoteException {
+ if (DBG) Log.d(TAG, "Handling a publisher id request event");
+
+ VehiclePropValue vehicleProp = createVmsMessage(VmsMessageType.PUBLISHER_ID_RESPONSE);
+ // Publisher ID
+ vehicleProp.value.int32Values.add(mPublisherService.getPublisherId(payload));
+
+ setPropertyValue(vehicleProp);
+ }
+
+
+ /**
+ * PUBLISHER_INFORMATION_REQUEST message format:
+ * <ul>
+ * <li>Message type
+ * <li>Publisher ID
+ * </ul>
+ *
+ * PUBLISHER_INFORMATION_RESPONSE message format:
+ * <ul>
+ * <li>Message type
+ * <li>Publisher info (bytes)
+ * </ul>
+ */
+ private void handlePublisherInfoRequest(List<Integer> message)
+ throws RemoteException {
+ if (DBG) Log.d(TAG, "Handling a publisher info request event");
+ int publisherId = message.get(VmsPublisherInformationIntegerValuesIndex.PUBLISHER_ID);
+
+ VehiclePropValue vehicleProp =
+ createVmsMessage(VmsMessageType.PUBLISHER_INFORMATION_RESPONSE);
+ // Publisher Info
+ appendBytes(vehicleProp.value.bytes, mSubscriberService.getPublisherInfo(publisherId));
+
+ setPropertyValue(vehicleProp);
+ }
+
+ /**
+ * OFFERING message format:
+ * <ul>
+ * <li>Message type
+ * <li>Publisher ID
+ * <li>Number of offerings.
+ * <li>Offerings (x number of offerings)
+ * <ul>
+ * <li>Layer ID
+ * <li>Layer subtype
+ * <li>Layer version
+ * <li>Number of layer dependencies.
+ * <li>Layer dependencies (x number of layer dependencies)
+ * <ul>
+ * <li>Layer ID
+ * <li>Layer subtype
+ * <li>Layer version
+ * </ul>
+ * </ul>
+ * </ul>
+ */
+ private void handleOfferingEvent(List<Integer> message) throws RemoteException {
+ // Publisher ID for OFFERING is stored at a different index than in other message types
+ int publisherId = message.get(VmsOfferingMessageIntegerValuesIndex.PUBLISHER_ID);
+ int numLayerDependencies =
+ message.get(
+ VmsOfferingMessageIntegerValuesIndex.NUMBER_OF_OFFERS);
+ if (DBG) {
+ Log.d(TAG, "Handling an offering event of " + numLayerDependencies
+ + " layers for Publisher: " + publisherId);
+ }
+
+ Set<VmsLayerDependency> offeredLayers = new ArraySet<>(numLayerDependencies);
+ int idx = VmsOfferingMessageIntegerValuesIndex.OFFERING_START;
+ for (int i = 0; i < numLayerDependencies; i++) {
+ VmsLayer offeredLayer = parseVmsLayerAtIndex(message, idx);
+ idx += NUM_INTEGERS_IN_VMS_LAYER;
+
+ int numDependenciesForLayer = message.get(idx++);
+ if (numDependenciesForLayer == 0) {
+ offeredLayers.add(new VmsLayerDependency(offeredLayer));
+ } else {
+ Set<VmsLayer> dependencies = new HashSet<>();
+
+ for (int j = 0; j < numDependenciesForLayer; j++) {
+ VmsLayer dependantLayer = parseVmsLayerAtIndex(message, idx);
+ idx += NUM_INTEGERS_IN_VMS_LAYER;
+ dependencies.add(dependantLayer);
+ }
+ offeredLayers.add(new VmsLayerDependency(offeredLayer, dependencies));
+ }
+ }
+
+ VmsLayersOffering offering = new VmsLayersOffering(offeredLayers, publisherId);
+ VmsOperationRecorder.get().setHalPublisherLayersOffering(offering);
+ mPublisherService.setLayersOffering(mPublisherToken, offering);
+ }
+
+ /**
+ * AVAILABILITY_REQUEST message format:
+ * <ul>
+ * <li>Message type
+ * </ul>
+ */
+ private void handleAvailabilityRequestEvent() throws RemoteException {
+ setPropertyValue(
+ createAvailableLayersMessage(VmsMessageType.AVAILABILITY_RESPONSE,
+ mSubscriberService.getAvailableLayers()));
+ }
+
+ /**
+ * SUBSCRIPTION_REQUEST message format:
+ * <ul>
+ * <li>Message type
+ * </ul>
+ */
+ private void handleSubscriptionsRequestEvent() throws RemoteException {
+ setPropertyValue(
+ createSubscriptionStateMessage(VmsMessageType.SUBSCRIPTIONS_RESPONSE,
+ mPublisherService.getSubscriptions()));
+ }
+
+ private void setPropertyValue(VehiclePropValue vehicleProp) throws RemoteException {
+ int messageType = vehicleProp.value.int32Values.get(
+ VmsBaseMessageIntegerValuesIndex.MESSAGE_TYPE);
+
+ if (!mIsSupported) {
+ Log.w(TAG, "HAL unsupported while attempting to send "
+ + VmsMessageType.toString(messageType));
+ return;
+ }
+
+ try {
+ mVehicleHal.set(vehicleProp);
+ } catch (PropertyTimeoutException e) {
+ Log.e(CarLog.TAG_PROPERTY,
+ "set, property not ready 0x" + toHexString(HAL_PROPERTY_ID));
+ throw new RemoteException(
+ "Timeout while sending " + VmsMessageType.toString(messageType));
+ }
+ }
+
+ /**
+ * Creates a DATA type {@link VehiclePropValue}.
+ *
+ * DATA message format:
+ * <ul>
+ * <li>Message type
+ * <li>Layer ID
+ * <li>Layer subtype
+ * <li>Layer version
+ * <li>Publisher ID
+ * <li>Payload
+ * </ul>
+ *
+ * @param layer Layer for which message was published.
+ */
+ private static VehiclePropValue createDataMessage(VmsLayer layer, int publisherId,
+ byte[] payload) {
+ // Message type + layer
+ VehiclePropValue vehicleProp = createVmsMessageWithLayer(VmsMessageType.DATA, layer);
+ List<Integer> message = vehicleProp.value.int32Values;
+
+ // Publisher ID
+ message.add(publisherId);
+
+ // Payload
+ appendBytes(vehicleProp.value.bytes, payload);
+ return vehicleProp;
+ }
+
+ /**
+ * Creates a SUBSCRIPTION_CHANGE or SUBSCRIPTION_RESPONSE type {@link VehiclePropValue}.
+ *
+ * Both message types have the same format:
+ * <ul>
+ * <li>Message type
+ * <li>Sequence number
+ * <li>Number of layers
+ * <li>Number of associated layers
+ * <li>Layers (x number of layers) (see {@link #appendLayer})
+ * <li>Associated layers (x number of associated layers) (see {@link #appendAssociatedLayer})
+ * </ul>
+ *
+ * @param messageType Either SUBSCRIPTIONS_CHANGE or SUBSCRIPTIONS_RESPONSE.
+ * @param subscriptionState The subscription state to encode in the message.
+ */
+ private static VehiclePropValue createSubscriptionStateMessage(int messageType,
+ VmsSubscriptionState subscriptionState) {
+ // Message type
+ VehiclePropValue vehicleProp = createVmsMessage(messageType);
+ List<Integer> message = vehicleProp.value.int32Values;
+
+ // Sequence number
+ message.add(subscriptionState.getSequenceNumber());
+
+ Set<VmsLayer> layers = subscriptionState.getLayers();
+ Set<VmsAssociatedLayer> associatedLayers = subscriptionState.getAssociatedLayers();
+
+ // Number of layers
+ message.add(layers.size());
+ // Number of associated layers
+ message.add(associatedLayers.size());
+
+ // Layers
+ for (VmsLayer layer : layers) {
+ appendLayer(message, layer);
+ }
+
+ // Associated layers
+ for (VmsAssociatedLayer layer : associatedLayers) {
+ appendAssociatedLayer(message, layer);
+ }
+ return vehicleProp;
+ }
+
+ /**
+ * Creates an AVAILABILITY_CHANGE or AVAILABILITY_RESPONSE type {@link VehiclePropValue}.
+ *
+ * Both message types have the same format:
+ * <ul>
+ * <li>Message type
+ * <li>Sequence number.
+ * <li>Number of associated layers.
+ * <li>Associated layers (x number of associated layers) (see {@link #appendAssociatedLayer})
+ * </ul>
+ *
+ * @param messageType Either AVAILABILITY_CHANGE or AVAILABILITY_RESPONSE.
+ * @param availableLayers The available layers to encode in the message.
+ */
+ private static VehiclePropValue createAvailableLayersMessage(int messageType,
+ VmsAvailableLayers availableLayers) {
+ // Message type
+ VehiclePropValue vehicleProp = createVmsMessage(messageType);
+ List<Integer> message = vehicleProp.value.int32Values;
+
+ // Sequence number
+ message.add(availableLayers.getSequence());
+
+ // Number of associated layers
+ message.add(availableLayers.getAssociatedLayers().size());
+
+ // Associated layers
+ for (VmsAssociatedLayer layer : availableLayers.getAssociatedLayers()) {
+ appendAssociatedLayer(message, layer);
+ }
+ return vehicleProp;
+ }
+
+ /**
+ * Creates a base {@link VehiclePropValue} of the requested message type, with no message fields
+ * populated.
+ *
+ * @param messageType Type of message, from {@link VmsMessageType}
+ */
+ private static VehiclePropValue createVmsMessage(int messageType) {
VehiclePropValue vehicleProp = new VehiclePropValue();
vehicleProp.prop = HAL_PROPERTY_ID;
vehicleProp.areaId = VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL;
- VehiclePropValue.RawValue v = vehicleProp.value;
-
- v.int32Values.add(messageType);
+ vehicleProp.value.int32Values.add(messageType);
return vehicleProp;
}
/**
- * Creates a {@link VehiclePropValue}
+ * Creates a {@link VehiclePropValue} of the requested message type, with layer message fields
+ * populated. Other message fields are *not* populated.
+ *
+ * @param messageType Type of message, from {@link VmsMessageType}
+ * @param layer Layer affected by message.
*/
- private static VehiclePropValue toTypedVmsVehiclePropValueWithLayer(
+ private static VehiclePropValue createVmsMessageWithLayer(
int messageType, VmsLayer layer) {
- VehiclePropValue vehicleProp = toTypedVmsVehiclePropValue(messageType);
- VehiclePropValue.RawValue v = vehicleProp.value;
- v.int32Values.add(layer.getType());
- v.int32Values.add(layer.getSubtype());
- v.int32Values.add(layer.getVersion());
+ VehiclePropValue vehicleProp = createVmsMessage(messageType);
+ appendLayer(vehicleProp.value.int32Values, layer);
return vehicleProp;
}
- private static VehiclePropValue toAvailabilityUpdateVehiclePropValue(
- VmsAvailableLayers availableLayers, int messageType) {
-
- if (!AVAILABILITY_MESSAGE_TYPES.contains(messageType)) {
- throw new IllegalArgumentException("Unsupported availability type: " + messageType);
- }
- VehiclePropValue vehicleProp =
- toTypedVmsVehiclePropValue(messageType);
- populateAvailabilityPropValueFields(availableLayers, vehicleProp);
- return vehicleProp;
-
+ /**
+ * Appends a {@link VmsLayer} to an encoded VMS message.
+ *
+ * Layer format:
+ * <ul>
+ * <li>Layer ID
+ * <li>Layer subtype
+ * <li>Layer version
+ * </ul>
+ *
+ * @param message Message to append to.
+ * @param layer Layer to append.
+ */
+ private static void appendLayer(List<Integer> message, VmsLayer layer) {
+ message.add(layer.getType());
+ message.add(layer.getSubtype());
+ message.add(layer.getVersion());
}
- private static void populateAvailabilityPropValueFields(
- VmsAvailableLayers availableAssociatedLayers,
- VehiclePropValue vehicleProp) {
- VehiclePropValue.RawValue v = vehicleProp.value;
- v.int32Values.add(availableAssociatedLayers.getSequence());
- int numLayers = availableAssociatedLayers.getAssociatedLayers().size();
- v.int32Values.add(numLayers);
- for (VmsAssociatedLayer layer : availableAssociatedLayers.getAssociatedLayers()) {
- v.int32Values.add(layer.getVmsLayer().getType());
- v.int32Values.add(layer.getVmsLayer().getSubtype());
- v.int32Values.add(layer.getVmsLayer().getVersion());
- v.int32Values.add(layer.getPublisherIds().size());
- for (int publisherId : layer.getPublisherIds()) {
- v.int32Values.add(publisherId);
- }
+ /**
+ * Appends a {@link VmsAssociatedLayer} to an encoded VMS message.
+ *
+ * AssociatedLayer format:
+ * <ul>
+ * <li>Layer ID
+ * <li>Layer subtype
+ * <li>Layer version
+ * <li>Number of publishers
+ * <li>Publisher ID (x number of publishers)
+ * </ul>
+ *
+ * @param message Message to append to.
+ * @param layer Layer to append.
+ */
+ private static void appendAssociatedLayer(List<Integer> message, VmsAssociatedLayer layer) {
+ message.add(layer.getVmsLayer().getType());
+ message.add(layer.getVmsLayer().getSubtype());
+ message.add(layer.getVmsLayer().getVersion());
+ message.add(layer.getPublisherIds().size());
+ for (int publisherId : layer.getPublisherIds()) {
+ message.add(publisherId);
}
}
+
+ private static void appendBytes(ArrayList<Byte> dst, byte[] src) {
+ dst.ensureCapacity(src.length);
+ for (byte b : src) {
+ dst.add(b);
+ }
+ }
+
+ private static VmsLayer parseVmsLayerFromMessage(List<Integer> message) {
+ return parseVmsLayerAtIndex(message,
+ VmsMessageWithLayerIntegerValuesIndex.LAYER_TYPE);
+ }
+
+ private static VmsLayer parseVmsLayerAtIndex(List<Integer> message, int index) {
+ List<Integer> layerValues = message.subList(index, index + NUM_INTEGERS_IN_VMS_LAYER);
+ return new VmsLayer(layerValues.get(0), layerValues.get(1), layerValues.get(2));
+ }
+
+ private static int parsePublisherIdFromMessage(List<Integer> message) {
+ return message.get(VmsMessageWithLayerAndPublisherIdIntegerValuesIndex.PUBLISHER_ID);
+ }
}
diff --git a/service/src/com/android/car/trust/CarTrustAgentEnrollmentService.java b/service/src/com/android/car/trust/CarTrustAgentEnrollmentService.java
index e6aeeac..cd421e7 100644
--- a/service/src/com/android/car/trust/CarTrustAgentEnrollmentService.java
+++ b/service/src/com/android/car/trust/CarTrustAgentEnrollmentService.java
@@ -86,7 +86,8 @@
}
@Override
- public void activateToken(long handle) {
+ public boolean isEscrowTokenActive(long handle, int uid) {
+ return false;
}
@Override
@@ -94,9 +95,8 @@
}
@Override
- public int[] getEnrollmentHandlesForUser(int uid) {
- int[] handles = {};
- return handles;
+ public long[] getEnrollmentHandlesForUser(int uid) {
+ return new long[0];
}
/**
diff --git a/service/src/com/android/car/vms/VmsBrokerService.java b/service/src/com/android/car/vms/VmsBrokerService.java
new file mode 100644
index 0000000..36f7fff
--- /dev/null
+++ b/service/src/com/android/car/vms/VmsBrokerService.java
@@ -0,0 +1,370 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.vms;
+
+import android.car.vms.IVmsSubscriberClient;
+import android.car.vms.VmsAvailableLayers;
+import android.car.vms.VmsLayer;
+import android.car.vms.VmsLayersOffering;
+import android.car.vms.VmsOperationRecorder;
+import android.car.vms.VmsSubscriptionState;
+import android.os.IBinder;
+import android.util.Log;
+
+import com.android.car.VmsLayersAvailability;
+import com.android.car.VmsPublishersInfo;
+import com.android.car.VmsRouting;
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+/**
+ * Broker service facilitating subscription handling and message passing between
+ * VmsPublisherService, VmsSubscriberService, and VmsHalService.
+ */
+public class VmsBrokerService {
+ private static final boolean DBG = true;
+ private static final String TAG = "VmsBrokerService";
+
+ private CopyOnWriteArrayList<PublisherListener> mPublisherListeners =
+ new CopyOnWriteArrayList<>();
+ private CopyOnWriteArrayList<SubscriberListener> mSubscriberListeners =
+ new CopyOnWriteArrayList<>();
+
+ private final Object mLock = new Object();
+ @GuardedBy("mLock")
+ private final VmsRouting mRouting = new VmsRouting();
+ @GuardedBy("mLock")
+ private final Map<IBinder, Map<Integer, VmsLayersOffering>> mOfferings = new HashMap<>();
+ @GuardedBy("mLock")
+ private final VmsLayersAvailability mAvailableLayers = new VmsLayersAvailability();
+ @GuardedBy("mLock")
+ private final VmsPublishersInfo mPublishersInfo = new VmsPublishersInfo();
+
+ /**
+ * The VMS publisher and HAL services implement this interface to receive publisher callbacks.
+ */
+ public interface PublisherListener {
+ /**
+ * Callback triggered when publisher subscription state changes.
+ *
+ * @param subscriptionState Current subscription state.
+ */
+ void onSubscriptionChange(VmsSubscriptionState subscriptionState);
+ }
+
+ /**
+ * The VMS publisher and HAL services implement this interface to receive subscriber callbacks.
+ */
+ public interface SubscriberListener {
+ /**
+ * Callback triggered when data is published for a given layer.
+ *
+ * @param layer Layer data is being published for
+ * @param publisherId Publisher of data
+ * @param payload Layer data
+ */
+ void onMessageReceived(VmsLayer layer, int publisherId, byte[] payload);
+
+ /**
+ * Callback triggered when the layers available for subscription changes.
+ *
+ * @param availableLayers Current layer availability
+ */
+ void onLayersAvailabilityChange(VmsAvailableLayers availableLayers);
+ }
+
+ /**
+ * Constructs new broker service.
+ */
+ public VmsBrokerService() {
+ if (DBG) Log.d(TAG, "Started VmsBrokerService!");
+ }
+
+ /**
+ * Adds a listener for publisher callbacks.
+ *
+ * @param listener Publisher callback listener
+ */
+ public void addPublisherListener(PublisherListener listener) {
+ mPublisherListeners.add(listener);
+ }
+
+ /**
+ * Adds a listener for subscriber callbacks.
+ *
+ * @param listener Subscriber callback listener
+ */
+ public void addSubscriberListener(SubscriberListener listener) {
+ mSubscriberListeners.add(listener);
+ }
+
+ /**
+ * Removes a listener for publisher callbacks.
+ *
+ * @param listener Publisher callback listener
+ */
+ public void removePublisherListener(PublisherListener listener) {
+ mPublisherListeners.remove(listener);
+ }
+
+ /**
+ * Removes a listener for subscriber callbacks.
+ *
+ * @param listener Subscriber callback listener
+ */
+ public void removeSubscriberListener(SubscriberListener listener) {
+ mSubscriberListeners.remove(listener);
+ }
+
+ /**
+ * Adds a subscription to all layers.
+ *
+ * @param subscriber Subscriber client to send layer data
+ */
+ public void addSubscription(IVmsSubscriberClient subscriber) {
+ synchronized (mLock) {
+ mRouting.addSubscription(subscriber);
+ }
+ }
+
+ /**
+ * Removes a subscription to all layers.
+ *
+ * @param subscriber Subscriber client to remove subscription for
+ */
+ public void removeSubscription(IVmsSubscriberClient subscriber) {
+ synchronized (mLock) {
+ mRouting.removeSubscription(subscriber);
+ }
+ }
+
+ /**
+ * Adds a layer subscription.
+ *
+ * @param subscriber Subscriber client to send layer data
+ * @param layer Layer to send
+ */
+ public void addSubscription(IVmsSubscriberClient subscriber, VmsLayer layer) {
+ boolean firstSubscriptionForLayer;
+ if (DBG) Log.d(TAG, "Checking for first subscription. Layer: " + layer);
+ synchronized (mLock) {
+ // Check if publishers need to be notified about this change in subscriptions.
+ firstSubscriptionForLayer = !mRouting.hasLayerSubscriptions(layer);
+
+ // Add the listeners subscription to the layer
+ mRouting.addSubscription(subscriber, layer);
+ }
+ if (firstSubscriptionForLayer) {
+ notifyOfSubscriptionChange();
+ }
+ }
+
+ /**
+ * Removes a layer subscription.
+ *
+ * @param subscriber Subscriber client to remove subscription for
+ * @param layer Layer to remove
+ */
+ public void removeSubscription(IVmsSubscriberClient subscriber, VmsLayer layer) {
+ boolean layerHasSubscribers;
+ synchronized (mLock) {
+ if (!mRouting.hasLayerSubscriptions(layer)) {
+ if (DBG) Log.d(TAG, "Trying to remove a layer with no subscription: " + layer);
+ return;
+ }
+
+ // Remove the listeners subscription to the layer
+ mRouting.removeSubscription(subscriber, layer);
+
+ // Check if publishers need to be notified about this change in subscriptions.
+ layerHasSubscribers = mRouting.hasLayerSubscriptions(layer);
+ }
+ if (!layerHasSubscribers) {
+ notifyOfSubscriptionChange();
+ }
+ }
+
+ /**
+ * Adds a publisher-specific layer subscription.
+ *
+ * @param subscriber Subscriber client to send layer data
+ * @param layer Layer to send
+ * @param publisherId Publisher of layer
+ */
+ public void addSubscription(IVmsSubscriberClient subscriber, VmsLayer layer, int publisherId) {
+ boolean firstSubscriptionForLayer;
+ synchronized (mLock) {
+ // Check if publishers need to be notified about this change in subscriptions.
+ firstSubscriptionForLayer = !(mRouting.hasLayerSubscriptions(layer)
+ || mRouting.hasLayerFromPublisherSubscriptions(layer, publisherId));
+
+ // Add the listeners subscription to the layer
+ mRouting.addSubscription(subscriber, layer, publisherId);
+ }
+ if (firstSubscriptionForLayer) {
+ notifyOfSubscriptionChange();
+ }
+ }
+
+ /**
+ * Removes a publisher-specific layer subscription.
+ *
+ * @param subscriber Subscriber client to remove subscription for
+ * @param layer Layer to remove
+ * @param publisherId Publisher of layer
+ */
+ public void removeSubscription(IVmsSubscriberClient subscriber, VmsLayer layer,
+ int publisherId) {
+ boolean layerHasSubscribers;
+ synchronized (mLock) {
+ if (!mRouting.hasLayerFromPublisherSubscriptions(layer, publisherId)) {
+ Log.i(TAG, "Trying to remove a layer with no subscription: "
+ + layer + ", publisher ID:" + publisherId);
+ return;
+ }
+
+ // Remove the listeners subscription to the layer
+ mRouting.removeSubscription(subscriber, layer, publisherId);
+
+ // Check if publishers need to be notified about this change in subscriptions.
+ layerHasSubscribers = mRouting.hasLayerSubscriptions(layer)
+ || mRouting.hasLayerFromPublisherSubscriptions(layer, publisherId);
+ }
+ if (!layerHasSubscribers) {
+ notifyOfSubscriptionChange();
+ }
+ }
+
+ /**
+ * Removes a disconnected subscriber's subscriptions
+ *
+ * @param subscriber Subscriber that was disconnected
+ */
+ public void removeDeadSubscriber(IVmsSubscriberClient subscriber) {
+ synchronized (mLock) {
+ mRouting.removeDeadSubscriber(subscriber);
+ }
+ }
+
+ /**
+ * Gets all subscribers for a specific layer/publisher combination.
+ *
+ * @param layer Layer to query
+ * @param publisherId Publisher of layer
+ */
+ public Set<IVmsSubscriberClient> getSubscribersForLayerFromPublisher(VmsLayer layer,
+ int publisherId) {
+ synchronized (mLock) {
+ return mRouting.getSubscribersForLayerFromPublisher(layer, publisherId);
+ }
+ }
+
+ /**
+ * Gets the state of all layer subscriptions.
+ */
+ public VmsSubscriptionState getSubscriptionState() {
+ synchronized (mLock) {
+ return mRouting.getSubscriptionState();
+ }
+ }
+
+ /**
+ * Assigns an idempotent ID for publisherInfo and stores it. The idempotency in this case means
+ * that the same publisherInfo will always, within a trip of the vehicle, return the same ID.
+ * The publisherInfo should be static for a binary and should only change as part of a software
+ * update. The publisherInfo is a serialized proto message which VMS clients can interpret.
+ */
+ public int getPublisherId(byte[] publisherInfo) {
+ if (DBG) Log.i(TAG, "Getting publisher static ID");
+ synchronized (mLock) {
+ return mPublishersInfo.getIdForInfo(publisherInfo);
+ }
+ }
+
+ /**
+ * Gets the publisher information data registered in {@link #getPublisherId(byte[])}
+ *
+ * @param publisherId Publisher ID to query
+ * @return Publisher information
+ */
+ public byte[] getPublisherInfo(int publisherId) {
+ if (DBG) Log.i(TAG, "Getting information for publisher ID: " + publisherId);
+ synchronized (mLock) {
+ return mPublishersInfo.getPublisherInfo(publisherId);
+ }
+ }
+
+ /**
+ * Sets the layers offered by the publisher with the given publisher token.
+ *
+ * @param publisherToken Identifier token of publisher
+ * @param offering Layers offered by publisher
+ */
+ public void setPublisherLayersOffering(IBinder publisherToken, VmsLayersOffering offering) {
+ synchronized (mLock) {
+ Map<Integer, VmsLayersOffering> publisherOfferings = mOfferings.computeIfAbsent(
+ publisherToken, k -> new HashMap<>());
+ publisherOfferings.put(offering.getPublisherId(), offering);
+
+ // Update layers availability.
+ Set<VmsLayersOffering> allPublisherOfferings = new HashSet<>();
+ for (Map<Integer, VmsLayersOffering> offerings : mOfferings.values()) {
+ allPublisherOfferings.addAll(offerings.values());
+ }
+ if (DBG) Log.d(TAG, "New layer availability: " + allPublisherOfferings);
+ mAvailableLayers.setPublishersOffering(allPublisherOfferings);
+ }
+ VmsOperationRecorder.get().setPublisherLayersOffering(offering);
+ notifyOfAvailabilityChange();
+ }
+
+ /**
+ * Gets all layers available for subscription.
+ *
+ * @return All available layers
+ */
+ public VmsAvailableLayers getAvailableLayers() {
+ synchronized (mLock) {
+ return mAvailableLayers.getAvailableLayers();
+ }
+ }
+
+ private void notifyOfSubscriptionChange() {
+ if (DBG) Log.d(TAG, "Notifying publishers on subscriptions");
+
+ VmsSubscriptionState subscriptionState = getSubscriptionState();
+ // Notify the App publishers
+ for (PublisherListener listener : mPublisherListeners) {
+ listener.onSubscriptionChange(subscriptionState);
+ }
+ }
+
+ private void notifyOfAvailabilityChange() {
+ if (DBG) Log.d(TAG, "Notifying subscribers on layers availability");
+
+ VmsAvailableLayers availableLayers = getAvailableLayers();
+ // Notify the App subscribers
+ for (SubscriberListener listener : mSubscriberListeners) {
+ listener.onLayersAvailabilityChange(availableLayers);
+ }
+ }
+}
diff --git a/service/src/com/android/car/vms/VmsClientManager.java b/service/src/com/android/car/vms/VmsClientManager.java
index c341b7a..0657cba 100644
--- a/service/src/com/android/car/vms/VmsClientManager.java
+++ b/service/src/com/android/car/vms/VmsClientManager.java
@@ -313,6 +313,15 @@
mBinder = null;
}
+ void rebind() {
+ unbind();
+ if (DBG) {
+ Log.d(TAG,
+ String.format("rebinding %s after %dms", mFullName, mMillisBeforeRebind));
+ }
+ mHandler.postDelayed(this::bind, mMillisBeforeRebind);
+ }
+
@Override
public void onServiceConnected(ComponentName name, IBinder binder) {
if (DBG) Log.d(TAG, "onServiceConnected: " + mFullName);
@@ -323,20 +332,13 @@
@Override
public void onServiceDisconnected(ComponentName name) {
if (DBG) Log.d(TAG, "onServiceDisconnected: " + mFullName);
- if (mBinder != null) {
- notifyListenersOnClientDisconnected(mFullName);
- }
- mBinder = null;
- // No need to unbind and rebind, per onServiceDisconnected documentation:
- // "binding to the service will remain active, and you will receive a call
- // to onServiceConnected when the Service is next running"
+ rebind();
}
@Override
public void onBindingDied(ComponentName name) {
if (DBG) Log.d(TAG, "onBindingDied: " + mFullName);
- unbind();
- mHandler.postDelayed(this::bind, mMillisBeforeRebind);
+ rebind();
}
@Override
diff --git a/tests/DirectRenderingClusterSample/res/values-en-rUS/dimens.xml b/tests/DirectRenderingClusterSample/res/values-en-rUS/dimens.xml
new file mode 100644
index 0000000..05eef22
--- /dev/null
+++ b/tests/DirectRenderingClusterSample/res/values-en-rUS/dimens.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- -->
+ <!-- Sensor value conversion constants -->
+ <!-- -->
+ <!-- Speed: meters per second to miles per hour -->
+ <item name="speed_factor" format="float" type="dimen">2.236936</item>
+ <!-- Distance: miles to meters -->
+ <item name="distance_factor" format="float" type="dimen">1609.344</item>
+</resources>
\ No newline at end of file
diff --git a/tests/DirectRenderingClusterSample/res/values/dimens.xml b/tests/DirectRenderingClusterSample/res/values/dimens.xml
index 2979b36..6d22a70 100644
--- a/tests/DirectRenderingClusterSample/res/values/dimens.xml
+++ b/tests/DirectRenderingClusterSample/res/values/dimens.xml
@@ -40,4 +40,12 @@
<dimen name="laneview_height">25dp</dimen>
<dimen name="lane_width">50dp</dimen>
<dimen name="lane_height">50dp</dimen>
+
+ <!-- -->
+ <!-- Sensor value conversion constants -->
+ <!-- -->
+ <!-- Speed: meters per second to kilometers per hour -->
+ <item name="speed_factor" format="float" type="dimen">3.6</item>
+ <!-- Distance: kilometers to meters -->
+ <item name="distance_factor" format="float" type="dimen">1000</item>
</resources>
diff --git a/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/ActivityMonitor.java b/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/ActivityMonitor.java
index d102a49..6cabd69 100644
--- a/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/ActivityMonitor.java
+++ b/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/ActivityMonitor.java
@@ -60,6 +60,10 @@
}
@Override
+ public void onForegroundServicesChanged(int pid, int uid, int fgServicetypes) {
+ }
+
+ @Override
public void onProcessDied(int pid, int uid) {
notifyTopActivities();
}
diff --git a/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/ClusterRenderingServiceImpl.java b/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/ClusterRenderingServiceImpl.java
index 84d67b9..e3ee05a 100644
--- a/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/ClusterRenderingServiceImpl.java
+++ b/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/ClusterRenderingServiceImpl.java
@@ -27,12 +27,9 @@
import android.content.Intent;
import android.graphics.Rect;
import android.hardware.display.DisplayManager.DisplayListener;
+import android.os.Binder;
import android.os.Bundle;
-import android.os.Handler;
import android.os.IBinder;
-import android.os.Message;
-import android.os.Messenger;
-import android.os.RemoteException;
import android.os.SystemClock;
import android.os.UserHandle;
import android.provider.Settings;
@@ -47,37 +44,41 @@
import java.io.FileDescriptor;
import java.io.PrintWriter;
-import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
+import java.util.function.Consumer;
/**
* Implementation of {@link InstrumentClusterRenderingService} which renders an activity on a
* virtual display that is transmitted to an external screen.
*/
-public class ClusterRenderingServiceImpl extends InstrumentClusterRenderingService {
+public class ClusterRenderingServiceImpl extends InstrumentClusterRenderingService implements
+ ImageResolver.BitmapFetcher {
private static final String TAG = "Cluster.SampleService";
private static final int NO_DISPLAY = -1;
+ static final int NAV_STATE_EVENT_ID = 1;
static final String LOCAL_BINDING_ACTION = "local";
static final String NAV_STATE_BUNDLE_KEY = "navstate";
- static final int NAV_STATE_EVENT_ID = 1;
- static final int MSG_SET_ACTIVITY_LAUNCH_OPTIONS = 1;
- static final int MSG_ON_NAVIGATION_STATE_CHANGED = 2;
- static final int MSG_ON_KEY_EVENT = 3;
- static final int MSG_REGISTER_CLIENT = 4;
- static final int MSG_UNREGISTER_CLIENT = 5;
- static final String MSG_KEY_CATEGORY = "category";
- static final String MSG_KEY_ACTIVITY_DISPLAY_ID = "activity_display_id";
- static final String MSG_KEY_ACTIVITY_STATE = "activity_state";
- static final String MSG_KEY_KEY_EVENT = "key_event";
- private List<Messenger> mClients = new ArrayList<>();
+ private List<ServiceClient> mClients = new ArrayList<>();
private ClusterDisplayProvider mDisplayProvider;
private int mDisplayId = NO_DISPLAY;
- private final IBinder mLocalBinder = new Messenger(new MessageHandler(this)).getBinder();
+ private final IBinder mLocalBinder = new LocalBinder();
+ private final ImageResolver mImageResolver = new ImageResolver(this);
+
+ public interface ServiceClient {
+ void onKeyEvent(KeyEvent keyEvent);
+ void onNavigationStateChange(NavigationState navState);
+ }
+
+ public class LocalBinder extends Binder {
+ ClusterRenderingServiceImpl getService() {
+ return ClusterRenderingServiceImpl.this;
+ }
+ }
private final DisplayListener mDisplayListener = new DisplayListener() {
@Override
@@ -98,43 +99,33 @@
}
};
- private static class MessageHandler extends Handler {
- private final WeakReference<ClusterRenderingServiceImpl> mService;
-
- MessageHandler(ClusterRenderingServiceImpl service) {
- mService = new WeakReference<>(service);
+ public void setActivityLaunchOptions(int displayId, ClusterActivityState state) {
+ ActivityOptions options = displayId != Display.INVALID_DISPLAY
+ ? ActivityOptions.makeBasic().setLaunchDisplayId(displayId)
+ : null;
+ setClusterActivityLaunchOptions(options);
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, String.format("activity options set: %s (displayeId: %d)",
+ options, options.getLaunchDisplayId()));
}
-
- @Override
- public void handleMessage(Message msg) {
- Log.d(TAG, "handleMessage: " + msg.what);
- switch (msg.what) {
- case MSG_SET_ACTIVITY_LAUNCH_OPTIONS: {
- int displayId = msg.getData().getInt(MSG_KEY_ACTIVITY_DISPLAY_ID);
- Bundle state = msg.getData().getBundle(MSG_KEY_ACTIVITY_STATE);
- String category = msg.getData().getString(MSG_KEY_CATEGORY);
- ActivityOptions options = displayId != Display.INVALID_DISPLAY
- ? ActivityOptions.makeBasic().setLaunchDisplayId(displayId)
- : null;
- mService.get().setClusterActivityLaunchOptions(category, options);
- Log.d(TAG, String.format("activity options set: %s = %s (displayeId: %d)",
- category, options, options.getLaunchDisplayId()));
- mService.get().setClusterActivityState(category, state);
- Log.d(TAG, String.format("activity state set: %s = %s", category, state));
- break;
- }
- case MSG_REGISTER_CLIENT:
- mService.get().mClients.add(msg.replyTo);
- break;
- case MSG_UNREGISTER_CLIENT:
- mService.get().mClients.remove(msg.replyTo);
- break;
- default:
- super.handleMessage(msg);
- }
+ setClusterActivityState(state);
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, String.format("activity state set: %s", state));
}
}
+ public void registerClient(ServiceClient client) {
+ mClients.add(client);
+ }
+
+ public void unregisterClient(ServiceClient client) {
+ mClients.remove(client);
+ }
+
+ public ImageResolver getImageResolver() {
+ return mImageResolver;
+ }
+
@Override
public IBinder onBind(Intent intent) {
Log.d(TAG, "onBind, intent: " + intent);
@@ -161,30 +152,20 @@
@Override
public void onKeyEvent(KeyEvent keyEvent) {
- Log.d(TAG, "onKeyEvent, keyEvent: " + keyEvent);
- Bundle data = new Bundle();
- data.putParcelable(MSG_KEY_KEY_EVENT, keyEvent);
- broadcastClientMessage(MSG_ON_KEY_EVENT, data);
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "onKeyEvent, keyEvent: " + keyEvent);
+ }
+ broadcastClientEvent(client -> client.onKeyEvent(keyEvent));
}
/**
- * Broadcasts a message to all the registered service clients
+ * Broadcasts an event to all the registered service clients
*
- * @param what event identifier
- * @param data event data
+ * @param event event to broadcast
*/
- private void broadcastClientMessage(int what, Bundle data) {
- Log.d(TAG, "broadcast message " + what + " to " + mClients.size() + " clients");
- for (int i = mClients.size() - 1; i >= 0; i--) {
- Messenger client = mClients.get(i);
- try {
- Message msg = Message.obtain(null, what);
- msg.setData(data);
- client.send(msg);
- } catch (RemoteException ex) {
- Log.e(TAG, "Client " + i + " is dead", ex);
- mClients.remove(i);
- }
+ private void broadcastClientEvent(Consumer<ServiceClient> event) {
+ for (ServiceClient client : mClients) {
+ event.accept(client);
}
}
@@ -210,7 +191,7 @@
bundleSummary.append(navState.toString());
// Update clients
- broadcastClientMessage(MSG_ON_NAVIGATION_STATE_CHANGED, bundle);
+ broadcastClientEvent(client -> client.onNavigationStateChange(navState));
} else {
for (String key : bundle.keySet()) {
bundleSummary.append(key);
@@ -222,9 +203,8 @@
Log.d(TAG, "onEvent(" + eventType + ", " + bundleSummary + ")");
} catch (Exception e) {
Log.e(TAG, "Error parsing event data (" + eventType + ", " + bundle + ")", e);
- bundle.putParcelable(NAV_STATE_BUNDLE_KEY, new NavigationState.Builder().build()
- .toParcelable());
- broadcastClientMessage(MSG_ON_NAVIGATION_STATE_CHANGED, bundle);
+ NavigationState navState = new NavigationState.Builder().build();
+ broadcastClientEvent(client -> client.onNavigationStateChange(navState));
}
}
};
diff --git a/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/ClusterViewModel.java b/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/ClusterViewModel.java
index a53f4ba..d1e6ee6 100644
--- a/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/ClusterViewModel.java
+++ b/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/ClusterViewModel.java
@@ -28,6 +28,7 @@
import android.content.ServiceConnection;
import android.os.IBinder;
import android.util.Log;
+import android.util.TypedValue;
import androidx.annotation.NonNull;
import androidx.core.util.Preconditions;
@@ -36,6 +37,7 @@
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.Transformations;
+import java.text.DecimalFormat;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
@@ -48,6 +50,9 @@
private static final int PROPERTIES_REFRESH_RATE_UI = 5;
+ private float mSpeedFactor;
+ private float mDistanceFactor;
+
public enum NavigationActivityState {
/** No activity has been selected to be displayed on the navigation fragment yet */
NOT_SELECTED,
@@ -110,42 +115,43 @@
}
}
-
private CarPropertyManager.CarPropertyEventListener mCarPropertyEventListener =
new CarPropertyManager.CarPropertyEventListener() {
- @Override
- public void onChangeEvent(CarPropertyValue value) {
- if (Log.isLoggable(TAG, Log.DEBUG)) {
- Log.d(TAG, "CarProperty change: property " + value.getPropertyId() + ", area"
- + value.getAreaId() + ", value: " + value.getValue());
- }
- for (Sensor<?> sensorId : Sensors.getInstance()
- .getSensorsForPropertyId(value.getPropertyId())) {
- if (sensorId.mAreaId == Sensors.GLOBAL_AREA_ID
- || (sensorId.mAreaId & value.getAreaId()) != 0) {
- setSensorValue(sensorId, value);
+ @Override
+ public void onChangeEvent(CarPropertyValue value) {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG,
+ "CarProperty change: property " + value.getPropertyId() + ", area"
+ + value.getAreaId() + ", value: " + value.getValue());
+ }
+ for (Sensor<?> sensorId : Sensors.getInstance()
+ .getSensorsForPropertyId(value.getPropertyId())) {
+ if (sensorId.mAreaId == Sensors.GLOBAL_AREA_ID
+ || (sensorId.mAreaId & value.getAreaId()) != 0) {
+ setSensorValue(sensorId, value);
+ }
+ }
}
- }
- }
- @Override
- public void onErrorEvent(int propId, int zone) {
- for (Sensor<?> sensorId : Sensors.getInstance().getSensorsForPropertyId(propId)) {
- if (sensorId.mAreaId == VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL
- || (sensorId.mAreaId & zone) != 0) {
- setSensorValue(sensorId, null);
+ @Override
+ public void onErrorEvent(int propId, int zone) {
+ for (Sensor<?> sensorId : Sensors.getInstance().getSensorsForPropertyId(
+ propId)) {
+ if (sensorId.mAreaId == VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL
+ || (sensorId.mAreaId & zone) != 0) {
+ setSensorValue(sensorId, null);
+ }
+ }
}
- }
- }
- private <T> void setSensorValue(Sensor<T> id, CarPropertyValue<?> value) {
- T newValue = value != null ? id.mAdapter.apply(value) : null;
- if (Log.isLoggable(TAG, Log.DEBUG)) {
- Log.d(TAG, "Sensor " + id.mName + " = " + newValue);
- }
- getSensorMutableLiveData(id).setValue(newValue);
- }
- };
+ private <T> void setSensorValue(Sensor<T> id, CarPropertyValue<?> value) {
+ T newValue = value != null ? id.mAdapter.apply(value) : null;
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "Sensor " + id.mName + " = " + newValue);
+ }
+ getSensorMutableLiveData(id).setValue(newValue);
+ }
+ };
/**
* New {@link ClusterViewModel} instance
@@ -154,6 +160,13 @@
super(application);
mCar = Car.createCar(application, mCarServiceConnection);
mCar.connect();
+
+ TypedValue tv = new TypedValue();
+ getApplication().getResources().getValue(R.dimen.speed_factor, tv, true);
+ mSpeedFactor = tv.getFloat();
+
+ getApplication().getResources().getValue(R.dimen.distance_factor, tv, true);
+ mDistanceFactor = tv.getFloat();
}
@Override
@@ -187,7 +200,7 @@
* own data type. The list of all supported sensors can be found at {@link Sensors}
*
* @param sensor sensor to observe
- * @param <T> data type of such sensor
+ * @param <T> data type of such sensor
*/
@SuppressWarnings("unchecked")
@NonNull
@@ -199,8 +212,8 @@
* Returns the current value of the sensor, directly from the VHAL.
*
* @param sensor sensor to read
- * @param <V> VHAL data type
- * @param <T> data type of such sensor
+ * @param <V> VHAL data type
+ * @param <T> data type of such sensor
*/
@Nullable
public <T> T getSensorValue(@NonNull Sensor<T> sensor) {
@@ -210,21 +223,48 @@
}
/**
- * Returns a {@link LiveData} that tracks the fuel level in a range from 0.0 to 1.0.
+ * Returns a {@link LiveData} that tracks the fuel level in a range from 0 to 100.
*/
- public LiveData<Float> getFuelLevel() {
+ public LiveData<Integer> getFuelLevel() {
return Transformations.map(getSensor(Sensors.SENSOR_FUEL), (fuelValue) -> {
Float fuelCapacityValue = getSensorValue(Sensors.SENSOR_FUEL_CAPACITY);
if (fuelValue == null || fuelCapacityValue == null || fuelCapacityValue == 0) {
return null;
}
if (fuelValue < 0.0f) {
- return 0.0f;
+ return 0;
}
if (fuelValue > fuelCapacityValue) {
- return 1.0f;
+ return 100;
}
- return fuelValue / fuelCapacityValue;
+ return Math.round(fuelValue / (fuelCapacityValue * 100f));
+ });
+ }
+
+ /**
+ * Returns a {@link LiveData} that tracks the RPM x 1000
+ */
+ public LiveData<String> getRPM() {
+ return Transformations.map(getSensor(Sensors.SENSOR_RPM), (rpmValue) -> {
+ return new DecimalFormat("#0.0").format(rpmValue / 1000f);
+ });
+ }
+
+ /**
+ * Returns a {@link LiveData} that tracks the speed in either mi/h or km/h depending on locale.
+ */
+ public LiveData<Integer> getSpeed() {
+ return Transformations.map(getSensor(Sensors.SENSOR_SPEED), (speedValue) -> {
+ return Math.round(speedValue * mSpeedFactor);
+ });
+ }
+
+ /**
+ * Returns a {@link LiveData} that tracks the range the vehicle has until it runs out of gas.
+ */
+ public LiveData<Integer> getRange() {
+ return Transformations.map(getSensor(Sensors.SENSOR_FUEL_RANGE), (rangeValue) -> {
+ return Math.round(rangeValue / mDistanceFactor);
});
}
diff --git a/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/CueView.java b/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/CueView.java
index e0d0d12..ae86850 100644
--- a/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/CueView.java
+++ b/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/CueView.java
@@ -18,19 +18,34 @@
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
+import android.os.Handler;
import android.text.SpannableStringBuilder;
import android.text.style.ImageSpan;
import android.util.AttributeSet;
+import android.util.Log;
import android.widget.TextView;
+import androidx.car.cluster.navigation.ImageReference;
import androidx.car.cluster.navigation.RichText;
import androidx.car.cluster.navigation.RichTextElement;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.CompletableFuture;
+import java.util.stream.Collectors;
+
/**
* View component that displays the Cue information on the instrument cluster display
*/
public class CueView extends TextView {
+ private static final String TAG = "Cluster.CueView";
+
private String mImageSpanText;
+ private CompletableFuture<?> mFuture;
+ private Handler mHandler = new Handler();
+ private RichText mContent;
public CueView(Context context) {
super(context);
@@ -45,20 +60,45 @@
mImageSpanText = context.getString(R.string.span_image);
}
- public void setRichText(RichText richText) {
+ public void setRichText(RichText richText, ImageResolver imageResolver) {
if (richText == null) {
setText(null);
return;
}
+ if (mFuture != null && !Objects.equals(richText, mContent)) {
+ mFuture.cancel(true);
+ }
+
+ List<ImageReference> imageReferences = richText.getElements().stream()
+ .filter(element -> element.getImage() != null)
+ .map(element -> element.getImage())
+ .collect(Collectors.toList());
+ mFuture = imageResolver
+ .getBitmaps(imageReferences, 0, getLineHeight())
+ .thenAccept(bitmaps -> {
+ mHandler.post(() -> update(richText, bitmaps));
+ mFuture = null;
+ })
+ .exceptionally(ex -> {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "Unable to fetch images for cue: " + richText);
+ }
+ mHandler.post(() -> update(richText, Collections.emptyMap()));
+ return null;
+ });
+ mContent = richText;
+ }
+
+ private void update(RichText richText, Map<ImageReference, Bitmap> bitmaps) {
SpannableStringBuilder builder = new SpannableStringBuilder();
+
for (RichTextElement element : richText.getElements()) {
if (builder.length() > 0) {
builder.append(" ");
}
if (element.getImage() != null) {
- Bitmap bitmap = ImageResolver.getInstance().getBitmapConstrained(getContext(),
- element.getImage(), 0, getLineHeight());
+ Bitmap bitmap = bitmaps.get(element.getImage());
if (bitmap != null) {
String imageText = element.getText().isEmpty() ? mImageSpanText :
element.getText();
diff --git a/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/ImageResolver.java b/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/ImageResolver.java
index f306143..5e03b9b 100644
--- a/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/ImageResolver.java
+++ b/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/ImageResolver.java
@@ -15,95 +15,126 @@
*/
package android.car.cluster.sample;
-import android.content.ContentResolver;
-import android.content.Context;
import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
import android.graphics.Point;
import android.net.Uri;
-import android.os.ParcelFileDescriptor;
import android.util.Log;
+import android.util.LruCache;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.car.cluster.navigation.ImageReference;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+import java.util.stream.Collectors;
/**
* Class for retrieving bitmap images from a ContentProvider
*/
public class ImageResolver {
private static final String TAG = "Cluster.ImageResolver";
+ private static final int IMAGE_CACHE_SIZE_BYTES = 4 * 1024 * 1024; /* 4 mb */
- private static ImageResolver sImageResolver = new ImageResolver();
+ private final BitmapFetcher mFetcher;
+ private final LruCache<String, Bitmap> mCache = new LruCache<String, Bitmap>(
+ IMAGE_CACHE_SIZE_BYTES) {
+ @Override
+ protected int sizeOf(String key, Bitmap value) {
+ return value.getByteCount();
+ }
+ };
- private ImageResolver() {}
-
- public static ImageResolver getInstance() {
- return sImageResolver;
+ public interface BitmapFetcher {
+ Bitmap getBitmap(Uri uri);
}
/**
- * Returns a bitmap from an URI string from a content provider
- *
- * @param context View context
+ * Creates a resolver that delegate the image retrieval to the given fetcher.
*/
- @Nullable
- public Bitmap getBitmap(Context context, Uri uri) {
- if (Log.isLoggable(TAG, Log.DEBUG)) {
- Log.d(TAG, "Requesting: " + uri);
- }
- try {
- ContentResolver contentResolver = context.getContentResolver();
- ParcelFileDescriptor fileDesc = contentResolver.openFileDescriptor(uri, "r");
- if (fileDesc != null) {
- Bitmap bitmap = BitmapFactory.decodeFileDescriptor(fileDesc.getFileDescriptor());
- fileDesc.close();
- return bitmap;
- } else {
- Log.e(TAG, "Null pointer: Failed to create pipe for uri string: " + uri);
- }
- } catch (FileNotFoundException e) {
- Log.e(TAG, "File not found for uri string: " + uri, e);
- } catch (IOException e) {
- Log.e(TAG, "File descriptor could not close: ", e);
- }
-
- return null;
+ public ImageResolver(BitmapFetcher fetcher) {
+ mFetcher = fetcher;
}
/**
- * Returns a bitmap from a Car Instrument Cluster {@link ImageReference} that would fit inside
- * the provided size. Either width, height or both should be greater than 0.
+ * Returns a {@link CompletableFuture} that provides a bitmap from a {@link ImageReference}.
+ * This image would fit inside the provided size. Either width, height or both should be greater
+ * than 0.
*
- * @param context View context
* @param width required width, or 0 if width is flexible based on height.
* @param height required height, or 0 if height is flexible based on width.
*/
- @Nullable
- public Bitmap getBitmapConstrained(Context context, ImageReference img, int width,
- int height) {
+ @NonNull
+ public CompletableFuture<Bitmap> getBitmap(@NonNull ImageReference img, int width, int height) {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, String.format("Requesting image %s (width: %d, height: %d)",
img.getRawContentUri(), width, height));
}
- // Adjust the size to fit in the requested box.
- Point adjusted = getAdjustedSize(img.getOriginalWidth(), img.getOriginalHeight(), width,
- height);
- if (adjusted == null) {
- Log.e(TAG, "The provided image has no original size: " + img.getRawContentUri());
- return null;
- }
- Bitmap bitmap = getBitmap(context, img.getContentUri(adjusted.x, adjusted.y));
- if (Log.isLoggable(TAG, Log.DEBUG)) {
- Log.d(TAG, String.format("Returning image %s (width: %d, height: %d)",
- img.getRawContentUri(), width, height));
- }
- return bitmap != null ? Bitmap.createScaledBitmap(bitmap, adjusted.x, adjusted.y, true)
- : null;
+ return CompletableFuture.supplyAsync(() -> {
+ // Adjust the size to fit in the requested box.
+ Point adjusted = getAdjustedSize(img.getOriginalWidth(), img.getOriginalHeight(), width,
+ height);
+ if (adjusted == null) {
+ Log.e(TAG, "The provided image has no original size: " + img.getRawContentUri());
+ return null;
+ }
+ Uri uri = img.getContentUri(adjusted.x, adjusted.y);
+ Bitmap bitmap = mCache.get(uri.toString());
+ if (bitmap == null) {
+ bitmap = mFetcher.getBitmap(uri);
+ if (bitmap == null) {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "Unable to fetch image: " + uri);
+ }
+ return null;
+ }
+ if (bitmap.getWidth() != adjusted.x || bitmap.getHeight() != adjusted.y) {
+ bitmap = Bitmap.createScaledBitmap(bitmap, adjusted.x, adjusted.y, true);
+ }
+ mCache.put(uri.toString(), bitmap);
+ }
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, String.format("Returning image %s (width: %d, height: %d)",
+ img.getRawContentUri(), width, height));
+ }
+ return bitmap != null ? Bitmap.createScaledBitmap(bitmap, adjusted.x, adjusted.y, true)
+ : null;
+ });
+ }
+
+ /**
+ * Same as {@link #getBitmap(ImageReference, int, int)} but it works on a list of images. The
+ * returning {@link CompletableFuture} will contain a map from each {@link ImageReference} to
+ * its bitmap. If any image fails to be fetched, the whole future completes exceptionally.
+ *
+ * @param width required width, or 0 if width is flexible based on height.
+ * @param height required height, or 0 if height is flexible based on width.
+ */
+ @NonNull
+ public CompletableFuture<Map<ImageReference, Bitmap>> getBitmaps(
+ @NonNull List<ImageReference> imgs, int width, int height) {
+ CompletableFuture<Map<ImageReference, Bitmap>> future = new CompletableFuture<>();
+
+ Map<ImageReference, CompletableFuture<Bitmap>> bitmapFutures = imgs.stream().collect(
+ Collectors.toMap(
+ img -> img,
+ img -> getBitmap(img, width, height)));
+
+ CompletableFuture.allOf(bitmapFutures.values().toArray(new CompletableFuture[0]))
+ .thenAccept(v -> {
+ Map<ImageReference, Bitmap> bitmaps = bitmapFutures.entrySet().stream()
+ .collect(Collectors.toMap(Map.Entry::getKey, entry -> entry
+ .getValue().join()));
+ future.complete(bitmaps);
+ })
+ .exceptionally(ex -> {
+ future.completeExceptionally(ex);
+ return null;
+ });
+
+ return future;
}
/**
diff --git a/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/MainClusterActivity.java b/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/MainClusterActivity.java
index 3181b1e..9f4f931 100644
--- a/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/MainClusterActivity.java
+++ b/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/MainClusterActivity.java
@@ -16,17 +16,6 @@
package android.car.cluster.sample;
import static android.car.cluster.sample.ClusterRenderingServiceImpl.LOCAL_BINDING_ACTION;
-import static android.car.cluster.sample.ClusterRenderingServiceImpl.MSG_KEY_ACTIVITY_DISPLAY_ID;
-import static android.car.cluster.sample.ClusterRenderingServiceImpl.MSG_KEY_ACTIVITY_STATE;
-import static android.car.cluster.sample.ClusterRenderingServiceImpl.MSG_KEY_CATEGORY;
-import static android.car.cluster.sample.ClusterRenderingServiceImpl.MSG_KEY_KEY_EVENT;
-import static android.car.cluster.sample.ClusterRenderingServiceImpl.MSG_ON_KEY_EVENT;
-import static android.car.cluster.sample.ClusterRenderingServiceImpl
- .MSG_ON_NAVIGATION_STATE_CHANGED;
-import static android.car.cluster.sample.ClusterRenderingServiceImpl.MSG_REGISTER_CLIENT;
-import static android.car.cluster.sample.ClusterRenderingServiceImpl
- .MSG_SET_ACTIVITY_LAUNCH_OPTIONS;
-import static android.car.cluster.sample.ClusterRenderingServiceImpl.MSG_UNREGISTER_CLIENT;
import static android.content.Intent.ACTION_USER_SWITCHED;
import static android.content.Intent.ACTION_USER_UNLOCKED;
@@ -48,9 +37,6 @@
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
-import android.os.Message;
-import android.os.Messenger;
-import android.os.RemoteException;
import android.os.UserHandle;
import android.util.Log;
import android.util.SparseArray;
@@ -69,7 +55,6 @@
import androidx.fragment.app.FragmentPagerAdapter;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.ViewModelProviders;
-import androidx.versionedparcelable.ParcelUtils;
import androidx.viewpager.widget.ViewPager;
import java.lang.ref.WeakReference;
@@ -95,7 +80,8 @@
* This is necessary because the navigation app runs under a normal user, and different users will
* see different instances of the same application, with their own personalized data.
*/
-public class MainClusterActivity extends FragmentActivity {
+public class MainClusterActivity extends FragmentActivity implements
+ ClusterRenderingServiceImpl.ServiceClient {
private static final String TAG = "Cluster.MainActivity";
private static final NavigationState NULL_NAV_STATE = new NavigationState.Builder().build();
@@ -110,8 +96,7 @@
private Map<Sensors.Gear, View> mGearsToIcon = new HashMap<>();
private InputMethodManager mInputMethodManager;
- private Messenger mService;
- private Messenger mServiceCallbacks = new Messenger(new MessageHandler(this));
+ private ClusterRenderingServiceImpl mService;
private VirtualDisplay mPendingVirtualDisplay = null;
private static final int NAVIGATION_ACTIVITY_RETRY_INTERVAL_MS = 1000;
@@ -142,7 +127,7 @@
@Override
public void onFocusChange(View v, boolean hasFocus) {
if (hasFocus) {
- mPager.setCurrentItem(mButtonToFacet.get(v).order);
+ mPager.setCurrentItem(mButtonToFacet.get(v).mOrder);
}
}
};
@@ -151,8 +136,9 @@
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.i(TAG, "onServiceConnected, name: " + name + ", service: " + service);
- mService = new Messenger(service);
- sendServiceMessage(MSG_REGISTER_CLIENT, null, mServiceCallbacks);
+ mService = ((ClusterRenderingServiceImpl.LocalBinder) service).getService();
+ mService.registerClient(MainClusterActivity.this);
+ mNavStateController.setImageResolver(mService.getImageResolver());
if (mPendingVirtualDisplay != null) {
// If haven't reported the virtual display yet, do so on service connect.
reportNavDisplay(mPendingVirtualDisplay);
@@ -164,44 +150,11 @@
public void onServiceDisconnected(ComponentName name) {
Log.i(TAG, "onServiceDisconnected, name: " + name);
mService = null;
+ mNavStateController.setImageResolver(null);
onNavigationStateChange(NULL_NAV_STATE);
}
};
- private static class MessageHandler extends Handler {
- private final WeakReference<MainClusterActivity> mActivity;
-
- MessageHandler(MainClusterActivity activity) {
- mActivity = new WeakReference<>(activity);
- }
-
- @Override
- public void handleMessage(Message msg) {
- Bundle data = msg.getData();
- switch (msg.what) {
- case MSG_ON_KEY_EVENT:
- KeyEvent event = data.getParcelable(MSG_KEY_KEY_EVENT);
- if (event != null) {
- mActivity.get().onKeyEvent(event);
- }
- break;
- case MSG_ON_NAVIGATION_STATE_CHANGED:
- if (data == null) {
- mActivity.get().onNavigationStateChange(null);
- } else {
- data.setClassLoader(ParcelUtils.class.getClassLoader());
- NavigationState navState = NavigationState
- .fromParcelable(data.getParcelable(
- ClusterRenderingServiceImpl.NAV_STATE_BUNDLE_KEY));
- mActivity.get().onNavigationStateChange(navState);
- }
- break;
- default:
- super.handleMessage(msg);
- }
- }
- }
-
private ActivityMonitor.ActivityListener mNavigationActivityMonitor = (displayId, activity) -> {
if (displayId != mNavigationDisplayId) {
return;
@@ -259,7 +212,7 @@
mPager = findViewById(R.id.pager);
mPager.setAdapter(new ClusterPageAdapter(getSupportFragmentManager()));
- mOrderToFacet.get(0).button.requestFocus();
+ mOrderToFacet.get(0).mButton.requestFocus();
mNavStateController = new NavStateController(findViewById(R.id.navigation_state));
mClusterViewModel = ViewModelProviders.of(this).get(ClusterViewModel.class);
@@ -274,12 +227,9 @@
mClusterViewModel.getSensor(Sensors.SENSOR_GEAR).observe(this, this::updateSelectedGear);
registerSensor(findViewById(R.id.info_fuel), mClusterViewModel.getFuelLevel());
- registerSensor(findViewById(R.id.info_speed),
- mClusterViewModel.getSensor(Sensors.SENSOR_SPEED));
- registerSensor(findViewById(R.id.info_range),
- mClusterViewModel.getSensor(Sensors.SENSOR_FUEL_RANGE));
- registerSensor(findViewById(R.id.info_rpm),
- mClusterViewModel.getSensor(Sensors.SENSOR_RPM));
+ registerSensor(findViewById(R.id.info_speed), mClusterViewModel.getSpeed());
+ registerSensor(findViewById(R.id.info_range), mClusterViewModel.getRange());
+ registerSensor(findViewById(R.id.info_rpm), mClusterViewModel.getRPM());
mActivityMonitor.start();
@@ -300,13 +250,14 @@
mUserReceiver.unregister(this);
mActivityMonitor.stop();
if (mService != null) {
- sendServiceMessage(MSG_UNREGISTER_CLIENT, null, mServiceCallbacks);
+ mService.unregisterClient(this);
mService = null;
}
unbindService(mClusterRenderingServiceConnection);
}
- private void onKeyEvent(KeyEvent event) {
+ @Override
+ public void onKeyEvent(KeyEvent event) {
Log.i(TAG, "onKeyEvent, event: " + event);
// This is a hack. We use SOURCE_CLASS_POINTER here because this type of input is associated
@@ -316,7 +267,8 @@
mInputMethodManager.dispatchKeyEventFromInputMethod(getCurrentFocus(), event);
}
- private void onNavigationStateChange(NavigationState state) {
+ @Override
+ public void onNavigationStateChange(NavigationState state) {
Log.d(TAG, "onNavigationStateChange: " + state);
if (mNavStateController != null) {
mNavStateController.update(state);
@@ -338,32 +290,9 @@
}
private void reportNavDisplay(VirtualDisplay virtualDisplay) {
- Bundle data = new Bundle();
- data.putString(MSG_KEY_CATEGORY, Car.CAR_CATEGORY_NAVIGATION);
- data.putInt(MSG_KEY_ACTIVITY_DISPLAY_ID, virtualDisplay.mDisplayId);
- data.putBundle(MSG_KEY_ACTIVITY_STATE, ClusterActivityState
+ mService.setActivityLaunchOptions(virtualDisplay.mDisplayId, ClusterActivityState
.create(virtualDisplay.mDisplayId != Display.INVALID_DISPLAY,
- virtualDisplay.mUnobscuredBounds)
- .toBundle());
- sendServiceMessage(MSG_SET_ACTIVITY_LAUNCH_OPTIONS, data, null);
- }
-
- /**
- * Sends a message to the {@link ClusterRenderingServiceImpl}, which runs on a different
- * process.
- * @param what action to perform
- * @param data action data
- * @param replyTo {@link Messenger} where to reply back
- */
- private void sendServiceMessage(int what, Bundle data, Messenger replyTo) {
- try {
- Message message = Message.obtain(null, what);
- message.setData(data);
- message.replyTo = replyTo;
- mService.send(message);
- } catch (RemoteException ex) {
- Log.e(TAG, "Unable to deliver message " + what + ". Service died");
- }
+ virtualDisplay.mUnobscuredBounds));
}
public class ClusterPageAdapter extends FragmentPagerAdapter {
@@ -383,21 +312,21 @@
}
private <T> void registerFacet(Facet<T> facet) {
- mOrderToFacet.append(facet.order, facet);
- mButtonToFacet.put(facet.button, facet);
+ mOrderToFacet.append(facet.mOrder, facet);
+ mButtonToFacet.put(facet.mButton, facet);
- facet.button.setOnFocusChangeListener(mFacetButtonFocusListener);
+ facet.mButton.setOnFocusChangeListener(mFacetButtonFocusListener);
}
private static class Facet<T> {
- Button button;
- Class<T> clazz;
- int order;
+ Button mButton;
+ Class<T> mClazz;
+ int mOrder;
Facet(Button button, int order, Class<T> clazz) {
- this.button = button;
- this.order = order;
- this.clazz = clazz;
+ this.mButton = button;
+ this.mOrder = order;
+ this.mClazz = clazz;
}
private Fragment mFragment;
@@ -405,8 +334,9 @@
Fragment getOrCreateFragment() {
if (mFragment == null) {
try {
- mFragment = (Fragment) clazz.getConstructors()[0].newInstance();
- } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) {
+ mFragment = (Fragment) mClazz.getConstructors()[0].newInstance();
+ } catch (InstantiationException | IllegalAccessException
+ | InvocationTargetException e) {
throw new RuntimeException(e);
}
}
diff --git a/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/NavStateController.java b/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/NavStateController.java
index 0d07962..3ba31d7 100644
--- a/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/NavStateController.java
+++ b/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/NavStateController.java
@@ -46,6 +46,7 @@
private TextView mEta;
private CueView mCue;
private Context mContext;
+ private ImageResolver mImageResolver;
/**
* Creates a controller to coordinate updates to the views displaying navigation state
@@ -63,6 +64,10 @@
mContext = container.getContext();
}
+ public void setImageResolver(@Nullable ImageResolver imageResolver) {
+ mImageResolver = imageResolver;
+ }
+
/**
* Updates views to reflect the provided navigation state
*/
@@ -80,7 +85,7 @@
mEta.setTextColor(getTrafficColor(traffic));
mManeuver.setImageDrawable(getManeuverIcon(step != null ? step.getManeuver() : null));
mDistance.setText(formatDistance(step != null ? step.getDistance() : null));
- mCue.setRichText(step != null ? step.getCue() : null);
+ mCue.setRichText(step != null ? step.getCue() : null, mImageResolver);
if (step != null && step.getLanes().size() > 0) {
mLane.setLanes(step.getLanes());
diff --git a/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/sensors/Sensors.java b/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/sensors/Sensors.java
index 6e49931..90d6350 100644
--- a/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/sensors/Sensors.java
+++ b/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/sensors/Sensors.java
@@ -60,12 +60,12 @@
"RPM", VehiclePropertyIds.ENGINE_RPM, GLOBAL_AREA_ID,
VehiclePropertyType.FLOAT,
value -> (Float) value.getValue());
- /** Fuel range in kilometers */
+ /** Fuel range in meters */
public static final Sensor<Float> SENSOR_FUEL_RANGE = registerSensor(
"Fuel Range", VehiclePropertyIds.RANGE_REMAINING, GLOBAL_AREA_ID,
VehiclePropertyType.FLOAT,
value -> (Float) value.getValue());
- /** Speed in kph */
+ /** Speed in meters per second */
public static final Sensor<Float> SENSOR_SPEED = registerSensor(
"Speed", VehiclePropertyIds.PERF_VEHICLE_SPEED, GLOBAL_AREA_ID,
VehiclePropertyType.FLOAT,
diff --git a/tests/DirectRenderingClusterSample/tests/robotests/src/android/car/cluster/sample/ImageResolverTest.java b/tests/DirectRenderingClusterSample/tests/robotests/src/android/car/cluster/sample/ImageResolverTest.java
index 746c0df..0d1cece 100644
--- a/tests/DirectRenderingClusterSample/tests/robotests/src/android/car/cluster/sample/ImageResolverTest.java
+++ b/tests/DirectRenderingClusterSample/tests/robotests/src/android/car/cluster/sample/ImageResolverTest.java
@@ -32,7 +32,7 @@
@Before
public void setup() {
- mImageResolver = ImageResolver.getInstance();
+ mImageResolver = new ImageResolver((uri) -> null);
}
@Test
diff --git a/tests/UxRestrictionsSample/Android.mk b/tests/UxRestrictionsSample/Android.mk
index 50419f4..3e006b4 100644
--- a/tests/UxRestrictionsSample/Android.mk
+++ b/tests/UxRestrictionsSample/Android.mk
@@ -42,7 +42,6 @@
LOCAL_STATIC_JAVA_LIBRARIES += vehicle-hal-support-lib
LOCAL_STATIC_ANDROID_LIBRARIES += \
- androidx.car_car \
androidx.legacy_legacy-support-v4 \
androidx.appcompat_appcompat
diff --git a/tests/UxRestrictionsSample/res/layout/activity_sample_message.xml b/tests/UxRestrictionsSample/res/layout/activity_sample_message.xml
deleted file mode 100644
index a778975..0000000
--- a/tests/UxRestrictionsSample/res/layout/activity_sample_message.xml
+++ /dev/null
@@ -1,36 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2018 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<LinearLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="match_parent">
- <Button
- android:id="@+id/home_button"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="@string/return_home"
- android:textAllCaps="false"
- android:textAppearance="?android:textAppearanceLarge"/>
- <LinearLayout
- android:layout_width="match_parent"
- android:layout_height="wrap_content">
- <androidx.car.widget.PagedListView
- android:id="@+id/paged_list_view"
- android:layout_height="match_parent"
- android:layout_width="wrap_content"
- android:layout_weight="4"/>
- </LinearLayout>
-</LinearLayout>
diff --git a/tests/UxRestrictionsSample/res/layout/main_activity.xml b/tests/UxRestrictionsSample/res/layout/main_activity.xml
index 0b097fb..1d21b50 100644
--- a/tests/UxRestrictionsSample/res/layout/main_activity.xml
+++ b/tests/UxRestrictionsSample/res/layout/main_activity.xml
@@ -125,27 +125,6 @@
android:layout_marginBottom="10dp"
android:background="@android:color/darker_gray"/>
- <TextView
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:text="@string/sample_header"
- android:padding="@dimen/section_padding"
- android:textSize="@dimen/header_text_size"
- android:textAppearance="?android:textAppearanceLarge"/>
-
- <LinearLayout
- android:layout_width="match_parent"
- android:layout_height="wrap_content">
- <Button
- android:id="@+id/launch_message"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:padding="@dimen/section_padding"
- android:text="@string/sample_msg_activity"
- android:textAllCaps="false"
- android:textSize="@dimen/info_text_size"/>
- </LinearLayout>
-
<View
android:layout_width="match_parent"
android:layout_height="1dp"
diff --git a/tests/UxRestrictionsSample/res/values/strings.xml b/tests/UxRestrictionsSample/res/values/strings.xml
index b36d7f9..aae0e0d 100644
--- a/tests/UxRestrictionsSample/res/values/strings.xml
+++ b/tests/UxRestrictionsSample/res/values/strings.xml
@@ -25,9 +25,6 @@
<string name="disable_uxr" translatable="false">Disable Ux Restriction Engine</string>
<string name="show_staged_config" translatable="false">Show Staged Config</string>
<string name="show_prod_config" translatable="false">Show Production Config</string>
- <string name="sample_header" translatable="false"><u>Sample Activities</u></string>
- <string name="sample_msg_activity" translatable="false">Sample Message Activity</string>
- <string name="return_home" translatable="false"><u>Return Home</u></string>
<string name="save_uxr_config_header" translatable="false"><u>Save UX Restrictions For Next Boot</u></string>
<string name="save_uxr_config" translatable="false">Save UX Restrictions</string>
<string name="set_uxr_config_dialog_title" translatable="false">Select restrictions for IDLING/MOVING</string>
diff --git a/tests/UxRestrictionsSample/res/values/styles.xml b/tests/UxRestrictionsSample/res/values/styles.xml
index 05d0d03..b752323 100644
--- a/tests/UxRestrictionsSample/res/values/styles.xml
+++ b/tests/UxRestrictionsSample/res/values/styles.xml
@@ -15,7 +15,7 @@
-->
<resources>
- <style name="AppTheme" parent="@style/Theme.Car.NoActionBar">
+ <style name="AppTheme" parent="@style/android:Theme.DeviceDefault.NoActionBar">
<item name="android:windowBackground">@android:color/black</item>
</style>
</resources>
\ No newline at end of file
diff --git a/tests/UxRestrictionsSample/src/com/google/android/car/uxr/sample/MainActivity.java b/tests/UxRestrictionsSample/src/com/google/android/car/uxr/sample/MainActivity.java
index 0fa9259..60268ea 100644
--- a/tests/UxRestrictionsSample/src/com/google/android/car/uxr/sample/MainActivity.java
+++ b/tests/UxRestrictionsSample/src/com/google/android/car/uxr/sample/MainActivity.java
@@ -29,13 +29,11 @@
import android.car.drivingstate.CarUxRestrictionsConfiguration;
import android.car.drivingstate.CarUxRestrictionsManager;
import android.content.ComponentName;
-import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.util.JsonWriter;
import android.util.Log;
-import android.view.View;
import android.widget.Button;
import android.widget.TextView;
@@ -188,9 +186,6 @@
mShowProdConfig.setOnClickListener(v -> showProdUxRestrictionsConfig());
mToggleButton.setOnClickListener(v -> updateToggleUxREnable());
- mSampleMsgButton = findViewById(R.id.launch_message);
- mSampleMsgButton.setOnClickListener(this::launchSampleMsgActivity);
-
// Connect to car service
mCar = Car.createCar(this, mCarConnectionListener);
mCar.connect();
@@ -273,11 +268,6 @@
}
}
- private void launchSampleMsgActivity(View view) {
- Intent msgIntent = new Intent(this, SampleMessageActivity.class);
- startActivity(msgIntent);
- }
-
@Override
protected void onDestroy() {
super.onDestroy();
diff --git a/tests/UxRestrictionsSample/src/com/google/android/car/uxr/sample/SampleMessageActivity.java b/tests/UxRestrictionsSample/src/com/google/android/car/uxr/sample/SampleMessageActivity.java
deleted file mode 100644
index 1c9bc95..0000000
--- a/tests/UxRestrictionsSample/src/com/google/android/car/uxr/sample/SampleMessageActivity.java
+++ /dev/null
@@ -1,121 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.android.car.uxr.sample;
-
-import android.annotation.DrawableRes;
-import android.app.Activity;
-import android.content.Intent;
-import android.os.Bundle;
-import android.view.View;
-import android.widget.Button;
-
-import androidx.car.widget.ListItem;
-import androidx.car.widget.ListItemAdapter;
-import androidx.car.widget.ListItemProvider;
-import androidx.car.widget.PagedListView;
-import androidx.car.widget.TextListItem;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * A Sample Messaging Activity that illustrates how to truncate the string length on receiving the
- * corresponding UX restriction.
- */
-public class SampleMessageActivity extends Activity {
- private Button mHomeButton;
- private PagedListView mPagedListView;
- private ListItemAdapter mAdapter;
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_sample_message);
- mHomeButton = findViewById(R.id.home_button);
- mHomeButton.setOnClickListener(this::returnHome);
-
- mPagedListView = findViewById(R.id.paged_list_view);
- setUpPagedListView();
- }
-
- @Override
- protected void onStart() {
- super.onStart();
- if (mAdapter != null) {
- mAdapter.start();
- }
- }
-
- @Override
- protected void onStop() {
- super.onStop();
- if (mAdapter != null) {
- mAdapter.stop();
- }
- }
-
- private void returnHome(View view) {
- Intent homeIntent = new Intent(this, MainActivity.class);
- startActivity(homeIntent);
- }
-
- private void setUpPagedListView() {
- mAdapter = new ListItemAdapter(this, populateData());
- mPagedListView.setAdapter(mAdapter);
- }
-
- private ListItemProvider populateData() {
- List<ListItem> items = new ArrayList<>();
- items.add(createMessage(android.R.drawable.ic_menu_myplaces, "alice",
- "i have a really important message but it may hinder your ability to drive. "));
-
- items.add(createMessage(android.R.drawable.ic_menu_myplaces, "bob",
- "hey this is a really long message that i have always wanted to say. but before "
- + "saying it i feel it's only appropriate if i lay some groundwork for it. "
- + ""));
- items.add(createMessage(android.R.drawable.ic_menu_myplaces, "mom",
- "i think you are the best. i think you are the best. i think you are the best. "
- + "i think you are the best. i think you are the best. i think you are the "
- + "best. "
- + "i think you are the best. i think you are the best. i think you are the "
- + "best. "
- + "i think you are the best. i think you are the best. i think you are the "
- + "best. "
- + "i think you are the best. i think you are the best. i think you are the "
- + "best. "
- + "i think you are the best. i think you are the best. "));
- items.add(createMessage(android.R.drawable.ic_menu_myplaces, "john", "hello world"));
- items.add(createMessage(android.R.drawable.ic_menu_myplaces, "jeremy",
- "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor "
- + "incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, "
- + "quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo "
- + "consequat. Duis aute irure dolor in reprehenderit in voluptate velit "
- + "esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat "
- + "cupidatat non proident, sunt in culpa qui officia deserunt mollit "
- + "anim id est laborum."));
- return new ListItemProvider.ListProvider(items);
- }
-
- private TextListItem createMessage(@DrawableRes int profile, String contact, String message) {
- TextListItem item = new TextListItem(this);
- item.setPrimaryActionIcon(profile, TextListItem.PRIMARY_ACTION_ICON_SIZE_SMALL);
- item.setTitle(contact);
- item.setBody(message);
- item.setSupplementalIcon(android.R.drawable.stat_notify_chat, false);
- return item;
- }
-
-}
diff --git a/tests/android_car_api_test/src/android/car/apitest/CarProjectionManagerTest.java b/tests/android_car_api_test/src/android/car/apitest/CarProjectionManagerTest.java
index abfeb11..db04c24 100644
--- a/tests/android_car_api_test/src/android/car/apitest/CarProjectionManagerTest.java
+++ b/tests/android_car_api_test/src/android/car/apitest/CarProjectionManagerTest.java
@@ -85,7 +85,7 @@
try {
mManager.registerProjectionListener(null, CarProjectionManager.PROJECTION_VOICE_SEARCH);
fail();
- } catch (IllegalArgumentException e) {
+ } catch (NullPointerException e) {
// expected.
}
}
diff --git a/tests/carservice_test/src/com/android/car/CarUxRestrictionsManagerServiceTest.java b/tests/carservice_test/src/com/android/car/CarUxRestrictionsManagerServiceTest.java
index 5ee92d7..3c545d6 100644
--- a/tests/carservice_test/src/com/android/car/CarUxRestrictionsManagerServiceTest.java
+++ b/tests/carservice_test/src/com/android/car/CarUxRestrictionsManagerServiceTest.java
@@ -96,6 +96,7 @@
@After
public void tearDown() throws Exception {
mService = null;
+ CarLocalServices.removeAllServices();
}
@Test
diff --git a/tests/carservice_test/src/com/android/car/VmsHalServiceSubscriptionEventTest.java b/tests/carservice_test/src/com/android/car/VmsHalServiceSubscriptionEventTest.java
index ad586ae..1eb0a95 100644
--- a/tests/carservice_test/src/com/android/car/VmsHalServiceSubscriptionEventTest.java
+++ b/tests/carservice_test/src/com/android/car/VmsHalServiceSubscriptionEventTest.java
@@ -28,7 +28,6 @@
import android.hardware.automotive.vehicle.V2_0.VmsAvailabilityStateIntegerValuesIndex;
import android.hardware.automotive.vehicle.V2_0.VmsBaseMessageIntegerValuesIndex;
import android.hardware.automotive.vehicle.V2_0.VmsMessageType;
-import android.hardware.automotive.vehicle.V2_0.VmsMessageWithLayerIntegerValuesIndex;
import android.hardware.automotive.vehicle.V2_0.VmsSubscriptionsStateIntegerValuesIndex;
import androidx.test.filters.MediumTest;
@@ -106,13 +105,14 @@
assertTrue(mHalHandlerSemaphore.tryAcquire(2L, TimeUnit.SECONDS));
// Validate response.
ArrayList<Integer> v = mHalHandler.getValues();
- int messageType = v.get(VmsBaseMessageIntegerValuesIndex.MESSAGE_TYPE);
- int sequenceNumber = v.get(VmsAvailabilityStateIntegerValuesIndex.SEQUENCE_NUMBER);
- assertEquals(VmsMessageType.AVAILABILITY_CHANGE, messageType);
- assertEquals(0, sequenceNumber);
+ assertEquals(VmsMessageType.AVAILABILITY_CHANGE,
+ (int) v.get(VmsBaseMessageIntegerValuesIndex.MESSAGE_TYPE));
+ assertEquals(0, (int) v.get(VmsAvailabilityStateIntegerValuesIndex.SEQUENCE_NUMBER));
+ int sequenceNumber = 0;
for (VmsLayer layer : layers) {
- subscribeViaHal(layer);
+ sequenceNumber++;
+ subscribeViaHal(sequenceNumber, layer);
}
// Send subscription request.
mHal.injectEvent(createHalSubscriptionRequest());
@@ -122,14 +122,15 @@
// Validate response.
v = mHalHandler.getValues();
- messageType = v.get(VmsBaseMessageIntegerValuesIndex.MESSAGE_TYPE);
- assertEquals(VmsMessageType.SUBSCRIPTIONS_RESPONSE, messageType);
- sequenceNumber = v.get(VmsSubscriptionsStateIntegerValuesIndex.SEQUENCE_NUMBER);
- int numberLayers = v.get(VmsSubscriptionsStateIntegerValuesIndex.NUMBER_OF_LAYERS);
- assertEquals(layers.size(), numberLayers);
+ assertEquals(VmsMessageType.SUBSCRIPTIONS_RESPONSE,
+ (int) v.get(VmsBaseMessageIntegerValuesIndex.MESSAGE_TYPE));
+ assertEquals(sequenceNumber,
+ (int) v.get(VmsSubscriptionsStateIntegerValuesIndex.SEQUENCE_NUMBER));
+ assertEquals(layers.size(),
+ (int) v.get(VmsSubscriptionsStateIntegerValuesIndex.NUMBER_OF_LAYERS));
List<VmsLayer> receivedLayers = new ArrayList<>();
int start = VmsSubscriptionsStateIntegerValuesIndex.SUBSCRIPTIONS_START;
- int end = VmsSubscriptionsStateIntegerValuesIndex.SUBSCRIPTIONS_START + 3 * numberLayers;
+ int end = VmsSubscriptionsStateIntegerValuesIndex.SUBSCRIPTIONS_START + 3 * layers.size();
while (start < end) {
int type = v.get(start++);
int subtype = v.get(start++);
@@ -140,23 +141,22 @@
}
/**
- * Subscribes to a layer, waits for the event to propagate back to the HAL layer and validates
- * the propagated message.
+ * Subscribes to a layer, waits for the state change to propagate back to the HAL layer and
+ * validates the propagated message.
*/
- private void subscribeViaHal(VmsLayer layer) throws Exception {
+ private void subscribeViaHal(int sequenceNumber, VmsLayer layer) throws Exception {
// Send subscribe request.
mHal.injectEvent(createHalSubscribeRequest(layer));
// Wait for response.
assertTrue(mHalHandlerSemaphore.tryAcquire(2L, TimeUnit.SECONDS));
// Validate response.
ArrayList<Integer> v = mHalHandler.getValues();
- int messsageType = v.get(VmsBaseMessageIntegerValuesIndex.MESSAGE_TYPE);
- assertEquals(VmsMessageType.SUBSCRIBE, messsageType);
- int layerId = v.get(VmsMessageWithLayerIntegerValuesIndex.LAYER_TYPE);
- int layerVersion = v.get(VmsMessageWithLayerIntegerValuesIndex.LAYER_VERSION);
- int fused = v.get(VmsMessageWithLayerIntegerValuesIndex.LAYER_SUBTYPE);
- assertEquals(layer.getType(), layerId);
- assertEquals(layer.getVersion(), layerVersion);
+ assertEquals(VmsMessageType.SUBSCRIPTIONS_CHANGE,
+ (int) v.get(VmsBaseMessageIntegerValuesIndex.MESSAGE_TYPE));
+ assertEquals(sequenceNumber,
+ (int) v.get(VmsSubscriptionsStateIntegerValuesIndex.SEQUENCE_NUMBER));
+ assertEquals(sequenceNumber,
+ (int) v.get(VmsSubscriptionsStateIntegerValuesIndex.NUMBER_OF_LAYERS));
}
private VehiclePropValue createHalSubscribeRequest(VmsLayer layer) {
diff --git a/tests/carservice_unit_test/src/com/android/car/CarPowerManagementServiceTest.java b/tests/carservice_unit_test/src/com/android/car/CarPowerManagementServiceTest.java
index 557816a..e1d9c90 100644
--- a/tests/carservice_unit_test/src/com/android/car/CarPowerManagementServiceTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/CarPowerManagementServiceTest.java
@@ -16,8 +16,15 @@
package com.android.car;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
import android.car.hardware.power.CarPowerManager.CarPowerStateListener;
import android.car.hardware.power.ICarPowerStateListener;
+import android.car.userlib.CarUserManagerHelper;
import android.hardware.automotive.vehicle.V2_0.VehicleApPowerStateReq;
import android.hardware.automotive.vehicle.V2_0.VehicleApPowerStateShutdownParam;
import android.os.RemoteException;
@@ -52,6 +59,7 @@
private final MockWakeLockInterface mWakeLockInterface = new MockWakeLockInterface();
private final MockIOInterface mIOInterface = new MockIOInterface();
private final PowerSignalListener mPowerSignalListener = new PowerSignalListener();
+ private CarUserManagerHelper mCarUserManagerHelper;
private MockedPowerHalService mPowerHal;
private SystemInterface mSystemInterface;
@@ -68,6 +76,10 @@
.withSystemStateInterface(mSystemStateInterface)
.withWakeLockInterface(mWakeLockInterface)
.withIOInterface(mIOInterface).build();
+ mCarUserManagerHelper = mock(CarUserManagerHelper.class);
+ doReturn(true).when(mCarUserManagerHelper).switchToUserId(anyInt());
+ doReturn(10).when(mCarUserManagerHelper).getInitialUser();
+ doReturn(10).when(mCarUserManagerHelper).getCurrentForegroundUserId();
}
@Override
@@ -83,7 +95,8 @@
* Helper method to create mService and initialize a test case
*/
private void initTest(int wakeupTime) throws Exception {
- mService = new CarPowerManagementService(getContext(), mPowerHal, mSystemInterface);
+ mService = new CarPowerManagementService(getContext(), mPowerHal, mSystemInterface,
+ mCarUserManagerHelper);
mService.init();
CarPowerManagementService.setShutdownPrepareTimeout(0);
mPowerHal.setSignalListener(mPowerSignalListener);
@@ -103,7 +116,6 @@
mSystemInterface.setDisplayState(false);
mDisplayInterface.waitForDisplayStateChange(WAIT_TIMEOUT_MS);
initTest(0);
-
// Transition to ON state
mPowerHal.setCurrentPowerState(new PowerState(VehicleApPowerStateReq.ON, 0));
@@ -160,6 +172,13 @@
public void testSleepEntryAndWakeUpForProcessing() throws Exception {
final int wakeupTime = 100;
initTest(wakeupTime);
+
+ // set up for user switching after display on
+ final int currentUserId = 10;
+ final int newUserId = 11;
+ doReturn(newUserId).when(mCarUserManagerHelper).getInitialUser();
+ doReturn(currentUserId).when(mCarUserManagerHelper).getCurrentForegroundUserId();
+
mPowerHal.setCurrentPowerState(new PowerState(VehicleApPowerStateReq.ON, 0));
assertTrue(mDisplayInterface.waitForDisplayStateChange(WAIT_TIMEOUT_MS));
mPowerHal.setCurrentPowerState(new PowerState(VehicleApPowerStateReq.SHUTDOWN_PREPARE,
@@ -177,8 +196,12 @@
mService.scheduleNextWakeupTime(wakeupTime);
// second processing after wakeup
assertFalse(mDisplayInterface.getDisplayState());
+ // do not skip user switching part.
+ mService.clearIsBooting();
mPowerHal.setCurrentPowerState(new PowerState(VehicleApPowerStateReq.ON, 0));
assertTrue(mDisplayInterface.waitForDisplayStateChange(WAIT_TIMEOUT_MS));
+ // user switching should have been requested.
+ verify(mCarUserManagerHelper, times(1)).switchToUserId(newUserId);
mPowerHal.setCurrentPowerState(new PowerState(VehicleApPowerStateReq.SHUTDOWN_PREPARE,
VehicleApPowerStateShutdownParam.CAN_SLEEP));
assertStateReceivedForShutdownOrSleepWithPostpone(
diff --git a/tests/carservice_unit_test/src/com/android/car/CarProjectionServiceTest.java b/tests/carservice_unit_test/src/com/android/car/CarProjectionServiceTest.java
new file mode 100644
index 0000000..476c152
--- /dev/null
+++ b/tests/carservice_unit_test/src/com/android/car/CarProjectionServiceTest.java
@@ -0,0 +1,266 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car;
+
+import static android.car.projection.ProjectionStatus.PROJECTION_STATE_ACTIVE_FOREGROUND;
+import static android.car.projection.ProjectionStatus.PROJECTION_STATE_INACTIVE;
+import static android.car.projection.ProjectionStatus.PROJECTION_TRANSPORT_USB;
+import static android.car.projection.ProjectionStatus.PROJECTION_TRANSPORT_WIFI;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.when;
+
+import android.car.ICarProjectionStatusListener;
+import android.car.projection.ProjectionOptions;
+import android.car.projection.ProjectionStatus;
+import android.car.projection.ProjectionStatus.MobileDevice;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Rect;
+import android.net.wifi.WifiScanner;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.Spy;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+@RunWith(AndroidJUnit4.class)
+public class CarProjectionServiceTest {
+ private static final int MD_ID1 = 1;
+ private static final int MD_ID2 = 2;
+ private static final String MD_NAME1 = "Device1";
+ private static final String MD_NAME2 = "Device2";
+ private static final int DEFAULT_TIMEOUT_MS = 1000;
+ private static final String MD_EXTRA_KEY = "com.some.key.md";
+ private static final String MD_EXTRA_VALUE = "this is dummy value";
+ private static final String STATUS_EXTRA_KEY = "com.some.key.status";
+ private static final String STATUS_EXTRA_VALUE = "additional status value";
+
+ private final IBinder mToken = new Binder();
+
+ @Rule
+ public MockitoRule rule = MockitoJUnit.rule();
+
+ private CarProjectionService mService;
+
+ @Spy
+ private final Context mContext = ApplicationProvider.getApplicationContext();
+
+ @Mock
+ private Resources mResources;
+
+ private final Handler mHandler = new Handler(Looper.getMainLooper());
+ @Mock
+ private CarInputService mCarInputService;
+ @Mock
+ private CarBluetoothService mCarBluetoothService;
+
+ @Before
+ public void setUp() {
+ mService = new CarProjectionService(mContext, mHandler, mCarInputService,
+ mCarBluetoothService);
+ }
+
+ @Test
+ public void updateProjectionStatus_defaultState() throws Exception {
+ final CountDownLatch latch = new CountDownLatch(2);
+
+ mService.registerProjectionStatusListener(new ICarProjectionStatusListener.Stub() {
+ @Override
+ public void onProjectionStatusChanged(int projectionState,
+ String activeProjectionPackageName, List<ProjectionStatus> details) {
+ assertThat(projectionState).isEqualTo(PROJECTION_STATE_INACTIVE);
+ assertThat(activeProjectionPackageName).isNull();
+ assertThat(details).isEmpty();
+
+ latch.countDown();
+ }
+ });
+
+ latch.await(DEFAULT_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ }
+
+ @Test
+ public void updateProjectionStatus_subscribeAfterUpdate() throws Exception {
+ final ProjectionStatus status = createProjectionStatus();
+ mService.updateProjectionStatus(status, mToken);
+
+ final CountDownLatch latch = new CountDownLatch(1);
+
+ mService.registerProjectionStatusListener(new ICarProjectionStatusListener.Stub() {
+ @Override
+ public void onProjectionStatusChanged(int projectionState,
+ String activeProjectionPackageName, List<ProjectionStatus> details) {
+ assertThat(projectionState).isEqualTo(PROJECTION_STATE_ACTIVE_FOREGROUND);
+ assertThat(activeProjectionPackageName).isEqualTo(mContext.getPackageName());
+ assertThat(details).hasSize(1);
+ assertThat(details.get(0)).isEqualTo(status);
+ ProjectionStatus status = details.get(0);
+ assertThat(status.getTransport()).isEqualTo(PROJECTION_TRANSPORT_WIFI);
+ assertThat(status.getExtras()).isNotNull();
+ assertThat(status.getExtras().getString(STATUS_EXTRA_KEY))
+ .isEqualTo(STATUS_EXTRA_VALUE);
+ assertThat(status.getConnectedMobileDevices()).hasSize(2);
+ MobileDevice md1 = status.getConnectedMobileDevices().get(0);
+ assertThat(md1.getId()).isEqualTo(MD_ID1);
+ assertThat(md1.getName()).isEqualTo(MD_NAME1);
+ assertThat(md1.getExtras()).isNotNull();
+ assertThat(md1.getExtras().getString(MD_EXTRA_KEY)).isEqualTo(MD_EXTRA_VALUE);
+ assertThat(md1.getAvailableTransports()).hasSize(1);
+ assertThat(md1.getAvailableTransports()).containsExactly(
+ PROJECTION_TRANSPORT_USB);
+
+ MobileDevice md2 = status.getConnectedMobileDevices().get(1);
+ assertThat(md2.getId()).isEqualTo(MD_ID2);
+ assertThat(md2.getName()).isEqualTo(MD_NAME2);
+ assertThat(md2.getExtras()).isNotNull();
+ assertThat(md2.getExtras().isEmpty()).isTrue();
+ assertThat(md2.getAvailableTransports()).containsExactly(
+ PROJECTION_TRANSPORT_USB, PROJECTION_TRANSPORT_WIFI);
+
+ latch.countDown();
+ }
+ });
+
+ latch.await(DEFAULT_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ }
+
+ @Test
+ public void updateProjectionStatus_subscribeBeforeUpdate() throws Exception {
+
+ // We will receive notification twice - with default value and with updated one.
+ final CountDownLatch latch = new CountDownLatch(2);
+
+ mService.registerProjectionStatusListener(new ICarProjectionStatusListener.Stub() {
+ @Override
+ public void onProjectionStatusChanged(int projectionState,
+ String activeProjectionPackageName, List<ProjectionStatus> details) {
+ if (latch.getCount() == 2) {
+ assertThat(projectionState).isEqualTo(PROJECTION_STATE_INACTIVE);
+ assertThat(activeProjectionPackageName).isNull();
+ } else {
+ assertThat(projectionState).isEqualTo(PROJECTION_STATE_ACTIVE_FOREGROUND);
+ assertThat(activeProjectionPackageName).isEqualTo(mContext.getPackageName());
+ }
+
+ latch.countDown();
+ }
+ });
+ mService.updateProjectionStatus(createProjectionStatus(), mToken);
+
+ latch.await(DEFAULT_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ }
+
+ @Test
+ public void getProjectionOptions_defaults() {
+ when(mContext.getResources()).thenReturn(mResources);
+ final int uiMode = ProjectionOptions.UI_MODE_FULL_SCREEN;
+
+ when(mResources.getInteger(com.android.car.R.integer.config_projectionUiMode))
+ .thenReturn(uiMode);
+ when(mResources.getString(com.android.car.R.string.config_projectionConsentActivity))
+ .thenReturn("");
+ when(mResources.getInteger(com.android.car.R.integer.config_projectionActivityDisplayId))
+ .thenReturn(-1);
+ when(mResources.getIntArray(com.android.car.R.array.config_projectionActivityLaunchBounds))
+ .thenReturn(new int[0]);
+
+ Bundle bundle = mService.getProjectionOptions();
+
+ ProjectionOptions options = new ProjectionOptions(bundle);
+ assertThat(options.getActivityOptions()).isNull();
+ assertThat(options.getConsentActivity()).isNull();
+ assertThat(options.getUiMode()).isEqualTo(uiMode);
+ }
+
+ @Test
+ public void getProjectionOptions_nonDefaults() {
+ when(mContext.getResources()).thenReturn(mResources);
+ final int uiMode = ProjectionOptions.UI_MODE_BLENDED;
+ final String consentActivity = "com.my.app/.MyActivity";
+ final int[] bounds = new int[] {1, 2, 3, 4};
+ final int displayId = 1;
+
+ when(mResources.getInteger(com.android.car.R.integer.config_projectionUiMode))
+ .thenReturn(uiMode);
+ when(mResources.getString(com.android.car.R.string.config_projectionConsentActivity))
+ .thenReturn(consentActivity);
+ when(mResources.getInteger(com.android.car.R.integer.config_projectionActivityDisplayId))
+ .thenReturn(displayId);
+ when(mResources.getIntArray(com.android.car.R.array.config_projectionActivityLaunchBounds))
+ .thenReturn(bounds);
+
+ Bundle bundle = mService.getProjectionOptions();
+
+ ProjectionOptions options = new ProjectionOptions(bundle);
+ assertThat(options.getActivityOptions().getLaunchDisplayId()).isEqualTo(displayId);
+ assertThat(options.getActivityOptions().getLaunchBounds())
+ .isEqualTo(new Rect(bounds[0], bounds[1], bounds[2], bounds[3]));
+ assertThat(options.getConsentActivity()).isEqualTo(
+ ComponentName.unflattenFromString(consentActivity));
+ assertThat(options.getUiMode()).isEqualTo(uiMode);
+ }
+
+ @Test
+ public void getWifiChannels() {
+ int[] wifiChannels = mService.getAvailableWifiChannels(WifiScanner.WIFI_BAND_BOTH_WITH_DFS);
+ assertThat(wifiChannels).isNotNull();
+ assertThat(wifiChannels).isNotEmpty();
+ }
+
+ private ProjectionStatus createProjectionStatus() {
+ Bundle statusExtra = new Bundle();
+ statusExtra.putString(STATUS_EXTRA_KEY, STATUS_EXTRA_VALUE);
+ Bundle mdExtra = new Bundle();
+ mdExtra.putString(MD_EXTRA_KEY, MD_EXTRA_VALUE);
+
+ return ProjectionStatus
+ .builder(mContext.getPackageName(), PROJECTION_STATE_ACTIVE_FOREGROUND)
+ .setExtras(statusExtra)
+ .setProjectionTransport(PROJECTION_TRANSPORT_WIFI)
+ .addMobileDevice(MobileDevice
+ .builder(MD_ID1, MD_NAME1)
+ .addTransport(PROJECTION_TRANSPORT_USB)
+ .setExtras(mdExtra)
+ .build())
+ .addMobileDevice(MobileDevice
+ .builder(MD_ID2, MD_NAME2)
+ .addTransport(PROJECTION_TRANSPORT_USB)
+ .addTransport(PROJECTION_TRANSPORT_WIFI)
+ .setProjecting(true)
+ .build())
+ .build();
+ }
+}
diff --git a/tests/carservice_unit_test/src/com/android/car/hal/VmsHalServiceTest.java b/tests/carservice_unit_test/src/com/android/car/hal/VmsHalServiceTest.java
index 3e9eb56..baaab26 100644
--- a/tests/carservice_unit_test/src/com/android/car/hal/VmsHalServiceTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/hal/VmsHalServiceTest.java
@@ -15,153 +15,859 @@
*/
package com.android.car.hal;
-import static org.mockito.ArgumentMatchers.eq;
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import android.car.vms.IVmsPublisherClient;
+import android.car.vms.IVmsPublisherService;
+import android.car.vms.IVmsSubscriberClient;
+import android.car.vms.IVmsSubscriberService;
import android.car.vms.VmsAssociatedLayer;
import android.car.vms.VmsAvailableLayers;
import android.car.vms.VmsLayer;
import android.car.vms.VmsLayerDependency;
import android.car.vms.VmsLayersOffering;
+import android.car.vms.VmsSubscriptionState;
+import android.hardware.automotive.vehicle.V2_0.VehiclePropConfig;
+import android.hardware.automotive.vehicle.V2_0.VehiclePropValue;
+import android.hardware.automotive.vehicle.V2_0.VehicleProperty;
+import android.hardware.automotive.vehicle.V2_0.VmsMessageType;
import android.os.Binder;
import android.os.IBinder;
import androidx.test.runner.AndroidJUnit4;
-import com.google.android.collect.Sets;
-
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
@RunWith(AndroidJUnit4.class)
public class VmsHalServiceTest {
- @Rule public MockitoRule mockito = MockitoJUnit.rule();
- @Mock private VehicleHal mMockVehicleHal;
- @Mock private VmsHalService.VmsHalSubscriberListener mMockHalSusbcriber;
+ private static final int LAYER_TYPE = 1;
+ private static final int LAYER_SUBTYPE = 2;
+ private static final int LAYER_VERSION = 3;
+ private static final VmsLayer LAYER = new VmsLayer(LAYER_TYPE, LAYER_SUBTYPE, LAYER_VERSION);
+ private static final int PUBLISHER_ID = 12345;
+ private static final byte[] PAYLOAD = new byte[]{1, 2, 3, 4};
+ private static final List<Byte> PAYLOAD_AS_LIST = Arrays.asList(new Byte[]{1, 2, 3, 4});
+
+ @Rule
+ public MockitoRule mockito = MockitoJUnit.rule();
+ @Mock
+ private VehicleHal mVehicleHal;
+ @Mock
+ private IVmsPublisherService mPublisherService;
+ @Mock
+ private IVmsSubscriberService mSubscriberService;
+
private IBinder mToken;
private VmsHalService mHalService;
+ private IVmsPublisherClient mPublisherClient;
+ private IVmsSubscriberClient mSubscriberClient;
@Before
public void setUp() throws Exception {
+ mHalService = new VmsHalService(mVehicleHal);
+ mHalService.setVmsSubscriberService(mSubscriberService);
+
mToken = new Binder();
- mHalService = new VmsHalService(mMockVehicleHal);
- mHalService.addSubscriberListener(mMockHalSusbcriber);
+ mPublisherClient = IVmsPublisherClient.Stub.asInterface(mHalService.getPublisherClient());
+ mPublisherClient.setVmsPublisherService(mToken, mPublisherService);
+
+ VehiclePropConfig propConfig = new VehiclePropConfig();
+ propConfig.prop = VehicleProperty.VEHICLE_MAP_SERVICE;
+ mHalService.takeSupportedProperties(Collections.singleton(propConfig));
+
+ when(mSubscriberService.getAvailableLayers()).thenReturn(
+ new VmsAvailableLayers(Collections.emptySet(), 0));
+ mHalService.init();
+ waitForHandlerCompletion();
+
+ ArgumentCaptor<IVmsSubscriberClient> subscriberCaptor = ArgumentCaptor.forClass(
+ IVmsSubscriberClient.class);
+ verify(mSubscriberService).addVmsSubscriberToNotifications(subscriberCaptor.capture());
+ mSubscriberClient = subscriberCaptor.getValue();
+ reset(mSubscriberService);
+ verify(mVehicleHal).set(createHalMessage(
+ VmsMessageType.AVAILABILITY_CHANGE, // Message type
+ 0, // Sequence number
+ 0)); // # of associated layers
+ reset(mVehicleHal);
}
@Test
- public void testSetPublisherLayersOffering() {
- VmsLayer layer = new VmsLayer(1, 2, 3);
- VmsLayersOffering offering = new VmsLayersOffering(
- Sets.newHashSet(new VmsLayerDependency(layer)), 12345);
- mHalService.setPublisherLayersOffering(mToken, offering);
+ public void testTakeSupportedProperties() {
+ VehiclePropConfig vmsPropConfig = new VehiclePropConfig();
+ vmsPropConfig.prop = VehicleProperty.VEHICLE_MAP_SERVICE;
- VmsAssociatedLayer associatedLayer = new VmsAssociatedLayer(layer, Sets.newHashSet(12345));
- verify(mMockHalSusbcriber).onLayersAvaiabilityChange(eq(new VmsAvailableLayers(
- Sets.newHashSet(associatedLayer),
- 1)));
+ VehiclePropConfig otherPropConfig = new VehiclePropConfig();
+ otherPropConfig.prop = VehicleProperty.CURRENT_GEAR;
+
+ assertEquals(Collections.singleton(vmsPropConfig),
+ mHalService.takeSupportedProperties(Arrays.asList(otherPropConfig, vmsPropConfig)));
+ }
+
+ /**
+ * DATA message format:
+ * <ul>
+ * <li>Message type
+ * <li>Layer ID
+ * <li>Layer subtype
+ * <li>Layer version
+ * <li>Publisher ID
+ * <li>Payload
+ * </ul>
+ */
+ @Test
+ public void testHandleDataEvent() throws Exception {
+ VehiclePropValue message = createHalMessage(
+ VmsMessageType.DATA, // Message type
+ LAYER_TYPE, LAYER_SUBTYPE, LAYER_VERSION, // VmsLayer
+ PUBLISHER_ID // PublisherId
+ );
+ message.value.bytes.addAll(PAYLOAD_AS_LIST);
+
+ sendHalMessage(message);
+ verify(mPublisherService).publish(mToken, LAYER, PUBLISHER_ID, PAYLOAD);
+ }
+
+ /**
+ * SUBSCRIBE message format:
+ * <ul>
+ * <li>Message type
+ * <li>Layer ID
+ * <li>Layer subtype
+ * <li>Layer version
+ * </ul>
+ */
+ @Test
+ public void testHandleSubscribeEvent() throws Exception {
+ VehiclePropValue message = createHalMessage(
+ VmsMessageType.SUBSCRIBE, // Message type
+ LAYER_TYPE, LAYER_SUBTYPE, LAYER_VERSION // VmsLayer
+ );
+
+ sendHalMessage(message);
+ verify(mSubscriberService).addVmsSubscriber(mSubscriberClient, LAYER);
+ }
+
+ /**
+ * SUBSCRIBE_TO_PUBLISHER message format:
+ * <ul>
+ * <li>Message type
+ * <li>Layer ID
+ * <li>Layer subtype
+ * <li>Layer version
+ * <li>Publisher ID
+ * </ul>
+ */
+ @Test
+ public void testHandleSubscribeToPublisherEvent() throws Exception {
+ VehiclePropValue message = createHalMessage(
+ VmsMessageType.SUBSCRIBE_TO_PUBLISHER, // Message type
+ LAYER_TYPE, LAYER_SUBTYPE, LAYER_VERSION, // VmsLayer
+ PUBLISHER_ID // PublisherId
+ );
+
+ sendHalMessage(message);
+ verify(mSubscriberService).addVmsSubscriberToPublisher(mSubscriberClient, LAYER,
+ PUBLISHER_ID);
+ }
+
+ /**
+ * UNSUBSCRIBE message format:
+ * <ul>
+ * <li>Message type
+ * <li>Layer ID
+ * <li>Layer subtype
+ * <li>Layer version
+ * </ul>
+ */
+ @Test
+ public void testHandleUnsubscribeEvent() throws Exception {
+ VehiclePropValue message = createHalMessage(
+ VmsMessageType.UNSUBSCRIBE, // Message type
+ LAYER_TYPE, LAYER_SUBTYPE, LAYER_VERSION // VmsLayer
+ );
+
+ sendHalMessage(message);
+ verify(mSubscriberService).removeVmsSubscriber(mSubscriberClient, LAYER);
+ }
+
+ /**
+ * UNSUBSCRIBE_TO_PUBLISHER message format:
+ * <ul>
+ * <li>Message type
+ * <li>Layer ID
+ * <li>Layer subtype
+ * <li>Layer version
+ * <li>Publisher ID
+ * </ul>
+ */
+ @Test
+ public void testHandleUnsubscribeFromPublisherEvent() throws Exception {
+ VehiclePropValue message = createHalMessage(
+ VmsMessageType.UNSUBSCRIBE_TO_PUBLISHER, // Message type
+ LAYER_TYPE, LAYER_SUBTYPE, LAYER_VERSION, // VmsLayer
+ PUBLISHER_ID // PublisherId
+ );
+
+ sendHalMessage(message);
+ verify(mSubscriberService).removeVmsSubscriberToPublisher(mSubscriberClient, LAYER,
+ PUBLISHER_ID);
+ }
+
+ /**
+ * PUBLISHER_ID_REQUEST message format:
+ * <ul>
+ * <li>Message type
+ * <li>Publisher info (bytes)
+ * </ul>
+ *
+ * PUBLISHER_ID_RESPONSE message format:
+ * <ul>
+ * <li>Message type
+ * <li>Publisher ID
+ * </ul>
+ */
+ @Test
+ public void testHandlePublisherIdRequestEvent() throws Exception {
+ VehiclePropValue request = createHalMessage(
+ VmsMessageType.PUBLISHER_ID_REQUEST // Message type
+ );
+ request.value.bytes.addAll(PAYLOAD_AS_LIST);
+
+ when(mPublisherService.getPublisherId(PAYLOAD)).thenReturn(PUBLISHER_ID);
+
+ VehiclePropValue response = createHalMessage(
+ VmsMessageType.PUBLISHER_ID_RESPONSE, // Message type
+ PUBLISHER_ID // Publisher ID
+ );
+
+ sendHalMessage(request);
+ verify(mVehicleHal).set(response);
+ }
+
+ /**
+ * PUBLISHER_INFORMATION_REQUEST message format:
+ * <ul>
+ * <li>Message type
+ * <li>Publisher ID
+ * </ul>
+ *
+ * PUBLISHER_INFORMATION_RESPONSE message format:
+ * <ul>
+ * <li>Message type
+ * <li>Publisher info (bytes)
+ * </ul>
+ */
+ @Test
+ public void testHandlePublisherInformationRequestEvent() throws Exception {
+ VehiclePropValue request = createHalMessage(
+ VmsMessageType.PUBLISHER_INFORMATION_REQUEST, // Message type
+ PUBLISHER_ID // Publisher ID
+ );
+
+ when(mSubscriberService.getPublisherInfo(PUBLISHER_ID)).thenReturn(PAYLOAD);
+
+ VehiclePropValue response = createHalMessage(
+ VmsMessageType.PUBLISHER_INFORMATION_RESPONSE // Message type
+ );
+ response.value.bytes.addAll(PAYLOAD_AS_LIST);
+
+ sendHalMessage(request);
+ verify(mVehicleHal).set(response);
+ }
+
+ /**
+ * OFFERING message format:
+ * <ul>
+ * <li>Message type
+ * <li>Publisher ID
+ * <li>Number of offerings.
+ * <li>Offerings (x number of offerings)
+ * <ul>
+ * <li>Layer ID
+ * <li>Layer subtype
+ * <li>Layer version
+ * <li>Number of layer dependencies.
+ * <li>Layer dependencies (x number of layer dependencies)
+ * <ul>
+ * <li>Layer ID
+ * <li>Layer subtype
+ * <li>Layer version
+ * </ul>
+ * </ul>
+ * </ul>
+ */
+ @Test
+ public void testHandleOfferingEvent_ZeroOfferings() throws Exception {
+ VehiclePropValue message = createHalMessage(
+ VmsMessageType.OFFERING, // Message type
+ PUBLISHER_ID, // PublisherId
+ 0 // # of offerings
+ );
+
+ sendHalMessage(message);
+ verify(mPublisherService).setLayersOffering(
+ mToken,
+ new VmsLayersOffering(Collections.emptySet(), PUBLISHER_ID));
}
@Test
- public void testSetPublisherLayersOffering_Repeated() {
- VmsLayer layer = new VmsLayer(1, 2, 3);
- VmsLayersOffering offering = new VmsLayersOffering(
- Sets.newHashSet(new VmsLayerDependency(layer)), 12345);
- mHalService.setPublisherLayersOffering(mToken, offering);
- mHalService.setPublisherLayersOffering(mToken, offering);
+ public void testHandleOfferingEvent_LayerOnly() throws Exception {
+ VehiclePropValue message = createHalMessage(
+ VmsMessageType.OFFERING, // Message type
+ PUBLISHER_ID, // PublisherId
+ 1, // # of offerings
+ // Offered layer
+ LAYER_TYPE, LAYER_SUBTYPE, LAYER_VERSION,
+ 0 // # of dependencies
+ );
- VmsAssociatedLayer associatedLayer = new VmsAssociatedLayer(layer, Sets.newHashSet(12345));
- verify(mMockHalSusbcriber).onLayersAvaiabilityChange(eq(new VmsAvailableLayers(
- Sets.newHashSet(associatedLayer),
- 1)));
- verify(mMockHalSusbcriber).onLayersAvaiabilityChange(eq(new VmsAvailableLayers(
- Sets.newHashSet(associatedLayer),
- 2)));
-
+ sendHalMessage(message);
+ verify(mPublisherService).setLayersOffering(
+ mToken,
+ new VmsLayersOffering(Collections.singleton(
+ new VmsLayerDependency(LAYER)),
+ PUBLISHER_ID));
}
@Test
- public void testSetPublisherLayersOffering_MultiplePublishers() {
- VmsLayer layer = new VmsLayer(1, 2, 3);
- VmsLayersOffering offering = new VmsLayersOffering(
- Sets.newHashSet(new VmsLayerDependency(layer)), 12345);
- VmsLayersOffering offering2 = new VmsLayersOffering(
- Sets.newHashSet(new VmsLayerDependency(layer)), 54321);
- mHalService.setPublisherLayersOffering(mToken, offering);
- mHalService.setPublisherLayersOffering(new Binder(), offering2);
+ public void testHandleOfferingEvent_LayerAndDependency() throws Exception {
+ VehiclePropValue message = createHalMessage(
+ VmsMessageType.OFFERING, // Message type
+ PUBLISHER_ID, // PublisherId
+ 1, // # of offerings
+ LAYER_TYPE, LAYER_SUBTYPE, LAYER_VERSION, // Layer
+ 1, // # of dependencies
+ 4, 5, 6 // Dependency layer
+ );
- verify(mMockHalSusbcriber).onLayersAvaiabilityChange(eq(new VmsAvailableLayers(
- Sets.newHashSet(new VmsAssociatedLayer(layer, Sets.newHashSet(12345))),
- 1)));
- verify(mMockHalSusbcriber).onLayersAvaiabilityChange(eq(new VmsAvailableLayers(
- Sets.newHashSet(new VmsAssociatedLayer(layer, Sets.newHashSet(12345, 54321))),
- 2)));
-
+ sendHalMessage(message);
+ verify(mPublisherService).setLayersOffering(
+ mToken,
+ new VmsLayersOffering(Collections.singleton(
+ new VmsLayerDependency(LAYER, Collections.singleton(
+ new VmsLayer(4, 5, 6)))),
+ PUBLISHER_ID));
}
@Test
- public void testSetPublisherLayersOffering_MultiplePublishers_SharedToken() {
- VmsLayer layer = new VmsLayer(1, 2, 3);
- VmsLayersOffering offering = new VmsLayersOffering(
- Sets.newHashSet(new VmsLayerDependency(layer)), 12345);
- VmsLayersOffering offering2 = new VmsLayersOffering(
- Sets.newHashSet(new VmsLayerDependency(layer)), 54321);
- mHalService.setPublisherLayersOffering(mToken, offering);
- mHalService.setPublisherLayersOffering(mToken, offering2);
+ public void testHandleOfferingEvent_MultipleLayersAndDependencies() throws Exception {
+ VehiclePropValue message = createHalMessage(
+ VmsMessageType.OFFERING, // Message type
+ PUBLISHER_ID, // PublisherId
+ 3, // # of offerings
+ // Offered layer #1
+ LAYER_TYPE, LAYER_SUBTYPE, LAYER_VERSION, // Layer
+ 2, // # of dependencies
+ 4, 5, 6, // Dependency layer
+ 7, 8, 9, // Dependency layer
+ // Offered layer #2
+ 3, 2, 1, // Layer
+ 0, // # of dependencies
+ // Offered layer #3
+ 6, 5, 4, // Layer
+ 1, // # of dependencies
+ 7, 8, 9 // Dependency layer
+ );
- verify(mMockHalSusbcriber).onLayersAvaiabilityChange(eq(new VmsAvailableLayers(
- Sets.newHashSet(new VmsAssociatedLayer(layer, Sets.newHashSet(12345))),
- 1)));
- verify(mMockHalSusbcriber).onLayersAvaiabilityChange(eq(new VmsAvailableLayers(
- Sets.newHashSet(new VmsAssociatedLayer(layer, Sets.newHashSet(12345, 54321))),
- 2)));
+ sendHalMessage(message);
+ verify(mPublisherService).setLayersOffering(
+ mToken,
+ new VmsLayersOffering(new LinkedHashSet<>(Arrays.asList(
+ new VmsLayerDependency(LAYER, new LinkedHashSet<>(Arrays.asList(
+ new VmsLayer(4, 5, 6),
+ new VmsLayer(7, 8, 9)
+ ))),
+ new VmsLayerDependency(new VmsLayer(3, 2, 1), Collections.emptySet()),
+ new VmsLayerDependency(new VmsLayer(6, 5, 4), Collections.singleton(
+ new VmsLayer(7, 8, 9)
+ )))),
+ PUBLISHER_ID));
+ }
+
+ /**
+ * AVAILABILITY_REQUEST message format:
+ * <ul>
+ * <li>Message type
+ * </ul>
+ *
+ * AVAILABILITY_RESPONSE message format:
+ * <ul>
+ * <li>Message type
+ * <li>Sequence number.
+ * <li>Number of associated layers.
+ * <li>Associated layers (x number of associated layers)
+ * <ul>
+ * <li>Layer ID
+ * <li>Layer subtype
+ * <li>Layer version
+ * <li>Number of publishers
+ * <li>Publisher ID (x number of publishers)
+ * </ul>
+ * </ul>
+ */
+ @Test
+ public void testHandleAvailabilityRequestEvent_ZeroLayers() throws Exception {
+ VehiclePropValue request = createHalMessage(
+ VmsMessageType.AVAILABILITY_REQUEST // Message type
+ );
+
+ when(mSubscriberService.getAvailableLayers()).thenReturn(
+ new VmsAvailableLayers(Collections.emptySet(), 123));
+
+ VehiclePropValue response = createHalMessage(
+ VmsMessageType.AVAILABILITY_RESPONSE, // Message type
+ 123, // Sequence number
+ 0 // # of associated layers
+ );
+
+ sendHalMessage(request);
+ verify(mVehicleHal).set(response);
}
@Test
- public void testSetPublisherLayersOffering_MultiplePublishers_MultipleLayers() {
- VmsLayer layer = new VmsLayer(1, 2, 3);
- VmsLayer layer2 = new VmsLayer(2, 2, 3);
- VmsLayersOffering offering = new VmsLayersOffering(
- Sets.newHashSet(new VmsLayerDependency(layer)), 12345);
- VmsLayersOffering offering2 = new VmsLayersOffering(
- Sets.newHashSet(new VmsLayerDependency(layer2)), 54321);
- mHalService.setPublisherLayersOffering(mToken, offering);
- mHalService.setPublisherLayersOffering(new Binder(), offering2);
+ public void testHandleAvailabilityRequestEvent_OneLayer() throws Exception {
+ VehiclePropValue request = createHalMessage(
+ VmsMessageType.AVAILABILITY_REQUEST // Message type
+ );
- verify(mMockHalSusbcriber).onLayersAvaiabilityChange(eq(new VmsAvailableLayers(
- Sets.newHashSet(new VmsAssociatedLayer(layer, Sets.newHashSet(12345))),
- 1)));
- verify(mMockHalSusbcriber).onLayersAvaiabilityChange(eq(new VmsAvailableLayers(
- Sets.newHashSet(
- new VmsAssociatedLayer(layer, Sets.newHashSet(12345)),
- new VmsAssociatedLayer(layer2, Sets.newHashSet(54321))),
- 2)));
+ when(mSubscriberService.getAvailableLayers()).thenReturn(
+ new VmsAvailableLayers(Collections.singleton(
+ new VmsAssociatedLayer(LAYER, Collections.singleton(PUBLISHER_ID))), 123));
+ VehiclePropValue response = createHalMessage(
+ VmsMessageType.AVAILABILITY_RESPONSE, // Message type
+ 123, // Sequence number
+ 1, // # of associated layers
+ LAYER_TYPE, LAYER_SUBTYPE, LAYER_VERSION, // Layer
+ 1, // # of publisher IDs
+ PUBLISHER_ID // Publisher ID
+ );
+
+ sendHalMessage(request);
+ verify(mVehicleHal).set(response);
+ }
+
+
+ @Test
+ public void testHandleAvailabilityRequestEvent_MultipleLayers() throws Exception {
+ VehiclePropValue request = createHalMessage(
+ VmsMessageType.AVAILABILITY_REQUEST // Message type
+ );
+
+ when(mSubscriberService.getAvailableLayers()).thenReturn(
+ new VmsAvailableLayers(new LinkedHashSet<>(Arrays.asList(
+ new VmsAssociatedLayer(LAYER,
+ new LinkedHashSet<>(Arrays.asList(PUBLISHER_ID, 54321))),
+ new VmsAssociatedLayer(new VmsLayer(3, 2, 1),
+ Collections.emptySet()),
+ new VmsAssociatedLayer(new VmsLayer(6, 5, 4),
+ Collections.singleton(99999)))),
+ 123));
+
+ VehiclePropValue response = createHalMessage(
+ VmsMessageType.AVAILABILITY_RESPONSE, // Message type
+ 123, // Sequence number
+ 3, // # of associated layers
+ // Associated layer #1
+ LAYER_TYPE, LAYER_SUBTYPE, LAYER_VERSION, // Layer
+ 2, // # of publisher IDs
+ PUBLISHER_ID, // Publisher ID
+ 54321, // Publisher ID #2
+ // Associated layer #2
+ 3, 2, 1, // Layer
+ 0, // # of publisher IDs
+ // Associated layer #3
+ 6, 5, 4, // Layer
+ 1, // # of publisher IDs
+ 99999 // Publisher ID
+
+ );
+
+ sendHalMessage(request);
+ verify(mVehicleHal).set(response);
+ }
+
+ /**
+ * AVAILABILITY_CHANGE message format:
+ * <ul>
+ * <li>Message type
+ * <li>Sequence number.
+ * <li>Number of associated layers.
+ * <li>Associated layers (x number of associated layers)
+ * <ul>
+ * <li>Layer ID
+ * <li>Layer subtype
+ * <li>Layer version
+ * <li>Number of publishers
+ * <li>Publisher ID (x number of publishers)
+ * </ul>
+ * </ul>
+ */
+ @Test
+ public void testOnLayersAvailabilityChanged_ZeroLayers() throws Exception {
+ mSubscriberClient.onLayersAvailabilityChanged(
+ new VmsAvailableLayers(Collections.emptySet(), 123));
+
+ VehiclePropValue message = createHalMessage(
+ VmsMessageType.AVAILABILITY_CHANGE, // Message type
+ 123, // Sequence number
+ 0 // # of associated layers
+ );
+
+ waitForHandlerCompletion();
+ verify(mVehicleHal).set(message);
}
@Test
- public void testSetPublisherLayersOffering_MultiplePublishers_MultipleLayers_SharedToken() {
- VmsLayer layer = new VmsLayer(1, 2, 3);
- VmsLayer layer2 = new VmsLayer(2, 2, 3);
- VmsLayersOffering offering = new VmsLayersOffering(
- Sets.newHashSet(new VmsLayerDependency(layer)), 12345);
- VmsLayersOffering offering2 = new VmsLayersOffering(
- Sets.newHashSet(new VmsLayerDependency(layer2)), 54321);
- mHalService.setPublisherLayersOffering(mToken, offering);
- mHalService.setPublisherLayersOffering(mToken, offering2);
+ public void testOnLayersAvailabilityChanged_OneLayer() throws Exception {
+ mSubscriberClient.onLayersAvailabilityChanged(
+ new VmsAvailableLayers(Collections.singleton(
+ new VmsAssociatedLayer(LAYER, Collections.singleton(PUBLISHER_ID))), 123));
- verify(mMockHalSusbcriber).onLayersAvaiabilityChange(eq(new VmsAvailableLayers(
- Sets.newHashSet(new VmsAssociatedLayer(layer, Sets.newHashSet(12345))),
- 1)));
- verify(mMockHalSusbcriber).onLayersAvaiabilityChange(eq(new VmsAvailableLayers(
- Sets.newHashSet(
- new VmsAssociatedLayer(layer, Sets.newHashSet(12345)),
- new VmsAssociatedLayer(layer2, Sets.newHashSet(54321))),
- 2)));
+ VehiclePropValue message = createHalMessage(
+ VmsMessageType.AVAILABILITY_CHANGE, // Message type
+ 123, // Sequence number
+ 1, // # of associated layers
+ LAYER_TYPE, LAYER_SUBTYPE, LAYER_VERSION, // Layer
+ 1, // # of publisher IDs
+ PUBLISHER_ID // Publisher ID
+ );
+ waitForHandlerCompletion();
+ verify(mVehicleHal).set(message);
+ }
+
+
+ @Test
+ public void testOnLayersAvailabilityChanged_MultipleLayers() throws Exception {
+ mSubscriberClient.onLayersAvailabilityChanged(
+ new VmsAvailableLayers(new LinkedHashSet<>(Arrays.asList(
+ new VmsAssociatedLayer(LAYER,
+ new LinkedHashSet<>(Arrays.asList(PUBLISHER_ID, 54321))),
+ new VmsAssociatedLayer(new VmsLayer(3, 2, 1),
+ Collections.emptySet()),
+ new VmsAssociatedLayer(new VmsLayer(6, 5, 4),
+ Collections.singleton(99999)))),
+ 123));
+
+ VehiclePropValue message = createHalMessage(
+ VmsMessageType.AVAILABILITY_CHANGE, // Message type
+ 123, // Sequence number
+ 3, // # of associated layers
+ // Associated layer #1
+ LAYER_TYPE, LAYER_SUBTYPE, LAYER_VERSION, // Layer
+ 2, // # of publisher IDs
+ PUBLISHER_ID, // Publisher ID
+ 54321, // Publisher ID #2
+ // Associated layer #2
+ 3, 2, 1, // Layer
+ 0, // # of publisher IDs
+ // Associated layer #3
+ 6, 5, 4, // Layer
+ 1, // # of publisher IDs
+ 99999 // Publisher ID
+
+ );
+
+ waitForHandlerCompletion();
+ verify(mVehicleHal).set(message);
+ }
+
+ /**
+ * SUBSCRIPTION_REQUEST message format:
+ * <ul>
+ * <li>Message type
+ * </ul>
+ *
+ * SUBSCRIPTION_RESPONSE message format:
+ * <ul>
+ * <li>Message type
+ * <li>Sequence number
+ * <li>Number of layers
+ * <li>Number of associated layers
+ * <li>Layers (x number of layers)
+ * <ul>
+ * <li>Layer ID
+ * <li>Layer subtype
+ * <li>Layer version
+ * </ul>
+ * <li>Associated layers (x number of associated layers)
+ * <ul>
+ * <li>Layer ID
+ * <li>Layer subtype
+ * <li>Layer version
+ * <li>Number of publishers
+ * <li>Publisher ID (x number of publishers)
+ * </ul>
+ * </ul>
+ */
+ @Test
+ public void testHandleSubscriptionsRequestEvent_ZeroLayers() throws Exception {
+ VehiclePropValue request = createHalMessage(
+ VmsMessageType.SUBSCRIPTIONS_REQUEST // Message type
+ );
+
+ when(mPublisherService.getSubscriptions()).thenReturn(
+ new VmsSubscriptionState(123, Collections.emptySet(), Collections.emptySet()));
+
+ VehiclePropValue response = createHalMessage(
+ VmsMessageType.SUBSCRIPTIONS_RESPONSE, // Message type
+ 123, // Sequence number
+ 0, // # of layers
+ 0 // # of associated layers
+ );
+
+ sendHalMessage(request);
+ verify(mVehicleHal).set(response);
+ }
+
+ @Test
+ public void testHandleSubscriptionsRequestEvent_OneLayer_ZeroAssociatedLayers()
+ throws Exception {
+ VehiclePropValue request = createHalMessage(
+ VmsMessageType.SUBSCRIPTIONS_REQUEST // Message type
+ );
+
+ when(mPublisherService.getSubscriptions()).thenReturn(
+ new VmsSubscriptionState(123, Collections.singleton(LAYER),
+ Collections.emptySet()));
+
+ VehiclePropValue response = createHalMessage(
+ VmsMessageType.SUBSCRIPTIONS_RESPONSE, // Message type
+ 123, // Sequence number
+ 1, // # of layers
+ 0, // # of associated layers
+ LAYER_TYPE, LAYER_SUBTYPE, LAYER_VERSION // Layer
+ );
+
+ sendHalMessage(request);
+ verify(mVehicleHal).set(response);
+ }
+
+ @Test
+ public void testHandleSubscriptionsRequestEvent_ZeroLayers_OneAssociatedLayer()
+ throws Exception {
+ VehiclePropValue request = createHalMessage(
+ VmsMessageType.SUBSCRIPTIONS_REQUEST // Message type
+ );
+
+ when(mPublisherService.getSubscriptions()).thenReturn(
+ new VmsSubscriptionState(123, Collections.emptySet(), Collections.singleton(
+ new VmsAssociatedLayer(LAYER, Collections.singleton(PUBLISHER_ID)))));
+
+ VehiclePropValue response = createHalMessage(
+ VmsMessageType.SUBSCRIPTIONS_RESPONSE, // Message type
+ 123, // Sequence number
+ 0, // # of layers
+ 1, // # of associated layers
+ LAYER_TYPE, LAYER_SUBTYPE, LAYER_VERSION, // Layer
+ 1, // # of publisher IDs
+ PUBLISHER_ID // Publisher ID
+ );
+
+ sendHalMessage(request);
+ verify(mVehicleHal).set(response);
+ }
+
+ @Test
+ public void testHandleSubscriptionsRequestEvent_MultipleLayersAndAssociatedLayers()
+ throws Exception {
+ VehiclePropValue request = createHalMessage(
+ VmsMessageType.SUBSCRIPTIONS_REQUEST // Message type
+ );
+
+ when(mPublisherService.getSubscriptions()).thenReturn(
+ new VmsSubscriptionState(123,
+ new LinkedHashSet<>(Arrays.asList(
+ LAYER,
+ new VmsLayer(4, 5, 6),
+ new VmsLayer(7, 8, 9)
+ )),
+ new LinkedHashSet<>(Arrays.asList(
+ new VmsAssociatedLayer(LAYER, Collections.emptySet()),
+ new VmsAssociatedLayer(new VmsLayer(6, 5, 4),
+ new LinkedHashSet<>(Arrays.asList(
+ PUBLISHER_ID,
+ 54321))))))
+ );
+
+ VehiclePropValue response = createHalMessage(
+ VmsMessageType.SUBSCRIPTIONS_RESPONSE, // Message type
+ 123, // Sequence number
+ 3, // # of layers
+ 2, // # of associated layers
+ // Layer #1
+ LAYER_TYPE, LAYER_SUBTYPE, LAYER_VERSION, // Layer
+ // Layer #2
+ 4, 5, 6, // Layer
+ // Layer #3
+ 7, 8, 9, // Layer
+ LAYER_TYPE, LAYER_SUBTYPE, LAYER_VERSION, // Layer
+ 0, // # of publisher IDs
+ 6, 5, 4, // Layer
+ 2, // # of publisher IDs
+ PUBLISHER_ID, // Publisher ID
+ 54321 // Publisher ID #2
+ );
+
+ sendHalMessage(request);
+ verify(mVehicleHal).set(response);
+ }
+
+ /**
+ * SUBSCRIPTIONS_CHANGE message format:
+ * <ul>
+ * <li>Message type
+ * <li>Sequence number
+ * <li>Number of layers
+ * <li>Number of associated layers
+ * <li>Layers (x number of layers)
+ * <ul>
+ * <li>Layer ID
+ * <li>Layer subtype
+ * <li>Layer version
+ * </ul>
+ * <li>Associated layers (x number of associated layers)
+ * <ul>
+ * <li>Layer ID
+ * <li>Layer subtype
+ * <li>Layer version
+ * <li>Number of publishers
+ * <li>Publisher ID (x number of publishers)
+ * </ul>
+ * </ul>
+ */
+ @Test
+ public void testOnVmsSubscriptionChange_ZeroLayers() throws Exception {
+ mPublisherClient.onVmsSubscriptionChange(
+ new VmsSubscriptionState(123, Collections.emptySet(), Collections.emptySet()));
+
+ VehiclePropValue response = createHalMessage(
+ VmsMessageType.SUBSCRIPTIONS_CHANGE, // Message type
+ 123, // Sequence number
+ 0, // # of layers
+ 0 // # of associated layers
+ );
+
+ waitForHandlerCompletion();
+ verify(mVehicleHal).set(response);
+ }
+
+ @Test
+ public void testOnVmsSubscriptionChange_OneLayer_ZeroAssociatedLayers()
+ throws Exception {
+ mPublisherClient.onVmsSubscriptionChange(
+ new VmsSubscriptionState(123, Collections.singleton(LAYER),
+ Collections.emptySet()));
+
+ VehiclePropValue response = createHalMessage(
+ VmsMessageType.SUBSCRIPTIONS_CHANGE, // Message type
+ 123, // Sequence number
+ 1, // # of layers
+ 0, // # of associated layers
+ LAYER_TYPE, LAYER_SUBTYPE, LAYER_VERSION // Layer
+ );
+
+ waitForHandlerCompletion();
+ verify(mVehicleHal).set(response);
+ }
+
+ @Test
+ public void testOnVmsSubscriptionChange_ZeroLayers_OneAssociatedLayer()
+ throws Exception {
+ mPublisherClient.onVmsSubscriptionChange(
+ new VmsSubscriptionState(123, Collections.emptySet(), Collections.singleton(
+ new VmsAssociatedLayer(LAYER, Collections.singleton(PUBLISHER_ID)))));
+
+ VehiclePropValue response = createHalMessage(
+ VmsMessageType.SUBSCRIPTIONS_CHANGE, // Message type
+ 123, // Sequence number
+ 0, // # of layers
+ 1, // # of associated layers
+ LAYER_TYPE, LAYER_SUBTYPE, LAYER_VERSION, // Layer
+ 1, // # of publisher IDs
+ PUBLISHER_ID // Publisher ID
+ );
+
+ waitForHandlerCompletion();
+ verify(mVehicleHal).set(response);
+ }
+
+ @Test
+ public void testOnVmsSubscriptionChange_MultipleLayersAndAssociatedLayers()
+ throws Exception {
+ mPublisherClient.onVmsSubscriptionChange(
+ new VmsSubscriptionState(123,
+ new LinkedHashSet<>(Arrays.asList(
+ LAYER,
+ new VmsLayer(4, 5, 6),
+ new VmsLayer(7, 8, 9)
+ )),
+ new LinkedHashSet<>(Arrays.asList(
+ new VmsAssociatedLayer(LAYER, Collections.emptySet()),
+ new VmsAssociatedLayer(new VmsLayer(6, 5, 4),
+ new LinkedHashSet<>(Arrays.asList(
+ PUBLISHER_ID,
+ 54321))))))
+ );
+
+ VehiclePropValue response = createHalMessage(
+ VmsMessageType.SUBSCRIPTIONS_CHANGE, // Message type
+ 123, // Sequence number
+ 3, // # of layers
+ 2, // # of associated layers
+ // Layer #1
+ LAYER_TYPE, LAYER_SUBTYPE, LAYER_VERSION, // Layer
+ // Layer #2
+ 4, 5, 6, // Layer
+ // Layer #3
+ 7, 8, 9, // Layer
+ LAYER_TYPE, LAYER_SUBTYPE, LAYER_VERSION, // Layer
+ 0, // # of publisher IDs
+ 6, 5, 4, // Layer
+ 2, // # of publisher IDs
+ PUBLISHER_ID, // Publisher ID
+ 54321 // Publisher ID #2
+ );
+
+ waitForHandlerCompletion();
+ verify(mVehicleHal).set(response);
+ }
+
+ private static VehiclePropValue createHalMessage(Integer... message) {
+ VehiclePropValue result = new VehiclePropValue();
+ result.prop = VehicleProperty.VEHICLE_MAP_SERVICE;
+ result.value.int32Values.addAll(Arrays.asList(message));
+ return result;
+ }
+
+ private void sendHalMessage(VehiclePropValue message) {
+ mHalService.handleHalEvents(Collections.singletonList(message));
+ }
+
+ private void waitForHandlerCompletion() throws Exception {
+ final CountDownLatch latch = new CountDownLatch(1);
+ mHalService.getHandler().post(() -> {
+ latch.countDown();
+ });
+ latch.await(5, TimeUnit.SECONDS);
}
}
diff --git a/tests/carservice_unit_test/src/com/android/car/vms/VmsClientManagerTest.java b/tests/carservice_unit_test/src/com/android/car/vms/VmsClientManagerTest.java
index cd0e2a0..a1ff59f 100644
--- a/tests/carservice_unit_test/src/com/android/car/vms/VmsClientManagerTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/vms/VmsClientManagerTest.java
@@ -305,9 +305,13 @@
connection.onServiceConnected(null, new Binder());
connection.onServiceDisconnected(null);
+ verify(mContext).unbindService(connection);
verify(mConnectionListener).onClientDisconnected(
eq("com.google.android.apps.vms.test/com.google.android.apps.vms.test"
+ ".VmsSystemClient U=0"));
+
+ Thread.sleep(10);
+ verifySystemBind(1);
}
@Test
@@ -319,7 +323,11 @@
ServiceConnection connection = mConnectionCaptor.getValue();
connection.onServiceDisconnected(null);
+ verify(mContext).unbindService(connection);
verifyZeroInteractions(mConnectionListener);
+
+ Thread.sleep(10);
+ verifySystemBind(1);
}
@Test
@@ -332,9 +340,13 @@
connection.onServiceConnected(null, new Binder());
connection.onServiceDisconnected(null);
+ verify(mContext).unbindService(connection);
verify(mConnectionListener).onClientDisconnected(
eq("com.google.android.apps.vms.test/com.google.android.apps.vms.test"
+ ".VmsUserClient U=10"));
+
+ Thread.sleep(10);
+ verifyUserBind(1);
}
@Test
@@ -346,7 +358,11 @@
ServiceConnection connection = mConnectionCaptor.getValue();
connection.onServiceDisconnected(null);
+ verify(mContext).unbindService(connection);
verifyZeroInteractions(mConnectionListener);
+
+ Thread.sleep(10);
+ verifyUserBind(1);
}
@Test
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 78ea244..ef12ab4 100644
--- a/user/car-user-lib/src/android/car/userlib/CarUserManagerHelper.java
+++ b/user/car-user-lib/src/android/car/userlib/CarUserManagerHelper.java
@@ -34,6 +34,7 @@
import android.os.UserManager;
import android.provider.Settings;
import android.sysprop.CarProperties;
+import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.util.Log;
@@ -636,10 +637,17 @@
}
/**
- * Checks if the foreground user can switch to other users.
+ * Returns whether the foreground user can switch to other users.
+ *
+ * <p>For instance switching users is not allowed if the current user is in a phone call,
+ * or {@link #{UserManager.DISALLOW_USER_SWITCH} is set.
*/
public boolean canForegroundUserSwitchUsers() {
- return !foregroundUserHasUserRestriction(UserManager.DISALLOW_USER_SWITCH);
+ boolean inIdleCallState = TelephonyManager.getDefault().getCallState()
+ == TelephonyManager.CALL_STATE_IDLE;
+ boolean disallowUserSwitching =
+ foregroundUserHasUserRestriction(UserManager.DISALLOW_USER_SWITCH);
+ return (inIdleCallState && !disallowUserSwitching);
}
// Current process user information accessors
@@ -717,10 +725,17 @@
}
/**
- * Checks if the user running the current process is allowed to switch to another user.
+ * Returns whether the current process user can switch to other users.
+ *
+ * <p>For instance switching users is not allowed if the user is in a phone call,
+ * or {@link #{UserManager.DISALLOW_USER_SWITCH} is set.
*/
public boolean canCurrentProcessSwitchUsers() {
- return !isCurrentProcessUserHasRestriction(UserManager.DISALLOW_USER_SWITCH);
+ boolean inIdleCallState = TelephonyManager.getDefault().getCallState()
+ == TelephonyManager.CALL_STATE_IDLE;
+ boolean disallowUserSwitching =
+ isCurrentProcessUserHasRestriction(UserManager.DISALLOW_USER_SWITCH);
+ return (inIdleCallState && !disallowUserSwitching);
}
/**